Page 1 of 2

AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 7:42 pm
by BasicallyPure
And now for my next trick (nothing up my sleeve).

I have made a timer that is similar to AddWindowTimer() but is more accurate.
It also gives some extra features; pause, holdoff, and random intervals.
Multiple timers are allowed.
AccuTimer runs as a separate thread so you should enable 'create threadsafe executable' under compiler options.
AccuTimers generate '#PB_Event_Timer' events just like AddWindowTimer().

This is my first attempt at using threads that is not borrowed code so I will ask for some advice.
Do I need to use a mutex here and if so how do you code it?
The thread runs using each of the structured map elements in AccuTimer().
The support procedures can change the elements in the AccuTimer() map while the thread is running.
This is why I think I might need to use a mutex but it seems to work ok as is without it.

I have tested on Linux and Windows 7, it should work on Mac as well.

BP

7/21/2013
Edit: added mutex protection.

7/22/2013
Edit: bug fixes

Code: Select all

; AccuTimer.pbi
; by BasicallyPure
; version 1.2
; 7.22.2013
; license: Free
; platform = 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

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()
Global.i AccuTimerThreadID
Global.i StopAccuTimerThread

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 IsWindow(nWindow) = 0 Or timeout < 20 Or RandomSpan < 0
      ProcedureReturn result ; error, invalid parameters
   EndIf
   
   LockMutex(Mutex1)
      If FindMapElement(AccuTimer(), Str(nTimer)) = 0
         If IsThread(AccuTimerThreadID) = #False
            AccuTimerThreadID = CreateThread(@AccuTimer_Thread(), #NUL)
         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$)
      
      If MapSize(AccuTimer()) = 0
         UnlockMutex(Mutex1)
         StopAccuTimerThread = #True ; no AccuTimers exist so stop the thread
         
         WaitThread(AccuTimerThreadID, 2000)
         
         If IsThread(AccuTimerThreadID)
            KillThread(AccuTimerThreadID)
         EndIf
      Else
         UnlockMutex(Mutex1)
      EndIf
      
      result = #True
   Else
      UnlockMutex(Mutex1)
   EndIf
   
   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
      
      result = #True
   Else
      UnlockMutex(Mutex1)
   EndIf
   
   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

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 8:30 pm
by User_Russian
Why do you do not protect access to the map from threads? This will eventually lead to bugs.
You have two threads in the example and see what happens (not corresponding to the key and data).

Code: Select all

Global NewMap TestList.l() 

 Procedure Thread(Number) 
   Repeat 
     FindMapElement(TestList(), Str(Number))
     Delay(Random(400)+200) 
     Debug "Thread №"+Str(Number)+" data "+Str(TestList()) 
   ForEver 
 EndProcedure 

 For i=1 To 4 
   TestList(Str(i)) = i 
 Next 

 For i=1 To 4 
   CreateThread(@Thread(), i) 
 Next 

 MessageRequester("", "Click 'OK' to close the program")
Here is an example of the protection of access to the map from threads.

Code: Select all

Global NewMap TestList.l() 
Global Mutex = CreateMutex()

 Procedure Thread(Number) 
   Repeat 
     LockMutex(Mutex)
     FindMapElement(TestList(), Str(Number))
     Delay(Random(400)+200) 
     Debug "Thread №"+Str(Number)+" data "+Str(TestList()) 
     UnlockMutex(Mutex)
   ForEver 
 EndProcedure 

 For i=1 To 4 
   TestList(Str(i)) = i 
 Next 

 For i=1 To 4 
   CreateThread(@Thread(), i) 
 Next 

 MessageRequester("", "Click 'OK' to close the program")

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 8:45 pm
by jassing
Have you found that PB window timers are wildly inaccurate? You are still (by using postevent) at the mercy of pb's event handler.

Consider changing this: (there is no need to wait to run the file to be told it won't work)

Code: Select all

CompilerIf Not #PB_Compiler_Thread
	MessageRequester("WARNING!", "Set compiler options: 'Create threadsafe executable'")
	End
CompilerEndIf
to this

Code: Select all

CompilerIf Not #PB_Compiler_Thread
	CompilerError "WARNING! Set compiler options: 'Create threadsafe executable'"
CompilerEndIf
I'd be interested to hear what drove you to do this? I haven't found timers to be inaccurate; although in one project, I had to resort to using MSWindow's timers, but I don't recall why...

It does appear to work w/o issue...

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 8:59 pm
by IdeasVacuum
Is it something to do with process priority - i.e., if you have a ton of apps running, the Window Timer could lag?

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 10:09 pm
by BasicallyPure
jassing wrote:I'd be interested to hear what drove you to do this? I haven't found timers to be inaccurate; although in one project, I had to resort to using MSWindow's timers, but I don't recall why...It does appear to work w/o issue...
Now I am confused, I thought my example demonstrated that AddWindowTimer() produces a timer that is inaccurate.
When I run it the lower timer runs slow and falls behind after a few seconds.
So when you run my code do you not see the lower clock running slow?
Help wrote:Note: The timer events will only be generated when there are no other events to be processed (timers are low-priority events). This means that the time that elapses between two timer events may be larger than the specified Timeout value. Timers are therefore not suited for precise timing but are rather intended to perform periodic tasks such as updating a gadget content or similar.
I find that using AddWindowTimer() of 1000 milliseconds to periodically update a clock is not good because the average events are longer than 1000 mS and eventually it will skip an update.

BP

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sat Jul 20, 2013 10:53 pm
by IdeasVacuum
I thought my example demonstrated that AddWindowTimer() produces a timer that is inaccurate.
It does - in fact it becomes seconds slower after only a minute or two.

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 11:29 am
by Joris
I'd be interested to hear what drove you to do this? I haven't found timers to be inaccurate; although in one project, I had to resort to using MSWindow's timers, but I don't recall why...
You didn't question me, but... for music, movies etc. precise timing is needed.

Using threads like more people do recommend has my interest : http://www.purebasic.fr/english/viewtop ... 13&t=55461
But I still don't know how to do this with only PB commands, as the helpfiles says for :
* ElapsedMilliseconds() => The absolute value returned is of no use since it varies depending on the operating system;
* Delay() => The delay time in milliseconds. The actual delay may be longer than the specified time;
* AddWindowTimer() => Note: The timer events will only be generated when there are no other events to be processed (timers are low-priority events).

So, how to do it with threads and having accurate timeintervals of 1/1000 sec looping for minutes or more... (with only PB commands) ?
(Do I need to create another separated topic for this ?)

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 11:53 am
by IdeasVacuum
Hi Joris - nope, your question is on 100% topic as BasicallyPure is tackling timer accuracy using threads.

It would be great to see Fred/Freak's input on this subject. There are unknowns. Jassing is querying the dependability of the PB Post Event for example. On the other hand, it looks like BasicallyPure generally has the timing faults covered (A built-in error correction removes accumulated errors from timer events).

So, with some refinement of the code to make it more solid - i.e. User_Russian's suggestions, AccuTimer() looks to be near your requirement. I say near because I know that when processing movie frames for edit, sometimes the time span can be very short - not sure if the error correction kicks-in for very short spans, something for you to experiment with and report back?

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 5:58 pm
by BasicallyPure
I believe that AccuTimer will post events with millisecond accuracy but there will be some variable delay while the event works its way through the event queue.
I tried some experiments that indicated the received events could vary as much as 12 milliseconds. This number should not be taken as an absolute because my experiment probably did not reflect any worst case scenario.
The point of AccuTimer is that over longer periods of time it will transmit the correct number of events. If you choose a timeout value of 1000 mS then after 1 minute you will get 60 events and after 1 hour you will get 3600 events, no more no less. This is not the case if you use AddWindowTimer().
There is one exception I have noticed; if you hold the mouse button down on the title bar of a window some of the events will be missed. This is true of windows but on Linux I did not notice this problem.

User_Russian has indicated that I need to use a mutex. To demonstrate a problem his example used four threads that each use the same thread procedure but AccuTimer only has one thread using my timer procedure. I am reluctant to place a lock/unlock mutex inside the timer thread because of the overhead it will add. I was wondering if the lock/unlock mutex can be done outside the thread in the main program. The support procedures that infrequently modify the map contents would be my preferred way of handling this. As an experiment I added the lock/unlock mutex inside the PauseAccuTimer() procedure. The compiler did not complain and the program ran without problems but I have no way of knowing if the mutex is actually working.

A question I have is; what protection do you get by using the 'Create threadsafe executable' compiler option?

BP

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 6:12 pm
by User_Russian
In your program two thread and therefore will still be bugs.

Code: Select all

Global NewMap TestList.s() 

Procedure Thread(Number) 
  Repeat 
    FindMapElement(TestList(), "Thread")
    Delay(Random(400)+200) 
    Debug "Thread - "+TestList() 
  ForEver 
EndProcedure 


TestList("Main") = "Main" 
TestList("Thread") = "Thread" 

CreateThread(@Thread(), 0)  

Repeat 
  FindMapElement(TestList(), "Main")
  Delay(Random(400)+200) 
  Debug "Main - "+TestList() 
ForEver 
Any access to a map, you need to protect with a mutex.

Code: Select all

Global NewMap TestList.s()
Global Mutex = CreateMutex()

Procedure Thread(Number) 
  Repeat 
    LockMutex(Mutex)
    FindMapElement(TestList(), "Thread")
    Delay(Random(400)+200) 
    Debug "Thread - "+TestList() 
    UnlockMutex(Mutex)
  ForEver 
EndProcedure 


TestList("Main") = "Main" 
TestList("Thread") = "Thread" 

CreateThread(@Thread(), 0)  

Repeat 
  LockMutex(Mutex)
  FindMapElement(TestList(), "Main")
  Delay(Random(400)+200) 
  Debug "Main - "+TestList()
  UnlockMutex(Mutex)
ForEver 

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 6:39 pm
by BasicallyPure
Would it be better to place the delay outside of the locked mutex otherwise the mutex is locked most of the time.

Code: Select all

Procedure Thread(Number) 
  Repeat 
    LockMutex(Mutex)
    FindMapElement(TestList(), "Thread")
    Debug "Thread - "+TestList() 
    UnlockMutex(Mutex)
    Delay(Random(400)+200) ; <--- better outside of LockMutex? 
  ForEver 
EndProcedure 
BP

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 7:00 pm
by User_Russian
Delay is only necessary to demonstrate the bug. In the real code is not needed.

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 7:16 pm
by BasicallyPure
Delay is only necessary to demonstrate the bug. In the real code is not needed.
Without delay cpu load is high.

My timer thread does have a delay so is it better to have it inside or outside the locked mutex?
I think outside is better.
Would this be acceptable?

Code: Select all

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

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 7:33 pm
by User_Russian
BasicallyPure wrote: Would this be acceptable?
Yes.
Protect only need access from map, and not all of the code thread.

It is also necessary to protect access to the map in all procedures, working with the map, for example.

Code: Select all

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

Re: AccuTimer().pbi, a replacement for AddWindowTimer()

Posted: Sun Jul 21, 2013 9:58 pm
by BasicallyPure
User_Russian wrote:It is also necessary to protect access to the map in all procedures
I have updated the original post with the mutex additions.
Thank you for the help.
BP