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