blob: b9912afa7f50dd608d20629dec9292e3965bd995 [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
Zesstra70c60032020-05-02 18:25:22 +020085// Prueft, ob <consumer> den Hooktyp <type> mit der Prioritaet <prio> im
86// Hook <hook> benutzen darf.
87protected int HPriorityIsAllowed(int hookid, int type, int prio,
88 object consumer)
89{
MG Mud User88f12472016-06-24 23:31:02 +020090 return 1;
91}
92
93// clean internal hook data structures of stale hook consumers.
Zesstrab6842712018-02-12 20:53:26 +010094// returns the number of valid consumers left.
95protected int CleanHookMapping(int *hookids)
96{
MG Mud User88f12472016-06-24 23:31:02 +020097 // hooks enthaelt die aufzuraeumenden Hooks. Wenn kein Array -> alle Hooks
98 if (!pointerp(hookids))
99 hookids=m_indices(hookMapping);
100
Zesstrab6842712018-02-12 20:53:26 +0100101 int count;
102 foreach(int hookid : hookids)
103 { // alle Hooks
MG Mud User88f12472016-06-24 23:31:02 +0200104 struct hook_s hooks = hookMapping[hookid];
105 if (!structp(hooks))
106 continue;
107 // ueber alle Consumertypen laufen
108 foreach (string consumertype: H_CONSUMERNAMES)
109 {
110 // Yeah - compute struct lookup at runtime... ;-)
111 struct hook_entry_s *consumers = hooks->(consumertype);
112 // Hookeintraege / Consumer
Zesstrab6842712018-02-12 20:53:26 +0100113 foreach(struct hook_entry_s h : &consumers)
114 {
MG Mud User88f12472016-06-24 23:31:02 +0200115 // alle abgelaufenen Eintraege oder solche mit zerstoerten Objekten
Zesstrab6842712018-02-12 20:53:26 +0100116 // nullen und die anderen/gueltigen zaehlen.
MG Mud User88f12472016-06-24 23:31:02 +0200117 if (!h->cl || h->endtime < time() )
118 h = 0;
Zesstrab6842712018-02-12 20:53:26 +0100119 else
120 ++count;
MG Mud User88f12472016-06-24 23:31:02 +0200121 }
122 // 0 noch rauswerfen.
123 hooks->(consumertype) -= ({0});
124 }
125 }
Zesstrab6842712018-02-12 20:53:26 +0100126 return count;
127}
128
129// Returns the number of valid consumers for the given hooks (or all, if
130// hooks==0).
131// Side effect: Cleans the internal structures and removes any stale
132// consumers.
133public varargs int HHasConsumers(int *hookids)
134{
135 return CleanHookMapping(hookids);
MG Mud User88f12472016-06-24 23:31:02 +0200136}
137
138protected void offerHook(int hookid, int offerstate)
139{
140 H_DMSG(sprintf("offerHook hookid %d offerstate %d\n",hookid,offerstate));
141 if (hookid>0)
142 {
143 if (offerstate) {
144 if (!member(hookMapping,hookid)) {
145 struct hook_s hook = (<hook_s>
146 surveyors: ({}),
147 hmods: ({}),
148 dmods: ({}),
149 listeners: ({}) );
150 hookMapping[hookid] = hook;
151 }
152 }
153 else {
154 if (member(hookMapping,hookid)) {
155 m_delete(hookMapping,hookid);
156 }
157 }
158 }
159 H_DMSG(sprintf(" result %O\n",hookMapping));
160}
161
162// hookConsumerInfo() liefert Array von hook_entry_s zurueck. D.h. bei Abfrage
163// von Objekten alle Closures dieses Objekts und jede davon erzeugt ein
164// Element hook_entry_s im Ergebnisarray. Bei Abfrage von Closures hat das
165// Array immer genau 1 oder kein Element.
166// WARNING: whoever has a hook_entry_s can change/delete the hook!
167// NEVER return the original to an external caller!
168// NOTE: whoever has the cl from hook_entry_s can call it, even if the lfun
169// is private (and this object the only one knowing it).
Bugfix15229482022-10-06 11:41:13 +0200170protected struct hook_entry_s* hookConsumerInfo(int hookid, object|closure consumer)
MG Mud User88f12472016-06-24 23:31:02 +0200171{
172 closure filter_cl;
173
174 if (!member(hookMapping,hookid))
175 return ({});
176
177 // Closure zum Filtern bestimmen - je nachdem, was gesucht wird.
178 if (closurep(consumer))
179 {
180 filter_cl = function int (struct hook_entry_s h)
181 { return h->cl == consumer
182 && h->endtime >= time(); };
183 }
184 else if (objectp(consumer))
185 {
186 filter_cl = function int (struct hook_entry_s h)
187 { return GET_OBJECT(h->cl) == consumer
188 && h->endtime >= time(); };
189 }
190 else
191 {
192 return ({});
193 }
194
195 struct hook_s hook = hookMapping[hookid];
196 struct hook_entry_s *result = ({});
197 foreach (string consumertype: H_CONSUMERNAMES )
198 {
199 result += filter(hook->(consumertype), filter_cl);
200 }
201 return result;
202}
203
Bugfix15229482022-10-06 11:41:13 +0200204int HIsHookConsumer(int hookid, object|closure consumer) {
MG Mud User88f12472016-06-24 23:31:02 +0200205 return sizeof(hookConsumerInfo(hookid,consumer)) != 0;
206}
207
208int* HListHooks() {
209 return m_indices(hookMapping);
210}
211
Bugfix15229482022-10-06 11:41:13 +0200212int HUnregisterFromHook(int hookid, object|closure consumer) {
MG Mud User88f12472016-06-24 23:31:02 +0200213
214 H_DMSG(sprintf("HUnregisterFromHook hookid %d consumer %O\n",hookid,consumer));
215 if (objectp(consumer))
216 consumer = symbol_function("HookCallback", consumer);
Bugfix15229482022-10-06 11:41:13 +0200217 if (!consumer)
MG Mud User88f12472016-06-24 23:31:02 +0200218 return 0;
219
220 struct hook_entry_s *info = hookConsumerInfo(hookid,consumer);
221
222 // it should never happen that a closure is registered more than once, i.e.
223 // the result contains more than one element.
224 if (sizeof(info)) {
225 struct hook_entry_s h = info[0];
226 h->cl = 0;
227 H_DMSG(sprintf(" result %O\n", hookMapping));
228 // the now invalid h will be cleaned up later.
229 return 1;
230 }
231 return 0;
232}
233
234// surveyors are asked for registration permittance
235protected int askSurveyorsForRegistrationAllowance(
236 struct hook_entry_s *surveyors, object consumer,int hookid,
237 int hookprio,int consumertype)
238{
239 H_DMSG(sprintf("askSurveyorsForRegistrationAllowance surveyors %O, "
240 "consumer %O, hookid %d, hookprio %d, consumertype %d\n",
241 surveyors,consumer,hookid,hookprio,consumertype));
242
243 foreach(struct hook_entry_s surveyor : surveyors) {
244 if (closurep(surveyor->cl) && surveyor->endtime >= time())
245 {
246 // surveyor hook gueltig.
247 object sob = GET_OBJECT(surveyor->cl);
248 if (!sob->HookRegistrationCallback(consumer, hookid,
249 this_object(), hookprio, consumertype))
250 return 0;
251 }
252 }
253 return 1;
254}
255
Bugfix15229482022-10-06 11:41:13 +0200256int HRegisterToHook(int hookid, object|closure consumer, int hookprio,
MG Mud User88f12472016-06-24 23:31:02 +0200257 int consumertype, int timeInSeconds) {
258 int ret, regtime;
259
Bugfix15229482022-10-06 11:41:13 +0200260 if (!consumer)
MG Mud User88f12472016-06-24 23:31:02 +0200261 raise_error(sprintf("Wrong argument %.50O to HRegisterToHook(): consumer "
262 "must be closure or object.\n",consumer));
263
264 if (!member(hookMapping, hookid))
265 return -1;
266
267 if (objectp(consumer)) {
268 consumer = symbol_function("HookCallback", consumer);
269 if (!closurep(consumer))
270 return -2;
271 }
272
273 if (timeInSeconds > 0) {
274 regtime=time() + timeInSeconds;
275 }
276 else {
277 regtime=__INT_MAX__;
278 }
279
280 H_DMSG(sprintf("HRegisterToHook hookid %d consumer %O\n hookprio %d "
281 "consumertype %d\n",hookid,consumer,hookprio,consumertype));
282
283 CleanHookMapping(({hookid})); // entfernt ungueltige/abgelaufene Eintraege
284
285 // nur einmal pro closure registrieren!
286 if (HIsHookConsumer(hookid, consumer))
287 return -3;
288
289 // Consumertyp erlaubt?
290 if (H_CONSUMERCHECK(consumertype) == -1
Zesstra3d3bb422020-05-02 18:24:32 +0200291 || !HConsumerTypeIsAllowed(hookid, consumertype,GET_OBJECT(consumer)))
MG Mud User88f12472016-06-24 23:31:02 +0200292 return -4;
293
294 // Prioritaet erlaubt?
295 if (H_HOOK_VALIDPRIO(hookprio) == -1
Zesstra70c60032020-05-02 18:25:22 +0200296 || !HPriorityIsAllowed(hookid, consumertype, hookprio,
297 GET_OBJECT(consumer)))
MG Mud User88f12472016-06-24 23:31:02 +0200298 return -5;
299
300 struct hook_s hook = hookMapping[hookid];
301
302 // Und surveyors erlauben die Registierung?
303 if (!askSurveyorsForRegistrationAllowance(hook->surveyors,
304 GET_OBJECT(consumer),hookid,
305 hookprio,consumertype))
306 return -6;
307
308 string ctypename = H_CONSUMERNAMES[consumertype];
309
310 // get the consumer array
311 struct hook_entry_s *consumers = hook->(ctypename);
312
313 // assemble new hook consumer struct
314 struct hook_entry_s newconsumer = (<hook_entry_s>
315 cl : consumer,
316 prio : hookprio,
317 endtime : regtime,
318 type : consumertype );
319
320 // consumers enthaelt die Hookeintraege
321 if (sizeof(consumers) < MAX_HOOK_COUNTS[consumertype]) {
322 // max. Anzahl an Eintraegen fuer diesen Typ noch nicht
323 // erreicht, direkt anhaengen.
324 consumers += ({ newconsumer });
325 hook->(ctypename) = consumers;
326 ret=1;
327 }
328 else {
329 // gibt es einen Eintrag mit hoeherem Priowert (niedrigere
330 // Prioritaet), den man ersetzen koennte?
331 // Das Array ist sortiert, mit hoechsten Priowerten am
332 // Ende. Ersetzt werden soll der Eintrag mit dem hoechsten
333 // Zahlenwert, falls der neue Eintrag einen niedrigeren Wert
334 // hat, d.h. es muss nur er letzte Consumer im Array geprueft werden.
335 // Pruefung auf Closureexistenz, falls der Surveyor Objekte
336 // zerstoert (hat)...
337 struct hook_entry_s oh = consumers[<1];
338 if (!oh->cl || oh->prio > newconsumer->prio) {
339 // Found superseedable entry - replace it, but inform the object.
340 H_DMSG("Found superseedable entry\n");
341 consumers[<1] = newconsumer;
342 GET_OBJECT(oh->cl)->superseededHook(hookid, this_object());
343 ret = 1;
344 // nicht noetig, consumers wieder in sein hook_s zu haenngen wegen Array
345 // -> Referenz
346 }
347 }
348
349 // wenn ein Eintrag hinzugefuegt wurde, muss neu sortiert werden
350 if (ret) {
351 hook->(ctypename) = sort_array(consumers,
352 function int (struct hook_entry_s a, struct hook_entry_s b) {
353 return a->prio > b->prio; } );
354 }
355
356 H_DMSG(sprintf(" result %O\n",hookMapping));
357
358 // -7, wenn kein Eintrag mehr frei / zuviele Hooks
359 return (ret > 0 ? 1 : -7);
360}
361
362// Conveniences wrapper for simple listener hooks
Bugfix15229482022-10-06 11:41:13 +0200363int HRegisterListener(int hookid, object|closure consumer)
MG Mud User88f12472016-06-24 23:31:02 +0200364{
365 return HRegisterToHook(hookid, consumer, H_HOOK_OTHERPRIO(2), H_LISTENER, 0);
366}
367
368// Cnveniences wrapper for simple modificator hooks
Bugfix15229482022-10-06 11:41:13 +0200369int HRegisterModifier(int hookid, object|closure consumer)
MG Mud User88f12472016-06-24 23:31:02 +0200370{
371 return HRegisterToHook(hookid, consumer, H_HOOK_OTHERPRIO(2),
372 H_HOOK_MODIFICATOR, 0);
373}
374
375// surveyors are asked for cancellation permittance
Bugfix15229482022-10-06 11:41:13 +0200376protected int askSurveyorsForCancelAllowance(struct hook_entry_s* surveyors,
MG Mud User88f12472016-06-24 23:31:02 +0200377 object modifiyingOb,mixed data,int hookid,int prio,object hookOb){
378
379 foreach(struct hook_entry_s surveyor : surveyors) {
380 if (closurep(surveyor->cl) && surveyor->endtime >= time())
381 {
382 // surveyor hook gueltig.
383 object sob = GET_OBJECT(surveyor->cl);
384 if (!sob->HookCancelAllowanceCallback(modifiyingOb, hookid,
385 hookOb, prio, data))
386 return 0;
387 }
388 }
389 return 1;
390}
391
392// surveyors are asked for data change permittance
Bugfix15229482022-10-06 11:41:13 +0200393protected int askSurveyorsForModificationAllowance(struct hook_entry_s* surveyors,
MG Mud User88f12472016-06-24 23:31:02 +0200394 object modifiyingOb,mixed data,int hookid,int prio,object hookOb){
395
396 foreach(struct hook_entry_s surveyor : surveyors) {
397 if (closurep(surveyor->cl) && surveyor->endtime >= time())
398 {
399 // surveyor hook gueltig.
400 object sob = GET_OBJECT(surveyor->cl);
401 if (!sob->HookModificationAllowanceCallback(modifiyingOb,
402 hookid, hookOb, prio, data))
403 return 0;
404 }
405 }
406 return 1;
407}
408
409protected mixed HookFlow(int hookid, mixed hookdata){
410 mixed tmp, ret;
411
412 ret=({H_NO_MOD,hookdata});
413
414 H_DMSG(sprintf("HookFlow hookid %d hookdata %O\n",hookid,hookdata));
415
416 if (!member(hookMapping, hookid))
417 return ret;
418
419 struct hook_s hook = hookMapping[hookid];
420
421 // notify surveyors
422 foreach(struct hook_entry_s h : hook->surveyors) {
423 if (closurep(h->cl) && h->endtime >= time())
424 {
425 // Hook gueltig
426 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
427 if(tmp[H_RETCODE]==H_CANCELLED) {
428 ret[H_RETCODE]=H_CANCELLED;
429 return ret; // und weg...
430 }
431 else if(tmp[H_RETCODE]==H_ALTERED){
432 ret[H_RETCODE]=H_ALTERED;
433 ret[H_RETDATA]=tmp[H_RETDATA];
434 }
435 }
436 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
437 else if (find_call_out(#'CleanHookMapping) == -1) {
438 call_out(#'CleanHookMapping, 0, ({hookid}));
439 }
440 } // surveyors fertig
441
442 // notify hmods
443 foreach(struct hook_entry_s h : hook->hmods) {
444 if (closurep(h->cl) && h->endtime >= time())
445 {
446 // Hook gueltig
447 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
448 if(tmp[H_RETCODE]==H_CANCELLED) {
449 // ask allowance in surveyors
450 if(h->cl &&
451 askSurveyorsForCancelAllowance(hook->surveyors,
452 GET_OBJECT(h->cl), hookdata, hookid,
453 h->prio, this_object()))
454 {
455 ret[H_RETCODE] = H_CANCELLED;
456 return ret; // und raus...
457 }
458 }
459 else if(tmp[H_RETCODE]==H_ALTERED) {
460 // ask allowance in surveyors
461 if(h->cl &&
462 askSurveyorsForModificationAllowance(hook->surveyors,
463 GET_OBJECT(h->cl),hookdata, hookid,
464 h->prio,this_object()))
465 {
466 ret[H_RETCODE] = H_ALTERED;
467 ret[H_RETDATA] = tmp[H_RETDATA];
468 }
469 }
470 }
471 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
472 else if (find_call_out(#'CleanHookMapping) == -1) {
473 call_out(#'CleanHookMapping, 0, ({hookid}));
474 }
475 } // hmods fertig
476
477 // notify dmods
478 foreach(struct hook_entry_s h : hook->dmods) {
479 if (closurep(h->cl) && h->endtime >= time())
480 {
481 // Hook gueltig
482 tmp = funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
483 if(tmp[H_RETCODE]==H_ALTERED) {
484 // ask allowance in surveyors
485 if (h->cl &&
486 askSurveyorsForModificationAllowance(hook->surveyors,
487 GET_OBJECT(h->cl),hookdata, hookid,
488 h->prio,this_object()))
489 {
490 ret[H_RETCODE] = H_ALTERED;
491 ret[H_RETDATA] = tmp[H_RETDATA];
492 }
493 }
494 }
495 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
496 else if (find_call_out(#'CleanHookMapping) == -1) {
497 call_out(#'CleanHookMapping, 0, ({hookid}));
498 }
499 } // dmods fertig
500
501 // notify listener
502 foreach(struct hook_entry_s h : hook->listeners) {
503 if (closurep(h->cl) && h->endtime >= time())
504 {
505 // Hook gueltig
506 funcall(h->cl, this_object(), hookid, ret[H_RETDATA]);
507 }
508 // ungueltige/abgelaufene Eintraege -> Aufraeumen, aber nicht jetzt.
509 else if (find_call_out(#'CleanHookMapping) == -1) {
510 call_out(#'CleanHookMapping, 0, ({hookid}));
511 }
512 } // listener fertig
513
514 return ret;
515}
516