|
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_ |