Fleshed out forward system and extensively documented its design considerations
Implemented and did basic tests on new IPluginFunction type Split function types into a separate file --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40182
This commit is contained in:
+123
-68
@@ -7,6 +7,11 @@
|
||||
#define SMINTERFACE_FORWARDMANAGER_NAME "IForwardManager"
|
||||
#define SMINTERFACE_FORWARDMANAGER_VERSION 1
|
||||
|
||||
/**
|
||||
* There is some very important documentation at the bottom of this file.
|
||||
* Readers interested in knowing more about the forward system, scrolling down is a must!
|
||||
*/
|
||||
|
||||
namespace SourceMod
|
||||
{
|
||||
enum ResultType
|
||||
@@ -25,12 +30,14 @@ namespace SourceMod
|
||||
};
|
||||
|
||||
/**
|
||||
* :TODO: finish this spec
|
||||
* @brief Abstracts multiple function calling.
|
||||
*
|
||||
* NOTE: Parameters should be pushed in forward order, unlike
|
||||
* the virtual machine/IPluginContext order.
|
||||
* NOTE: Some functions are repeated in here because their documentation differs
|
||||
* from their IPluginFunction equivalents.
|
||||
*/
|
||||
class IForward : public IPluginFunction
|
||||
class IForward : public ICallable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -40,61 +47,6 @@ namespace SourceMod
|
||||
*/
|
||||
virtual const char *GetForwardName() =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a cell onto the current call.
|
||||
*
|
||||
* @param cell Parameter value to push.
|
||||
* @return True if successful, false if type or count mismatch.
|
||||
*/
|
||||
virtual bool PushCell(cell_t cell) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a float onto the current call.
|
||||
*
|
||||
* @param float Parameter value to push.
|
||||
* @return True if successful, false if type or count mismatch.
|
||||
*/
|
||||
virtual bool PushFloat(float number) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes an array of cells onto the current call, each cell individually.
|
||||
* NOTE: This is essentially a crippled version of PushArray().
|
||||
*
|
||||
* @param array Array of cells.
|
||||
* @param numcells Number of cells in array.
|
||||
* @param each Whether or not to push as an array or individual cells.
|
||||
* @return True if successful, false if too many cells were pushed.
|
||||
*/
|
||||
virtual bool PushCells(cell_t array[], unsigned int numcells, bool each) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes an array of cells onto the current call.
|
||||
*
|
||||
* @param array Array to copy, NULL if no initial array should be copied.
|
||||
* @param cells Number of cells to allocate and optionally read from the input array.
|
||||
* @param phys_addr Optional return address for physical array.
|
||||
* @param copyback Whether or not changes should be copied back to the input array.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
virtual bool PushArray(cell_t *inarray, size_t cells, cell_t **phys_addr, bool copyback) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a string onto the current call.
|
||||
*
|
||||
* @param string String to push.
|
||||
* @return True if successful, false if type or count mismatch.
|
||||
*/
|
||||
virtual bool PushString(const char *string) =0;
|
||||
|
||||
/**
|
||||
* @brief Executes the forward, resets the pushed parameter list, and performs any copybacks.
|
||||
*
|
||||
* @param result Pointer to store return value in (dependent on forward type).
|
||||
* @param last_err Pointer to store number of successful executions in.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int Execute(cell_t *result, unsigned int *num_functions) =0;
|
||||
|
||||
/**
|
||||
* @brief Returns the number of functions in this forward.
|
||||
*
|
||||
@@ -105,9 +57,18 @@ namespace SourceMod
|
||||
/**
|
||||
* @brief Returns the method of multi-calling this forward has.
|
||||
*
|
||||
* @return ResultType of the forward.
|
||||
* @return ExecType of the forward.
|
||||
*/
|
||||
virtual ResultType GetResultType() =0;
|
||||
virtual ExecType GetExecType() =0;
|
||||
|
||||
/**
|
||||
* @brief Executes the forward.
|
||||
*
|
||||
* @param result Pointer to store result in.
|
||||
* @param num_functions Optionally filled with the number of function sucessfully executed.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int Execute(cell_t *result, unsigned int *num_functions) =0;
|
||||
};
|
||||
|
||||
|
||||
@@ -127,24 +88,37 @@ namespace SourceMod
|
||||
* @param plugin Plugin to remove instances of.
|
||||
* @return Number of functions removed therein.
|
||||
*/
|
||||
virtual void RemoveFunctionsOfPlugin(IPlugin *plugin) =0;
|
||||
virtual unsigned int RemoveFunctionsOfPlugin(IPlugin *plugin) =0;
|
||||
|
||||
/**
|
||||
* @brief Adds a function to the call list.
|
||||
* NOTE: Cannot be used during a call.
|
||||
*
|
||||
* @param func Function to add.
|
||||
* @return True on success, otherwise false.
|
||||
*/
|
||||
virtual void AddFunction(IPluginFunction *func) =0;
|
||||
virtual bool AddFunction(IPluginFunction *func) =0;
|
||||
|
||||
/**
|
||||
* @brief Adds a function to the call list.
|
||||
* NOTE: Cannot be used during a call.
|
||||
*
|
||||
* @param ctx Context to use as a look-up.
|
||||
* @param funcid Function id to add.
|
||||
* @return True on success, otherwise false.
|
||||
*/
|
||||
virtual bool AddFunction(sp_context_t *ctx, funcid_t index) =0;
|
||||
};
|
||||
|
||||
enum ParamType
|
||||
{
|
||||
Param_Any = 0,
|
||||
Param_Cell = 1,
|
||||
Param_Float = 2,
|
||||
Param_String = 3,
|
||||
Param_Array = 4,
|
||||
Param_VarArgs = 5,
|
||||
Param_Any = 0, //Any type will be accepted
|
||||
Param_Cell = 1, //Only a cell will be accepted
|
||||
Param_Float = 2, //Only a float value will be accepted
|
||||
Param_String = 3, //Only a string will be accepted
|
||||
Param_Array = 4, //Only a 1D array will be accepted
|
||||
Param_VarArgs = 5, //Anything will be accepted
|
||||
ParamTypes_Total = 6,
|
||||
};
|
||||
|
||||
class IForwardManager : public SMInterface
|
||||
@@ -174,7 +148,11 @@ namespace SourceMod
|
||||
* @param ... If types is NULL, num_params ParamTypes should be pushed.
|
||||
* @return A new IForward on success, NULL if type combination is impossible.
|
||||
*/
|
||||
virtual IForward *CreateForward(const char *name, ExecType et, int num_params, ParamType *types, ...) =0;
|
||||
virtual IForward *CreateForward(const char *name,
|
||||
ExecType et,
|
||||
unsigned int num_params,
|
||||
ParamType *types,
|
||||
...) =0;
|
||||
|
||||
/**
|
||||
* @brief Creates an unmanaged forward. This forward exists privately.
|
||||
@@ -208,4 +186,81 @@ namespace SourceMod
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* In the AMX Mod X model of forwarding, each forward contained a list of pairs, each pair containing
|
||||
* a function ID and an AMX structure. The forward structure itself did very little but hold parameter types.
|
||||
* An execution call worked like this:
|
||||
* - executeForward() took in a function id and a list of parameters
|
||||
* - for each contained plugin:
|
||||
* - the list of parameters was preprocessed and pushed
|
||||
* - the call was made
|
||||
* - the list was freed and copybacks were made
|
||||
* - return
|
||||
*
|
||||
* The advantages to this is that the system is very easy to implement, and it's fast. The disadvantage is
|
||||
* varargs tend to be very unforgiving and inflexible, and thus weird problems arose with casting. You also
|
||||
* lose flexibility, type checking, and the ability to reasonably use variable arguments lists in the VM.
|
||||
*
|
||||
* SourceMod replaces this forward system with a far more advanced, but a bit bulkier one. The idea is that
|
||||
* each plugin has a table of functions, and each function is an ICallable object. As well as being an ICallable,
|
||||
* each function is an IPluginFunction. An ICallable simply describes the process of adding parameters to a
|
||||
* function call. An IPluginFunction describes the process of actually calling a function and performing allocation,
|
||||
* copybacks, and deallocations.
|
||||
*
|
||||
* A very powerful forward system emerges: a Forward is just a collection of IPluginFunctions. Thus, the same
|
||||
* API can be easily wrapped around a simple list, and it will look transparent to the user.
|
||||
* Advantages:
|
||||
* 1) "SP Forwards" from AMX Mod X are simply IPluginFunctions without a collection.
|
||||
* 2) Forwards are function based, rather than plugin based, and are thus far more flexible at runtime..
|
||||
* 3) [2] Individual functions can be paused and more than one function from the same plugin can be hooked.
|
||||
* 4) [2] One hook type that used to map to many SP Forwards can now be centralized as one Forward.
|
||||
* This helps alleviate messes like Fakemeta.
|
||||
* 5) Parameter pushing is type-checked and allows for variable arguments.
|
||||
*
|
||||
* Note that while #2,3,4 could be added to AMX Mod X, the real binding property is #1, which makes the system
|
||||
* object oriented, rather than AMX Mod X, which hides the objects behind static functions. It is entirely a design
|
||||
* issue, rather than a usability one. The interesting part is when it gets to implementation, which is when the
|
||||
* problems for SourceMod arise. Specifically, the implementation is easier in GENERAL -- the tough part is the oddball
|
||||
* cases.
|
||||
*
|
||||
* Observe the calling process:
|
||||
* - Each parameter is pushed using the ICallable interface. For each parameter:
|
||||
* - For each function in the collection, the parameter is processed and pushed.
|
||||
* - For each function in the collection:
|
||||
* - The call is made.
|
||||
* - Copy backs are performed.
|
||||
* - Return
|
||||
*
|
||||
* Astute readers will note the problems - the parameters are processed individually for each push,
|
||||
* rather than for each call. This means:
|
||||
* 1) More memory is used. Specifically, rather than N params of memory, you now have N params * M plugins.
|
||||
* This is because, again, parameters are processed per function object, per-push, before the call.
|
||||
* 2) There are slightly more calls going around: one extra call for each parameter, since each push is manual.
|
||||
* 3) Copybacks won't work as expected.
|
||||
*
|
||||
* Number 3 is hard to see. For example, say a forward has two functions, and an array is pushed with copyback.
|
||||
* The array gets pushed and copied into each plugin. When the call is made, each plugin now has separate copies of
|
||||
* the array. When the copyback is performed, it gets mirrored to the originall address, but not to the next plugin!
|
||||
|
||||
* Implementing this is "difficult." To be re-entrant, an IPluginFunction can't tell you anything
|
||||
* about its copybacks after it has returned, because its internal states were reset long ago.
|
||||
*
|
||||
* In order to implement this feature, IPluginFunction::Execute function now lets you pass a listener in.
|
||||
* This listener is notified each time an array parameter is about to be copied back. Specifically, the forward
|
||||
* uses this to detect when it needs to push a new parameter out to the next plugin. When the forward gets the
|
||||
* call back, it detects whether there is another plugin in the queue. If there is, it grabs the address it will
|
||||
* be using for the same arrays, and specifies it as the new copy-back point. If no plugins are left, it allows
|
||||
* the copyback to chain up to the original address.
|
||||
*
|
||||
* This wonderful little hack is somewhat reminiscent of how SourceHook parameter rewrite chains work. It seems
|
||||
* ugly at first, but it is actually the correct design pattern to apply to an otherwise awful problem.
|
||||
*
|
||||
* Note that there are other solutions to this that aren't based on a visitor-like pattern. For example, the Function
|
||||
* class could expose a "set new original address" function. Then after arrays are pushed, the arrays are re-browsed,
|
||||
* and function #1 gets function #2's original address, function #2 gets function #3's original address, et cetera.
|
||||
* This extra browse step is a bit less efficient though, since the "visitor" method implies only taking action when
|
||||
* necessary. Furthermore, it would require exposing such a "set address" function, which should fire a red flag
|
||||
* that the API is doing something it shouldn't (namely, exposing the direct setting of very internal properties).
|
||||
*/
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
#ifndef _INCLUDE_SOURCEMOD_PLUGINFUNCTION_INTERFACE_H_
|
||||
#define _INCLUDE_SOURCEMOD_PLUGINFUNCTION_INTERFACE_H_
|
||||
|
||||
#include <IPluginSys.h>
|
||||
|
||||
namespace SourceMod
|
||||
{
|
||||
#define SMFUNC_COPYBACK_NONE (0) /* Never copy an array back */
|
||||
#define SMFUNC_COPYBACK_ONCE (1<<0) /* Copy an array back after call */
|
||||
#define SMFUNC_COPYBACK_ALWAYS (1<<1) /* Copy an array back after subsequent calls (forwards) */
|
||||
|
||||
/**
|
||||
* @brief Represents what a function needs to implement in order to be callable.
|
||||
*/
|
||||
class ICallable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Pushes a cell onto the current call.
|
||||
*
|
||||
* @param cell Parameter value to push.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushCell(cell_t cell) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a cell by reference onto the current call.
|
||||
* NOTE: On Execute, the pointer passed will be modified if copyback is enabled.
|
||||
*
|
||||
* @param cell Address containing parameter value to push.
|
||||
* @param flags Copy-back flags.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushCellByRef(cell_t *cell, int flags) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a float onto the current call.
|
||||
*
|
||||
* @param float Parameter value to push.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushFloat(float number) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a float onto the current call by reference.
|
||||
* NOTE: On Execute, the pointer passed will be modified if copyback is enabled.
|
||||
*
|
||||
* @param float Parameter value to push.
|
||||
& @param flags Copy-back flags.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushFloatByRef(float *number, int flags) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes an array of cells onto the current call, each cell individually.
|
||||
* NOTE: This is essentially a crippled version of PushArray().
|
||||
*
|
||||
* @param array Array of cells.
|
||||
* @param numcells Number of cells in array.
|
||||
* @param each Whether or not to push as an array or individual cells.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushCells(cell_t array[], unsigned int numcells, bool each) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes an array of cells onto the current call.
|
||||
* NOTE: On Execute, the pointer passed will be modified if non-NULL and copy-back
|
||||
* is enabled.
|
||||
*
|
||||
* @param array Array to copy, NULL if no initial array should be copied.
|
||||
* @param cells Number of cells to allocate and optionally read from the input array.
|
||||
* @param phys_addr Optional return address for physical array (if array was non-NULL, will be array).
|
||||
* @param flags Whether or not changes should be copied back to the input array.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushArray(cell_t *inarray,
|
||||
unsigned int cells,
|
||||
cell_t **phys_addr,
|
||||
int flags=SMFUNC_COPYBACK_NONE) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a string onto the current call.
|
||||
*
|
||||
* @param string String to push.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushString(const char *string) =0;
|
||||
|
||||
/**
|
||||
* @brief Pushes a string onto the current call.
|
||||
* NOTE: On Execute, the pointer passed will be modified if copy-back is enabled.
|
||||
*
|
||||
* @param string String to push.
|
||||
* @param flags Copy-back flags.
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int PushStringByRef(char *string, int flags) =0;
|
||||
|
||||
/**
|
||||
* @brief Cancels a function call that is being pushed but not yet executed.
|
||||
* This can be used be reset for CallFunction() use.
|
||||
*/
|
||||
virtual void Cancel() =0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Used for copy-back notification
|
||||
*/
|
||||
class IFunctionCopybackReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Called before an array is copied back.
|
||||
*
|
||||
* @param param Parameter index.
|
||||
* @param cells Number of cells in the array.
|
||||
* @param source_addr Source address in the plugin that will be copied.
|
||||
* @param orig_addr Destination addres in plugin that will be overwritten.
|
||||
* @param flags Copy flags.
|
||||
* @return True to copy back, false otherwise.
|
||||
*/
|
||||
virtual bool OnCopybackArray(unsigned int param,
|
||||
unsigned int cells,
|
||||
cell_t *source_addr,
|
||||
cell_t *orig_addr,
|
||||
int flags) =0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Encapsulates a function call in a plugin.
|
||||
* NOTE: Function calls must be atomic to one execution context.
|
||||
* NOTE: This object should not be deleted. It lives for the lifetime of the plugin.
|
||||
*/
|
||||
class IPluginFunction : public ICallable
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Executes the forward, resets the pushed parameter list, and performs any copybacks.
|
||||
*
|
||||
* @param result Pointer to store return value in.
|
||||
* @param reader Copy-back listener. NULL to specify
|
||||
* @return Error code, if any.
|
||||
*/
|
||||
virtual int Execute(cell_t *result, IFunctionCopybackReader *reader) =0;
|
||||
|
||||
/**
|
||||
* @brief Executes the function with the given parameter array.
|
||||
* Parameters are read in forward order (i.e. index 0 is parameter #1)
|
||||
* NOTE: You will get an error if you attempt to use CallFunction() with
|
||||
* previously pushed parameters.
|
||||
*
|
||||
* @param param Array of cell parameters.
|
||||
* @param num_params Number of parameters to push.
|
||||
* @param result Pointer to store result of function on return.
|
||||
* @return SourcePawn error code (if any).
|
||||
*/
|
||||
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) =0;
|
||||
|
||||
/**
|
||||
* @brief Returns which plugin this function belongs to.
|
||||
*
|
||||
* @return IPlugin pointer to parent plugin.
|
||||
*/
|
||||
virtual IPlugin *GetParentPlugin() =0;
|
||||
|
||||
/**
|
||||
* @brief Returns the physical address of a by-reference parameter.
|
||||
*
|
||||
* @param Parameter index to read (beginning at 0).
|
||||
* @return Address, or NULL if invalid parameter specified.
|
||||
*/
|
||||
virtual cell_t *GetAddressOfPushedParam(unsigned int param) =0;
|
||||
};
|
||||
};
|
||||
|
||||
#endif //_INCLUDE_SOURCEMOD_PLUGINFUNCTION_INTERFACE_H_
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace SourceMod
|
||||
{
|
||||
class IPlugin;
|
||||
|
||||
/**
|
||||
* @brief Encapsulates plugin public information.
|
||||
*/
|
||||
@@ -48,39 +50,7 @@ namespace SourceMod
|
||||
PluginType_Global, /* Plugin will never be unloaded or updated */
|
||||
};
|
||||
|
||||
|
||||
class IPlugin;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Encapsulates a basic function call.
|
||||
* NOTE: Function calls must be atomic to one execution context.
|
||||
* NOTE: This object should not be deleted. It lives for the lifetime of the plugin.
|
||||
*/
|
||||
class IPluginFunction
|
||||
{
|
||||
public:
|
||||
virtual ~IPluginFunction()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the function with the given parameter array.
|
||||
* Parameters are read in forward order (i.e. index 0 is parameter #1)
|
||||
*
|
||||
* @param param Array of cell parameters.
|
||||
* @param num_params Number of parameters to push.
|
||||
* @param result Pointer to store result of function on return.
|
||||
* @return SourcePawn error code (if any).
|
||||
*/
|
||||
virtual int CallFunction(const cell_t *params, unsigned int num_params, cell_t *result) =0;
|
||||
|
||||
/**
|
||||
* @brief Returns which plugin this function belongs to.
|
||||
*/
|
||||
virtual IPlugin *GetParentPlugin() =0;
|
||||
};
|
||||
|
||||
class IPluginFunction;
|
||||
|
||||
/**
|
||||
* @brief Encapsulates a run-time plugin as maintained by SourceMod.
|
||||
|
||||
Reference in New Issue
Block a user