]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/mutators/base.qh
Move mutator system to common
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / mutators / base.qh
1 #ifndef MUTATORS_BASE_H
2 #define MUTATORS_BASE_H
3
4 const int CBC_ORDER_FIRST = 1;
5 const int CBC_ORDER_LAST = 2;
6 const int CBC_ORDER_EXCLUSIVE = 3;
7 const int CBC_ORDER_ANY = 4;
8
9 bool CallbackChain_ReturnValue; // read-only field of the current return value
10
11 /**
12  * Callbacks may be added to zero or more callback chains.
13  */
14 CLASS(Callback, Object)
15     /**
16      * a callback function is like this:
17      * bool mycallback()
18      * {
19      *     do something
20      *     return false;
21      * }
22      */
23     ATTRIB(Callback, cbc_func, bool(), func_null)
24     CONSTRUCTOR(Callback, bool() func) {
25         CONSTRUCT(Callback);
26         this.cbc_func = func;
27         return this;
28     }
29 ENDCLASS(Callback)
30
31 /**
32  * Callback chains contain zero or more callbacks.
33  */
34 CLASS(CallbackChain, Object)
35     CLASS(CallbackNode, Object)
36         ATTRIB(CallbackNode, cbc, Callback, NULL)
37         ATTRIB(CallbackNode, cbc_next, CallbackNode, NULL)
38         ATTRIB(CallbackNode, cbc_order, int, 0)
39         CONSTRUCTOR(CallbackNode, Callback it, int order) {
40             CONSTRUCT(CallbackNode);
41             this.cbc = it;
42             this.cbc_order = order;
43             return this;
44         }
45     ENDCLASS(CallbackNode)
46
47     ATTRIB(CallbackChain, cbc_next, CallbackNode, NULL)
48     ATTRIB(CallbackChain, cbc_order, int, 0)
49     CONSTRUCTOR(CallbackChain, string name) {
50         CONSTRUCT(CallbackChain);
51         this.netname = name;
52         return this;
53     }
54
55     bool CallbackChain_Add(CallbackChain this, Callback cb, int order)
56     {
57         if (order & CBC_ORDER_FIRST) {
58             if (order & CBC_ORDER_LAST)
59                 if (this.cbc_order & CBC_ORDER_ANY)
60                     return false;
61             if (this.cbc_order & CBC_ORDER_FIRST)
62                 return false;
63         } else if (order & CBC_ORDER_LAST) {
64             if (this.cbc_order & CBC_ORDER_LAST)
65                 return false;
66         }
67         entity node = NEW(CallbackNode, cb, order);
68         if (order & CBC_ORDER_FIRST) {
69             node.cbc_next = this.cbc_next;
70             this.cbc_next = node;
71         } else if (order & CBC_ORDER_LAST) {
72             CallbackNode prev = NULL, it = this.cbc_next;
73             while (it) { prev = it, it = it.cbc_next; }
74             if (prev) prev.cbc_next = node;
75             else this.cbc_next = node;
76         } else {
77             // by default we execute last, but before a possible CBC_ORDER_LAST callback
78             CallbackNode prev = NULL, it = this.cbc_next;
79             while (it && !(it.cbc_order & CBC_ORDER_LAST)) { prev = it, it = it.cbc_next; }
80             node.cbc_next = it;
81             if (prev) prev.cbc_next = node;
82             else this.cbc_next = node;
83         }
84         this.cbc_order |= (order | CBC_ORDER_ANY);
85         return true;
86     }
87     int CallbackChain_Remove(CallbackChain this, Callback cb)
88     {
89         int n = 0, order = 0;
90         for (Callback prev = NULL, it = this.cbc_next; it; prev = it, it = it.cbc_next) {
91             if (it.cbc == cb) {
92                 // remove it from the chain
93                 Callback next = it.cbc_next;
94                 if (prev) prev.cbc_next = next;
95                 else this.cbc_next = next;
96                 ++n;
97             }
98             // it is now something we want to keep
99             order |= (it.cbc_order & CBC_ORDER_ANY);
100         }
101         this.cbc_order = order;
102         return n;
103     }
104     bool CallbackChain_Call(CallbackChain this)
105     {
106         bool r = false;
107         for (Callback it = this.cbc_next; it; it = it.cbc_next) {
108             CallbackChain_ReturnValue = r;
109             r |= it.cbc.cbc_func();
110         }
111         return r; // callbacks return an error status, so 0 is default return value
112     }
113 ENDCLASS(CallbackChain)
114
115 #define _MUTATOR_HANDLE_NOP(type, id)
116 #define _MUTATOR_HANDLE_PARAMS(type, id) , type in_##id
117 #define _MUTATOR_HANDLE_PREPARE(type, id) id = in_##id;
118 #define _MUTATOR_HANDLE_PUSHTMP(type, id) type tmp_##id = id;
119 #define _MUTATOR_HANDLE_PUSHOUT(type, id) type out_##id = id;
120 #define _MUTATOR_HANDLE_POPTMP(type, id) id = tmp_##id;
121 #define _MUTATOR_HANDLE_POPOUT(type, id) id = out_##id;
122
123 #define _MUTATOR_HOOKABLE(id, ...) CallbackChain HOOK_##id; bool __Mutator_Send_##id(__VA_ARGS__)
124 #define MUTATOR_HOOKABLE(id, params) \
125     _MUTATOR_HOOKABLE(id, int params(_MUTATOR_HANDLE_PARAMS, _MUTATOR_HANDLE_NOP)) { \
126         params(_MUTATOR_HANDLE_PUSHTMP, _MUTATOR_HANDLE_NOP) \
127         params(_MUTATOR_HANDLE_PREPARE, _MUTATOR_HANDLE_NOP) \
128         bool ret = CallbackChain_Call(HOOK_##id); \
129         params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_PUSHOUT) \
130         params(_MUTATOR_HANDLE_POPTMP,  _MUTATOR_HANDLE_NOP) \
131         params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_POPOUT) \
132         return ret; \
133     } \
134     STATIC_INIT(HOOK_##id) { HOOK_##id = NEW(CallbackChain, #id); }
135 #define MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0, ##__VA_ARGS__)
136
137 enum {
138     MUTATOR_REMOVING,
139     MUTATOR_ADDING,
140     MUTATOR_ROLLING_BACK
141 };
142
143 typedef bool(int) mutatorfunc_t;
144
145 CLASS(Mutator, Object)
146     ATTRIB(Mutator, mutatorname, string, string_null)
147     ATTRIB(Mutator, mutatorfunc, mutatorfunc_t, func_null)
148     CONSTRUCTOR(Mutator, string name, mutatorfunc_t func) {
149         CONSTRUCT(Mutator);
150         this.mutatorname = name;
151         this.mutatorfunc = func;
152         return this;
153     }
154 ENDCLASS(Mutator)
155
156 const int MAX_MUTATORS = 15;
157 Mutator loaded_mutators[MAX_MUTATORS];
158
159 bool Mutator_Add(Mutator mut)
160 {
161     int j = -1;
162     for (int i = 0; i < MAX_MUTATORS; ++i) {
163         if (loaded_mutators[i] == mut)
164             return true; // already added
165         if (!(loaded_mutators[i]))
166             j = i;
167     }
168     if (j < 0) {
169         backtrace("WARNING: too many mutators, cannot add any more\n");
170         return false;
171     }
172     loaded_mutators[j] = mut;
173     mutatorfunc_t func = mut.mutatorfunc;
174     if (!func(MUTATOR_ADDING)) {
175         // good
176         return true;
177     }
178     backtrace("WARNING: when adding mutator: adding failed, rolling back\n");
179     if (func(MUTATOR_ROLLING_BACK)) {
180         // baaaaad
181         error("WARNING: when adding mutator: rolling back failed");
182     }
183     return false;
184 }
185
186 void Mutator_Remove(Mutator mut)
187 {
188     int i;
189     for (i = 0; i < MAX_MUTATORS; ++i)
190         if (loaded_mutators[i] == mut)
191             break;
192     if (i >= MAX_MUTATORS) {
193         backtrace("WARNING: removing not-added mutator\n");
194         return;
195     }
196     loaded_mutators[i] = NULL;
197     mutatorfunc_t func = mut.mutatorfunc;
198     if (func(MUTATOR_REMOVING)) {
199         // baaaaad
200         error("Mutator_Remove: removing mutator failed");
201     }
202 }
203
204 #define MUTATOR_DECLARATION(name) \
205     Mutator MUTATOR_##name
206 #define MUTATOR_DEFINITION(name) \
207     bool MUTATORFUNCTION_##name(int mode); \
208     STATIC_INIT(MUTATOR_##name) { MUTATOR_##name = NEW(Mutator, #name, MUTATORFUNCTION_##name); } \
209     bool MUTATORFUNCTION_##name(int mode)
210 #define MUTATOR_ONADD                   if (mode == MUTATOR_ADDING)
211 #define MUTATOR_ONREMOVE                if (mode == MUTATOR_REMOVING)
212 #define MUTATOR_ONROLLBACK_OR_REMOVE    if (mode == MUTATOR_REMOVING || mode == MUTATOR_ROLLING_BACK)
213 #define MUTATOR_ADD(name)               Mutator_Add(MUTATOR_##name)
214 #define MUTATOR_REMOVE(name)            Mutator_Remove(MUTATOR_##name)
215 #define MUTATOR_RETURNVALUE             CallbackChain_ReturnValue
216 #define MUTATOR_HOOKFUNCTION(name) \
217     bool HOOKFUNCTION_##name(); \
218     Callback CALLBACK_##name; STATIC_INIT(CALLBACK_##name) { CALLBACK_##name = NEW(Callback, HOOKFUNCTION_##name); } \
219     bool HOOKFUNCTION_##name()
220 #define MUTATOR_HOOK(cb, func, order) do {                              \
221     MUTATOR_ONADD {                                                     \
222         if (!CallbackChain_Add(HOOK_##cb, CALLBACK_##func, order)) {    \
223             print("HOOK FAILED: ", #cb, ":", #func, "\n");              \
224             return true;                                                \
225         }                                                               \
226     }                                                                   \
227     MUTATOR_ONROLLBACK_OR_REMOVE {                                      \
228         CallbackChain_Remove(HOOK_##cb, CALLBACK_##func);               \
229     }                                                                   \
230 } while (0)
231
232 #include "events.qh"
233
234 #endif