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