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

From AlliedModders Wiki
Jump to: navigation, search
(New page: 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 i...)
 
(Correct void forward event type)
 
(17 intermediate revisions by 8 users not shown)
Line 8: Line 8:
 
There are four steps to calling a function in a plugin:
 
There are four steps to calling a function in a plugin:
 
<ol>
 
<ol>
  <li>Obtaining a "call Handle."  This is either in the form of a function ID, tagged with <tt>Function:</tt>, or a Forward Handle, tagged with <tt>Handle:</tt>.</li>
+
  <li>Obtaining a "call Handle."  This is either in the form of a function ID, tagged with <tt>Function</tt>, or a Forward Handle, tagged with <tt>Handle</tt>.</li>
 
  <li>Starting the call.
 
  <li>Starting the call.
 
  <li>Pushing parameters in increasing order in a way that matches the function prototype.</li>
 
  <li>Pushing parameters in increasing order in a way that matches the function prototype.</li>
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 OnClientDied(attacker, victim, const String:weapon[], bool:headshot)
+
<sourcepawn>public void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot)
 
{
 
{
  decl String:name[65]
+
    char name[MAX_NAME_LENGTH];
  GetClientName(victim, name, sizeof(name))
+
    GetClientName(victim, name, sizeof(name));
 
+
 
  if (attacker != victim)
+
    if (attacker != victim)
  {
+
    {
      decl String:other[65]
+
        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 EventHandler(Handle:event, const String:name[], bool:dontBroadcast)
+
public void EventHandler(Event event, const char[] name, bool dontBroadcast)
 
{
 
{
  if (StrEqual(name, "player_death"))
+
    if (StrEqual(name, "player_death"))
  {
+
    {
      decl String:weapon[64], result
+
        char weapon[64];
      GetEventString(event, "weapon", weapon, sizeof(weapon))
+
        int result;
 +
 
 +
        event.GetString("weapon", weapon, sizeof(weapon));
  
      /* Start function call */
+
        /* Start function call */
      Call_StartFunction(INVALID_HANDLE, OnClientDied)
+
        Call_StartFunction(null, OnClientDied);
  
      /* Push parameters one at a time */
+
        /* Push parameters one at a time */
      Call_PushCell(GetClientOfUserId(GetEventInt(event, "attacker")))
+
        Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
      Call_PushCell(GetClientOfUserId(GetEventInt(event, "userid")))
+
        Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
      Call_PushString(weapon)
+
        Call_PushString(weapon);
      Call_PushCell(GetEventInt(event, "headshot"))
+
        Call_PushCell(event.GetInt("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 82: 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 OnClientDied(attacker, victim, const String:weapon[], bool:headshot);</pawn>
+
<sourcepawn>forward void OnClientDied(int attacker, int victim, const char[] weapon, bool headshot);</sourcepawn>
  
 
Implementation:
 
Implementation:
<pawn>new Handle:g_DeathForward
+
<sourcepawn>GlobalForward g_DeathForward;
  
public OnPluginStart()
+
public void OnPluginStart()
 
{
 
{
  g_DeathForward = CreateGlobalForward("OnClientDied", ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell)
+
    g_DeathForward = new GlobalForward("OnClientDied", ET_Ignore, Param_Cell, Param_Cell, Param_String, Param_Cell);
  HookEvent("player_death", EventHandler)
+
    HookEvent("player_death", EventHandler);
 
}
 
}
  
public Action:EventHandler(Handle:event, const String:name[], bool:dontBroadcast)
+
public APLRes AskPluginLoad2(Handle plugin, bool late, char[] error, int err_max)
 
{
 
{
  decl String:weapon[64], Action:result
+
    RegPluginLibrary("my_plugin");
  GetEventString(event, "weapon", weapon, sizeof(weapon))
+
}
  
  /* Start function call */
+
public Action EventHandler(Event event, const char[] name, bool dontBroadcast)
  Call_StartForward(g_DeathForward)
+
{
 +
    char weapon[64];
 +
    event.GetString("weapon", weapon, sizeof(weapon));
  
  /* Push parameters one at a time */
+
    /* Start function call */
  Call_PushCell(GetClientOfUserId(GetEventInt(event, "attacker")))
+
    Call_StartForward(g_DeathForward);
  Call_PushCell(GetClientOfUserId(GetEventInt(event, "userid")))
 
  Call_PushString(weapon)
 
  Call_PushCell(GetEventInt(event, "headshot"))
 
  
  /* Finish the call, get the result */
+
    /* Push parameters one at a time */
  Call_Finish(_:result)
+
    Call_PushCell(GetClientOfUserId(event.GetInt("attacker")));
 
+
    Call_PushCell(GetClientOfUserId(event.GetInt("userid")));
  return result
+
    Call_PushString(weapon);
}</pawn>
+
    Call_PushCell(GetEventInt(event, "headshot"));
 +
 
 +
    /* Finish the call */
 +
    Call_Finish();
 +
 
 +
    return result;
 +
}</sourcepawn>
  
 
==Private Forwards==
 
==Private Forwards==
Line 117: Line 128:
  
 
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>functag OnClientDiedFunc Action:public(attacker, victim, const String:weapon[], bool:headshot);
+
<sourcepawn>typedef OnClientDiedFunc = function Action (int attacker, int victim, const char[] weapon, bool headshot);
  
 
/**
 
/**
Line 125: Line 136:
 
  * @noreturn
 
  * @noreturn
 
  */
 
  */
native 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>public OnPluginStart()
+
<sourcepawn>PrivateForward g_DeathForward;
 +
 
 +
public void OnPluginStart()
 
{
 
{
  g_DeathForward = CreateForward(ET_Event, Param_Cell, Param_Cell, Param_String, Param_Cell)
+
    g_DeathForward = new PrivateForward(ET_Ignore, 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 Native_HookClientDeath(Handle:plugin, numParams)
+
public int Native_HookClientDeath(Handle plugin, int numParams)
 
{
 
{
  AddToForward(g_DeathForward, plugin, Function:GetNativeCell(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:
 +
<sourcepawn>typedef MyFunction = function void (int client);
 +
native void My_NativeEx(MyFunction func);</sourcepawn>
 +
 +
<sourcepawn>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();
 +
}</sourcepawn>
  
 
[[Category:SourceMod Scripting]]
 
[[Category:SourceMod Scripting]]
[[Category:SourceMod Development]]
 

Latest revision as of 05:45, 6 January 2022

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_Ignore, 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];
    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 */
    Call_Finish();
 
    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_Ignore, 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();
}