Difference between revisions of "Function Calling API (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
(Syntax update)
m (Private Forwards: remove const)
Line 168: Line 168:
 
// typedef MyFunction = function void (int client);
 
// typedef MyFunction = function void (int client);
 
// native void My_NativeEx(MyFunction func);
 
// native void My_NativeEx(MyFunction func);
public APLRes AskPluginLoad2(Handle plugin, bool late, const char[] error, int err_max)
+
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
 
{
 
{
 
     RegPluginLibrary("MyPlugin");
 
     RegPluginLibrary("MyPlugin");

Revision as of 15:31, 1 August 2021

SourceMod provides plugins with an API for calling functions. This API can be used to call public functions in any plugin, including public functions in the same plugin.

This article is split into two sections. The first is on generic function calling, which is used for single function calls. The second is on Forwards, which is used for calling multiple functions in one operation.

For more information on forwards, readers should see forwards in extensions.

Generic Calling

There are four steps to calling a function in a plugin:

  1. Obtaining a "call Handle." This is either in the form of a function ID, tagged with Function, or a Forward Handle, tagged with Handle.
  2. Starting the call.
  3. Pushing parameters in increasing order in a way that matches the function prototype.
  4. Ending the the call, which performs the call operation and returns the result.

For simplicity, let's consider calling a function in our own plugin. We have the following function:

public void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot)
{
    char name[MAX_NAME_LENGTH];
    GetClientName(victim, name, sizeof(name));
 
    if (attacker != victim)
    {
        char other[MAX_NAME_LENGTH];
        GetClientName(attacker, other, sizeof(other));
        PrintToServer("<\"%s\"> killed by <\"%s\"> with \"%s\" (headshot: %d)", name, other, weapon, headshot);
    }
    else if (!attacker)
    {
        PrintToServer("<\"%s\"> killed by \"world\" with \"%s\" (headshot: %d)", name, weapon, headshot);
    }
    else
    {
        PrintToServer("<\"%s\"> killed by \"self\" with \"%s\" (headshot: %d)", name, weapon, headshot);
    }
}

An indirect way to call this function would be:

public void EventHandler(Event event, const char[] name, bool dontBroadcast)
{
    if (StrEqual(name, "player_death"))
    {
        char weapon[64];
        int result;
 
        event.GetString("weapon", weapon, sizeof(weapon));
 
        /* Start function call */
        Call_StartFunction(null, OnClientDied);
 
        /* Push parameters one at a time */
        Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
        Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
        Call_PushString(weapon);
        Call_PushCell(event.GetInt("headshot"));
 
        /* Finish the call, get the result */
        Call_Finish(result);
    }
}

This basic example shows starting and completing a function call. However, the real use of function calling is with forwards, which is covered in the next section.

Forwards

Forwards are much more advantageous over single function calls. They are expandable containers, so you can store and complete many calls with very little action. Furthermore, they also adjust themselves when contained plugins are unloaded. Lastly, they are type-checked; each forward's parameter types must be known in advance, and if you push a mismatching type, the call will not complete.

Forwards must be created using the following types:

  • Param_Any - Any parameter type can be pushed
  • Param_Cell - A non-Float cell can be pushed
  • Param_Float - A Float cell can be pushed
  • Param_String - A string can be pushed
  • Param_Array - An array can be pushed
  • Param_VarArgs - This and all further parameters can be any type, but will be by reference. This cannot be the first parameter type, and if it is used, it must be the last parameter type.
  • Param_CellByRef - A non-Float cell by reference
  • Param_FloatByRef - A Float cell by reference

Strings and arrays are implicitly by-reference. When pushing variable argument parameters, if anything is pushed by-value, it will be internally automatically converted to by-reference.

Since Forwards will call multiple functions in a row, it needs to know how to interpret the return values of functions. There are four predefined methods:

  • ET_Ignore - All return values will be ignored; 0 will be returned at the end.
  • ET_Single - Only the last return value will be returned.
  • ET_Event - Function should return an Action value (core.inc). Plugin_Stop acts as Plugin_Handled. The highest value is returned.
  • ET_Hook - Function should return an Action value. Plugin_Stop ends the forward call immediately.

Let's write a simple example. Our plugin, Plugin A, wants to tell other plugins when a player dies. It has two ways of doing this, either via a global forward or a private forward. A global forward acts upon all functions in all plugins that match a single name. A private forward lets you explicitly manage which functions are in the container.

Global Forwards

Global forwards are very simple to use. After creation, they do not need to be maintained. An example plugin below creates a global forward with the following prototype:

forward void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot);

Implementation:

GlobalForward g_DeathForward;
 
public void OnPluginStart()
{
    g_DeathForward = new GlobalForward("OnClientDied", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
    HookEvent("player_death", EventHandler);
}
 
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
{
    RegPluginLibrary("my_plugin");
}
 
public Action EventHandler(Event event, const char[] name, bool dontBroadcast)
{
    char weapon[64];
    Action result;
 
    event.GetString("weapon", weapon, sizeof(weapon));
 
    /* Start function call */
    Call_StartForward(g_DeathForward);
 
    /* Push parameters one at a time */
    Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
    Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
    Call_PushString(weapon);
    Call_PushCell(GetEventInt(event, "headshot"));
 
    /* Finish the call, get the result */
    Call_Finish(result);
 
    return result;
}

Private Forwards

Private forwards require you to manually add functions to its container. This can leave you with much more flexibility. Like global forwards, they automatically remove functions from unloaded plugins.

Usually, this is done using dynamic natives; a plugin will expose a function to add to its own forwards. For example:

typedef OnClientDiedFunc = function Action (int attacker, int victim, const char[] weapon, bool headshot);
 
/**
 * Calls the target function when a client dies.
 *
 * @param func      OnClientDiedFunc function.
 * @noreturn
 */
native void HookClientDeath(OnClientDiedFunc func);

An implementation of this might look like:

PrivateForward g_DeathForward;
 
public void OnPluginStart()
{
    g_DeathForward = new PrivateForward(ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
 
    CreateNative("HookClientDeath", Native_HookClientDeath);
 
    HookEvent("player_death", EventHandler);
}
 
public int Native_HookClientDeath(Handle plugin, int numParams)
{
    g_DeathForward.AddFunction(plugin, GetNativeFunction(1));
}

Note that the code to call the forward does not need to change at all.

A complete implementation of a private forward may look like this:

typedef MyFunction = function void (int client);
native void My_NativeEx(MyFunction func);
PrivateForward g_hDeathFwd;
 
 
// typedef MyFunction = function void (int client);
// native void My_NativeEx(MyFunction func);
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
{
    RegPluginLibrary("MyPlugin");
 
    CreateNative("My_NativeEx", My_Native);
 
    return APLRes_Success;
}
 
public void OnPluginStart()
{
    HookEvent("player_death", Event_Death);
    g_hDeathFwd = new PrivateForward(ET_Ignore, Param_Cell);
}
 
public int My_Native(Handle plugin, int numParams)
{
    g_hDeathFwd.AddFunction(plugin, GetNativeFunction(1));
}
 
public Action Event_Death(Event event, const char[] name, bool dontBroadcast)
{
    int client = GetClientOfUserId(event.GetInt("userid"));
 
    Call_StartForward(g_hDeathFwd);
    Call_PushCell(client);
    Call_Finish();
}