Request for help making an ingame Schedule Manager

BlitzMax Forums/BlitzMax Beginners Area/Request for help making an ingame Schedule Manager

Hey all... scroll down a few boxes and you'll see the actual program. Everything is working exquisitely!


Thank you again to Perturbatio who pointed me towards reflection.. what a great module!


Original Post Below...
------------------------------------------------------------------------------------------------------
Hi everyone. I've been playing with a version of blitz3d they gave me at school for awhile now, but finally decided to step it up and buy my own copy of BlitzMax. I'm very glad I did!


I'm currently building a trading game, where the player can navigate his ship through a small solar system, visiting different planets and stations, and buying low/selling high. The main point of the game is to build an entirely dynamic economy. No faking allowed!

Anyway, one of the things I haven't quite figured out is how to implement a 'Schedule'. Here's the example:

Each station/planet is an instantiation of my custom 'TStation' type. Those instantiations have methods such as "produce" or "consume", etc. I would like for some stations to "produce" something every 30 seconds, for example.

So what I need to build is a scheduler, to handle the timing of all these production events. This way, when the appropriate time rolls around for something to be produced, the proper method will be called by the proper object, with the proper parameters.


Now, I've thought of a number of ways to do this. The simplest is to simply compile a list of events and run through them once every second (seconds are the smallest unit of schedulable time). If the system time is greater than the 'scheduled time', then execute the event.

Unfortunately, this would be far too slow. I plan on having at most 1000 stations available, each with multiple future events scheduled at a time. this would result in potentially having to loop through ~4000 scheduled events per tick! No thanks.


So, the current method I'm exploring creates an array - one for every second of time. Of course, I need to limit how far in the future an event can be scheduled, but that's fine. An hour in advance is more than enough - meaning the array only needs to be 3600 seconds (elements) long.

That 'time array' will contain dynamically created TLists of events (so that multiple events can happen at once).

The problem is, with no method pointers available, how in the world can this be done! I suppose I need to somehow pass the 'target' object to the array, along with the 'target' method and parameters. But this is where I'm stuck.

It's probably possible to do this by building another custom type (a "MyEvent" type), which stores the object and the method and the parameters in its fields, all as global objects so I can use a function pointer to that 'MyEvent' object. But how again, how do I point to the particular method I want called?

Perhaps I could make a "TFunctions" type, and store all the methods for my station type in that TFunction type as functions, thus being able to use function pointers to refer to those functions. This way, I'd be able to pass the function pointers to the "MyEvent" object, and separately pass the target object.

If I did this, I wonder if I could even include that "TFunciton" type as a field within my 'station' type.. That's a side question though.


All of these 'options' seem so horribly, unnecessarily complex. All that is needed is a simple method pointer :(

So, are there any suggestions? I'm kinda hoping someone has built something like this Scheduler before and might know of a much simpler way to go about it. Again, it would be easy with method pointers, but as far as I understand we don't have those in BlitzMax.


Thank you for taking the time to read this post!

-NAVY

Well, if it is only a single-player game, which it appears to be, you could just record the last time the player was there and compute the "stock" for each station next time the player arrives.

I agree with Khomy Prime. No need to do all of it real time. When the player docks at the station just check the last visited time and then indeed do your calculations.

Are you really planning on running 1000 stations at the same time? I don't know how much routines they'll be executing but I don't think that would be very efficient. Considering there will be a lot more objects as well.

Took me a while to read through the original post and I'd have to agree with the solution offered. There's absolutely no need to run through a huge data structure (if you have 1000 stations it would be a complete resoure hog for starters and your game could crawl) when you can just compare times since last visit and calculate on the fly.

The only scenario where you might have to do a bit more work than just checking the current station when you land is where you perhaps generate a list of stations on screen to work out where you're travelling to next. In that situation you would need to be looking at more than one sites data. But that could be got around by showing only the local stations to you rather than all of them (and you could cheat a little by saying other stations are out of range for example).

Failing that you could do something like Elite and just create random data each time you land on a station but that kind of goes against your idea of having a "realistic" economy and development progress so probably not such a good idea.

Thank you for the replies, but unfortunately the solutions posed will not work.

The economy is full of NPC traders, who make their decisions based upon the information in real-time. It IS, effectively, a multiplayer game.


I guess my main request is for any help calling a method from an array, i.e. using a pointer. Has anyone established a good way to do this yet?

Thanks again for trying to help

Does this help any?
SuperStrict

Type TCallable
	
	Field X:Int
	
	Method callMe(){callable=1}
		Print "I've been called!"
	End Method
	
End Type

Local callable:TCallable = New TCallable

Local id:TTypeId = TTypeId.ForObject( callable)

Local call:TMethod = id.FindMethod( "callMe" )

call.Invoke(callable, null)


Wow, that simple? Neat..

So, I take it that any method without {callable=1} cannot be 'invoked'..?

Is the second parameter of Invoke is an object array that will be passed to the function?

Thank you very much for your assistance..

NO, all methods found by id.FindMethod( "callMe" )

When you want to look at the metadata {callable=1} try this:
SuperStrict

Type TCallable
	
	Field X:Int
	
	Method callMe() {callable=1}
		Print "I've been called!"
	End Method

	Method DontCallMe() {callable=0}
		Print "I've been called!"
	End Method

	Method BestMethodEver() {callable=1}
		Print "I've been called!"
	End Method
	
End Type

Local callable:TCallable = New TCallable

Local id:TTypeId = TTypeId.ForObject( callable)

For Local i:TMethod = EachIn id.EnumMethods()
	
	Print i.MetaData("callable")
	If i.MetaData("callable")="1" Then i.Invoke(callable, Null)

Next



Ohh, I understand now. The metadata should come in quite handy, too.

Again, thanks both of you - this is just the thing I need.

-NAVY

Well, you wouldn't have to check every event and every station if you create a queue that stores the times in sorted order. You only have to check one event to see if it has elapsed. If so, then you check the next and the next until all elapsed events have been handled. Part of handling a continuous event would be to schedule it again once you are done with handling it.

Since all stations should be the same object, you shouldn't have a problem with calling the event or knowing what parameters to pass.

You could have a very simple structure like this:

Type TStationEvent
TimeAmount:Float ' Stored in Game Time
Station:TStation
End Type

You would only require one event stored for each station. It would be best if each station had some randomization like the occasional manufacturing problem that shutsdown the plant for a short amount of time. If you do this, then you just add an EventTypeID to your structure and decide how to handle the event when you pass it to the Station object.

Once the Game Time has elapsed for the first event, you dequeue the event and call your method from the Station field. Then reschedule the event into your queue. You can either keep looping or allow the main logic loop to continue and catch the next elapsed event the next pass through. This way there isn't long pauses from multiple events and the amount of time between station events is large enough that most computers should be able to loop through 1000 stations in that amount of time. You might get a couple events that are slightly older than they should be but to the player, they wont notice since it would only be a small fraction of a second.

Redspark:

This is one of the options I considered. I'm curious how slow the sorting routine would be, however. I think my initial post was a bit long-winded and confusing. Let me clarify what method I'm proposing:


-Create 3600-element array, with each element representing one second. The array 'loops' using enqueue and dequeue points.

-Each second, check the next element in the array, execute any events stored within that particular element (wipe the events when done).

-When adding events, calculate where that event should be placed in the 3600-element array. Place it there. If an event already exists in that array element, simply add the new event to the end of a TList (technically the 3600-element array stores TLists of ScheduleEvents, not sole ScheduleEvents).



For example, say our game has progressed so that the current time is five minutes - this is element 299 (if you start from 0). If a new event is created which should execute in 30 seconds, that event will be added to time-array cell # 329.

If we're at 59 minutes and 30 seconds, or element #3569, no problem. The array loops. So an event scheduled for 60 seconds into the future will simply be placed in element# 029.



Obviously, I'll have a master timer which fires an event every 1 second, and a hook function which intercepts that event and executes the schedule manager, which will call any scheduled events.

This implementation will be very fast, since it will require absolutely no sorting, no looping through events or stations, and very minimal 'placement calculations'. The obvious disadvantage is that events may not be scheduled more than one-hour in advance. Of course, you could always increase the size of the time array if neccessary.



Is anyone able to explain to me how large this array is in memory?

Namely, it is a 3600-element object pointer array (since any events scheduled are simply the first element in a TList.

These TLists are created dynamically as needed. So in all likelihood, a majority of the cells will not have any data. How much RAM do these 'empty' cells consume?

Seems like I can do this:

Local id: TTypeId = TTypeId.ForName("tmytype") 
Local call: TMethod = id.FindMethod("add")



And then simply use 'call' for the 'add' method for ever single object of type 'tmytype.' In other words, it seems like I don't have to create a new TTypeId and TMethod for each instance.

That probably shouldn't work, but it appears to. Is there a problem I'm not seeing?



EDIT: Just realized the above wouldn't be useful in my code, since I'd have to somehow pass the specific object into my schedule array anyway.

I think I'm going to create a delegate class, just found one which Mark Sibly described in his worklog

Woot! Everything is working quite well.

I'm going ahead and building a pretty generalized Scheduler, just 2 types.

Will post when done. Thanks again jsp, perturbatio, and everyone else too.

Great Success!

I will post this in the Showcase forum, but I'd like some feedback first.


Here is the code and a short description. Full code including an example program (fully runnable from the get-go) is included below that.



--------------------------------------------------------------------------------------------------------------------------


Event Scheduling Program
Bryan Fishman (USNavyFish), AUG 2008


This program was designed to provide an easy means of scheduling the execution of events within a game
or simulation. The system was designed for minimal overhead. It is not designed for extremely high
time sensitivity, and use of a time resolution less than one hundredth of a second is not recomended.

Essentially, the system provides an array where each cell represents one 'moment' of time. When events
are scheduled, this system places them in the appropriate cell which represents the desired time of execution
for that event. The system then simply checks one cell per 'moment' and executes any event stored there.
In this manner, the system can support an extremely large number of concurrent and overlapping events, without
any performance loss, as the program is not required to loop through all scheduled events during every cycle.


This program provides two Custom Types:

A"TFutureEvent" type with the following functions, methods, and fields:
|| Field obj_:Object The targe objet whose method will be called
|| Field method_:TMethod This is the specific method which will be called
|| Field args_:Object[] An object array containing the arguments to be passed
||
|| Function Create:TFutureEvent( obj:Object,methodName:String,args:Object[] )
|| -This function creates and returns the FutureEvent.
|| -'methodName:String' is the exact name of the method that will be called
||
|| Function invoke:object() executes the FutureEvent.


A "TScheduler" type with the following functions and Globals (There are no Methods or locals)

|| Global TimeArray_Length:Int Size of the TimeArray (# of cells)
|| Global TimeArray_StepDuration:Float Duration of time represented by each cell

|| - Note that (Length * Duration) yields the furthest schedulable time
|| - i.e. 3600 cells of 1 second duration each allows events to be scheduled
|| up to one hour (inclusive) in advance.
||
|| Global TimeArray:TList[] Each cell stores a list of scheduled TFutureEvents
|| Global CurrentIndex:Int Current Index of the Time Array
|| Global ScheduleTimer:TTimer System Timer for Scheduler object
||
|| Function Initialize(Length:Int = 3600 , Seconds:Float = 1)
|| - Call this once at the beginning of your program
|| - For a schedule that can store events up to 24 hours (inclusive) into the future,
|| and that has a schedule resolution of 0.1 seconds, use the following parameters:
|| Length = 3600 * 24 * 10
|| Seconds = 0.1
|| - Each Index specified by Length requires 4 bytes of memory. The above example requires
|| a total of ~3.3 MB of memory at runtime.
||
|| Function Begin()
|| - Activates the system timer
|| - Call this once you wish to actually begin schedule execution.
|| - Events may be scheduled both prior to and during the schedule's execution
||
|| Function ScheduleTimerHook:Object(id:Int, data:Object, context:Object)
|| - Internal funciton, intercepts system timer hook (returns null if intercepted)
|| - Calls the ScheduleAdvance and ScheduleExecute functions
||
|| Function ScheduleAdvance()
|| - Internal function, advances current Schedule Index
||
|| Function ScheduleExecute(index:Int)
|| - Internal Function, executes all events scheduled for current time index.
|| - This function removes all references to the scheduled events once executed
||
|| Function ScheduleEvent(event:TFutureEvent, SecondsDelay:Float)
|| - Call this function to schedule an event which will execute once in the future,
|| after the number of seconds specified by SecondsDelay has passed.
|| - For example, when called with SecondsDelay = 20, the passed event will execute
|| approximately 20 seconds after the "ScheduleEvent" function is called
|| - Scheduling accuracy reduces as "SecondsDelay" approaches the TimeArray_StepDuration
|| For best results, use a TimeArray_StepDuration value at least twice as short as
|| your quickest-scheduled events. For example, a program that regularly schedules
|| events at intervals as short as 1 second apart should use a StepDuration value of
|| at most 0.5 seconds. A value of 0.1 seconds wil provide much better accuracy.



Example usages of the scheduler function:


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "textoutput" , ["You Called Me!"]) , 2)
||
||This calls obj1's "textoutput" method, with the input parameter "You Called Me!", in two seconds from this call.


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "add" , [String(1)]) , 4)
||
||This calls obj1's "add" method, with the input parameter of 1. Note the floating point value must be converted
||into a string to be accepted by the function. This event will execute in four seconds.


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "multiparam" , [String(4.3),String(17),"TEXT"]) , 20)
||
||This calls obj1's "multiparam" method, which accepts a float, an int, and a string as inputs to the method.
||Note how any non-string parameter inputs must be converted to strings using the String() function. This
||event will be executed in 20 seconds.


--------------------------------------------------------------------------------------------------------------

Program Code (No example code):

SuperStrict


Type TFutureEvent
	
	Field obj_:Object 
	Field method_:TMethod
	Field args_:Object[]
	
	Function Create:TFutureEvent( obj:Object,methodName:String,args:Object[] )
    		Local temp:TFutureEvent=New TFutureEvent
    		temp.obj_ = obj
    		temp.method_ = TTypeId.ForObject(obj).findMethod( methodName )
		temp.args_ = args
		
    		Return temp
  	End Function

	Method Invoke:Object() 
		Return method_.Invoke( obj_,args_ )
  	End Method
	
End Type



Type TScheduler
	Global TimeArray_Length:Int
	Global TimeArray_StepDuration:Float
	Global TimeArray:TList[]
	Global CurrentIndex:Int
	Global ScheduleTimer:TTimer	
	
	
	Function Initialize(Length:Int = 3600 , Seconds:Float = 1)
		DebugLog "Master Schedule Initialized"
		DebugLog "Schedule Outlook:    " + Length / Seconds + " Seconds"
		DebugLog "Schedule Resolution: " + Seconds + " Seconds"
		
		TimeArray_Length = Length + 1		'One is added so total duration is inclusive
		TimeArray_StepDuration = Seconds
		TimeArray = New TList[TimeArray_Length]
		CurrentIndex = -1
		
	End Function


	
	Function Begin() 
		AddHook EmitEventHook , ScheduleTimerHook
		ScheduleTimer = CreateTimer(1 / TimeArray_StepDuration)
		DebugLog "Schedule Execution Commenced at System Time: " + CurrentTime() 
	End Function
			
	
	
	Function ScheduleTimerHook:Object(id:Int, data:Object, context:Object)
		
		Local ev:TEvent = TEvent(data)
		
		If ev = Null Then Return Null
		
		If ev.id = EVENT_TIMERTICK
			If ev.source = ScheduleTimer 
								
				ScheduleAdvance() 
				DebugLog "ScheduleTimer Fires. Executing Index["+CurrentIndex+"]"
				ScheduleExecute(CurrentIndex)
				
				Return Null
			
			EndIf
		EndIf
		
		Return data
	End Function
	
	
	
	Function ScheduleAdvance() 
		CurrentIndex = (CurrentIndex + 1)	Mod TimeArray_Length
	End Function
	
	
	
	
	Function ScheduleExecute(index:Int) 
		If TimeArray[index]
			For Local event:TFutureEvent = EachIn TimeArray[index]
				event.invoke
			Next
			
			TimeArray[index] = Null	'Removes all references to TList and stored TFutureEvents
		EndIf

	End Function
	
	
	
	Function ScheduleEvent(event:TFutureEvent, SecondsDelay:Float)
		Local index:Int
		index = Ceil(SecondsDelay / TimeArray_StepDuration)
			
		If index > TimeArray_Length
			DebugLog "Critical Error!~nObject:~t" + event.obj_.tostring() 
			DebugLog "Scheduled Time (" + SecondsDelay+") converts to " + index + " future cells"
			DebugLog "This exceeds the length of schedulable time (" + TimeArray_Length + " cells)"
			
			End
		EndIf
					
		index :+ CurrentIndex + 1	
		index :Mod TimeArray_Length
				
		
		If TimeArray[index] = Null Then TimeArray[index] = CreateList() 
		
		ListAddLast TimeArray[index] , event
		
		DebugLog "Event Scheduled for object $" + event.obj_.tostring() + " in " + SecondsDelay + " seconds."

	End function
	
		
	
End Type



End Function



Program Code WITH Example Code: Just copy/paste and run in Debug Mode.

Rem 
--------------------------------------------------------------------------------------------------------------

Event Scheduling Program
	Bryan Fishman (USNavyFish), AUG 2008


This program was designed to provide an easy means of scheduling the execution of events within a game
or simulation.  The system was designed for minimal overhead.  It is not designed for extremely high
time sensitivity, and use of a time resolution less than one hundredth of a second is not recomended.

Essentially, the system provides an array where each cell represents one 'moment' of time.  When events
are scheduled, this system places them in the appropriate cell which represents the desired time of execution
for that event.   The system then simply checks one cell per 'moment' and executes any event stored there.
In this manner, the system can support an extremely large number of concurrent and overlapping events, without 
any performance loss, as the program is not required to loop through all scheduled events during every cycle.  


This program provides two Custom Types:

	 A"TFutureEvent" type with the following functions, methods, and fields:
		|| Field obj_:Object	The targe objet whose method will be called
		|| Field method_:TMethod This is the specific method which will be called
		|| Field args_:Object[] An object array containing the arguments to be passed
		||
		|| Function  Create:TFutureEvent( obj:Object,methodName:String,args:Object[] )
		||		-This function creates and returns the FutureEvent. 
		||		-'methodName:String' is the exact name of the method that will be called
		||
		|| Function invoke:object() executes the FutureEvent.


	A "TScheduler" type with the following functions and Globals  (There are no Methods or locals)

		|| Global TimeArray_Length:Int   			Size of the TimeArray (# of cells)
		|| Global TimeArray_StepDuration:Float   	Duration of time represented by each cell
		
		||		- Note that (Length * Duration) yields the furthest schedulable time
		||		- i.e. 3600 cells of 1 second duration each allows events to be scheduled 
		||			up to one hour (inclusive) in advance.
		||
		|| Global TimeArray:TList[]		Each cell stores a list of scheduled TFutureEvents
		|| Global CurrentIndex:Int		Current Index of the Time Array
		|| Global ScheduleTimer:TTimer  	System Timer for Scheduler object
		||
		|| Function Initialize(Length:Int = 3600 , Seconds:Float = 1)
		||		- Call this once at the beginning of your program
		||		- For a schedule that can store events up to 24 hours (inclusive) into the future,
		||			and that has a schedule resolution of 0.1 seconds, use the following parameters:  
		||			Length = 3600 * 24 * 10
		||			Seconds = 0.1
		||		- Each Index specified by Length requires 4 bytes of memory. The above example requires
		||			a total of ~3.3 MB of memory at runtime.
		||
		|| Function Begin()
		||		- Activates the system timer
		||		- Call this once you wish to actually begin schedule execution.
		||		- Events may be scheduled both prior to and during the schedule's execution
		||
		|| Function ScheduleTimerHook:Object(id:Int, data:Object, context:Object)
		||		- Internal funciton, intercepts system timer hook (returns null if intercepted)
		||		- Calls the ScheduleAdvance and ScheduleExecute functions
		||
		|| Function ScheduleAdvance()
		||		- Internal function, advances current Schedule Index
		||
		|| Function ScheduleExecute(index:Int) 
		||		- Internal Function, executes all events scheduled for current time index.
		||		- This function removes all references to the scheduled events once executed
		||
		|| Function ScheduleEvent(event:TFutureEvent, SecondsDelay:Float)
		||		- Call this function to schedule an event which will execute once in the future,
		||			after the number of seconds specified by SecondsDelay has passed.
		||		- For example, when called with SecondsDelay = 20, the passed event will execute
		||			approximately 20 seconds after the "ScheduleEvent" function is called
		|| 		- Scheduling accuracy reduces as "SecondsDelay" approaches the TimeArray_StepDuration
		||			For best results, use a TimeArray_StepDuration value at least twice as short as
		||			your quickest-scheduled events.   For example, a program that regularly schedules
		||			events at intervals as short as 1 second apart should use a StepDuration value of
		||			at most 0.5 seconds. A value of 0.1 seconds wil provide much better accuracy.

		

Example usages of the scheduler function:


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "textoutput" , ["You Called Me!"]) , 2)
||
||This calls obj1's "textoutput" method, with the input parameter "You Called Me!", in two seconds from this call.


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "add" , [String(1)]) , 4) 
||
||This calls obj1's "add" method, with the input parameter of 1.  Note the floating point value must be converted
||into a string to be accepted by the function. This event will execute in four seconds.


TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "multiparam" , [String(4.3),String(17),"TEXT"]) , 20) 
||
||This calls obj1's "multiparam" method, which accepts a float, an int, and a string as inputs to the method.
||Note how any non-string parameter inputs must be converted to strings using the String() function.  This
||event will be executed in 20 seconds.

	
--------------------------------------------------------------------------------------------------------------
End Rem

SuperStrict


Type TFutureEvent
	
	Field obj_:Object 
	Field method_:TMethod
	Field args_:Object[]
	
	Function Create:TFutureEvent( obj:Object,methodName:String,args:Object[] )
    		Local temp:TFutureEvent=New TFutureEvent
    		temp.obj_ = obj
    		temp.method_ = TTypeId.ForObject(obj).findMethod( methodName )
		temp.args_ = args
		
    		Return temp
  	End Function

	Method Invoke:Object() 
		Return method_.Invoke( obj_,args_ )
  	End Method
	
End Type



Type TScheduler
	Global TimeArray_Length:Int
	Global TimeArray_StepDuration:Float
	Global TimeArray:TList[]
	Global CurrentIndex:Int
	Global ScheduleTimer:TTimer	
	
	
	Function Initialize(Length:Int = 3600 , Seconds:Float = 1)
		DebugLog "Master Schedule Initialized"
		DebugLog "Schedule Outlook:    " + Length / Seconds + " Seconds"
		DebugLog "Schedule Resolution: " + Seconds + " Seconds"
		
		TimeArray_Length = Length + 1		'One is added so total duration is inclusive
		TimeArray_StepDuration = Seconds
		TimeArray = New TList[TimeArray_Length]
		CurrentIndex = -1
		
	End Function


	
	Function Begin() 
		AddHook EmitEventHook , ScheduleTimerHook
		ScheduleTimer = CreateTimer(1 / TimeArray_StepDuration)
		DebugLog "Schedule Execution Commenced at System Time: " + CurrentTime() 
	End Function
			
	
	
	Function ScheduleTimerHook:Object(id:Int, data:Object, context:Object)
		
		Local ev:TEvent = TEvent(data)
		
		If ev = Null Then Return Null
		
		If ev.id = EVENT_TIMERTICK
			If ev.source = ScheduleTimer 
								
				ScheduleAdvance() 
				DebugLog "ScheduleTimer Fires. Executing Index["+CurrentIndex+"]"
				ScheduleExecute(CurrentIndex)
				
				Return Null
			
			EndIf
		EndIf
		
		Return data
	End Function
	
	
	
	Function ScheduleAdvance() 
		CurrentIndex = (CurrentIndex + 1)	Mod TimeArray_Length
	End Function
	
	
	
	
	Function ScheduleExecute(index:Int) 
		If TimeArray[index]
			For Local event:TFutureEvent = EachIn TimeArray[index]
				event.invoke
			Next
			
			TimeArray[index] = Null	'Removes all references to TList and stored TFutureEvents
		EndIf

	End Function
	
	
	
	Function ScheduleEvent(event:TFutureEvent, SecondsDelay:Float)
		Local index:Int
		index = Ceil(SecondsDelay / TimeArray_StepDuration)
			
		If index > TimeArray_Length
			DebugLog "Critical Error!~nObject:~t" + event.obj_.tostring() 
			DebugLog "Scheduled Time (" + SecondsDelay+") converts to " + index + " future cells"
			DebugLog "This exceeds the length of schedulable time (" + TimeArray_Length + " cells)"
			
			End
		EndIf
					
		index :+ CurrentIndex + 1	
		index :Mod TimeArray_Length
				
		
		If TimeArray[index] = Null Then TimeArray[index] = CreateList() 
		
		ListAddLast TimeArray[index] , event
		
		DebugLog "Event Scheduled for object $" + event.obj_.tostring() + " in " + SecondsDelay + " seconds."

	End function
	
		
	
End Type





Type TTestType
	Field Value_:Float
	Field name_:String
	
	Function Create:TTestType(objname:String)
		Local temp:TTestType = New TTestType
		temp.name_= objname
		temp.value_ = 0
		Print temp.name_ + " Created: " + temp.value_
		Return temp
	End Function
	
	Method add:Float(num:Float)
		value_:+ num
		Print name_ +"'s Counter increased to "+value_
	End Method
	
	Method textoutput:String(output:String) 
		Print name_ +" Says: "+output
	End Method	
	
	
End Type



Function KeyHook:Object(id:Int , data:Object , context:Object) 
	Local ev:TEvent = TEvent(data) 
	
	If ev = Null Then Return Null
	
	Select ev.id
		Case EVENT_KEYDOWN
		'DebugLog "KEYDOWN~t" + ev.data
		
			Select ev.data
		
				Case 49
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "textoutput" , ["You Called Me!"]) , 1)
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "add" , [String(1)]) , 1) 
 				
				Case 50
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "textoutput" , ["You Called Me!"]) , 2)
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "add" , [String(1)]) , 2) 
				
				Case 51
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "textoutput" , ["You Called Me!"]) , 4)
				TScheduler.ScheduleEvent(TFutureEvent.Create(obj1 , "add" , [String(1)]) , 4) 
				
					
			End Select
				
		Case EVENT_KEYUP
		'DebugLog "KEYUP~t"+ev.data
		
	End Select

	
	Return data
	
End Function		








Graphics 640 , 480

AddHook EmitEventHook , Keyhook


TScheduler.Initialize(3600*24*4,0.25)

Global obj1:TTestType = TTestType.Create("Object1") 

GCCollect
Print GCMemAlloced() + " Bytes Allocated"

Cls
Print "Press any key to begin!  Make all key presses to the graphics window."
DrawText "Press any key to begin!  Watch the console window.",0,0
Flip
WaitKey() 

TScheduler.Begin() 

Repeat

Cls	
DrawText "Press 1, 2, or 3 to schedule an event for 1, 2, or 4 seconds in the future" , 0 , 0
DrawText "Watch the Console for output." , 0 , 30
DrawText "Observe the TimeArray cells advancing once every " + TScheduler.TimeArray_StepDuration + " seconds." , 0 , 60
DrawText "Now Press a key, wait the appropriate amount of time,",0,90 
DrawText "And watch for the event response in the console!" , 20 , 110
DrawText "If you don't see anything, make sure you're running this in Debug Mode!",30,180

Flip

Until KeyHit(key_escape) 


End