blob: ae92a699dba7265bf0d8b16a971077d21c19b3b8 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// /std/hook_provider.c - Hooksystem
4//
5// $Id: hook_provider.c 9453 2016-02-04 21:22:54Z Zesstra $
6
7#pragma strong_types
8#pragma save_types
9#pragma no_clone
10#pragma range_check
MG Mud User88f12472016-06-24 23:31:02 +020011
12#define NEED_PROTOTYPES
13#include <hook.h>
14#undef NEED_PROTOTYPES
15
16// get the object fur closure <cl> (where the code is!)
17#define GET_OBJECT(cl) get_type_info(cl,2)
18
19// Struct describing one hook consumer. <cl> will be called when the hook is
20// triggered. The lower <prio>, the earlier the consumer will be called.
21// <type> is a consumertype, e.g. surveyor, listener etc. (see hook.h)
22struct hook_entry_s {
23 closure cl;
24 int prio;
25 int endtime;
26 int type;
27};
28
29// Struct holding the consumers of one specific hook.
30// the value arrays are guarenteed to exist, but may be empty.
31struct hook_s {
32 struct hook_entry_s *surveyors;
33 struct hook_entry_s *hmods;
34 struct hook_entry_s *dmods;
35 struct hook_entry_s *listeners;
36};
37
38/* hook mapping
39 the list of all offered hooks in the following structure:
40 ([hookid: (<hook_s>), ...
41 )]
42*/
43private nosave mapping hookMapping=([]);
44
Zesstrab6842712018-02-12 20:53:26 +010045protected int CleanHookMapping(int *hookids);
MG Mud User88f12472016-06-24 23:31:02 +020046
47// Debugging - ggf. ueberschreiben
48protected int h_dbg() {return 0;}
49
50void HookTestOffer(int id, int stat){
51 if(h_dbg()) {
52 offerHook(id,stat);
53 }
54}
55
56void HookTestTrigger(int id, mixed data){
57 if(h_dbg()) {
58 HookFlow(id,data);
59 }
60}
61
62// NOTE: if you have the closure, you can call the lfun, even if it is
63// private. Therefore access to this data should be restricted to
64// this_object().
65// These two functions should be used for debugging purposes.
66protected mapping HCopyHookMapping(){
67 return deep_copy(hookMapping);
68}
69
70protected mapping HCopyHookConsumers(int hookid){
71 if(member(hookMapping,hookid)) {
72 CleanHookMapping(({hookid}));
73 return deep_copy(hookMapping[hookid]);
74 }
75 return 0;
76}
77
78// Ggf. zum Ueberschreiben.
Zesstra3d3bb422020-05-02 18:24:32 +020079// Prueft, ob <consumer> den Hooktyp <type> im Hook <hook> benutzen darf.
80protected int HConsumerTypeIsAllowed(int hookid, int type, object consumer)
81{
MG Mud User88f12472016-06-24 23:31:02 +020082 return 1;
83}
84
85int HPriorityIsAllowed(int prio, object consumer){
86 return 1;
87}
88
89// clean internal hook data structures of stale hook consumers.
Zesstrab6842712018-02-12 20:53:26 +010090// returns the number of valid consumers left.
91protected int CleanHookMapping(int *hookids)
92{
MG Mud User88f12472016-06-24 23:31:02 +020093 // hooks enthaelt die aufzuraeumenden Hooks. Wenn kein Array -> alle Hooks
94 if (!pointerp(hookids))
95 hookids=m_indices(hookMapping);
96
Zesstrab6842712018-02-12 20:53:26 +010097 int count;
98 foreach(int hookid : hookids)
99 { // alle Hooks
MG Mud User88f12472016-06-24 23:31:02 +0200100 struct hook_s hooks = hookMapping[hookid];
101 if (!structp(hooks))
102 continue;
103 // ueber alle Consumertypen laufen
104 foreach (string consumertype: H_CONSUMERNAMES)
105 {
106 // Yeah - compute struct lookup at runtime... ;-)
107 struct hook_entry_s *consumers = hooks->(consumertype);
108 // Hookeintraege / Consumer
Zesstrab6842712018-02-12 20:53:26 +0100109 foreach(struct hook_entry_s h : &consumers)
110 {
MG Mud User88f12472016-06-24 23:31:02 +0200111 // alle abgelaufenen Eintraege oder solche mit zerstoerten Objekten
Zesstrab6842712018-02-12 20:53:26 +0100112 // nullen und die anderen/gueltigen zaehlen.
MG Mud User88f12472016-06-24 23:31:02 +0200113 if (!h->cl || h->endtime < time() )
114 h = 0;
Zesstrab6842712018-02-12 20:53:26 +0100115 else
116 ++count;
MG Mud User88f12472016-06-24 23:31:02 +0200117 }
118 // 0 noch rauswerfen.
119 hooks->(consumertype) -= ({0});
120 }
121 }
Zesstrab6842712018-02-12 20:53:26 +0100122 return count;
123}
124
125// Returns the number of valid consumers for the given hooks (or all, if
126// hooks==0).
127// Side effect: Cleans the internal structures and removes any stale
128// consumers.
129public varargs int HHasConsumers(int *hookids)
130{
131 return CleanHookMapping(hookids);
MG Mud User88f12472016-06-24 23:31:02 +0200132}
133
134protected void offerHook(int hookid, int offerstate)
135{
136 H_DMSG(sprintf("offerHook hookid %d offerstate %d\n",hookid,offerstate));
137 if (hookid>0)
138 {
139 if (offerstate) {
140 if (!member(hookMapping,hookid)) {
141 struct hook_s hook = (<hook_s>
142 surveyors: ({}),
143 hmods: ({}),
144 dmods: ({}),
145 listeners: ({}) );
146 hookMapping[hookid] = hook;
147 }
148 }
149 else {
150 if (member(hookMapping,hookid)) {
151 m_delete(hookMapping,hookid);
152 }
153 }
154 }
155 H_DMSG(sprintf(" result %O\n",hookMapping));
156}
157
158// hookConsumerInfo() liefert Array von hook_entry_s zurueck. D.h. bei Abfrage
159// von Objekten alle Closures dieses Objekts und jede davon erzeugt ein
160// Element hook_entry_s im Ergebnisarray. Bei Abfrage von Closures hat das
161// Array immer genau 1 oder kein Element.
162// WARNING: whoever has a hook_entry_s can change/delete the hook!
163// NEVER return the original to an external caller!
164// NOTE: whoever has the cl from hook_entry_s can call it, even if the lfun
165// is private (and this object the only one knowing it).
166protected mixed * hookConsumerInfo(int hookid, object|closure consumer)
167{
168 closure filter_cl;
169
170 if (!member(hookMapping,hookid))
171 return ({});
172
173 // Closure zum Filtern bestimmen - je nachdem, was gesucht wird.
174 if (closurep(consumer))
175 {
176 filter_cl = function int (struct hook_entry_s h)
177 { return h->cl == consumer
178 && h->endtime >= time(); };
179 }
180 else if (objectp(consumer))
181 {
182 filter_cl = function int (struct hook_entry_s h)
183 { return GET_OBJECT(h->cl) == consumer
184 && h->endtime >= time(); };
185 }
186 else
187 {
188 return ({});
189 }
190
191 struct hook_s hook = hookMapping[hookid];
192 struct hook_entry_s *result = ({});
193 foreach (string consumertype: H_CONSUMERNAMES )
194 {
195 result += filter(hook->(consumertype), filter_cl);
196 }
197 return result;
198}
199
200int HIsHookConsumer(int hookid, mixed consumer) {
201 return sizeof(hookConsumerInfo(hookid,consumer)) != 0;
202}
203
204int* HListHooks() {
205 return m_indices(hookMapping);
206}
207
208int HUnregisterFromHook(int hookid, mixed consumer) {
209
210 H_DMSG(sprintf("HUnregisterFromHook hookid %d consumer %O\n",hookid,consumer));
211 if (objectp(consumer))
212 consumer = symbol_function("HookCallback", consumer);
213 if (!closurep(consumer))
214 return 0;
215
216 struct hook_entry_s *info = hookConsumerInfo(hookid,consumer);
217
218 // it should never happen that a closure is registered more than once, i.e.
219 // the result contains more than one element.
220 if (sizeof(info)) {
221 struct hook_entry_s h = info[0];
222 h->cl = 0;
223 H_DMSG(sprintf(" result %O\n", hookMapping));
224 // the now invalid h will be cleaned up later.
225 return 1;
226 }
227 return 0;
228}
229
230// surveyors are asked for registration permittance
231protected int askSurveyorsForRegistrationAllowance(
232 struct hook_entry_s *surveyors, object consumer,int hookid,
233 int hookprio,int consumertype)
234{
235 H_DMSG(sprintf("askSurveyorsForRegistrationAllowance surveyors %O, "
236 "consumer %O, hookid %d, hookprio %d, consumertype %d\n",
237 surveyors,consumer,hookid,hookprio,consumertype));
238
239 foreach(struct hook_entry_s surveyor : surveyors) {
240 if (closurep(surveyor->cl) && surveyor->endtime >= time())
241 {
242 // surveyor hook gueltig.
243 object sob = GET_OBJECT(surveyor->cl);
244 if (!sob->HookRegistrationCallback(consumer, hookid,
245 this_object(), hookprio, consumertype))
246 return 0;
247 }
248 }
249 return 1;
250}
251
252int HRegisterToHook(int hookid, mixed consumer, int hookprio,
253 int consumertype, int timeInSeconds) {
254 int ret, regtime;
255
256 if (!closurep(consumer) && !objectp(consumer))
257 raise_error(sprintf("Wrong argument %.50O to HRegisterToHook(): consumer "
258 "must be closure or object.\n",consumer));
259
260 if (!member(hookMapping, hookid))
261 return -1;
262
263 if (objectp(consumer)) {
264 consumer = symbol_function("HookCallback", consumer);
265 if (!closurep(consumer))
266 return -2;
267 }
268
269 if (timeInSeconds > 0) {
270 regtime=time() + timeInSeconds;
271 }
272 else {
273 regtime=__INT_MAX__;
274 }
275
276 H_DMSG(sprintf("HRegisterToHook hookid %d consumer %O\n hookprio %d "
277 "consumertype %d\n",hookid,consumer,hookprio,consumertype));
278
279 CleanHookMapping(({hookid})); // entfernt ungueltige/abgelaufene Eintraege
280
281 // nur einmal pro closure registrieren!
282 if (HIsHookConsumer(hookid, consumer))
283 return -3;
284
285 // Consumertyp erlaubt?
286 if (H_CONSUMERCHECK(consumertype) == -1
Zesstra3d3bb422020-05-02 18:24:32 +0200287 || !HConsumerTypeIsAllowed(hookid, consumertype,GET_OBJECT(consumer)))
MG Mud User88f12472016-06-24 23:31:02 +0200288 return -4;
289
290 // Prioritaet erlaubt?
291 if (H_HOOK_VALIDPRIO(hookprio) == -1
292 || !HPriorityIsAllowed(hookprio, GET_OBJECT(consumer)))
293 return -5;
294
295 struct hook_s hook = hookMapping[hookid];
296
297 // Und surveyors erlauben die Registierung?
298 if (!askSurveyorsForRegistrationAllowance(hook->surveyors,
299 GET_OBJECT(consumer),hookid,
300 hookprio,consumertype))
301 return -6;
302
303 string ctypename = H_CONSUMERNAMES[consumertype];
304
305 // get the consumer array
306 struct hook_entry_s *consumers = hook->(ctypename);
307
308 // assemble new hook consumer struct
309 struct hook_entry_s newconsumer = (<hook_entry_s>
310 cl : consumer,
311 prio : hookprio,
312 endtime : regtime,
313 type : consumertype );
314
315 // consumers enthaelt die Hookeintraege
316 if (sizeof(consumers) < MAX_HOOK_COUNTS[consumertype]) {
317 // max. Anzahl an Eintraegen fuer diesen Typ noch nicht
318 // erreicht, direkt anhaengen.
319 consumers += ({ newconsumer });
320 hook->(ctypename) = consumers;
321 ret=1;
322 }
323 else {
324 // gibt es einen Eintrag mit hoeherem Priowert (niedrigere
325 // Prioritaet), den man ersetzen koennte?
326 // Das Array ist sortiert, mit hoechsten Priowerten am
327 // Ende. Ersetzt werden soll der Eintrag mit dem hoechsten
328 // Zahlenwert, falls der neue Eintrag einen niedrigeren Wert
329 // hat, d.h. es muss nur er letzte Consumer im Array geprueft werden.
330 // Pruefung auf Closureexistenz, falls der Surveyor Objekte
331 // zerstoert (hat)...
332 struct hook_entry_s oh = consumers[<1];
333 if (!oh->cl || oh->prio > newconsumer->prio) {
334 // Found superseedable entry - replace it, but inform the object.
335 H_DMSG("Found superseedable entry\n");
336 consumers[<1] = newconsumer;
337 GET_OBJECT(oh->cl)->superseededHook(hookid, this_object());
338 ret = 1;
339 // nicht noetig, consumers wieder in sein hook_s zu haenngen wegen Array
340 // -> Referenz
341 }
342 }
343
344 // wenn ein Eintrag hinzugefuegt wurde, muss neu sortiert werden
345 if (ret) {
346 hook->(ctypename) = sort_array(consumers,
347 function int (struct hook_entry_s a, struct hook_entry_s b) {
348 return a->prio > b->prio; } );
349 }
350
351 H_DMSG(sprintf(" result %O\n",hookMapping));
352
353 // -7, wenn kein Eintrag mehr frei / zuviele Hooks
354 return (ret > 0 ? 1 : -7);
355}
356
357// Conveniences wrapper for simple listener hooks
358int HRegisterListener(int hookid, mixed consumer)
359{
360 return HRegisterToHook(hookid, consumer, H_HOOK_OTHERPRIO(2), H_LISTENER, 0);
361}
362
363// Cnveniences wrapper for simple modificator hooks
364int HRegisterModifier(int hookid, mixed consumer)
365{
366 return HRegisterToHook(hookid, consumer, H_HOOK_OTHERPRIO(2),
367 H_HOOK_MODIFICATOR, 0);
368}
369
370// surveyors are asked for cancellation permittance
371protected int askSurveyorsForCancelAllowance(mixed surveyors,
372 object modifiyingOb,mixed data,int hookid,int prio,object hookOb){
373
374 foreach(struct hook_entry_s surveyor : surveyors) {
375 if (closurep(surveyor->cl) && surveyor->endtime >= time())
376 {
377 // surveyor hook gueltig.
378 object sob = GET_OBJECT(surveyor->cl);
379 if (!sob->HookCancelAllowanceCallback(modifiyingOb, hookid,
380 hookOb, prio, data))
381 return 0;
382 }
383 }
384 return 1;
385}
386
387// surveyors are asked for data change permittance
388protected int askSurveyorsForModificationAllowance(mixed surveyors,
389 object modifiyingOb,mixed data,int hookid,int prio,object hookOb){
390
391 foreach(struct hook_entry_s surveyor : surveyors) {
392 if (closurep(surveyor->cl) && surveyor->endtime >= time())
393 {
394 // surveyor hook gueltig.
395 object sob = GET_OBJECT(surveyor->cl);
396 if (!sob->HookModificationAllowanceCallback(modifiyingOb,
397 hookid, hookOb, prio, data))
398 return 0;
399 }
400 }
401 return 1;
402}
403
404protected mixed HookFlow(int hookid, mixed hookdata){
405 mixed tmp, ret;
406
407 ret=({H_NO_MOD,hookdata});
408
409 H_DMSG(sprintf("HookFlow hookid %d hookdata %O\n",hookid,hookdata));
410
411 if (!member(hookMapping, hookid))
412 return ret;
413
414 struct hook_s hook = hookMapping[hookid];
415
416 // notify surveyors
417 foreach(struct hook_entry_s h : hook->surveyors) {
418 if (closurep(h->cl) && h->endtime >= time())
419 {
420 // Hook gueltig
421 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
422 if(tmp[H_RETCODE]==H_CANCELLED) {
423 ret[H_RETCODE]=H_CANCELLED;
424 return ret; // und weg...
425 }
426 else if(tmp[H_RETCODE]==H_ALTERED){
427 ret[H_RETCODE]=H_ALTERED;
428 ret[H_RETDATA]=tmp[H_RETDATA];
429 }
430 }
431 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
432 else if (find_call_out(#'CleanHookMapping) == -1) {
433 call_out(#'CleanHookMapping, 0, ({hookid}));
434 }
435 } // surveyors fertig
436
437 // notify hmods
438 foreach(struct hook_entry_s h : hook->hmods) {
439 if (closurep(h->cl) && h->endtime >= time())
440 {
441 // Hook gueltig
442 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
443 if(tmp[H_RETCODE]==H_CANCELLED) {
444 // ask allowance in surveyors
445 if(h->cl &&
446 askSurveyorsForCancelAllowance(hook->surveyors,
447 GET_OBJECT(h->cl), hookdata, hookid,
448 h->prio, this_object()))
449 {
450 ret[H_RETCODE] = H_CANCELLED;
451 return ret; // und raus...
452 }
453 }
454 else if(tmp[H_RETCODE]==H_ALTERED) {
455 // ask allowance in surveyors
456 if(h->cl &&
457 askSurveyorsForModificationAllowance(hook->surveyors,
458 GET_OBJECT(h->cl),hookdata, hookid,
459 h->prio,this_object()))
460 {
461 ret[H_RETCODE] = H_ALTERED;
462 ret[H_RETDATA] = tmp[H_RETDATA];
463 }
464 }
465 }
466 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
467 else if (find_call_out(#'CleanHookMapping) == -1) {
468 call_out(#'CleanHookMapping, 0, ({hookid}));
469 }
470 } // hmods fertig
471
472 // notify dmods
473 foreach(struct hook_entry_s h : hook->dmods) {
474 if (closurep(h->cl) && h->endtime >= time())
475 {
476 // Hook gueltig
477 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
478 if(tmp[H_RETCODE]==H_ALTERED) {
479 // ask allowance in surveyors
480 if (h->cl &&
481 askSurveyorsForModificationAllowance(hook->surveyors,
482 GET_OBJECT(h->cl),hookdata, hookid,
483 h->prio,this_object()))
484 {
485 ret[H_RETCODE] = H_ALTERED;
486 ret[H_RETDATA] = tmp[H_RETDATA];
487 }
488 }
489 }
490 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
491 else if (find_call_out(#'CleanHookMapping) == -1) {
492 call_out(#'CleanHookMapping, 0, ({hookid}));
493 }
494 } // dmods fertig
495
496 // notify listener
497 foreach(struct hook_entry_s h : hook->listeners) {
498 if (closurep(h->cl) && h->endtime >= time())
499 {
500 // Hook gueltig
501 funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
502 }
503 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
504 else if (find_call_out(#'CleanHookMapping) == -1) {
505 call_out(#'CleanHookMapping, 0, ({hookid}));
506 }
507 } // listener fertig
508
509 return ret;
510}
511