core/interfaces/IForwardSys.h
author David Anderson <dvander@alliedmods.net>
Sat Nov 11 11:10:24 2006 +0000 (2006-11-11)
changeset 177 d29ade52d953
parent 174 5611e4b3fa42
child 180 fe406c5f2aeb
permissions -rw-r--r--
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
dvander@174
     1
#ifndef _INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_
dvander@174
     2
#define _INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_
dvander@174
     3
dvander@174
     4
#include <IForwardSys.h>
dvander@174
     5
#include <IPluginSys.h>
dvander@174
     6
dvander@174
     7
#define SMINTERFACE_FORWARDMANAGER_NAME		"IForwardManager"
dvander@174
     8
#define SMINTERFACE_FORWARDMANAGER_VERSION	1
dvander@174
     9
dvander@177
    10
/**
dvander@177
    11
 * There is some very important documentation at the bottom of this file.
dvander@177
    12
 * Readers interested in knowing more about the forward system, scrolling down is a must!
dvander@177
    13
 */
dvander@177
    14
dvander@174
    15
namespace SourceMod
dvander@174
    16
{
dvander@174
    17
	enum ResultType
dvander@174
    18
	{
dvander@174
    19
		Pl_Continue = 0,	/* No result */
dvander@174
    20
		Pl_Handled = 1,		/* Result was handled, stop at the end */
dvander@174
    21
		Pl_Stop = 2,		/* Result was handled, stop now */
dvander@174
    22
	};
dvander@174
    23
dvander@174
    24
	enum ExecType
dvander@174
    25
	{
dvander@174
    26
		ET_Ignore = 0,		/* Ignore all return values, return 0 */
dvander@174
    27
		ET_Single = 1,		/* Only return the first exec, ignore all others */
dvander@174
    28
		ET_Event = 2,		/* Acts as an event with the ResultTypes above, no mid-Stops allowed, returns highest */
dvander@174
    29
		ET_Hook = 3,		/* Acts as a hook with the ResultTypes above, mid-Stops allowed, returns highest */
dvander@174
    30
	};
dvander@174
    31
dvander@174
    32
	/**
dvander@177
    33
	 * :TODO: finish this spec
dvander@174
    34
	 * @brief Abstracts multiple function calling.
dvander@174
    35
	 * NOTE: Parameters should be pushed in forward order, unlike
dvander@174
    36
	 * the virtual machine/IPluginContext order.
dvander@177
    37
	 * NOTE: Some functions are repeated in here because their documentation differs
dvander@177
    38
	 * from their IPluginFunction equivalents.
dvander@174
    39
	 */
dvander@177
    40
	class IForward : public ICallable
dvander@174
    41
	{
dvander@174
    42
	public:
dvander@174
    43
		/**
dvander@174
    44
		 * @brief Returns the name of the forward.
dvander@174
    45
		 *
dvander@174
    46
		 * @return			Forward name.
dvander@174
    47
		 */
dvander@174
    48
		virtual const char *GetForwardName() =0;
dvander@174
    49
dvander@174
    50
		/**
dvander@174
    51
		 * @brief Returns the number of functions in this forward.
dvander@174
    52
		 *
dvander@174
    53
		 * @return			Number of functions in forward.
dvander@174
    54
		 */
dvander@174
    55
		virtual unsigned int GetFunctionCount() =0;
dvander@174
    56
dvander@174
    57
		/**
dvander@174
    58
		 * @brief Returns the method of multi-calling this forward has.
dvander@174
    59
		 *
dvander@177
    60
		 * @return			ExecType of the forward.
dvander@174
    61
		 */
dvander@177
    62
		virtual ExecType GetExecType() =0;
dvander@177
    63
dvander@177
    64
		/**
dvander@177
    65
		 * @brief Executes the forward.
dvander@177
    66
		 *
dvander@177
    67
		 * @param result		Pointer to store result in.
dvander@177
    68
		 * @param num_functions	Optionally filled with the number of function sucessfully executed.
dvander@177
    69
		 * @return				Error code, if any.
dvander@177
    70
		 */
dvander@177
    71
		virtual int Execute(cell_t *result, unsigned int *num_functions) =0;
dvander@174
    72
	};
dvander@174
    73
dvander@174
    74
dvander@174
    75
	class IChangeableForward : public IForward
dvander@174
    76
	{
dvander@174
    77
	public:
dvander@174
    78
		/** 
dvander@174
    79
		 * @brief Removes a function from the call list.
dvander@174
    80
		 *
dvander@174
    81
		 * @param func		Function to remove.
dvander@174
    82
		 */
dvander@174
    83
		virtual void RemoveFunction(IPluginFunction *func) =0;
dvander@174
    84
dvander@174
    85
		/** 
dvander@174
    86
		 * @brief Removes all instances of a plugin from the call list.
dvander@174
    87
		 *
dvander@174
    88
		 * @param plugin	Plugin to remove instances of.
dvander@174
    89
		 * @return			Number of functions removed therein.
dvander@174
    90
		 */
dvander@177
    91
		virtual unsigned int RemoveFunctionsOfPlugin(IPlugin *plugin) =0;
dvander@174
    92
dvander@174
    93
		/**
dvander@174
    94
		 * @brief Adds a function to the call list.
dvander@177
    95
		 * NOTE: Cannot be used during a call.
dvander@174
    96
		 *
dvander@174
    97
		 * @param func		Function to add.
dvander@177
    98
		 * @return			True on success, otherwise false.
dvander@174
    99
		 */
dvander@177
   100
		virtual bool AddFunction(IPluginFunction *func) =0;
dvander@177
   101
dvander@177
   102
		/**
dvander@177
   103
		 * @brief Adds a function to the call list.
dvander@177
   104
		 * NOTE: Cannot be used during a call.
dvander@177
   105
		 *
dvander@177
   106
		 * @param ctx		Context to use as a look-up.
dvander@177
   107
		 * @param funcid	Function id to add.
dvander@177
   108
		 * @return			True on success, otherwise false.
dvander@177
   109
		 */
dvander@177
   110
		virtual bool AddFunction(sp_context_t *ctx, funcid_t index) =0;
dvander@174
   111
	};
dvander@174
   112
dvander@174
   113
	enum ParamType
dvander@174
   114
	{
dvander@177
   115
		Param_Any = 0,		//Any type will be accepted
dvander@177
   116
		Param_Cell = 1,		//Only a cell will be accepted
dvander@177
   117
		Param_Float = 2,	//Only a float value will be accepted
dvander@177
   118
		Param_String = 3,	//Only a string will be accepted
dvander@177
   119
		Param_Array = 4,	//Only a 1D array will be accepted
dvander@177
   120
		Param_VarArgs = 5,	//Anything will be accepted
dvander@177
   121
		ParamTypes_Total = 6,
dvander@174
   122
	};
dvander@174
   123
	
dvander@174
   124
	class IForwardManager : public SMInterface
dvander@174
   125
	{
dvander@174
   126
	public:
dvander@174
   127
		virtual const char *GetInterfaceName()
dvander@174
   128
		{
dvander@174
   129
			return SMINTERFACE_FORWARDMANAGER_NAME;
dvander@174
   130
		}
dvander@174
   131
		virtual unsigned int GetInterfaceVersion()
dvander@174
   132
		{
dvander@174
   133
			return SMINTERFACE_FORWARDMANAGER_VERSION;
dvander@174
   134
		}
dvander@174
   135
	public:
dvander@174
   136
		/**
dvander@174
   137
		 * @brief Creates a managed forward.  This forward exists globally.
dvander@174
   138
		 *  The name used to create the forward is used as its public function in all target plugins.
dvander@174
   139
		 *  As new non-private plugins become loaded or unloaded, they will be automatically added
dvander@174
   140
		 * or removed.  This is ideal for global, static forwards that are never changed.
dvander@174
   141
		 *
dvander@174
   142
		 * @param name			Name of public function to use in forward.
dvander@174
   143
		 * @param et			Execution type to be used.
dvander@174
   144
		 * @param num_params	Number of parameter this function will have.
dvander@174
   145
		 *						NOTE: For varargs, this should include the vararg parameter.
dvander@174
   146
		 * @param types			Array of type information about each parameter.  If NULL, types
dvander@174
   147
		 *						are read off the vararg stream.
dvander@174
   148
		 * @param ...			If types is NULL, num_params ParamTypes should be pushed.
dvander@174
   149
		 * @return				A new IForward on success, NULL if type combination is impossible.
dvander@174
   150
		 */
dvander@177
   151
		virtual IForward *CreateForward(const char *name, 
dvander@177
   152
										ExecType et, 
dvander@177
   153
										unsigned int num_params, 
dvander@177
   154
										ParamType *types, 
dvander@177
   155
										...) =0;
dvander@174
   156
dvander@174
   157
		/**
dvander@174
   158
		 * @brief Creates an unmanaged forward.  This forward exists privately.
dvander@174
   159
		 * Unlike managed forwards, no functions are ever added by the Manager.
dvander@174
   160
		 * However, functions will be removed automatically if their parent plugin is unloaded.
dvander@174
   161
		 *
dvander@174
   162
		 * @param name			Name of forward (unused except for lookup, can be NULL for anonymous).
dvander@174
   163
		 * @param et			Execution type to be used.
dvander@174
   164
		 * @param num_params	Number of parameter this function will have.
dvander@174
   165
		 *						NOTE: For varargs, this should include the vararg parameter.
dvander@174
   166
		 * @param types			Array of type information about each parameter.  If NULL, types
dvander@174
   167
		 *						are read off the vararg stream.
dvander@174
   168
		 * @param ...			If types is NULL, num_params ParamTypes should be pushed.
dvander@174
   169
		 * @return				A new IChangeableForward on success, NULL if type combination is impossible.
dvander@174
   170
		 */
dvander@174
   171
		virtual IChangeableForward *CreateForwardEx(const char *name, 
dvander@174
   172
													ExecType et, 
dvander@174
   173
													int num_params, 
dvander@174
   174
													ParamType *types, 
dvander@174
   175
													...) =0;
dvander@174
   176
dvander@174
   177
		/**
dvander@174
   178
		 * @brief Finds a forward by name.  Does not return anonymous forwards (named NULL or "").
dvander@174
   179
		 *
dvander@174
   180
		 * @param name			Name of forward.
dvander@174
   181
		 * @param ifchng		Optionally store either NULL or an IChangeableForward pointer
dvander@174
   182
		 *                      depending on type of forward.
dvander@174
   183
		 * @return				IForward pointer, or NULL if none found matching the name.
dvander@174
   184
		 */
dvander@174
   185
		virtual IForward *FindForward(const char *name, IChangeableForward **ifchng) =0;
dvander@174
   186
	};
dvander@174
   187
};
dvander@174
   188
dvander@177
   189
/**
dvander@177
   190
 *  In the AMX Mod X model of forwarding, each forward contained a list of pairs, each pair containing
dvander@177
   191
 * a function ID and an AMX structure.  The forward structure itself did very little but hold parameter types.
dvander@177
   192
 * An execution call worked like this:
dvander@177
   193
 *  - executeForward() took in a function id and a list of parameters
dvander@177
   194
 *  - for each contained plugin:
dvander@177
   195
 *   - the list of parameters was preprocessed and pushed
dvander@177
   196
 *   - the call was made
dvander@177
   197
 *   - the list was freed and copybacks were made
dvander@177
   198
 *  - return
dvander@177
   199
 * 
dvander@177
   200
 *  The advantages to this is that the system is very easy to implement, and it's fast. The disadvantage is 
dvander@177
   201
 * varargs tend to be very unforgiving and inflexible, and thus weird problems arose with casting.  You also 
dvander@177
   202
 * lose flexibility, type checking, and the ability to reasonably use variable arguments lists in the VM.
dvander@177
   203
 *
dvander@177
   204
 *  SourceMod replaces this forward system with a far more advanced, but a bit bulkier one. The idea is that 
dvander@177
   205
 * each plugin has a table of functions, and each function is an ICallable object.  As well as being an ICallable, 
dvander@177
   206
 * each function is an IPluginFunction.  An ICallable simply describes the process of adding parameters to a 
dvander@177
   207
 * function call.  An IPluginFunction describes the process of actually calling a function and performing allocation, 
dvander@177
   208
 * copybacks, and deallocations.
dvander@177
   209
 *
dvander@177
   210
 * A very powerful forward system emerges: a Forward is just a collection of IPluginFunctions.  Thus, the same 
dvander@177
   211
 * API can be easily wrapped around a simple list, and it will look transparent to the user.
dvander@177
   212
 * Advantages:
dvander@177
   213
 * 1) "SP Forwards" from AMX Mod X are simply IPluginFunctions without a collection.
dvander@177
   214
 * 2) Forwards are function based, rather than plugin based, and are thus far more flexible at runtime..
dvander@177
   215
 * 3) [2] Individual functions can be paused and more than one function from the same plugin can be hooked.
dvander@177
   216
 * 4) [2] One hook type that used to map to many SP Forwards can now be centralized as one Forward.
dvander@177
   217
 *    This helps alleviate messes like Fakemeta.
dvander@177
   218
 * 5) Parameter pushing is type-checked and allows for variable arguments.
dvander@177
   219
 *
dvander@177
   220
 *  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
   221
 * object oriented, rather than AMX Mod X, which hides the objects behind static functions.  It is entirely a design
dvander@177
   222
 * issue, rather than a usability one.  The interesting part is when it gets to implementation, which is when the 
dvander@177
   223
 * problems for SourceMod arise.  Specifically, the implementation is easier in GENERAL -- the tough part is the oddball
dvander@177
   224
 * cases.
dvander@177
   225
 *
dvander@177
   226
 * Observe the calling process:
dvander@177
   227
 * - Each parameter is pushed using the ICallable interface.  For each parameter:
dvander@177
   228
 *  - For each function in the collection, the parameter is processed and pushed.
dvander@177
   229
 * - For each function in the collection:
dvander@177
   230
 *  - The call is made.
dvander@177
   231
 *  - Copy backs are performed.
dvander@177
   232
 * - Return
dvander@177
   233
 *
dvander@177
   234
 *  Astute readers will note the problems - the parameters are processed individually for each push,
dvander@177
   235
 * rather than for each call.  This means:
dvander@177
   236
 * 1) More memory is used.  Specifically, rather than N params of memory, you now have N params * M plugins.
dvander@177
   237
 *    This is because, again, parameters are processed per function object, per-push, before the call.
dvander@177
   238
 * 2) There are slightly more calls going around: one extra call for each parameter, since each push is manual.
dvander@177
   239
 * 3) Copybacks won't work as expected.  
dvander@177
   240
 *
dvander@177
   241
 *  Number 3 is hard to see.  For example, say a forward has two functions, and an array is pushed with copyback.
dvander@177
   242
 * The array gets pushed and copied into each plugin.  When the call is made, each plugin now has separate copies of 
dvander@177
   243
 * the array.  When the copyback is performed, it gets mirrored to the originall address, but not to the next plugin!
dvander@177
   244
dvander@177
   245
 *  Implementing this is "difficult."  To be re-entrant, an IPluginFunction can't tell you anything
dvander@177
   246
 * about its copybacks after it has returned, because its internal states were reset long ago.
dvander@177
   247
 * 
dvander@177
   248
 *  In order to implement this feature, IPluginFunction::Execute function now lets you pass a listener in.
dvander@177
   249
 * This listener is notified each time an array parameter is about to be copied back.  Specifically, the forward 
dvander@177
   250
 * uses this to detect when it needs to push a new parameter out to the next plugin.  When the forward gets the 
dvander@177
   251
 * call back, it detects whether there is another plugin in the queue.  If there is, it grabs the address it will
dvander@177
   252
 * be using for the same arrays, and specifies it as the new copy-back point.  If no plugins are left, it allows
dvander@177
   253
 * the copyback to chain up to the original address.
dvander@177
   254
 * 
dvander@177
   255
 *  This wonderful little hack is somewhat reminiscent of how SourceHook parameter rewrite chains work.  It seems 
dvander@177
   256
 * ugly at first, but it is actually the correct design pattern to apply to an otherwise awful problem.
dvander@177
   257
 *
dvander@177
   258
 *  Note that there are other solutions to this that aren't based on a visitor-like pattern.  For example, the Function
dvander@177
   259
 * class could expose a "set new original address" function.  Then after arrays are pushed, the arrays are re-browsed,
dvander@177
   260
 * and function #1 gets function #2's original address, function #2 gets function #3's original address, et cetera.
dvander@177
   261
 * This extra browse step is a bit less efficient though, since the "visitor" method implies only taking action when
dvander@177
   262
 * necessary.  Furthermore, it would require exposing such a "set address" function, which should fire a red flag 
dvander@177
   263
 * that the API is doing something it shouldn't (namely, exposing the direct setting of very internal properties).
dvander@177
   264
 */
dvander@177
   265
dvander@174
   266
#endif //_INCLUDE_SOURCEMOD_FORWARDINTERFACE_H_