Difference between revisions of "Timers (SourceMod Scripting)"
(→Repeatable Timers) |
Joinedsenses (talk | contribs) m (Update syntax, highlighting, formatting) |
||
(18 intermediate revisions by 7 users not shown) | |||
Line 10: | Line 10: | ||
One-time timers are timers that only execute once. An example of a one-time timer might look like: | One-time timers are timers that only execute once. An example of a one-time timer might look like: | ||
− | < | + | <sourcepawn>public void OnPluginStart() |
{ | { | ||
− | + | CreateTimer(5.0, LoadStuff); | |
} | } | ||
− | public Action | + | public Action LoadStuff(Handle timer) |
{ | { | ||
− | + | PrintToServer("Loading stuff!"); | |
− | }</ | + | }</sourcepawn> |
This code will print "Loading stuff!" to the server console five seconds after the map loads. The return value has no meaning for one-time timers. | This code will print "Loading stuff!" to the server console five seconds after the map loads. The return value has no meaning for one-time timers. | ||
Line 26: | Line 26: | ||
For example, say you want to display a message five times, once every three seconds: | For example, say you want to display a message five times, once every three seconds: | ||
− | < | + | <sourcepawn> |
− | + | void someFunction() | |
{ | { | ||
− | + | CreateTimer(3.0, Timer_PrintMessageFiveTimes, _, TIMER_REPEAT); | |
} | } | ||
− | public Action | + | public Action Timer_PrintMessageFiveTimes(Handle timer) |
{ | { | ||
− | + | // Create a global variable visible only in the local scope (this function). | |
− | + | static int numPrinted = 0; | |
− | + | if (numPrinted >= 5) | |
− | + | { | |
− | + | numPrinted = 0; | |
+ | return Plugin_Stop; | ||
+ | } | ||
− | + | PrintToServer("Warning! This is a message."); | |
− | + | numPrinted++; | |
− | + | return Plugin_Continue; | |
− | }</ | + | }</sourcepawn> |
Note that <tt>Plugin_Stop</tt> stops the timer, and <tt>Plugin_Continue</tt> allows it to continue repeating. | Note that <tt>Plugin_Stop</tt> stops the timer, and <tt>Plugin_Continue</tt> allows it to continue repeating. | ||
Line 53: | Line 55: | ||
As mentioned earlier, timers let you pass values on to the callback function. This value can be any type. For example, let's say we want to print a message to a player fifteen seconds after they connect. However, we want to cancel the timer if the player disconnects. Implementing this is very easy: | As mentioned earlier, timers let you pass values on to the callback function. This value can be any type. For example, let's say we want to print a message to a player fifteen seconds after they connect. However, we want to cancel the timer if the player disconnects. Implementing this is very easy: | ||
− | < | + | <sourcepawn> |
+ | Handle WelcomeTimers[MAXPLAYERS+1]; | ||
− | + | public void OnClientPutInServer(int client) | |
+ | { | ||
+ | WelcomeTimers[client] = CreateTimer(15.0, WelcomePlayer, client); | ||
+ | } | ||
− | public | + | public void OnClientDisconnect(int client) |
{ | { | ||
− | + | delete WelcomeTimers[client]; | |
+ | |||
+ | /** | ||
+ | * 'delete handle;' equates to: | ||
+ | * if (handle != null) { | ||
+ | * CloseHandle(handle); | ||
+ | * handle = null; | ||
+ | * } | ||
+ | */ | ||
} | } | ||
− | public | + | public Action WelcomePlayer(Handle timer, int client) |
{ | { | ||
− | + | PrintToConsole(client, "Welcome to the server!"); | |
− | + | WelcomeTimers[client] = null; | |
− | + | }</sourcepawn> | |
− | + | ||
− | + | Another example without using handles is to use either UserID or ClientSerial (which is unique to each player): | |
+ | |||
+ | <sourcepawn> | ||
+ | public void OnClientPutInServer(int client) | ||
+ | { | ||
+ | CreateTimer(15.0, WelcomePlayer, GetClientSerial(client)); // You could also use GetClientUserId(client) | ||
} | } | ||
− | public Action | + | public Action WelcomePlayer(Handle timer, int serial) |
{ | { | ||
− | + | int client = GetClientFromSerial(serial); // Validate the client serial | |
− | + | ||
− | }</ | + | if (client == 0) // The serial is no longer valid, the player must have disconnected |
+ | { | ||
+ | return Plugin_Stop; | ||
+ | } | ||
+ | |||
+ | PrintToConsole(client, "Welcome to the server!"); | ||
+ | |||
+ | return Plugin_Continue; | ||
+ | }</sourcepawn> | ||
+ | If you want to close or cancel a timer when a client disconnects, then use the first example with handles. | ||
==Handles== | ==Handles== | ||
− | If you want to pass a Handle as a value, you have the option of using <tt> | + | If you want to pass a Handle as a value, you have the option of using <tt>TIMER_DATA_HNDL_CLOSE</tt>, which will automatically call <tt>CloseHandle()</tt> for you once the timer dies. |
==Data Packs== | ==Data Packs== | ||
Line 84: | Line 112: | ||
The above example could be rewritten as: | The above example could be rewritten as: | ||
− | < | + | <sourcepawn> |
− | + | Handle WelcomeTimers[MAXPLAYERS+1]; | |
− | |||
− | public OnClientPutInServer(client) | + | public void OnClientPutInServer(int client) |
{ | { | ||
− | + | DataPack pack; | |
− | + | WelcomeTimers[client] = CreateDataTimer(15.0, WelcomePlayer, pack); | |
− | + | pack.WriteCell(client); | |
− | + | pack.WriteString("Welcome to the server!"); | |
} | } | ||
− | public OnClientDisconnect(client) | + | public void OnClientDisconnect(int client) |
{ | { | ||
− | + | delete WelcomeTimers[client]; | |
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | public Action | + | public Action WelcomePlayer(Handle timer, DataPack pack) |
{ | { | ||
− | + | char str[128]; | |
− | + | int client; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | /* Set to the beginning and unpack it */ | |
− | + | pack.Reset(); | |
− | }</ | + | client = pack.ReadCell(); |
+ | pack.ReadString(str, sizeof(str)); | ||
+ | PrintToConsole(client, "%s", str); | ||
+ | WelcomeTimers[client] = null; | ||
+ | }</sourcepawn> | ||
=Notes= | =Notes= | ||
==Accuracy== | ==Accuracy== | ||
The smallest possible interval is 0.1 seconds. Timers have high precision (floating point) but low accuracy, as the current time is based on the in-game tick count, not the system clock. This has two implications: | The smallest possible interval is 0.1 seconds. Timers have high precision (floating point) but low accuracy, as the current time is based on the in-game tick count, not the system clock. This has two implications: | ||
− | *If the server is paused (not ticking), timers will not run. | + | *If the server is paused (not ticking or hibernating), timers will not run. |
*The server will not always tick at an exact interval. | *The server will not always tick at an exact interval. | ||
Line 135: | Line 157: | ||
*Execution of a one-time timer finishes. | *Execution of a one-time timer finishes. | ||
− | When a timer dies, if <tt> | + | When a timer dies, if <tt>TIMER_DATA_HNDL_CLOSE</tt> is set, the Handle will always be closed with the permissions that <tt>CloseHandle()</tt> uses by default. Since timers cannot be cloned, there should be no ownership issues. |
==Changing Intervals== | ==Changing Intervals== |
Latest revision as of 18:38, 29 March 2020
Timers in SourceMod are timed events that occur once or repeatedly at a given interval.
Contents
Introduction
Timers allow you to set an interval, a function to use as the event callback, and an optional Handle to pass through the callback. This is useful for saving data asynchronously.
All timer functions are in plugins/include/timers.inc.
Basic Usage
One-Time Timers
One-time timers are timers that only execute once. An example of a one-time timer might look like:
public void OnPluginStart() { CreateTimer(5.0, LoadStuff); } public Action LoadStuff(Handle timer) { PrintToServer("Loading stuff!"); }
This code will print "Loading stuff!" to the server console five seconds after the map loads. The return value has no meaning for one-time timers.
Repeatable Timers
Repeatable timers execute infinitely many times, once every interval. Based on the return value, they either continue or cancel.
For example, say you want to display a message five times, once every three seconds:
void someFunction() { CreateTimer(3.0, Timer_PrintMessageFiveTimes, _, TIMER_REPEAT); } public Action Timer_PrintMessageFiveTimes(Handle timer) { // Create a global variable visible only in the local scope (this function). static int numPrinted = 0; if (numPrinted >= 5) { numPrinted = 0; return Plugin_Stop; } PrintToServer("Warning! This is a message."); numPrinted++; return Plugin_Continue; }
Note that Plugin_Stop stops the timer, and Plugin_Continue allows it to continue repeating.
Passing Data
Simple Values
As mentioned earlier, timers let you pass values on to the callback function. This value can be any type. For example, let's say we want to print a message to a player fifteen seconds after they connect. However, we want to cancel the timer if the player disconnects. Implementing this is very easy:
Handle WelcomeTimers[MAXPLAYERS+1]; public void OnClientPutInServer(int client) { WelcomeTimers[client] = CreateTimer(15.0, WelcomePlayer, client); } public void OnClientDisconnect(int client) { delete WelcomeTimers[client]; /** * 'delete handle;' equates to: * if (handle != null) { * CloseHandle(handle); * handle = null; * } */ } public Action WelcomePlayer(Handle timer, int client) { PrintToConsole(client, "Welcome to the server!"); WelcomeTimers[client] = null; }
Another example without using handles is to use either UserID or ClientSerial (which is unique to each player):
public void OnClientPutInServer(int client) { CreateTimer(15.0, WelcomePlayer, GetClientSerial(client)); // You could also use GetClientUserId(client) } public Action WelcomePlayer(Handle timer, int serial) { int client = GetClientFromSerial(serial); // Validate the client serial if (client == 0) // The serial is no longer valid, the player must have disconnected { return Plugin_Stop; } PrintToConsole(client, "Welcome to the server!"); return Plugin_Continue; }
If you want to close or cancel a timer when a client disconnects, then use the first example with handles.
Handles
If you want to pass a Handle as a value, you have the option of using TIMER_DATA_HNDL_CLOSE, which will automatically call CloseHandle() for you once the timer dies.
Data Packs
Data packs are packable structures that can be used to hold asynchronous data (data that must be saved and unpacked for later). They are especially useful for timers, and thus there exists a helper function, called CreateDataTimer(), which creates a timer using a data pack handle. The handle is created and closed automatically for you.
The above example could be rewritten as:
Handle WelcomeTimers[MAXPLAYERS+1]; public void OnClientPutInServer(int client) { DataPack pack; WelcomeTimers[client] = CreateDataTimer(15.0, WelcomePlayer, pack); pack.WriteCell(client); pack.WriteString("Welcome to the server!"); } public void OnClientDisconnect(int client) { delete WelcomeTimers[client]; } public Action WelcomePlayer(Handle timer, DataPack pack) { char str[128]; int client; /* Set to the beginning and unpack it */ pack.Reset(); client = pack.ReadCell(); pack.ReadString(str, sizeof(str)); PrintToConsole(client, "%s", str); WelcomeTimers[client] = null; }
Notes
Accuracy
The smallest possible interval is 0.1 seconds. Timers have high precision (floating point) but low accuracy, as the current time is based on the in-game tick count, not the system clock. This has two implications:
- If the server is paused (not ticking or hibernating), timers will not run.
- The server will not always tick at an exact interval.
For example, a 1.234 second interval timer starting from time t might not tick until t+1.242 at a tickrate of 66 ticks per second.
Timer Death
All timers are guaranteed to die either by:
- CloseHandle() being called (or on plugin unload);
- KillTimer() being called;
- Plugin_Stop being returned from a repeatable timer;
- TriggerTimer() being called on a one-time timer;
- Execution of a one-time timer finishes.
When a timer dies, if TIMER_DATA_HNDL_CLOSE is set, the Handle will always be closed with the permissions that CloseHandle() uses by default. Since timers cannot be cloned, there should be no ownership issues.
Changing Intervals
The interval of a timer cannot be changed as of this writing. The timer must be killed and a new one created.
AMX Mod X
Unlike AMX Mod X's set_task function, you cannot pass arrays using CreateTimer. To accomplish this, you should use the data pack functionality explained above.
Similarly, there are no flags for counted repeats (non-infinite loops) or timers based on the map start or end. These must be done manually.
External Links
- On Timer Design, Part 3 (SourceMod DevLog)
- On Timer Design, Part 2 (SourceMod DevLog)
- On Timer Design, Part 1 (SourceMod DevLog)
Warning: This template (and by extension, language format) should not be used, any pages using it should be switched to Template:Languages | |
View this page in: English Russian 简体中文(Simplified Chinese) |