Difference between revisions of "Timers (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
m (a year later and yet...)
m (Update syntax, highlighting, formatting)
 
(19 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:
  
<pawn>public OnPluginStart()
+
<sourcepawn>public void OnPluginStart()
 
{
 
{
CreateTimer(5.0, LoadStuff)
+
    CreateTimer(5.0, LoadStuff);
 
}
 
}
  
public Action:LoadStuff(Handle:timer)
+
public Action LoadStuff(Handle timer)
 
{
 
{
PrintToServer("Loading stuff!")
+
    PrintToServer("Loading stuff!");
}</pawn>
+
}</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:
<pawn>
+
<sourcepawn>
DoMessage()
+
void someFunction()
 
{
 
{
CreateTimer(3.0, PrintMsg, _, TIMER_REPEAT)
+
    CreateTimer(3.0, Timer_PrintMessageFiveTimes, _, TIMER_REPEAT);
 
}
 
}
  
public Action:PrintMsg(Handle:timer)
+
public Action Timer_PrintMessageFiveTimes(Handle timer)
 
{
 
{
static NumPrinted = 0
+
    // Create a global variable visible only in the local scope (this function).
if (NumPrinted++ >= 5)
+
    static int numPrinted = 0;
{
 
PrintToServer("Warning! This is a message.")
 
NumPrinted = 0
 
  
return Plugin_Stop
+
    if (numPrinted >= 5)
}
+
    {
 +
        numPrinted = 0;
 +
        return Plugin_Stop;
 +
    }
  
return Plugin_Continue
+
    PrintToServer("Warning! This is a message.");
}</pawn>
+
    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 52: 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:
  
<pawn>#define MAX_PLAYERS 256
+
<sourcepawn>
 +
Handle WelcomeTimers[MAXPLAYERS+1];
  
new Handle:WelcomeTimers[MAX_PLAYERS+1]
+
public void OnClientPutInServer(int client)
 +
{
 +
    WelcomeTimers[client] = CreateTimer(15.0, WelcomePlayer, client);
 +
}
  
public OnClientPutInServer(client)
+
public void OnClientDisconnect(int client)
 
{
 
{
WelcomeTimers[client] = CreateTimer(15.0, WelcomePlayer, client)
+
    delete WelcomeTimers[client];
 +
 
 +
    /**
 +
    * 'delete handle;' equates to:
 +
    * if (handle != null) {
 +
    *    CloseHandle(handle);
 +
    *    handle = null;
 +
    * }
 +
    */
 
}
 
}
  
public OnClientDisconnect(client)
+
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)
 
{
 
{
if (WelcomeTimers[client] != INVALID_HANDLE)
+
    CreateTimer(15.0, WelcomePlayer, GetClientSerial(client)); // You could also use GetClientUserId(client)
{
 
KillTimer(WelcomeTimers[client])
 
WelcomeTimers[client] = INVALID_HANDLE
 
}
 
 
}
 
}
  
public Action:WelcomePlayer(Handle:timer, any:client)
+
public Action WelcomePlayer(Handle timer, int serial)
 
{
 
{
PrintToConsole(client, "Welcome to the server!")
+
    int client = GetClientFromSerial(serial); // Validate the client serial
WelcomeTimers[client] = INVALID_HANDLE
+
   
}</pawn>
+
    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>TIMER_HNDL_CLOSE</tt>, which will automatically call <tt>CloseHandle()</tt> for you once the timer dies.   
+
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 83: Line 112:
  
 
The above example could be rewritten as:
 
The above example could be rewritten as:
<pawn>#define MAX_PLAYERS 256
+
<sourcepawn>
 +
Handle WelcomeTimers[MAXPLAYERS+1];
  
new Handle:WelcomeTimers[MAX_PLAYERS+1]
+
public void OnClientPutInServer(int client)
 
 
public OnClientPutInServer(client)
 
 
{
 
{
new Handle:pack
+
    DataPack pack;
WelcomeTimers[client] = CreateDataTimer(15.0, WelcomePlayer, pack)
+
    WelcomeTimers[client] = CreateDataTimer(15.0, WelcomePlayer, pack);
WritePackCell(pack, client)
+
    pack.WriteCell(client);
WritePackString(pack, "Welcome to the server!")
+
    pack.WriteString("Welcome to the server!");
 
}
 
}
  
public OnClientDisconnect(client)
+
public void OnClientDisconnect(int client)
 
{
 
{
if (WelcomeTimers[client] != INVALID_HANDLE)
+
    delete WelcomeTimers[client];
{
 
KillTimer(WelcomeTimers[client])
 
WelcomeTimers[client] = INVALID_HANDLE
 
}
 
 
}
 
}
  
public Action:WelcomePlayer(Handle:timer, Handle:pack)
+
public Action WelcomePlayer(Handle timer, DataPack pack)
 
{
 
{
decl String:str[128]
+
    char str[128];
new client
+
    int client;
 
 
/* Set to the beginning and unpack it */
 
ResetPack(pack)
 
client = ReadPackCell(pack)
 
ReadPackString(pack, str, sizeof(str))
 
  
PrintToConsole(client, "%s", str)
+
    /* Set to the beginning and unpack it */
WelcomeTimers[client] = INVALID_HANDLE
+
    pack.Reset();
}</pawn>
+
    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 134: Line 157:
 
*Execution of a one-time timer finishes.
 
*Execution of a one-time timer finishes.
  
When a timer dies, if <tt>TIMER_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.
+
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 19:38, 29 March 2020

Timers in SourceMod are timed events that occur once or repeatedly at a given interval.

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

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)