SimpleTimer (single thread and threadsafe variations)

Share your advanced PureBasic knowledge/code with the community.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

SimpleTimer (single thread and threadsafe variations)

Post 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.
Last edited by Rescator on Tue Apr 12, 2005 8:52 pm, edited 4 times in total.
Tommeh
Enthusiast
Enthusiast
Posts: 149
Joined: Sun Aug 29, 2004 2:25 pm
Location: United Kingdom

Post by Tommeh »

Nice one Rescator, and well documented too 8)

I'm gonna fit this into a couple of my apps, thanks
Fred
Administrator
Administrator
Posts: 18253
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

Good one :)
User avatar
NoahPhense
Addict
Addict
Posts: 1999
Joined: Thu Oct 16, 2003 8:30 pm
Location: North Florida

Re: SimpleTimer

Post by NoahPhense »

Very nice..

- np
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post by Psychophanta »

Great, Rescator :D
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post 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...
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post 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()
Post Reply