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

From AlliedModders Wiki
Jump to: navigation, search
m
m (Update formatting for readability and consistency)
(One intermediate revision by the same user not shown)
Line 15: Line 15:
  
 
For simplicity, let's consider calling a function in our own plugin.  We have the following function:
 
For simplicity, let's consider calling a function in our own plugin.  We have the following function:
<pawn>public void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot)
+
<sourcepawn>public void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot)
 
{
 
{
char name[MAX_NAME_LENGTH];
+
    char name[MAX_NAME_LENGTH];
GetClientName(victim, name, sizeof(name));
+
    GetClientName(victim, name, sizeof(name));
+
 
if (attacker != victim)
+
    if (attacker != victim)
{
+
    {
char other[MAX_NAME_LENGTH];
+
        char other[MAX_NAME_LENGTH];
GetClientName(attacker, other, sizeof(other));
+
        GetClientName(attacker, other, sizeof(other));
PrintToServer("<\"%s\"> killed by <\"%s\"> with \"%s\" (headshot: %d)", name, other, weapon, headshot);
+
        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 if (!attacker)
} else {
+
    {
PrintToServer("<\"%s\"> killed by \"self\" with \"%s\" (headshot: %d)", name, weapon, headshot);
+
        PrintToServer("<\"%s\"> killed by \"world\" with \"%s\" (headshot: %d)", name, weapon, headshot);
}
+
    }
}</pawn>
+
    else
 +
    {
 +
        PrintToServer("<\"%s\"> killed by \"self\" with \"%s\" (headshot: %d)", name, weapon, headshot);
 +
    }
 +
}</sourcepawn>
  
 
An indirect way to call this function would be:
 
An indirect way to call this function would be:
<pawn>
+
<sourcepawn>
 
public void EventHandler(Event event, const char[] name, bool dontBroadcast)
 
public void EventHandler(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
if (StrEqual(name, "player_death"))
+
    if (StrEqual(name, "player_death"))
{
+
    {
char weapon[64];
+
        char weapon[64];
int result;
+
        int result;
  
event.GetString("weapon", weapon, sizeof(weapon));
+
        event.GetString("weapon", weapon, sizeof(weapon));
  
/* Start function call */
+
        /* Start function call */
Call_StartFunction(null, OnClientDied);
+
        Call_StartFunction(null, OnClientDied);
  
/* Push parameters one at a time */
+
        /* Push parameters one at a time */
Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
+
        Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
+
        Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
Call_PushString(weapon);
+
        Call_PushString(weapon);
Call_PushCell(GetEventInt(event, "headshot"));
+
        Call_PushCell(GetEventInt(event, "headshot"));
  
/* Finish the call, get the result */
+
        /* Finish the call, get the result */
Call_Finish(result);
+
        Call_Finish(result);
}
+
    }
}</pawn>
+
}</sourcepawn>
  
 
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.
 
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.
Line 84: Line 88:
 
==Global Forwards==
 
==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:
 
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:
<pawn>forward void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot);</pawn>
+
<sourcepawn>forward void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot);</sourcepawn>
  
 
Implementation:
 
Implementation:
<pawn>GlobalForward g_DeathForward;
+
<sourcepawn>GlobalForward g_DeathForward;
  
 
public void OnPluginStart()
 
public void OnPluginStart()
 
{
 
{
g_DeathForward = new GlobalForward("OnClientDied", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
+
    g_DeathForward = new GlobalForward("OnClientDied", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
HookEvent("player_death", EventHandler);
+
    HookEvent("player_death", EventHandler);
 
}
 
}
  
 
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
 
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
 
{
 
{
RegPluginLibrary("my_plugin");
+
    RegPluginLibrary("my_plugin");
 
}
 
}
  
 
public Action EventHandler(Event event, const char[] name, bool dontBroadcast)
 
public Action EventHandler(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
char weapon[64];
+
    char weapon[64];
Action result;
+
    Action result;
  
event.GetString("weapon", weapon, sizeof(weapon));
+
    event.GetString("weapon", weapon, sizeof(weapon));
  
/* Start function call */
+
    /* Start function call */
Call_StartForward(g_DeathForward);
+
    Call_StartForward(g_DeathForward);
  
/* Push parameters one at a time */
+
    /* Push parameters one at a time */
Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
+
    Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
+
    Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
Call_PushString(weapon);
+
    Call_PushString(weapon);
Call_PushCell(GetEventInt(event, "headshot"));
+
    Call_PushCell(GetEventInt(event, "headshot"));
  
/* Finish the call, get the result */
+
    /* Finish the call, get the result */
Call_Finish(result);
+
    Call_Finish(result);
 
+
 
return result;
+
    return result;
}</pawn>
+
}</sourcepawn>
  
 
==Private Forwards==
 
==Private Forwards==
Line 126: Line 130:
  
 
Usually, this is done using dynamic natives; a plugin will expose a function to add to its own forwards.  For example:
 
Usually, this is done using dynamic natives; a plugin will expose a function to add to its own forwards.  For example:
<pawn>typedef OnClientDiedFunc = function Action (int attacker, int victim, const char[] weapon, bool headshot);
+
<sourcepawn>typedef OnClientDiedFunc = function Action (int attacker, int victim, const char[] weapon, bool headshot);
  
 
/**
 
/**
Line 134: Line 138:
 
  * @noreturn
 
  * @noreturn
 
  */
 
  */
native void HookClientDeath(OnClientDiedFunc func);</pawn>
+
native void HookClientDeath(OnClientDiedFunc func);</sourcepawn>
  
 
An implementation of this might look like:
 
An implementation of this might look like:
<pawn>PrivateForward g_DeathForward;
+
<sourcepawn>PrivateForward g_DeathForward;
  
 
public void OnPluginStart()
 
public void OnPluginStart()
 
{
 
{
g_DeathForward = new PrivateForward(ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
+
    g_DeathForward = new PrivateForward(ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell);
CreateNative("HookClientDeath", Native_HookClientDeath);
+
 
HookEvent("player_death", EventHandler);
+
    CreateNative("HookClientDeath", Native_HookClientDeath);
 +
 
 +
    HookEvent("player_death", EventHandler);
 
}
 
}
  
 
public int Native_HookClientDeath(Handle plugin, int numParams)
 
public int Native_HookClientDeath(Handle plugin, int numParams)
 
{
 
{
g_DeathForward.AddFunction(plugin, GetNativeFunction(1));
+
    g_DeathForward.AddFunction(plugin, GetNativeFunction(1));
}</pawn>
+
}</sourcepawn>
  
 
Note that the code to call the forward does not need to change at all.
 
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:
 
A complete implementation of a private forward may look like this:
<pawn>typedef MyFunction = function void (int client);
+
<sourcepawn>typedef MyFunction = function void (int client);
native void My_NativeEx(MyFunction func);</pawn>
+
native void My_NativeEx(MyFunction func);</sourcepawn>
  
<pawn>PrivateForward g_hDeathFwd;
+
<sourcepawn>PrivateForward g_hDeathFwd;
  
  
Line 164: Line 170:
 
public APLRes AskPluginLoad2(Handle plugin, bool late, const char[] error, int err_max)
 
public APLRes AskPluginLoad2(Handle plugin, bool late, const char[] error, int err_max)
 
{
 
{
RegPluginLibrary("MyPlugin");
+
    RegPluginLibrary("MyPlugin");
 +
 
 +
    CreateNative("My_NativeEx", My_Native);
  
CreateNative("My_NativeEx", My_Native);
+
    return APLRes_Success;
return APLRes_Success;
 
 
}
 
}
  
 
public void OnPluginStart()
 
public void OnPluginStart()
 
{
 
{
HookEvent("player_death", Event_Death);
+
    HookEvent("player_death", Event_Death);
g_hDeathFwd = new PrivateForward(ET_Ignore, Param_Cell);
+
    g_hDeathFwd = new PrivateForward(ET_Ignore, Param_Cell);
 
}
 
}
  
 
public int My_Native(Handle plugin, int numParams)
 
public int My_Native(Handle plugin, int numParams)
 
{
 
{
g_hDeathFwd.AddFunction(plugin, GetNativeFunction(1));
+
    g_hDeathFwd.AddFunction(plugin, GetNativeFunction(1));
 
}
 
}
  
 
public Action Event_Death(Event event, const char[] name, bool dontBroadcast)
 
public Action Event_Death(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
int client = GetClientOfUserId(event.GetInt("userid"));
+
    int client = GetClientOfUserId(event.GetInt("userid"));
  
Call_StartForward(g_hDeathFwd);
+
    Call_StartForward(g_hDeathFwd);
Call_PushCell(client);
+
    Call_PushCell(client);
Call_Finish();
+
    Call_Finish();
}</pawn>
+
}</sourcepawn>
  
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]

Revision as of 20:34, 29 March 2020

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(GetEventInt(event, "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, const 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();
}