dvander@174: #ifndef _INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_ dvander@174: #define _INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_ dvander@174: dvander@174: #include dvander@174: #include dvander@174: dvander@174: #define SMINTERFACE_FORWARDMANAGER_NAME "IForwardManager" dvander@174: #define SMINTERFACE_FORWARDMANAGER_VERSION 1 dvander@174: dvander@177: /** dvander@177: * There is some very important documentation at the bottom of this file. dvander@177: * Readers interested in knowing more about the forward system, scrolling down is a must! dvander@177: */ dvander@177: dvander@174: namespace SourceMod dvander@174: { dvander@174: enum ResultType dvander@174: { dvander@174: Pl_Continue = 0, /* No result */ dvander@174: Pl_Handled = 1, /* Result was handled, stop at the end */ dvander@174: Pl_Stop = 2, /* Result was handled, stop now */ dvander@174: }; dvander@174: dvander@174: enum ExecType dvander@174: { dvander@174: ET_Ignore = 0, /* Ignore all return values, return 0 */ dvander@174: ET_Single = 1, /* Only return the first exec, ignore all others */ dvander@174: ET_Event = 2, /* Acts as an event with the ResultTypes above, no mid-Stops allowed, returns highest */ dvander@174: ET_Hook = 3, /* Acts as a hook with the ResultTypes above, mid-Stops allowed, returns highest */ dvander@174: }; dvander@174: dvander@174: /** dvander@177: * :TODO: finish this spec dvander@174: * @brief Abstracts multiple function calling. dvander@174: * NOTE: Parameters should be pushed in forward order, unlike dvander@174: * the virtual machine/IPluginContext order. dvander@177: * NOTE: Some functions are repeated in here because their documentation differs dvander@177: * from their IPluginFunction equivalents. dvander@174: */ dvander@177: class IForward : public ICallable dvander@174: { dvander@174: public: dvander@174: /** dvander@174: * @brief Returns the name of the forward. dvander@174: * dvander@174: * @return Forward name. dvander@174: */ dvander@174: virtual const char *GetForwardName() =0; dvander@174: dvander@174: /** dvander@174: * @brief Returns the number of functions in this forward. dvander@174: * dvander@174: * @return Number of functions in forward. dvander@174: */ dvander@174: virtual unsigned int GetFunctionCount() =0; dvander@174: dvander@174: /** dvander@174: * @brief Returns the method of multi-calling this forward has. dvander@174: * dvander@177: * @return ExecType of the forward. dvander@174: */ dvander@177: virtual ExecType GetExecType() =0; dvander@177: dvander@177: /** dvander@177: * @brief Executes the forward. dvander@177: * dvander@177: * @param result Pointer to store result in. dvander@177: * @param num_functions Optionally filled with the number of function sucessfully executed. dvander@177: * @return Error code, if any. dvander@177: */ dvander@177: virtual int Execute(cell_t *result, unsigned int *num_functions) =0; dvander@174: }; dvander@174: dvander@174: dvander@174: class IChangeableForward : public IForward dvander@174: { dvander@174: public: dvander@174: /** dvander@174: * @brief Removes a function from the call list. dvander@174: * dvander@174: * @param func Function to remove. dvander@174: */ dvander@174: virtual void RemoveFunction(IPluginFunction *func) =0; dvander@174: dvander@174: /** dvander@174: * @brief Removes all instances of a plugin from the call list. dvander@174: * dvander@174: * @param plugin Plugin to remove instances of. dvander@174: * @return Number of functions removed therein. dvander@174: */ dvander@177: virtual unsigned int RemoveFunctionsOfPlugin(IPlugin *plugin) =0; dvander@174: dvander@174: /** dvander@174: * @brief Adds a function to the call list. dvander@177: * NOTE: Cannot be used during a call. dvander@174: * dvander@174: * @param func Function to add. dvander@177: * @return True on success, otherwise false. dvander@174: */ dvander@177: virtual bool AddFunction(IPluginFunction *func) =0; dvander@177: dvander@177: /** dvander@177: * @brief Adds a function to the call list. dvander@177: * NOTE: Cannot be used during a call. dvander@177: * dvander@177: * @param ctx Context to use as a look-up. dvander@177: * @param funcid Function id to add. dvander@177: * @return True on success, otherwise false. dvander@177: */ dvander@177: virtual bool AddFunction(sp_context_t *ctx, funcid_t index) =0; dvander@174: }; dvander@174: dvander@174: enum ParamType dvander@174: { dvander@177: Param_Any = 0, //Any type will be accepted dvander@177: Param_Cell = 1, //Only a cell will be accepted dvander@177: Param_Float = 2, //Only a float value will be accepted dvander@177: Param_String = 3, //Only a string will be accepted dvander@177: Param_Array = 4, //Only a 1D array will be accepted dvander@177: Param_VarArgs = 5, //Anything will be accepted dvander@177: ParamTypes_Total = 6, dvander@174: }; dvander@174: dvander@174: class IForwardManager : public SMInterface dvander@174: { dvander@174: public: dvander@174: virtual const char *GetInterfaceName() dvander@174: { dvander@174: return SMINTERFACE_FORWARDMANAGER_NAME; dvander@174: } dvander@174: virtual unsigned int GetInterfaceVersion() dvander@174: { dvander@174: return SMINTERFACE_FORWARDMANAGER_VERSION; dvander@174: } dvander@174: public: dvander@174: /** dvander@174: * @brief Creates a managed forward. This forward exists globally. dvander@174: * The name used to create the forward is used as its public function in all target plugins. dvander@174: * As new non-private plugins become loaded or unloaded, they will be automatically added dvander@174: * or removed. This is ideal for global, static forwards that are never changed. dvander@174: * dvander@174: * @param name Name of public function to use in forward. dvander@174: * @param et Execution type to be used. dvander@174: * @param num_params Number of parameter this function will have. dvander@174: * NOTE: For varargs, this should include the vararg parameter. dvander@174: * @param types Array of type information about each parameter. If NULL, types dvander@174: * are read off the vararg stream. dvander@174: * @param ... If types is NULL, num_params ParamTypes should be pushed. dvander@174: * @return A new IForward on success, NULL if type combination is impossible. dvander@174: */ dvander@177: virtual IForward *CreateForward(const char *name, dvander@177: ExecType et, dvander@177: unsigned int num_params, dvander@177: ParamType *types, dvander@177: ...) =0; dvander@174: dvander@174: /** dvander@174: * @brief Creates an unmanaged forward. This forward exists privately. dvander@174: * Unlike managed forwards, no functions are ever added by the Manager. dvander@174: * However, functions will be removed automatically if their parent plugin is unloaded. dvander@174: * dvander@174: * @param name Name of forward (unused except for lookup, can be NULL for anonymous). dvander@174: * @param et Execution type to be used. dvander@174: * @param num_params Number of parameter this function will have. dvander@174: * NOTE: For varargs, this should include the vararg parameter. dvander@174: * @param types Array of type information about each parameter. If NULL, types dvander@174: * are read off the vararg stream. dvander@174: * @param ... If types is NULL, num_params ParamTypes should be pushed. dvander@174: * @return A new IChangeableForward on success, NULL if type combination is impossible. dvander@174: */ dvander@174: virtual IChangeableForward *CreateForwardEx(const char *name, dvander@174: ExecType et, dvander@174: int num_params, dvander@174: ParamType *types, dvander@174: ...) =0; dvander@174: dvander@174: /** dvander@174: * @brief Finds a forward by name. Does not return anonymous forwards (named NULL or ""). dvander@174: * dvander@174: * @param name Name of forward. dvander@174: * @param ifchng Optionally store either NULL or an IChangeableForward pointer dvander@174: * depending on type of forward. dvander@174: * @return IForward pointer, or NULL if none found matching the name. dvander@174: */ dvander@174: virtual IForward *FindForward(const char *name, IChangeableForward **ifchng) =0; dvander@174: }; dvander@174: }; dvander@174: dvander@177: /** dvander@177: * In the AMX Mod X model of forwarding, each forward contained a list of pairs, each pair containing dvander@177: * a function ID and an AMX structure. The forward structure itself did very little but hold parameter types. dvander@177: * An execution call worked like this: dvander@177: * - executeForward() took in a function id and a list of parameters dvander@177: * - for each contained plugin: dvander@177: * - the list of parameters was preprocessed and pushed dvander@177: * - the call was made dvander@177: * - the list was freed and copybacks were made dvander@177: * - return dvander@177: * dvander@177: * The advantages to this is that the system is very easy to implement, and it's fast. The disadvantage is dvander@177: * varargs tend to be very unforgiving and inflexible, and thus weird problems arose with casting. You also dvander@177: * lose flexibility, type checking, and the ability to reasonably use variable arguments lists in the VM. dvander@177: * dvander@177: * SourceMod replaces this forward system with a far more advanced, but a bit bulkier one. The idea is that dvander@177: * each plugin has a table of functions, and each function is an ICallable object. As well as being an ICallable, dvander@177: * each function is an IPluginFunction. An ICallable simply describes the process of adding parameters to a dvander@177: * function call. An IPluginFunction describes the process of actually calling a function and performing allocation, dvander@177: * copybacks, and deallocations. dvander@177: * dvander@177: * A very powerful forward system emerges: a Forward is just a collection of IPluginFunctions. Thus, the same dvander@177: * API can be easily wrapped around a simple list, and it will look transparent to the user. dvander@177: * Advantages: dvander@177: * 1) "SP Forwards" from AMX Mod X are simply IPluginFunctions without a collection. dvander@177: * 2) Forwards are function based, rather than plugin based, and are thus far more flexible at runtime.. dvander@177: * 3) [2] Individual functions can be paused and more than one function from the same plugin can be hooked. dvander@177: * 4) [2] One hook type that used to map to many SP Forwards can now be centralized as one Forward. dvander@177: * This helps alleviate messes like Fakemeta. dvander@177: * 5) Parameter pushing is type-checked and allows for variable arguments. dvander@177: * dvander@177: * Note that while #2,3,4 could be added to AMX Mod X, the real binding property is #1, which makes the system dvander@177: * object oriented, rather than AMX Mod X, which hides the objects behind static functions. It is entirely a design dvander@177: * issue, rather than a usability one. The interesting part is when it gets to implementation, which is when the dvander@177: * problems for SourceMod arise. Specifically, the implementation is easier in GENERAL -- the tough part is the oddball dvander@177: * cases. dvander@177: * dvander@177: * Observe the calling process: dvander@177: * - Each parameter is pushed using the ICallable interface. For each parameter: dvander@177: * - For each function in the collection, the parameter is processed and pushed. dvander@177: * - For each function in the collection: dvander@177: * - The call is made. dvander@177: * - Copy backs are performed. dvander@177: * - Return dvander@177: * dvander@177: * Astute readers will note the problems - the parameters are processed individually for each push, dvander@177: * rather than for each call. This means: dvander@177: * 1) More memory is used. Specifically, rather than N params of memory, you now have N params * M plugins. dvander@177: * This is because, again, parameters are processed per function object, per-push, before the call. dvander@177: * 2) There are slightly more calls going around: one extra call for each parameter, since each push is manual. dvander@177: * 3) Copybacks won't work as expected. dvander@177: * dvander@177: * Number 3 is hard to see. For example, say a forward has two functions, and an array is pushed with copyback. dvander@177: * The array gets pushed and copied into each plugin. When the call is made, each plugin now has separate copies of dvander@177: * the array. When the copyback is performed, it gets mirrored to the originall address, but not to the next plugin! dvander@177: dvander@177: * Implementing this is "difficult." To be re-entrant, an IPluginFunction can't tell you anything dvander@177: * about its copybacks after it has returned, because its internal states were reset long ago. dvander@177: * dvander@177: * In order to implement this feature, IPluginFunction::Execute function now lets you pass a listener in. dvander@177: * This listener is notified each time an array parameter is about to be copied back. Specifically, the forward dvander@177: * uses this to detect when it needs to push a new parameter out to the next plugin. When the forward gets the dvander@177: * call back, it detects whether there is another plugin in the queue. If there is, it grabs the address it will dvander@177: * be using for the same arrays, and specifies it as the new copy-back point. If no plugins are left, it allows dvander@177: * the copyback to chain up to the original address. dvander@177: * dvander@177: * This wonderful little hack is somewhat reminiscent of how SourceHook parameter rewrite chains work. It seems dvander@177: * ugly at first, but it is actually the correct design pattern to apply to an otherwise awful problem. dvander@177: * dvander@177: * Note that there are other solutions to this that aren't based on a visitor-like pattern. For example, the Function dvander@177: * class could expose a "set new original address" function. Then after arrays are pushed, the arrays are re-browsed, dvander@177: * and function #1 gets function #2's original address, function #2 gets function #3's original address, et cetera. dvander@177: * This extra browse step is a bit less efficient though, since the "visitor" method implies only taking action when dvander@177: * necessary. Furthermore, it would require exposing such a "set address" function, which should fire a red flag dvander@177: * that the API is doing something it shouldn't (namely, exposing the direct setting of very internal properties). dvander@177: */ dvander@177: dvander@174: #endif //_INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_