Page 1 of 1

SimpleTimer (single thread and threadsafe variations)

Posted: Thu Apr 07, 2005 12:46 pm
by Rescator
Code updated for 5.20+ (same as AddWindowTimer())

See *new* threadsafe implementation further down!

Old implementation:

Code: Select all

Structure SimpleTimer
  delay.i
  *variable
EndStructure

Procedure SimpleTimer_Thread(*simpletimer.SimpleTimer)
  delay=*simpletimer\delay
  *variable=*simpletimer\variable
  *simpletimer\delay=0 ;We have copied our values, signal SimpleTimer()
  Delay(delay)
  PokeI(*variable,delay)
EndProcedure

Procedure SimpleTimer(delay,*variable)
  Static simpletimer.SimpleTimer
  simpletimer\delay=delay
  simpletimer\variable=*variable
  CreateThread(@SimpleTimer_Thread(),@simpletimer)
  Repeat ;Wait until the thread signal us back.
    Delay(1)
  Until simpletimer\delay=0 ;Thread is setup and ready, we may continue.
EndProcedure
Then when you wish to start a timer do this n your code:

Code: Select all

delay.l=0
SimpleTimer(10000,@delay)
In this example SimpleTimer will wait ca. 10 seconds (time given in milliseconds, 1sec = 1000 ms),
when 10 seconds has passed, the SimpleTimer thread
will get out of it's delay/wait, and set the referenced (@delay) variable
to the same value as was given at timerstart.

As you can see SimpleTimer uses a static structure to pass the delay and variable pointer,
this is needed since the delay/wait is prformed in a seperate thread.
To avoid thread issues the structure is only used a fraction of a second to
pass the procedure arguments.
So unless you start a lot of timers at the exact same time,
SimpleTimer should be ok to use.

SimpleTimer is not intended for timecritical stuff,
but it is rather nice for other things:
Time delayed Splash screens,
popup timeouts,
events in games (like weather change, music change, sfx)
events in programs (autosaves, network check, memory check, etc)

This timer should also be platform independent,
so Windows, Linux, Mac and Amiga should all be able to use SimpleTimer.

There is no need to kill the timer as it dies on it's own.
One thread will be spawned per timer you use,
but as these will be delay/waiting threads, resource use should be very low.

The advantage of the variable passed as a reference with @
is that the delay/wait thread can set this variable to the waited value.
Thus you can do super simple checks in the program mainloop,
like if you reuse the "timer" maybe instead of setting delay2=0 (see below)
One could instead do that but also set a new timer tied to delay2,
thus reuse the delay2 variable.

Incomplete Example:

Code: Select all

     delay=0
     SimpleTimer(10000,@delay)
     delay2=0
     SimpleTimer(5000,@delay2)
     Repeat
      event=WindowEvent()
      If event
       gadget=EventGadgetID()
       If gadget=#Gadget_Splash
        If delay2<>0 : Debug "5 sec" : delay2=0 : EndIf
        If EventType()=#PB_EventType_LeftClick : event=#PB_Event_CloseWindow : EndIf
       EndIf
      EndIf
      Delay(1)
      If delay<>0 : event=#PB_Event_CloseWindow : EndIf ;no need to set delay=0 here as we'll quit anyway
     Until event=#PB_Event_CloseWindow
Here is what should be a threadsafe implementation of the SimpleTimer() and SimpleTimer_Thread() functions,
behaviour/use is same as above so this is a drop in replacement.

Threadsafe implementation:

Code: Select all

Procedure SimpleTimer_Thread(*simpletimer_mem)
 Delay(PeekI(*simpletimer_mem))
 PokeI(PeekI(*simpletimer_mem+4),PeekI(*simpletimer_mem))
 FreeMemory(*simpletimer_mem)
EndProcedure

Procedure SimpleTimer(timeout,*variable)
 *simpletimer_mem=AllocateMemory(SizeOf(Integer))
 If *simpletimer_mem
  PokeI(*simpletimer_mem,timeout)
  PokeI(*simpletimer_mem+SizeOf(integer),*variable)
  If CreateThread(@SimpleTimer_Thread(),*simpletimer_mem)
   ProcedureReturn #True
  EndIf
 EndIf
 ProcedureReturn #False
EndProcedure
SimpleTimer() returns #True if successful, and #False if it failed.
(memory allocation failed, or starting thread failed)

Have fun folks, and hope this was usefull to some :)
And if any major flaws in this SimpleTimer, or if you got improvements by all means post here.

Posted: Thu Apr 07, 2005 1:34 pm
by Tommeh
Nice one Rescator, and well documented too 8)

I'm gonna fit this into a couple of my apps, thanks

Posted: Thu Apr 07, 2005 2:14 pm
by Fred
Good one :)

Re: SimpleTimer

Posted: Thu Apr 07, 2005 10:17 pm
by NoahPhense
Very nice..

- np

Posted: Mon Apr 11, 2005 1:33 pm
by Psychophanta
Great, Rescator :D

Posted: Tue Apr 12, 2005 7:40 pm
by Rescator
Heh thanks!

The first implementation that I "almost" posted I had forgotten to check
what happen if two timers was used/started right after eachother.
amusing result but bad hehe!

So I had to add that tiny loop and value set in the thread,
to make it you know, semi-thread safe heh!
It's not fully thread safe due to that structure,
so odd things could probably happen if a SimpleTimer was
created at exactly the same split second in two different program threads.

I may try and improve that later, unless someone else has a few tricks to
make this timer fully threadsafe (while remaining platform independent, thats the hard part here).

Only "solution" I can think of is to dynamically allocate memory for the structure, and let the timer thread itsself free that memory when it has no more use for it. (i.e. after the Delay() has ended and the Long has been set, then free the structure.
Only solution I can think of, I wanted to avoid using a global timer list and stuff,
and also wanted to avoid having init and kill functions,
so a single function to use and a single variable that change state when time is up seems the cleanest solution...

Posted: Tue Apr 12, 2005 8:47 pm
by Rescator
Just made a threadsafe implementation, also added som checks in SimpleTimer() (see updated first post)

Only possible improvements I can think of is maybe checking if *variable is 0 or not to avoid memory pointer issues if it's 0.
Altough that is something the programmer should really know themselfs.

If anyone has further improvements please fire away.
The threadsafe variation of the timer is still platform independent
yet still threadsafe thanks to no dependancies.
(the stucture isn't needed at all any more)

Here is a fuller example of SimpleTimer() in use.
Replace "splash.png" with whatever picture you prefer (doublecheck that the image size/window size etc match first thouh.

Code: Select all

#Window_Splash=0
#Gadget_Splash=0
#Image_Splash=0

Procedure SimpleTimer_Thread(*simpletimer_mem)
 Delay(PeekL(*simpletimer_mem))
 PokeL(PeekL(*simpletimer_mem+4),PeekL(*simpletimer_mem))
 FreeMemory(*simpletimer_mem)
EndProcedure

Procedure SimpleTimer(timeout.l,*variable.l)
 *simpletimer_mem=AllocateMemory(8)
 If *simpletimer_mem
  PokeL(*simpletimer_mem,timeout)
  PokeL(*simpletimer_mem+4,*variable)
  If CreateThread(@SimpleTimer_Thread(),*simpletimer_mem)
   ProcedureReturn #True
  EndIf
 EndIf
 ProcedureReturn #False
EndProcedure

Procedure Splash()
 If OpenWindow(#Window_Splash,0,0,320,240,#WS_POPUP|#PB_Window_ScreenCentered|#PB_Window_Invisible,"")
  If CreateGadgetList(WindowID(#Window_Splash))
   UsePNGImageDecoder()
   If LoadImage(#Image_Splash,"splash.png")  
    If ImageGadget(#Gadget_Splash,0,0,320,240,UseImage(#Image_Splash))
     Delay(1)
     HideWindow(#Window_Splash,0)
     gadget=#False
     timeout1.l=0
     SimpleTimer(10000,@timeout1)
     timeout2.l=0
     SimpleTimer(5000,@timeout2)
     Repeat
      event=WindowEvent()
      If event
       gadget=EventGadgetID()
       If gadget=#Gadget_Splash
        If EventType()=#PB_EventType_LeftClick : event=#PB_Event_CloseWindow : EndIf
       EndIf
      EndIf
      Delay(1)
      If timeout1<>0 : event=#PB_Event_CloseWindow : EndIf
      If timeout2<>0 : Debug "hihi" : timeout2=0 : EndIf
     Until event=#PB_Event_CloseWindow
     FreeGadget(#Gadget_Splash)
    EndIf
    FreeImage(#Image_Splash)
   EndIf
  EndIf
  CloseWindow(#Window_Splash)
 EndIf
EndProcedure

Splash()
Run in debug mode to see multiple simultanious use of SimpleTimer()