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