YAP - Yet Another Progressbar
Posted: Sat Apr 16, 2011 6:02 pm
Dunno if You've seen to many of these routines already, but here's a procedure for initing, showing and ending a progress bar. Useful for heavy duty loops that perhaps already is optimized but still takes more then a few tens of a second to complete.
This procedure is fairly intelligent, adaptive, optimized and easy to use. Fairly. You can surely find improvements to do with it.
So suggestions, comments, improvements, questions will probably be read and perhaps processed, commented, validated and answered.
Well, enough scribbling, here's the code:
This procedure is fairly intelligent, adaptive, optimized and easy to use. Fairly. You can surely find improvements to do with it.

So suggestions, comments, improvements, questions will probably be read and perhaps processed, commented, validated and answered.
Well, enough scribbling, here's the code:
Code: Select all
; quick reference:
; ProgressInfo(#ProgressInit, topValue) ;before loop - topValue is where the job will end
; ProgressInfo(#ProgressShow, partOfTopValue) ;inside loop - partOfTopValue is where the job is
; ProgressInfo(#ProgressClose) ;after loop
;- Window Constants
;
Enumeration
#wProgress
EndEnumeration
;- Gadget Constants
;
Enumeration
#ProgressBar
EndEnumeration
;- Action Constants
;
Enumeration
#ProgressInit
#ProgressShow
#ProgressClose
EndEnumeration
Procedure ProgressInfo(action.l, value.q = -1)
; 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: ProgressInfo(#ProgressInit, topValue)
; show: ProgressInfo(#ProgressShow, partOfTopValue)
; quit: ProgressInfo(#ProgressClose)
;
; 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.
;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 = 15 ; minimum time between each update of the progress bar
; 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. :)
Static progressTimer.l, upperValue.l, windowOpen.l, totalTimer.l, interval.q, divider.q
Global loopSkip.l
Select action
Case #ProgressShow
; ; switch this error check on if you are uncertain of your code - switch it of to save a few cycles
; If value < 0
; MessageRequester("ProgressInfo", "value must be set at Show of progress")
; End
; EndIf
If windowOpen = #True And ElapsedMilliseconds() - progressTimer > interval
value / divider
SetGadgetState(#ProgressBar, value)
progressTimer = ElapsedMilliseconds()
ElseIf ElapsedMilliseconds() > progressTimer
If windowOpen = #False
If value * #maxProgressToOpenWindow < upperValue
upperValue / divider
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
value / divider
interval = upperValue / value * 2
If interval < #minInterval
interval = #minInterval
EndIf
If interval > #timeBeforeWindowOpening * 80 / 100
interval = #timeBeforeWindowOpening * 80 / 100
EndIf
EndIf
EndIf
EndIf
EndIf
EndIf
Case #ProgressInit
If value <= 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
; set divider for great values (for not messing up the gadget)
If value > 10000
divider = value / 10000
Else
divider = 1
EndIf
progressTimer = ElapsedMilliseconds() + #timeBeforeWindowOpening
totalTimer = progressTimer
upperValue = value
loopSkip = Log10(upperValue / 10000) / Log10(2) + 0.5
If loopSkip < 0: loopSkip = 0: EndIf
loopSkip = Pow(2, loopSkip) - 1
Case #ProgressClose
If windowOpen = #True
FreeGadget(#ProgressBar)
CloseWindow(#wProgress)
upperValue = 0
progressTimer = 0
windowOpen = #False
EndIf
EndSelect
EndProcedure
Macro ProgressInfo_laced(action, value)
If value & loopSkip = 0
ProgressInfo(action, value)
EndIf
EndMacro
; test with ordinary implementation
Debug "Ordinary method:"
outerLoopTop = 1
totalLoopSize = 20000000
loopMultiplier = 5
While totalLoopSize / outerLoopTop / loopMultiplier > 0
outerLoopTop = totalLoopSize / (totalLoopSize / outerLoopTop / loopMultiplier)
timeTracker = ElapsedMilliseconds()
ProgressInfo(#ProgressInit, outerLoopTop)
For i = 1 To outerLoopTop
For j = 1 To totalLoopSize / outerLoopTop
Next
ProgressInfo(#ProgressShow, i)
Next
ProgressInfo(#ProgressClose)
Debug "innerLoop: "+Str(totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker)
Wend
; for use with tight inner loops, but more complex
Debug ""
Debug "Method for tight inner loops:"
outerLoopTop = 1
totalLoopSize = 20000000
loopMultiplier = 5
While totalLoopSize / outerLoopTop / loopMultiplier > 0
outerLoopTop = totalLoopSize / (totalLoopSize / outerLoopTop / loopMultiplier)
timeTracker = ElapsedMilliseconds()
ProgressInfo(#ProgressInit, outerLoopTop)
For i = 1 To outerLoopTop
For j = 1 To totalLoopSize / outerLoopTop
Next
ProgressInfo_laced(#ProgressShow, i)
Next
ProgressInfo(#ProgressClose)
Debug "innerLoop: "+Str(totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker)
Wend
; only for reference - no progress info is used here
Debug ""
Debug "Reference run (no progress meter):"
outerLoopTop = 1
totalLoopSize = 20000000
loopMultiplier = 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
Debug "innerLoop: "+Str(totalLoopSize/outerLoopTop)+", outerLoop:"+Str(outerLoopTop)+", Time(ms):"+Str(ElapsedMilliseconds() - timeTracker)
Wend