Skip to content

Scheduler

GeckoN edited this page Jan 31, 2017 · 2 revisions

The scheduler is a class that can be used to schedule function calls for delayed execution. It allows you to set a function to be called, optionally with parameters, at a point in the future.

There are 2 ways to schedule a function: as a single timeout, or as a repeating interval. This allows you to have both one off and repeating execution.

Scheduling a function

Scheduling a function is fairly simple. Let's start by scheduling a function to execute once after a certain amount of time:

void Function1()
{
	g_Game.AlertMessage( at_console, "Function 1 at %1\n", g_Engine.time );
}

// ...

// In another function
// Call Function1 with no parameters after 5 seconds
g_Scheduler.SetTimeout( "Function1", 5 );

5 seconds after the timeout has been set, the function will be executed once.

Scheduling a function to be executed on an interval is similar, requiring only a few changes:

void Function2()
{
	g_Game.AlertMessage( at_console, "Function 2 at %1\n", g_Engine.time );
}

// ...

// In another function
// Call Function2 with no parameters after 5 seconds, repeating 10 times, with an interval of 5 seconds
g_Scheduler.SetInterval( "Function2", 5, 10 );

The first number is the interval, in seconds. The function will be executed starting at the current time + the interval. The second number is how many times to execute the function. Passing g_Scheduler.REPEAT_INFINITE_TIMES will cause it to repeat forever until stopped through other means.

Arguments and return type

It is possible to pass arguments to the function when scheduling a function. This is done by passing the arguments after the required arguments shown above.

/*
* Simple class to keep track of a counter
*/
class Counter
{
	uint m_uiCounter = 0;
}

void Function3( Counter@ pCounter )
{
	++pCounter.m_uiCounter;
	g_Game.AlertMessage( at_console, "Function 3 at %1, called %2 times\n", g_Engine.time, pCounter.m_uiCounter );
}

//Call Function3 with a counter after 3 seconds, repeating an infinite number of times, with an interval of 3 seconds
g_Scheduler.SetInterval( "Function3", 3, g_Scheduler.REPEAT_INFINITE_TIMES, @Counter() );

Here we schedule a function to be called with an interval of 3 seconds, repeating an infinite number of times, and having a single argument of the type Counter@. Argument state will persist over multiple executions, allowing you to to maintain state over each call.

Note: In order for objects to maintain state (be the same object), you must use handles. The argument itself must be a handle, and the object passed in must be converted to a handle (@Counter()) to avoid creating a copy. This is important to remember when writing functions that have persistent state.

The return type for all scheduled functions must be void. If you want to "return" a value after the function has been executed, use an argument with persistent state.

Modifying scheduler state

You can modify the state of the scheduler's data for tracking function execution using the CScheduledFunction class. This class represents a scheduled function and contains data on when the function should be next executed, as well as how often (interval) and how many times (repeat count). This allows you to alter the execution settings for existing functions.

We're going to modify Function3's code a bit so we can manipulate it:

CScheduledFunction@ g_pFunc3 = null;

void Function4()
{
	g_Game.AlertMessage( at_console, "Function 4 at %1\n", g_Engine.time );

	CScheduledFunction@ pThisFunc = g_Scheduler.GetCurrentFunction();

	if ( pThisFunc !is null )
	{
		g_Game.AlertMessage( at_console, "Next call time: %1, Repeat time: %2, repeat count: %3\n", pThisFunc.GetNextCallTime(), pThisFunc.GetRepeatTime(), pThisFunc.GetRepeatCount() );

		if ( pThisFunc.IsInfiniteRepeat() )
			pThisFunc.SetRepeatCount( 2 );

		if ( g_pFunc3 !is null )
		{
			g_Game.AlertMessage( at_console, "Has been removed: %1\n", g_pFunc3.HasBeenRemoved() );

			if ( pThisFunc.GetRepeatCount() == 1 )
			{
				// Allow one more execution
				if ( g_pFunc3.IsInfiniteRepeat() )
					g_pFunc3.SetRepeatCount( 1 );
			}

			if ( g_pFunc3.HasBeenRemoved() )
				@g_pFunc3 = null;
		}
	}
}

// In another function
// Call Function3 with a counter after 3 seconds, repeating an infinite number of times, with an interval of 3 seconds
@g_pFunc3 = @g_Scheduler.SetInterval( "Function3", 3, g_Scheduler.REPEAT_INFINITE_TIMES, @Counter() );

// Call Function4 after 10 seconds, repeating an infinite number of times, with an interval of 10 seconds
g_Scheduler.SetInterval( "Function4", 10, g_Scheduler.REPEAT_INFINITE_TIMES );

Function4 displays how you can modify the function being executed right now. g_Scheduler.GetCurrentFunction returns the current functions if there is one, allowing for this without having to keep track of the functions manually.

You can also remove a function by passing its CScheduledFunction object to g_Scheduler.RemoveTimer. Setting a function's repeat count to 0 will cause it to be removed without being executed. Prefer using RemoveTimer unless you have a specific use for setting the repeat count to 0 (e.g. allowing it to be changed again later before it is removed).

Scheduling object methods

It is also possible to schedule object methods. They follow the same exact rules as scheduling functions, except you also have to pass an object instance.

Let's modify the Counter class to have a method that increments and outputs the counter variable:

/*
* Simple class to keep track of a counter
*/
class Counter
{
	uint m_uiCounter = 0;

	void IncrementAndOutput()
	{
		m_uiCounter++;

		g_Game.AlertMessage( at_console, "%1 called %2 times\n", @this, m_uiCounter );
	}
}

// In another function
// Call object method IncrementAndOutput on the counter instance after 1 second, repeating 5 times, with an interval of 1 second
g_Scheduler.SetInterval( @Counter(), "IncrementAndOutput", 1, 5 );

As noted above, you must convert the object to a handle to avoid copy construction.

You can also use this to add additional think methods to entities. However, you must remove all scheduled methods on entities when those entities are destroyed. Otherwise, the game can and probably will crash when it tries to use the now invalid entity handle (see entity documentation for why this is an issue).

Sample

All of the code used in this documentation is included in a sample script: Sample
This is a plugin, so you will have to add it to the plugin list file to execute it.

Sample output when run (times and memory addresses may vary):

2E4BD088 called 1 times
2E4BD088 called 2 times
Function 3 at 4.002609, called 1 times
2E4BD088 called 3 times
2E4BD088 called 4 times
Function 2 at 6.0252
Function 1 at 6.0252
2E4BD088 called 5 times
Function 3 at 7.015248, called 2 times
Function 3 at 10.023279, called 3 times
Function 4 at 11.01133
Next call time: 21.01133, Repeat time: 10, repeat count: -1
Has been removed: false
Function 2 at 11.033432
Function 3 at 13.026757, called 4 times
Function 3 at 16.042831, called 5 times
Function 2 at 16.042831
Function 3 at 19.048531, called 6 times
Function 4 at 21.016544
Next call time: 31.016544, Repeat time: 10, repeat count: 1
Has been removed: false
Function 2 at 21.068937
Function 3 at 22.065699, called 7 times
Function 2 at 26.071709
Function 4 at 31.038286
Next call time: 41.038284, Repeat time: 10, repeat count: 0
Has been removed: true
Function 2 at 31.084364
Function 2 at 36.088947
Function 2 at 41.110546
Function 2 at 46.133747
Function 2 at 51.153774

In order to see this, you will have to enable developer mode (developer 1 or higher in the console before running the script). You may also notice other output in-between some of this output due to timing in the engine. You can also see that the exact execution time is not exactly what you'd expect, this is a result of floating point precision issues and timing in the engine.

Clone this wiki locally