SourcePawn Basics - Handles, DataPacks, and Timers
Prerequisite:
This guide assumes you have read Introduction to SourcePawn and Introduction to SourceMod Plugins.
Contents
Handles
A Handle is a cell data type in Pawn that contains a pointer to an object stored in the SourceMod core.
Handles have an associated handle type. Trying to pass the wrong kind of handle to a function will cause SourceMod to log an error and cause your current code to stop executing.
Before using a Handle, you should always make sure it doesn't equal INVALID_HANDLE.
You can make copies of a Handle using the CloneHandle function. Be aware that a cloned Handle still points to the same object as the original and that changes to the data on the object will show up in all clones. The object will only be destroyed when all Handles pointing to it are closed. You should clone a handle if you are receiving it from another plugin or extension that may close the handle before you finish with it.
For most Handle types, you should use CloseHandle on the Handle when you are done with it. The major exceptions are Convars, non-repeating Timers, and DataPacks created using CreateDataTimer.
If you fail to close a Handle, your plugin will leak Handles and memory and SourceMod will eventually restart the plugin.
DataPacks
Datapacks are a common type of Handle you will come across. They are a serialized set of data... that is, data must be read in the same order it was written. Datapacks are created with either the CreateDataTimer or CreateDataPack functions, and are assigned to a Handle variable.
Data is written to a Datapack using the WritePackCell, WritePackFloat, and WritePackString functions. Remember that enums and Handles are both cells, so you can write those using the WritePackCell function.
Before you read data back out of a Datapack, you must call ResetPack on it with the second argument set to false.
Data can be read back out using ReadPackCell, ReadPackFloat, and ReadPackString. These must be called in the same order your called your Write functions.
If you manually created a Data Pack, you should manually close the DataPack Handle when you are finished with it to avoid a Handle leak.
Note: You will get warnings if you attempt to ReadPackCell and save the value to a bool, enum, or Handle value. You must retag those like so:
ResetPack(data); bool myBool = ReadPackCell(data);
Timers
If you CloseHandle a timer, you should also set its variable to INVALID_HANDLE.
Timers are used to run a block of code after a delay, or as a repeating timer to run code at regular intervals.
Timers are created using one of two functions. The first is CreateTimer. The second is CreateDataTimer. The two functions have the same number of arguments, but CreateDataTimer will automatically create a DataPack, save it to the third argument, and implicitly have the TIMER_DATA_HNDL_CLOSE flag set.
The first argument is the time as a Float... that is to say, if you want a 2 second timer, you must instead pass 2.0 .
The second argument is a Timer callback function. See: Timer Callback Functions.
For the data argument, you will usually want to pass a single value, such as a client userid, or use CreateDataTimer to create a DataPack. Never pass a client index to a timer. I repeat, never pass a client index to a timer. In most situations, you'll want to pass a Userid, such as one created by GetClientUserId(client) .
Timer Flags
By default, a standard timer doesn't have any flags set. By default, a Data Timer has TIMER_DATA_HNDL_CLOSE set.
The valid flags are the fourth argument to CreateTimer and CreateDataTimer are
- TIMER_REPEAT
- The timer will be called repeatedly until its handle is closed or the timer callback function returns Plugin_Stop
- TIMER_FLAG_NO_MAPCHANGE
- The timer will automatically stop if the map changes
- TIMER_DATA_HNDL_CLOSE
- Automatically close the Handle when the Timer ends. This is the default for CreateDataTimer.
"Fire and Forget" vs. "Tracked" Timers
A "Fire and Forget" timer is one where you don't save the handle returned by the CreateTimer or CreateDataTimer functions. It is highly recommended that you pass the TIMER_FLAG_NO_MAPCHANGE flag for fire and forget timers.
A "tracked" timer is one you save the Handle of when you create it. You can then call CloseHandle or KillTimer on it to end it early, or call TriggerTimer to trigger it early. It is recommended that you not pass the TIMER_FLAG_NO_MAPCHANGE for tracked timers and instead manually end them in OnMapEnd and set their variable to INVALID_HANDLE.
Timer Callback Functions
Timer callbacks always return an action. The return value only matters for repeating timers, which will stop as soon as you return Plugin_Stop.
The 3 valid signatures are:
Action MyFunc(Handle timer) - Use this when you don't pass any data to a timer at all.
Action MyFunc(Handle timer, any data) - Use this when you pass a single value to a timer, such as a userid. Remember to convert userids back to client indexes using GetClientOfUserId and make sure it doesn't equal 0.
Action MyFunc(Handle timer, Handle hndl) - Use this for a Data timer
Timer Example
In this example, we want to pass a userid to a timer. Note that Translation Strings are covered in Translations (SourceMod Scripting).
public void OnClientPutInServer(int client) { CreateTimer(15.0, Timer_Greet, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } Action Timer_Greet(Handle timer, any data) { int client = GetClientOfUserId(data); if (client == 0) { return Plugin_Stop; } char name[MAX_NAME_LENGTH]; GetClientName(client, name, sizeof(name)); // In non-translation phrases use %N instead PrintToChat(client, "%t", "Hello", name); return Plugin_Stop; }