(PB) Timeout Library

Posted by on December 12, 2012

Intro

This simple “timeout” library allows you to easily perform timed operations in your games and applications. Thanks to the callbacks you can also perform arbitrary functions within each update cycle.

The library is very simple to use, you must define a maximum timeout period and an update rate when you create an instance of TIMEOUT.

The “maximum” is in milliseconds and corresponds to the timeout period, for example 1000 would be 1 second timeout.

The “update_rate” sets how many updates there will be within 0 and the “maximum” in this case 1000), if we were to use 10 updates then we would experience the callback to be called every 100ms.

Of course both the callback and the user variables are optional, but they’re a handy feature.
 

Simple use case:

Define.TIMEOUT *timeout = timeout_create( 1000 * 5, 100 ) ; 5 seconds maximum timeout, 100 updates a second.
If *timeout

	Debug "starting"
	Repeat
		timeout_update( *timeout )
		Debug "update"
	Until timeout_expired( *timeout )
	
	If timeout_expired( *timeout )
		Debug "timed out"
	EndIf
	
	timeout_destroy( *timeout )
EndIf

 
One of the many uses I have for this library includes waiting for a server to respond, sending timed events to clients, etc. I even use it to send notices to every client on the server before the server actually shuts down! (game server)
 
You can actually select whether the library should perform an actual sleep / delay operation to save cycles ( for example in cases where you have a waiting loop it’s ideal to sleep for a given period of time instead of wasting those cycles ) or you can have it compare old vs new time instead of performing a real delay. On timeout_update() the second parameter defines this behavior.
 

The library:

EnableExplicit

Structure TIMEOUT
	
	delay.i 			; based on how many updates a second are required, this holds the delay value in ms.
	current.i 			; the current timeout value in ms, this is the "counter" and uses the "delay" to increment.
	maximum.i 			; the timeout value in ms
	update_rate.i 			; holds the update speed in times per second
	
	old_time.i			; the old time in milliseconds
	new_time.i			; the new, current time in milliseconds
	
	*callback.i			; user callback pointer, we can't cast it as a timeout_callback prototype here for obvious reasons.
	user_variable.i			; a custom user variable, could be a pointer or anything at all, it's accessed through the instance ptr sent to the callback.
	
EndStructure

Prototype.i timeout_callback( *this.TIMEOUT )

Procedure.i timeout_create( maximum.i, update_rate.i, *callback.timeout_callback = #Null, UserVariable.i = #Null )
	
	Define.TIMEOUT *this = AllocateMemory( SizeOf(TIMEOUT) )
	; set all the pertinent values to this structure
	If *this
		With *this
			\maximum 	= maximum
			\update_rate 	= update_rate
			\delay		= ( 1000 / update_rate )
			\current	= 0
			\old_time	= 0
			\new_time	= 0
			\callback 	= *callback
			\user_variable 	= UserVariable
		EndWith
		
		ProcedureReturn *this
	EndIf
	
EndProcedure

Procedure.i timeout_destroy( *this.TIMEOUT )
	
	; check whether the pointer is not a null and proceed to free the memory.
	If *this
		FreeMemory( *this )
		ProcedureReturn 
	EndIf
	
EndProcedure

Procedure.i timeout_update( *this.TIMEOUT, Should_Delay.i = #True )
	
	If *this
		If Should_Delay
			
			Delay( *this\delay )
			*this\current + *this\delay
			ProcedureReturn *this\current
			
		Else
			
			*this\new_time = ElapsedMilliseconds() ; obtain the current time
			If *this\new_time > *this\old_time + *this\delay ; compare if the new time is bigger than the old time plus the delay factor
				; if so, update the old time with the new time and increase the current variable by the delay value.
				*this\old_time = *this\new_time
				*this\current + *this\delay
				; is there a callback? if so, call it.
				If *this\callback
					Define.timeout_callback *cb = *this\callback
					*cb( *this )
				EndIf
				; return the current value, in cases where the timeout has an update rate / interval of 1, we'll always get the full amount.
				ProcedureReturn *this\current
			EndIf
			
		EndIf
	EndIf
	
EndProcedure

Procedure.i timeout_reset( *this.TIMEOUT, WipeUserVal.i = #False )
	
	If *this
		; Wipe out the changing variables
		With *this
			\current	= 0
			\new_time	= 0
			\old_time	= 0
		EndWith
		; Optionally wipe out the user variable, this defaults to false.
		If WipeUserVal
			*this\user_variable = #Null
		EndIf
	EndIf
	
EndProcedure

Procedure.i timeout_expired( *this.TIMEOUT )
	
	; Has the timeout expired?, if so return true (1).
	If *this
		If *this\current => *this\maximum
			ProcedureReturn #True
		EndIf
	EndIf
	
EndProcedure

 

That’s all, enjoy!

If you have any comments, feel free to leave them here!
Any additions to the code will be welcomed.

Cheers.

 

1 comment on (PB) Timeout Library

Closed

  1. GuShH says:

    A general note about the library, if you’re using callbacks there is no internal check on whether the timeout has expired or not. So the function will still be called after the fact and nothing will stop it from overflowing. The way you handle this is by performing the check inside the callback, ie. timeout_expired() and if the condition is true, you can either reset the timer or do whatever you want with it.

    As an actual example let’s say you’ve got a game where movement is limited by the speed of the character, which is in turn dictated by it’s level. Thus, you’d need a timer to limit the user input based on this speed factor. In this case you’d check whether the timer has expired inside the callback and if so, reset it. After this condition you’d have the interpolation code for the screen point of this character. Inside this very same function you could also set or reset the “is_walking” flag as well.

    Extended behaviour could be added and the expired check could be an optional upon instantiation of the library, however that’s something you could still perform on your own inside the callback, so it’s up to the specific requirements of each individual since no one library can cater to everyone.