Code: Select all
If timeout < 20 Or RandomSpan < 0
ProcedureReturn 0 ; error, parameters are below minimum
EndIf
Code: Select all
; AccuTimer.pbi
; by BasicallyPure
; version 1.1
; 7.21.2013
; license: Free
; OS = All
;
; <><><><><><><><><><><><><><><> AccuTimer instructions <><><><><><><><><><><><><><><>
; Description:
; provides a timer similar To AddWindowTimer() but With more accuracy, and extra features
; including 'pause', 'holdoff', and 'random intervals'. AccuTimer timers are accurate
; over time with system clock accuracy. A built-in error correction removes accumulated
; errors from timer events.
;
; Syntax: AddAccuTimer(nWindow, nTimer, Timeout, [Holdoff], [RandomSpan])
;
; Parameters:
; nWindow | this is the number that can be retreived with EventWindow() after a #PB_Event_Timer event.
;
; nTimer | The Timer parameter is a user defined number that identifies this timer.
; This value will later be returned from EventTimer() when a #PB_Event_Timer is received.
; It can also be used To remove the timer again with the RemoveAccuTimer() function.
;
; Timeout | This specifies the amount of time in milliseconds between #PB_Event_Timer events.
; The value of timeout is required to be >= 20 milliseconds.
;
; Holdoff | This optional parameter produces an extra delay in milliseconds before the first timer event
; from this timer is produced. This can be used to produce a phase offset between two Accutimers that
; have the same timeout value.
;
; RandomSpan | This optional parameter will cause the timer to produce #PB_Event_Timer events that have
; random time intervals where timeout is the minimum interval and timeout + randomSpan is the maximum
; interval.
;
; <><><> Support procedures: <><><>
;
; PauseAccuTimer(nTimer, state)
; This procedure will pause or resume a timer's operation.
; nTimer | the timer number to pause or resume.
; state | 1 = pause, 0 = resume
;
; ReviseAccuTimer(nTimer.i, timeout.i, holdOff.i = 0, RandomSpan.i = 0)
; This procedure allows changing a timer's parameters after it has been created with AddAccuTimer().
; The parameters are the same as for AddAccuTimer().
;
; RemoveAccuTimer(nTimer)
; Removes the specified AccuTimer.
; nTimer is the same value that was used in AddAccuTimer() To create the timer.
; There will be no further events received from this timer.
; If all of the AccuTimers have been removed the AccuTimer thread will end.
;
; RemoveAllAccuTimers()
; Removes all AccuTimers created with AddAccuTimer()
; The AccuTimer thread will end.
;
; <><><><><><><><><><><><><><><><> End instructions <><><><><><><><><><><><><><><><>
CompilerIf Not #PB_Compiler_Thread
MessageRequester("WARNING!", "Set compiler options: 'Create threadsafe executable'")
End
CompilerEndIf
; this code is compatable with EnableExplicit
; EnableExplicit
Global.i AccuTimerThreadID
Global.i StopAccuTimerThread
Structure timerType
winNum.i ; window number returned by EventWindow()
timNum.i ; unique number to identify each AccuTimer
rtMax.i ; upper time limit for random timer, 0 for fixed timer
rtMin.i ; lower time limit for random timer, 0 for fixed timer
timeout.i ; time between #PB_Event_Timer events in milliseconds
paused.i ; flag to pause timer, 1 = paused, 0 = run
start.i ; set equal to ElapsedMilliseconds() when initalized
EndStructure
Global NewMap AccuTimer.timerType()
Global Mutex1 = CreateMutex()
Procedure AccuTimer_Thread(unused.i)
; Purpose generate #PB_Event_Timer events for each structured map element of AccuTimer().
; Multiple timers may run simultaneously.
Protected.i now, elapsed, err
StopAccuTimerThread = #False
Repeat
now = ElapsedMilliseconds()
LockMutex(Mutex1)
ForEach AccuTimer()
With AccuTimer()
If Not \paused
elapsed = now - \start
If elapsed >= \timeout
err = elapsed - \timeout
\start = now - err
PostEvent(#PB_Event_Timer, \winNum, \timNum)
If \rtMax : \timeout = Random(\rtMax, \rtMin) : EndIf
EndIf
EndIf
EndWith
Next
UnlockMutex(Mutex1)
Delay(10) ; arbitrary delay sets lower limit of timeout
Until StopAccuTimerThread
EndProcedure
Procedure AddAccuTimer(nWindow.i, nTimer.i, timeout.i, holdOff.i = 0, RandomSpan.i = 0)
; Purpose: create a new AccuTimer with the specified parameters. #PB_Event_Timer events
; will be received by WindowEvent() or WaitWindowEvent().
;
; #Window | this is the number that is returned by EventWindow() after a #PB_Event_Timer event.
;
; nTimer | The user defined number used to identify this timer.
;
; timeout | This specifies the amount of time in milliseconds between #PB_Event_Timer events.
; The value of timeout is required to be >= 20 milliseconds.
;
; holdoff | This optional parameter produces an extra delay in milliseconds before the first timer event
;
; RandomSpan | This optional parameter will cause the timer To produce #PB_Event_Timer events that have
; random time intervals where 'timeout' sets the minimum interval and 'timeout + RandomSpan'
; sets the maximum interval.
Protected result.i = #False
If Not IsWindow(nWindow)
ProcedureReturn result ; error, window is not valid
EndIf
LockMutex(Mutex1)
If FindMapElement(AccuTimer(), Str(nTimer)) = 0
If IsThread(AccuTimerThreadID) = #False
AccuTimerThreadID = CreateThread(@AccuTimer_Thread(), #NUL)
EndIf
If timeout < 20 Or RandomSpan < 0
UnlockMutex(Mutex1)
ProcedureReturn 0 ; error, parameters are below minimum
EndIf
With AccuTimer(Str(nTimer)) ; this creates a new map element
\winNum = nWindow
\timNum = nTimer
If RandomSpan
\rtMax = timeout + RandomSpan
\rtMin = timeout
\timeout = Random(\rtMax, \rtMin)
Else
\timeout = timeout
EndIf
\paused = #False
\start = ElapsedMilliseconds() + holdOff
EndWith
result = #True
EndIf
UnlockMutex(Mutex1)
ProcedureReturn result
EndProcedure
Procedure PauseAccuTimer(nTimer.i, state.i)
; suspend/enable timer events of the specified timer
; state = 0 (enable), state = 1 (suspend)
Protected nt$ = Str(nTimer), Result = #False
If state <> 0 : state = 1 : EndIf
LockMutex(Mutex1)
If FindMapElement(AccuTimer(), nt$) ; check for valid timer number
With AccuTimer(nt$)
\paused = state
If state = 0 : \start = ElapsedMilliseconds() : EndIf
EndWith
Result = #True
EndIf
UnlockMutex(Mutex1)
ProcedureReturn Result
EndProcedure
Procedure ReviseAccuTimer(nTimer.i, timeout.i, holdOff.i = 0, RandomSpan.i = 0)
; purpose: change the settings of a timer that has already been created with AddAccuTimer().
Protected nt$ = Str(nTimer), result.i = #False
LockMutex(Mutex1)
; check for valid timer number
If FindMapElement(AccuTimer(), nt$) And timeout >= 20
With AccuTimer(nt$)
If RandomSpan
\rtMax = timeout + RandomSpan
\rtMin = timeout
\timeout = Random(\rtMax, \rtMin)
Else
\rtMax = 0
\rtMin = 0
\timeout = timeout
EndIf
\start = ElapsedMilliseconds() + holdOff
EndWith
result = #True
EndIf
UnlockMutex(Mutex1)
ProcedureReturn result
EndProcedure
Procedure RemoveAccuTimer(nTimer.i)
; Purpose: Removes the specified AccuTimer.
; nTimer has To be the same value that was used in AddAccuTimer() To create the timer.
; There will be no further events received from this timer.
Protected nt$ = Str(nTimer), result.i = #False
LockMutex(Mutex1)
If FindMapElement(AccuTimer(), nt$) ; check for valid timer number
DeleteMapElement(AccuTimer(),nt$)
UnlockMutex(Mutex1)
If MapSize(AccuTimer()) = 0
StopAccuTimerThread = #True ; no AccuTimers exist so stop the thread
WaitThread(AccuTimerThreadID, 2000)
If IsThread(AccuTimerThreadID)
KillThread(AccuTimerThreadID)
EndIf
; While IsThread(AccuTimerThreadID) ; wait for the thread to end
; Delay(1)
; Wend
EndIf
result = #True
ProcedureReturn result
EndIf
UnlockMutex(Mutex1)
ProcedureReturn result
EndProcedure
Procedure RemoveAllAccuTimers()
; Purpose: removes all AccuTimers created with AddAccuTimer()
Protected result.i = #False
LockMutex(Mutex1)
If MapSize(AccuTimer())
ForEach AccuTimer()
DeleteMapElement(AccuTimer())
Next
UnlockMutex(Mutex1)
StopAccuTimerThread = #True
WaitThread(AccuTimerThreadID, 2000)
If IsThread(AccuTimerThreadID)
KillThread(AccuTimerThreadID)
EndIf
; While IsThread(AccuTimerThreadID) ; wait for the thread to end
; Delay(1)
; Wend
result = #True
ProcedureReturn result
EndIf
UnlockMutex(Mutex1)
ProcedureReturn result
EndProcedure
; ******************************************************************* ;
;- DEMONSTRATION -
; you can leave this in the include file
CompilerIf #PB_Compiler_IsMainFile
#WinMain = 0
#WinAux1 = 1
Enumeration
#strGad_01
#strGad_02
#strGad_03
#btnRevise
#btnPause
#btnRemove
#btnRemoveAll
#ProgBar
EndEnumeration
Enumeration
#AccuTimer_01
#AccuTimer_02
#AccuTimer_03
#WindowTimer_01
EndEnumeration
Define text.s = "Comparison AccuTimer vs WindowTimer"
If OpenWindow(#WinMain,020,300,280,250,text) = 0 : End : EndIf
text = "bar movement is triggered by AccuTimer's random interval event option"
If OpenWindow(#WinAux1,330,300,500,100,text,#PB_Window_TitleBar) = 0 : End : EndIf
SetActiveWindow(#WinMain)
Define Courier_ID = LoadFont(0,"Courier New",28,#PB_Font_Bold)
UseGadgetList(WindowID(#WinMain))
TextGadget(#PB_Any,10,3,250,20,"PB Date() function as reference")
StringGadget(#strGad_01,10,20,250,50,"00:00:00")
SetGadgetFont(#strGad_01,Courier_ID)
TextGadget(#PB_Any,10,83,250,20,"variable incremented with AccuTimer")
StringGadget(#strGad_02,10,100,250,50,"00:00:00")
SetGadgetFont(#strGad_02,Courier_ID)
TextGadget(#PB_Any,10,163,250,20,"variable incremented with PB WindowTimer")
StringGadget(#strGad_03,10,180,250,50,"00:00:00")
SetGadgetFont(#strGad_03,Courier_ID)
UseGadgetList(WindowID(#WinAux1))
ButtonGadget(#btnPause, 5, 70, 80, 25, "Pause bar", #PB_Button_Toggle)
ProgressBarGadget(#ProgBar, 0, 0, 500, 60, 0, 500)
ButtonGadget(#btnRevise, 90, 70, 130, 25, "Revise bar timer", #PB_Button_Toggle)
ButtonGadget(#btnRemove, 225, 70, 130, 25, "Remove AccuTimer_02")
ButtonGadget(#btnRemoveAll, 360, 70, 130, 25, "Remove all AccuTimers")
; ******* Initalize the timers ***************
;
AddAccuTimer(#WinMain, #AccuTimer_01, 1000) ; <-- event every second
AddAccuTimer(#WinMain, #AccuTimer_02, 100) ; <-- event every 100 mS
AddAccuTimer(#WinAux1, #AccuTimer_03, 200, 0, 2000) ;<-- random event intervals 200 to 200 + 2000 mS
;
AddWindowTimer(#WinMain, #WindowTimer_01, 100) ; <-- event approximately every 100 mS
;
; ********************************************
Define.i AccuCount ; <-- variable to be incremented by AccuTimer event
Define.i WindowTimerCount ; <-- variable to be incremented by WindowTimer event
; initalize both reference variables to the current time
Define ACref.i = Date() ; for AccuTimer
Define WTref.i = Date() ; for WindowTimer
Define progress.i = 10
Dim caption.s(1) : caption(0) = "Pause bar" : caption(1) = "Resume"
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow : End
Case #PB_Event_Timer; AccuTimer produces events just like the WindowTimer
Select EventTimer()
Case #AccuTimer_01 ; update the display of the Date() function
SetGadgetText(#strGad_01, FormatDate("%hh:%ii:%ss", Date()))
Case #AccuTimer_02 ; update the display of the AccuTimer counter
AccuCount + 1
SetGadgetText(#strGad_02, FormatDate("%hh:%ii:%ss", ACref + AccuCount/10)+"."+Str(AccuCount%10))
Case #WindowTimer_01 ; update the display of the WindowTimer counter
WindowTimerCount + 1
SetGadgetText(#strGad_03, FormatDate("%hh:%ii:%ss", WTref + WindowTimerCount/10)+"."+Str(WindowTimerCount%10))
Case #AccuTimer_03 ; move the bar at the randomly constrained time interval
SetGadgetState(#ProgBar, progress) : progress + 10
If progress > 500 : progress = 0 : EndIf
EndSelect
Case #PB_Event_Gadget
Select EventGadget()
Case #btnPause ; demonstrate PauseAccuTimer()
PauseAccuTimer(#AccuTimer_03, GetGadgetState(#btnPause))
SetGadgetText(#btnPause, caption(GetGadgetState(#btnPause)))
Case #btnRemove ; demonstrate RemoveAccuTimer()
RemoveAccuTimer(#AccuTimer_02)
DisableGadget(#btnRemove, #True)
Case #btnRemoveAll ; demonstrate RemoveAllAccuTimers()
RemoveAllAccuTimers()
DisableGadget(#btnRemove, #True)
DisableGadget(#btnPause, #True)
DisableGadget(#btnRemoveAll, #True)
Case #btnRevise ; demonstrate ReviseAccuTimer()
If GetGadgetState(#btnRevise)
ReviseAccuTimer(#AccuTimer_03, 50, 0, 250)
Else
ReviseAccuTimer(#AccuTimer_03, 200, 0, 2000)
EndIf
EndSelect
EndSelect
ForEver
CompilerEndIf