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