Difference between revisions of "Handles (SourceMod Scripting)"
m (Removed dead link.) |
MAGNAT2645 (talk | contribs) m (DBDrivers are not closeable) |
||
(14 intermediate revisions by 7 users not shown) | |||
Line 10: | Line 10: | ||
Opening a Handle is usually done implicitly by a native function. For example, OpenDirectory opens a new Handle which is used in other Directory natives. This associates a ''type'' with the Handle. Trying to use a Directory Handle with any other non-Directory native will cause an error (HandleError_Type), because Handles are type checked. | Opening a Handle is usually done implicitly by a native function. For example, OpenDirectory opens a new Handle which is used in other Directory natives. This associates a ''type'' with the Handle. Trying to use a Directory Handle with any other non-Directory native will cause an error (HandleError_Type), because Handles are type checked. | ||
− | < | + | <sourcepawn>Handle hndl = OpenDirectory("addons/sourcemod/configs"); |
− | </ | + | </sourcepawn> |
=Closing Handles= | =Closing Handles= | ||
==Basic operation== | ==Basic operation== | ||
− | Closing a Handle means seeing if the internal object can be removed from memory. For example, if a plugin creates an SQL connection, closing the Handle means closing and destroying the connection. | + | Closing a Handle means seeing if the internal object can be removed from memory. For example, if a plugin creates an SQL connection, closing the Handle means closing and destroying the connection. This is almost always done using the <tt>delete</tt> operator and specifying the handle to close, e.g. <tt>delete hDir;</tt> |
==When to close== | ==When to close== | ||
When a plugin unloads, all of its Handles are automatically destroyed. This does not mean, however, that closing Handles is optional. Consider the following code: | When a plugin unloads, all of its Handles are automatically destroyed. This does not mean, however, that closing Handles is optional. Consider the following code: | ||
− | < | + | <sourcepawn>bool IsFileInFolder(const char[] path, const char[] file) |
{ | { | ||
− | + | char path[PLATFORM_MAX_PATH]; | |
− | + | FileType type; | |
− | + | DirectoryListing dir = OpenDirectory(path); | |
− | + | if (dir == null) | |
− | + | { | |
− | + | return false; | |
− | + | } | |
− | + | while (dir.GetNext(path, sizeof(path), type)) | |
− | + | { | |
− | + | if (type == FileType_File && StrEqual(path, file)) | |
− | + | { | |
− | + | delete dir; | |
− | + | return true; | |
− | + | } | |
+ | } | ||
− | + | delete dir; | |
− | + | return false; | |
} | } | ||
− | </ | + | </sourcepawn> |
− | This function searches a directory for a single file. If this function were to | + | This function searches a directory for a single file. If this function were to not close the handle with the <tt>delete</tt> operator, it would [[Memory Leak|leak memory]] every time it was called, and eventually the script would fill up with a large number of lingering Handles that were left open. |
So, when do you close Handles? There are generally two rules of thumb to go by: | So, when do you close Handles? There are generally two rules of thumb to go by: | ||
Line 68: | Line 69: | ||
*Plugin 'B' tries to use the Handle. | *Plugin 'B' tries to use the Handle. | ||
− | To prevent this, the CloneHandle native is provided. This function returns a ''new'' Handle that is a "copy" of the original. While their values won't be equal, they contain the same internal data. The data itself will not be destroyed until each copy and the original are closed with | + | To prevent this, the CloneHandle native is provided. This function returns a ''new'' Handle that is a "copy" of the original. While their values won't be equal, they contain the same internal data. The data itself will not be destroyed until each copy and the original are closed with the <tt>delete</tt> operator. It does not matter what order they are freed in, however, each Handle can only be freed once. |
There are two ways of cloning. A plugin can either clone a Handle itself, and become the owner, or it can explicitly set a new owner. The difference is one of API design. Example of the former: | There are two ways of cloning. A plugin can either clone a Handle itself, and become the owner, or it can explicitly set a new owner. The difference is one of API design. Example of the former: | ||
− | < | + | <sourcepawn>Database g_hSQL; |
/** | /** | ||
* The other plugin calling this function must pass his ID in, | * The other plugin calling this function must pass his ID in, | ||
* but doesn't have to call CloneHandle() | * but doesn't have to call CloneHandle() | ||
*/ | */ | ||
− | public | + | public Database GetGlobalSQL(Handle otherPlugin) |
{ | { | ||
− | return CloneHandle(g_hSQL, otherPlugin); | + | return view_as<Database>(CloneHandle(g_hSQL, otherPlugin)); |
} | } | ||
Line 84: | Line 85: | ||
* This code would appear in the other plugin | * This code would appear in the other plugin | ||
*/ | */ | ||
− | + | Database sql = GetGlobalSQL(myself); | |
/* ... */ | /* ... */ | ||
− | + | delete sql; | |
− | </ | + | </sourcepawn> |
Example of the latter: | Example of the latter: | ||
− | < | + | <sourcepawn>Database g_hSQL; |
/** | /** | ||
* The calling plugin must call CloneHandle() himself. | * The calling plugin must call CloneHandle() himself. | ||
*/ | */ | ||
− | public | + | public Database GetGlobalSQL() |
{ | { | ||
return g_hSQL; | return g_hSQL; | ||
Line 102: | Line 103: | ||
* This code would appear in the other plugin | * This code would appear in the other plugin | ||
*/ | */ | ||
− | + | Database sql = GetGlobalSQL(); | |
− | if (sql != | + | if (sql != null) |
{ | { | ||
− | sql = CloneHandle(sql); | + | sql = view_as<Database>(CloneHandle(sql)); |
} | } | ||
− | + | delete sql; | |
− | </ | + | </sourcepawn> |
=Handle Types= | =Handle Types= | ||
− | The following is a list of some common Handle types provided by SourceMod and some documentation on each. | + | The following is a list of some common Handle types provided by SourceMod and some documentation on each. |
− | |||
− | |||
− | |||
− | |||
==BitBuffers== | ==BitBuffers== | ||
Line 123: | Line 120: | ||
==ConVars== | ==ConVars== | ||
− | {{HandleType|ConVar|No|No|Core| | + | {{HandleType|ConVar|No|No|Core|convars.inc}} |
ConVar Handles are primarily used for getting and setting a console variable's value. They cannot be cloned nor deleted since they exist until SourceMod is shut down. | ConVar Handles are primarily used for getting and setting a console variable's value. They cannot be cloned nor deleted since they exist until SourceMod is shut down. | ||
Line 140: | Line 137: | ||
==Database Drivers== | ==Database Drivers== | ||
− | {{HandleType|DBDriver| | + | {{HandleType|DBDriver|No|No|Core|dbi.inc}} |
DBDriver Handles contain information about a database driver. They are static and cannot be closed. | DBDriver Handles contain information about a database driver. They are static and cannot be closed. | ||
Line 186: | Line 183: | ||
Plugin Iterators allow you to walk over a list of plugins. They are intended for temporary use only. | Plugin Iterators allow you to walk over a list of plugins. They are intended for temporary use only. | ||
+ | |||
+ | ==Protobuf== | ||
+ | {{HandleType|protobuf|No|No|Core|protobuf.inc}} | ||
+ | |||
+ | Protobuf Handles are currently used for usermessages in supported games. They directly reference protobuf Messages, which can be a usermessage or a Message field inside of a usermessage. They are owned and managed by Core and cannot be cloned or closed by a plugin. | ||
==SMC Parsers== | ==SMC Parsers== | ||
Line 198: | Line 200: | ||
*The timer ends (via Plugin_Stop or being a one-time timer that is finished) | *The timer ends (via Plugin_Stop or being a one-time timer that is finished) | ||
*The timer is killed via <tt>KillTimer</tt> | *The timer is killed via <tt>KillTimer</tt> | ||
− | *The timer is closed via <tt> | + | *The timer is closed via <tt>delete hHandle;</tt> |
*The timer's parent plugin unloads | *The timer's parent plugin unloads | ||
[[Category:SourceMod Scripting]] | [[Category:SourceMod Scripting]] |
Latest revision as of 07:17, 17 December 2020
Handles are a special data type used in SourceMod Scripting. They represent a single internal and unique object. For example, there are "File" Handles for files, and "Menu" Handles for menus, but they are both encapsulated under the "Handle" tag.
Handles are more than "pointers" from C or C++ because they have a reference count. This means that the number of copies floating around in memory is tracked. The actual internal object is not destroyed until each copy is also destroyed.
Since SourcePawn does not have Garbage Collection, Handles are a special way of intelligently allowing plugins and SourceMod to manage their own memory.
For more information on using Handles in the SourceMod API, see Handle API (SourceMod).
Opening Handles
Opening a Handle is usually done implicitly by a native function. For example, OpenDirectory opens a new Handle which is used in other Directory natives. This associates a type with the Handle. Trying to use a Directory Handle with any other non-Directory native will cause an error (HandleError_Type), because Handles are type checked.
Handle hndl = OpenDirectory("addons/sourcemod/configs");
Closing Handles
Basic operation
Closing a Handle means seeing if the internal object can be removed from memory. For example, if a plugin creates an SQL connection, closing the Handle means closing and destroying the connection. This is almost always done using the delete operator and specifying the handle to close, e.g. delete hDir;
When to close
When a plugin unloads, all of its Handles are automatically destroyed. This does not mean, however, that closing Handles is optional. Consider the following code:
bool IsFileInFolder(const char[] path, const char[] file) { char path[PLATFORM_MAX_PATH]; FileType type; DirectoryListing dir = OpenDirectory(path); if (dir == null) { return false; } while (dir.GetNext(path, sizeof(path), type)) { if (type == FileType_File && StrEqual(path, file)) { delete dir; return true; } } delete dir; return false; }
This function searches a directory for a single file. If this function were to not close the handle with the delete operator, it would leak memory every time it was called, and eventually the script would fill up with a large number of lingering Handles that were left open.
So, when do you close Handles? There are generally two rules of thumb to go by:
- If the native uses terminology such as "Opens a [...]" or "Creates a [...]" or gives explicit directions for closing.
- If the Handle represents a temporary object or piece of information, and you will no longer be using it.
Most of the time, you will be closing Handles. Check the Handle Type documentation at the end of this page.
When you can't close
There are a few Handle types that cannot be closed. The major one is the Handle which identifies a plugin. Plugins cannot unload themselves, and thus destroying their own Handles is not allowed.
Furthermore, a script cannot close a Handle that is owned by another plugin. This is for protection against API mistakes and accidental errors. For example, if a Handle is shared between two plugins, one accidental free could invalidate a Handle unexpectedly. To allow the sharing of Handles, see Cloning Handles.
Reference counters
Closing Handles does not always destroy the internal data. This is the case when Handles are cloned. Each clone adds a reference count to the Handle, and closing each clone removes one reference count. The original object is only actually destroyed once the Handle has no more reference counters.
Cloning Handles
As briefly described earlier, there is a problem when scripts try to share Handles. Imagine this scenario:
- Plugin 'A' creates a Handle.
- Plugin 'B' received the Handle.
- Plugin 'A' is unloaded, and the Handle is destroyed.
- Plugin 'B' tries to use the Handle.
To prevent this, the CloneHandle native is provided. This function returns a new Handle that is a "copy" of the original. While their values won't be equal, they contain the same internal data. The data itself will not be destroyed until each copy and the original are closed with the delete operator. It does not matter what order they are freed in, however, each Handle can only be freed once.
There are two ways of cloning. A plugin can either clone a Handle itself, and become the owner, or it can explicitly set a new owner. The difference is one of API design. Example of the former:
Database g_hSQL; /** * The other plugin calling this function must pass his ID in, * but doesn't have to call CloneHandle() */ public Database GetGlobalSQL(Handle otherPlugin) { return view_as<Database>(CloneHandle(g_hSQL, otherPlugin)); } /** * This code would appear in the other plugin */ Database sql = GetGlobalSQL(myself); /* ... */ delete sql;
Example of the latter:
Database g_hSQL; /** * The calling plugin must call CloneHandle() himself. */ public Database GetGlobalSQL() { return g_hSQL; } /** * This code would appear in the other plugin */ Database sql = GetGlobalSQL(); if (sql != null) { sql = view_as<Database>(CloneHandle(sql)); } delete sql;
Handle Types
The following is a list of some common Handle types provided by SourceMod and some documentation on each.
BitBuffers
Type: | bf_read or bf_write |
Closeable: | Only if explicitly stated |
Cloneable: | Only if it can also be closed |
API: | Core |
Found in: | bitbuffer.inc |
There are four types of BitBuffer Handles. They are separated into bf_write (writable buffer) and bf_read (readable buffer). These are directly abstracted from their Half-Life 2 counterpart. Internally, there are separate Handle types for each; certain functions will use BitBuffer handles that cannot be freed.
ConVars
Type: | ConVar |
Closeable: | No |
Cloneable: | No |
API: | Core |
Found in: | convars.inc |
ConVar Handles are primarily used for getting and setting a console variable's value. They cannot be cloned nor deleted since they exist until SourceMod is shut down.
DataPacks
Type: | DataPack |
Closeable: | Yes |
Cloneable: | Yes |
API: | Core |
Found in: | datapack.inc |
DataPack Handles are used to pack data into a sequential stream, for unpacking later. They are unidirectional, meaning that reading and writing affects the same position in the stream.
The DataPack type is exposed for Extensions to use.
Directories
Type: | Directory |
Closeable: | Yes |
Cloneable: | Yes |
API: | Core |
Found in: | files.inc |
Directory Handles are used for opening and enumerating the contents of a directory (folder) on the filesystem.
Database Drivers
Type: | DBDriver |
Closeable: | No |
Cloneable: | No |
API: | Core |
Found in: | dbi.inc |
DBDriver Handles contain information about a database driver. They are static and cannot be closed.
Database Queries
Type: | IQuery |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | dbi.inc |
Type: | IPreparedQuery |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | dbi.inc |
Database Queries wrap an IQuery pointer for database queries. Closing a query frees its resources, including any prepared statement information and any result sets.
Databases
Type: | IDatabase |
Closeable: | Yes |
Cloneable: | Yes |
API: | Core |
Found in: | dbi.inc |
Database Handles wrap an IDatabase pointer for database connections. Closing these disconnects the database and frees any related resources.
Events
Type: | Event |
Closeable: | No |
Cloneable: | No |
API: | Core |
Found in: | events.inc |
Event Handles are used for getting and setting data in Half-Life 2 game events as well as for firing them. They can only be closed using CancelCreatedEvent() if the event was not fired for some reason.
Files
Type: | File |
Closeable: | Yes |
Cloneable: | Yes |
API: | Core |
Found in: | files.inc |
File Handles are used for opening, reading from, writing to, and creating to files on the file system.
Forwards
Type: | Forward |
Closeable: | Yes |
Cloneable: | Only if explicitly stated |
API: | Core |
Found in: | functions.inc |
Forward Handles are primarily used when calling functions inside a forward container. There are two types of forwards: global and private. Only private forwards can be cloned.
KeyValues
Type: | KeyValues |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | keyvalues.inc |
KeyValues Handles abstract the Valve data type KeyValues, which are tree-based, recursive key to value mapping structures, often used to enumerate properties or configuration file directives.
Plugins
Type: | Plugin |
Closeable: | No |
Cloneable: | No |
API: | Core |
Found in: | sourcemod.inc |
Plugin Handles are used for the unique identification of a Plugin. They are owned by Core and cannot be cloned or closed by any other plugin or extension. However, plugins can use Handles for certain natives which enumerate or retrieve information on plugins.
Plugin Handles should not be cached globally, as plugins can become unloaded, and the Handle will be invalid.
Plugin Iterators
Type: | PluginIter |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | sourcemod.inc |
Plugin Iterators allow you to walk over a list of plugins. They are intended for temporary use only.
Protobuf
Type: | protobuf |
Closeable: | No |
Cloneable: | No |
API: | Core |
Found in: | protobuf.inc |
Protobuf Handles are currently used for usermessages in supported games. They directly reference protobuf Messages, which can be a usermessage or a Message field inside of a usermessage. They are owned and managed by Core and cannot be cloned or closed by a plugin.
SMC Parsers
Type: | SMC Parser |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | textparse.inc |
SMC Parsers are Handles which contain a set of functions for hooking parse events in an SMC file. Since they directly reference functions in a plugin, they cannot be cloned.
Timers
Type: | Timer |
Closeable: | Yes |
Cloneable: | No |
API: | Core |
Found in: | timers.inc |
Timers are temporary Handles which are automatically closed on any of the following events:
- The timer ends (via Plugin_Stop or being a one-time timer that is finished)
- The timer is killed via KillTimer
- The timer is closed via delete hHandle;
- The timer's parent plugin unloads