I have finally come to posting updated code for my YAP.
The first one is about the same code as the first posted in this thread, but now it's a module instead, making it totally isolated from the code it's used in.
The other one is also a module but this one uses a dedicated thread for updating the progress - making the problem with tight inner loops totally transparent.
One problem/warning with this one though, it uses a window inside the thread. When trying to debug the modules code - it will fail, telling you that you can't use windows in threads...
... but I WANTED to use a window in a thread, so I turned off debugging and it worked. But turn on *threadsafe*, or it can break havoc.
If you try these routines, tell me if they work.
Comments can be found in the main routines.
Feel free to use this code as You wish.
Here are my results from YAP V2:
Code: Select all
Ordinary method:
innerLoop: 16000000, outerLoop:25, Time(ms):1359
innerLoop: 3200000, outerLoop:125, Time(ms):1344
innerLoop: 640000, outerLoop:625, Time(ms):1340
innerLoop: 128000, outerLoop:3125, Time(ms):1331
innerLoop: 25600, outerLoop:15625, Time(ms):1338
innerLoop: 5120, outerLoop:78125, Time(ms):1339
innerLoop: 1024, outerLoop:390625, Time(ms):1348
innerLoop: 204, outerLoop:1960784, Time(ms):1401
innerLoop: 40, outerLoop:10000000, Time(ms):1553
innerLoop: 8, outerLoop:50000000, Time(ms):2396
innerLoop: 1, outerLoop:400000000, Time(ms):9747
Method for tight inner loops:
innerLoop: 16000000, outerLoop:25, Time(ms):1101
innerLoop: 3200000, outerLoop:125, Time(ms):1110
innerLoop: 640000, outerLoop:625, Time(ms):1117
innerLoop: 128000, outerLoop:3125, Time(ms):1115
innerLoop: 25600, outerLoop:15625, Time(ms):1120
innerLoop: 5120, outerLoop:78125, Time(ms):1117
innerLoop: 1024, outerLoop:390625, Time(ms):1118
innerLoop: 204, outerLoop:1960784, Time(ms):1146
innerLoop: 40, outerLoop:10000000, Time(ms):1153
innerLoop: 8, outerLoop:50000000, Time(ms):1291
innerLoop: 1, outerLoop:400000000, Time(ms):2539
Reference run (no progress meter):
innerLoop: 16000000, outerLoop:25, Time(ms):1091
innerLoop: 3200000, outerLoop:125, Time(ms):1097
innerLoop: 640000, outerLoop:625, Time(ms):1095
innerLoop: 128000, outerLoop:3125, Time(ms):1090
innerLoop: 25600, outerLoop:15625, Time(ms):1092
innerLoop: 5120, outerLoop:78125, Time(ms):1091
innerLoop: 1024, outerLoop:390625, Time(ms):1097
innerLoop: 204, outerLoop:1960784, Time(ms):1121
innerLoop: 40, outerLoop:10000000, Time(ms):1234
innerLoop: 8, outerLoop:50000000, Time(ms):1263
innerLoop: 1, outerLoop:400000000, Time(ms):2451
Here are my results from YAP threaded:
Code: Select all
Ordinary method:
innerLoop: 16000000, outerLoop:25, Time(ms):1104
innerLoop: 3200000, outerLoop:125, Time(ms):1101
innerLoop: 640000, outerLoop:625, Time(ms):1101
innerLoop: 128000, outerLoop:3125, Time(ms):1106
innerLoop: 25600, outerLoop:15625, Time(ms):1112
innerLoop: 5120, outerLoop:78125, Time(ms):1110
innerLoop: 1024, outerLoop:390625, Time(ms):1116
innerLoop: 204, outerLoop:1960784, Time(ms):1133
innerLoop: 40, outerLoop:10000000, Time(ms):1258
innerLoop: 8, outerLoop:50000000, Time(ms):1262
innerLoop: 1, outerLoop:400000000, Time(ms):2468
Reference run (no progress meter):
innerLoop: 16000000, outerLoop:25, Time(ms):1099
innerLoop: 3200000, outerLoop:125, Time(ms):1095
innerLoop: 640000, outerLoop:625, Time(ms):1095
innerLoop: 128000, outerLoop:3125, Time(ms):1101
innerLoop: 25600, outerLoop:15625, Time(ms):1092
innerLoop: 5120, outerLoop:78125, Time(ms):1098
innerLoop: 1024, outerLoop:390625, Time(ms):1098
innerLoop: 204, outerLoop:1960784, Time(ms):1119
innerLoop: 40, outerLoop:10000000, Time(ms):1232
innerLoop: 8, outerLoop:50000000, Time(ms):1257
innerLoop: 1, outerLoop:400000000, Time(ms):2412
Source for YAP in a module:
(no special compiler restrictions)
Code: Select all
DeclareModule Progress
Declare Init(maxValue)
Declare Show(value)
Declare Close()
EndDeclareModule
Module Progress
DisableDebugger
;- Window Constants
;
Enumeration
#wProgress
EndEnumeration
;- Gadget Constants
;
Enumeration
#ProgressBar
EndEnumeration
;useful constants
#timeBeforeWindowOpening = 150 ;(unit: ms) 50 to 500 seems to be reasonable.
#maxProgressToOpenWindow = 6 ;(unit: parts) 4 means 1/4 (25%). If the job has progressed more than
; this, no progress bar will show up.
#minInterval = 10 ; minimum time between each update of the progress bar
Global windowOpen, upperValue, progressTimer, totalTimer, interval
Procedure Show(value)
;{- A progress bar is shown if something takes more than a defined time to complete and less than
; a defined part of the job is done. If the progress has past that part, no progress will show up.
; The progress bar is updateted depending on how fast the progress is as well as when the
; procedure is called. The slowest of the two decides the update interval.
; A deeper explanation of the last sentence:
; If a job has been completed 4% when the window is opened (all criteria satisfied), the update interval
; will be about 2 * #minInterval ms even if the calls only have 2 ms in delay.
; How to use:
; init: Progress::Init(topValue)
; show: Progress::Show(partOfTopValue)
; quit: Progress::Close()
;
; All three should be used. #ProgressShow is used inside the loop that should be monitored.
; topValue can preferably be the loops stop value and partOfTopValue the counting up (index) in the loop.
; Usually the outer loop is the one to monitor.
;
; Atm #PB_ProgressBar_Smooth is used to show how smooth/jaggy the bar can be, feel free change its looks.
; There will be a delay of #timeBeforeWindowOpening before testing if the progress is 1 / #maxProgressToOpenWindow
; of the upper value
; If the progress is less, the progress bar will be shown.
; If the progress is more, no progress bar will be shown.
; A loop that takes up to #timeBeforeWindowOpening * #maxProgressToOpenWindow milliseconds will not
; show a progress bar. Thats why the product of theese two is important. That's the max time before the
; user probably would get worried that the computer didn't respond well to his/her last command. :)
;}
; ; switch this error check on if you are uncertain of your code - switch it off to save a few cycles
; If value < 0
; MessageRequester("ProgressInfo", "value must be set at Show of progress")
; End
; EndIf
If windowOpen = #True
If ElapsedMilliseconds() - progressTimer > interval
SetGadgetState(#ProgressBar, value)
While WindowEvent()
Wend
progressTimer = ElapsedMilliseconds()
EndIf
Else
If ElapsedMilliseconds() > progressTimer And value * #maxProgressToOpenWindow < upperValue
If OpenWindow(#wProgress, 496, 300, 290, 51, "Busy", #PB_Window_TitleBar | #PB_Window_BorderLess | #PB_Window_ScreenCentered ) ; "Arbetar"
StickyWindow(#wProgress, #True)
ProgressBarGadget(#ProgressBar, 10, 10, 270, 30, 0, upperValue, #PB_ProgressBar_Smooth)
windowOpen = #True
; räkna ut ett lagom intervall för uppdateringen
If value = 0
interval = #timeBeforeWindowOpening * 80 / 100
Else
interval = upperValue / value * 2
If interval < #minInterval
interval = #minInterval
EndIf
If interval > #timeBeforeWindowOpening * 80 / 100
interval = #timeBeforeWindowOpening * 80 / 100
EndIf
EndIf
EndIf
EndIf
EndIf
EndProcedure
Procedure Init(maxValue)
If maxValue <= 0
MessageRequester("ProgressInfo", "Top value must be sent as argument at init")
End
EndIf
If #minInterval * 1.25 > #timeBeforeWindowOpening
MessageRequester("ProgressInfo", "#minInterval must be 25% greater than #timeBeforeWindowOpening")
End
EndIf
progressTimer = ElapsedMilliseconds() + #timeBeforeWindowOpening
totalTimer = progressTimer
upperValue = maxValue
EndProcedure
Procedure Close()
If windowOpen = #True
FreeGadget(#ProgressBar)
CloseWindow(#wProgress)
upperValue = 0
progressTimer = 0
windowOpen = #False
EndIf
EndProcedure
DisableDebugger
EndModule
#progressTestrun = #True
CompilerIf #progressTestrun
UseModule Progress
Macro Desnug(t)
CompilerIf #PB_Compiler_Debugger
Debug t
CompilerElse
PrintN(t)
CompilerEndIf
EndMacro
#loopMultiplier = 5
CompilerIf #PB_Compiler_Debugger = #True
#totalLoopSize = 40000000
CompilerElse
#totalLoopSize = 400000000
CompilerEndIf
CompilerIf #PB_Compiler_Debugger = #False
OpenConsole("Progressbar")
CompilerEndIf
Desnug( "Ordinary method:")
outerLoopTop = 5
While #totalLoopSize / outerLoopTop / #loopMultiplier > 0
outerLoopTop = #totalLoopSize / (#totalLoopSize / outerLoopTop / #loopMultiplier)
timeTracker = ElapsedMilliseconds()
Progress::Init(outerLoopTop)
For i = 1 To outerLoopTop
For j = 1 To #totalLoopSize / outerLoopTop
Next
Progress::Show(i)
Next
Progress::Close()
Desnug("innerLoop: "+Str(#totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker))
Wend
Desnug("")
Desnug("Method for tight inner loops:")
outerLoopTop = 5
While #totalLoopSize / outerLoopTop / #loopMultiplier > 0
outerLoopTop = #totalLoopSize / (#totalLoopSize / outerLoopTop / #loopMultiplier)
timeTracker = ElapsedMilliseconds()
Progress::Init(outerLoopTop)
; a trick to skip some calls to Show()
loopSkip = Log10(outerLoopTop / 10000) / Log10(2) + 0.5
If loopSkip < 0: loopSkip = 0: EndIf
loopSkip = Pow(2, loopSkip) - 1
For i = 1 To outerLoopTop
For j = 1 To #totalLoopSize / outerLoopTop
Next
; only Show() when progressed enough
If i & loopSkip = 0
Progress::Show(i)
EndIf
Next
Progress::Close()
Desnug("innerLoop: "+Str(#totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker))
Wend
Desnug("")
Desnug("Reference run (no progress meter):")
outerLoopTop = 5
While #totalLoopSize / outerLoopTop / #loopMultiplier > 0
outerLoopTop = #totalLoopSize / (#totalLoopSize / outerLoopTop / #loopMultiplier)
timeTracker = ElapsedMilliseconds()
For i = 1 To outerLoopTop
For j = 1 To #totalLoopSize / outerLoopTop
Next
Next
Desnug("innerLoop: "+Str(#totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker))
Wend
CompilerIf #PB_Compiler_Debugger = #False
Input()
CompilerEndIf
CompilerEndIf
and here is the code for YAP-threaded:
(compile with 'create threadsafe executeable' on - might work without it, but can fail hard)
Code: Select all
DeclareModule Progress
#noProgress = #False
Declare Supervise(maxValue, *valueToMonitor.Long)
Declare Cancel()
EndDeclareModule
Module Progress
DisableDebugger
;- Window Constants
;
Enumeration
#wProgress
EndEnumeration
;- Gadget Constants
;
Enumeration
#ProgressBar
EndEnumeration
;useful constants
#timeBeforeWindowOpening = 150 ;(unit: ms) 50 to 500 seems to be reasonable.
#maxProgressToOpenWindow = 6 ;(unit: parts) 4 means 1/4 (25%). If the job has progressed more than
; this, no progress bar will show up.
#minInterval = 10 ; minimum time between each update of the progress bar - 10 ms is fair on a multi core machine
Global windowOpen, upperValue, progressTimer, totalTimer.l, interval
Procedure Show(*valueToMonitor.Long)
;{- A progress bar is shown if something takes more than a defined time to complete and less than
; a defined part of the job is done. If the progress has past that part, no progress will show up.
; The progress bar is updateted with #minInterval ms delay
; How to use:
; show if needed: Progress::Supervise(topValue, *valueToMonitor)
; force quit: Progress::Close() - can be delayed up to #minInterval ms, but Init should force a wait if
; another loop tries to start a new process.
;
; Since Supervise() starts a thread, it runs when it needs, how it needs and quits when it needs to.
; If a loop has to be ended premature (whith break or something) - close() can be used to quit the progress nicely.
;
;*******************************************************************************************************
; MOST IMPORTANT:
; This implementation shall not work. If the debugger is activated, it will kick my code in the ...
; ... so use it at your own risk. If strange bugs happens elsewhere in your code - deactivate
; the progress info by setting #noProgress = #True
; IMPORTANT 1:
; The variable whos pointer you send to Supervise() WILL BE SET TO ZERO, so call Supervise() before you have
; started your loop. It's not advisable to start from values other than 0 or 1 since that will affect the
; calculations for opening the progressbar.
; IMPORTANT 2:
; The progress expects that the valueToMonitor is running uppwards to topValue.
; The process will automatically detect if the valueToMonitor jumps backwards (downwards) and will exit as it
; interprets this as if a new loop has started with the same variable.
; If you're lucky to change the value at the right moment and count up to the last reached value fast enough,
; you can fool the routine, but don't count on it. ;) The time comes when it catches you cold handed and it will just fold. :)
; IMPORTANT 3:
; The process will not close until valueToMonitor has reached the defined topValue.
; If it's not sure this will happen, use Close() to tell the process you're done with this instance.
; IMPORTANT 4:
; Supervise() has a feature that makes it wait if there's a window open that havn't closed - as a failsafe it will
; shout out to you if it has to wait for to long.
; There will be a delay of #timeBeforeWindowOpening before testing if the progress is 1 / #maxProgressToOpenWindow
; of the upper value
; If the progress is less, the progress bar will be shown.
; If the progress is more, no progress bar will be shown.
; A loop that takes up to #timeBeforeWindowOpening * #maxProgressToOpenWindow milliseconds will not
; show a progress bar. Thats why the product of theese two is important. That's the max time before the
; user probably would get worried that the computer didn't respond well to his/her last command. :)
;}
While *valueToMonitor\l < upperValue
Select windowOpen
Case #True
If *valueToMonitor\l < mem.l
Break ; if loop has restarted - quit fast
EndIf
SetGadgetState(#ProgressBar, *valueToMonitor\l)
While WindowEvent()
Wend
mem.l = *valueToMonitor\l
Delay(#minInterval)
Case -1
Break
Case #False
If ElapsedMilliseconds() > progressTimer And *valueToMonitor\l * #maxProgressToOpenWindow < upperValue
If OpenWindow(#wProgress, 496, 300, 290, 51, "Busy", #PB_Window_TitleBar | #PB_Window_BorderLess | #PB_Window_ScreenCentered ) ; "Arbetar"
StickyWindow(#wProgress, #True)
ProgressBarGadget(#ProgressBar, 10, 10, 270, 30, 0, upperValue, #PB_ProgressBar_Smooth)
windowOpen = #True
; räkna ut ett lagom intervall för uppdateringen
If *valueToMonitor\l = 0
interval = #timeBeforeWindowOpening * 80 / 100
Else
interval = upperValue / *valueToMonitor\l * 2
If interval < #minInterval
interval = #minInterval
EndIf
If interval > #timeBeforeWindowOpening * 80 / 100
interval = #timeBeforeWindowOpening * 80 / 100
EndIf
EndIf
EndIf
EndIf
Delay(#minInterval)
EndSelect
Wend
If windowOpen = #True Or windowOpen = -1
FreeGadget(#ProgressBar)
CloseWindow(#wProgress)
upperValue = 0
progressTimer = 0
windowOpen = #False
EndIf
EndProcedure
Procedure Supervise(maxValue, *valueToMonitor.Long)
While windowOpen = #True Or windowOpen = -1 ; se om någon process redan körs som använder fönstret
Delay(1)
err + 1
If err = 500
MessageRequester("ProgressInfo", "Hey, the process for the busy requester is still running!!!"+#CRLF$+
"Check your previous call (perhaps valueToMonitor didn't reach maxValue")
End
EndIf
Wend
If maxValue <= 0
MessageRequester("ProgressInfo", "Top value must be sent as argument at init")
End
EndIf
If #minInterval * 1.25 > #timeBeforeWindowOpening
MessageRequester("ProgressInfo", "#minInterval must be 25% greater than #timeBeforeWindowOpening")
End
EndIf
progressTimer = ElapsedMilliseconds() + #timeBeforeWindowOpening
totalTimer = progressTimer
upperValue = maxValue
windowOpen = #False
CompilerIf #noProgress = #False
*valueToMonitor\l = 0
CreateThread(@Show(),*valueToMonitor)
CompilerEndIf
EndProcedure
Procedure Cancel()
windowOpen - 2
EndProcedure
EnableDebugger
EndModule
#progressTestrun = #True
CompilerIf #progressTestrun
UseModule Progress
Macro Desnug(t)
CompilerIf #PB_Compiler_Debugger
Debug t
CompilerElse
PrintN(t)
CompilerEndIf
EndMacro
#loopMultiplier = 5
CompilerIf #PB_Compiler_Debugger = #True
#totalLoopSize = 40000000
CompilerElse
#totalLoopSize = 400000000
CompilerEndIf
CompilerIf #PB_Compiler_Debugger = #False
OpenConsole("Progressbar")
CompilerEndIf
i.l = 0
MessageRequester("ProgressInfo", "Demo of progress that is canceled after about 1/4.")
Progress::Supervise(984250, @i)
For i = 1 To 984250
For j = 1 To 4064
Next
If i = 250000
Progress::Cancel()
Break
EndIf
Next
Desnug( "Ordinary method:")
outerLoopTop = 5
While #totalLoopSize / outerLoopTop / #loopMultiplier > 0
outerLoopTop = #totalLoopSize / (#totalLoopSize / outerLoopTop / #loopMultiplier)
timeTracker = ElapsedMilliseconds()
Progress::Supervise(outerLoopTop, @i)
For i = 1 To outerLoopTop
For j = 1 To #totalLoopSize / outerLoopTop
Next
Next
Desnug("innerLoop: "+Str(#totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker))
Wend
Desnug("")
Desnug("Reference run (no progress meter):")
outerLoopTop = 5
While #totalLoopSize / outerLoopTop / #loopMultiplier > 0
outerLoopTop = #totalLoopSize / (#totalLoopSize / outerLoopTop / #loopMultiplier)
timeTracker = ElapsedMilliseconds()
For i = 1 To outerLoopTop
For j = 1 To #totalLoopSize / outerLoopTop
Next
Next
Desnug("innerLoop: "+Str(#totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker))
Wend
CompilerIf #PB_Compiler_Debugger = #False
Input()
CompilerEndIf
CompilerEndIf