MG Mud User | 88f1247 | 2016-06-24 23:31:02 +0200 | [diff] [blame^] | 1 | // MorgenGrauen MUDlib |
| 2 | // |
| 3 | // /p/daemon/eventd.c -- Event Dispatcher |
| 4 | // |
| 5 | // $Id$ |
| 6 | #pragma strict_types,save_types |
| 7 | #pragma no_clone |
| 8 | #pragma no_shadow |
| 9 | #pragma pedantic |
| 10 | #pragma range_check |
| 11 | |
| 12 | #include <wizlevels.h> |
| 13 | #include <defines.h> |
| 14 | #include <events.h> |
| 15 | |
| 16 | // Fuer Statistiken |
| 17 | #define STATISTICS |
| 18 | |
| 19 | #define HOME(x) (__PATH__(0)+x) |
| 20 | #define STORE HOME("save/eventd") |
| 21 | |
| 22 | |
| 23 | #define LOG(x) log_file("EVENTS", sprintf(\ |
| 24 | "[%s] %s\n",dtime(time()),x)) |
| 25 | //#define LOGEVENT(x,y,z) log_file("EVENTS", sprintf(\ |
| 26 | // "[%s] Event %s triggered by %O (Args: %.40O)\n",dtime(time()),x,y,z)) |
| 27 | #define LOGEVENT(x,y,z) |
| 28 | //#define LOGREGISTER(w,x,y,z) log_file("EVENTLISTENER",sprintf(\ |
| 29 | // "[%s] %O (Fun: %.25s) registered for %s by %O\n",dtime(time()),w,x,y,z)) |
| 30 | #define LOGREGISTER(w,x,y,z) |
| 31 | //#define LOGUNREGISTER(x,y,z) log_file("EVENTLISTENER",sprintf(\ |
| 32 | // "[%s] %O was unregistered from %s by %O\n",dtime(time()),x,y,z)) |
| 33 | #define LOGUNREGISTER(x,y,z) |
| 34 | //#define LOGEVENTFINISH(x,y) log_file("EVENTS", sprintf(\ |
| 35 | // "[%s] Event %s (triggered by %O) finished\n",dtime(time()),x,y)) |
| 36 | #define LOGEVENTFINISH(x,y) |
| 37 | |
| 38 | #ifndef DEBUG |
| 39 | #define DEBUG(x) if (find_player("zesstra"))\ |
| 40 | tell_object(find_player("zesstra"),\ |
| 41 | "EDBG: "+x+"\n") |
| 42 | #endif |
| 43 | |
| 44 | #define TICK_RESERVE 300000 |
| 45 | #define TICKSPERCALLBACK 30000 |
| 46 | |
| 47 | // Indizes fuer Callback-Infos (events, active) |
| 48 | #define CB_FUN 0 |
| 49 | #define CB_CLOSURE 1 |
| 50 | #define CB_OBJECT 2 |
| 51 | // Indizes fuer Pending |
| 52 | #define P_EID 0 |
| 53 | #define P_TRIGOB 1 |
| 54 | #define P_TRIGOBNAME 2 |
| 55 | #define P_ARGS 3 |
| 56 | #define P_TIME 4 |
| 57 | |
| 58 | // Indizes fuer Active |
| 59 | #define A_EID 0 |
| 60 | #define A_TRIGOB 1 |
| 61 | #define A_TRIGOBNAME 2 |
| 62 | #define A_ARGS 3 |
| 63 | #define A_LISTENERS 4 |
| 64 | #define A_TIME 5 |
| 65 | |
| 66 | /* alle events, die er kennt. |
| 67 | Datenstruktur events: |
| 68 | ([id:([obname:fun;closure;object, ... ]), |
| 69 | id2: ([....]), |
| 70 | ]) */ |
| 71 | mapping events=([]); |
| 72 | /* abzuarbeitende Events, Datenstruktur: |
| 73 | ({ ({id, trigob, trigobname, args}), |
| 74 | ({id2, trigob, trigobname, args}), ... }) */ |
| 75 | nosave mixed pending=({}); |
| 76 | /* Der gerade aktive Event, der wird gerade abgearbeitet. |
| 77 | Datenstruktur active: |
| 78 | ({ id:trigob;trigobname;args;([obname:fun;closure;object, ...]), trigtime}) |
| 79 | */ |
| 80 | nosave mixed active=({}); |
| 81 | |
| 82 | int lastboot; // Zeitstempel des letzten Reboots |
| 83 | // Flag, wenn gesetzt, zerstoert sich der Eventd, wenn keine Events |
| 84 | // abzuarbeiten sind. |
| 85 | nosave int updateme; |
| 86 | |
| 87 | // einfache Statistik, gespeichert wird ein Histogramm. KEys sind die |
| 88 | // Zeitdifferenzen zwschen Eintragen und Erledigen des Events, Values die |
| 89 | // Haeufigkeiten. |
| 90 | nosave mapping stats=([]); |
| 91 | |
| 92 | |
| 93 | protected void process_events(); |
| 94 | protected void save_me(); |
| 95 | varargs int remove(int silent); |
| 96 | |
| 97 | // ist der Event-Typ "eid" schon bekannt, d.h. gib es min. 1 Listener? |
| 98 | // Liefert Anzahl der Listener zurueck. |
| 99 | int CheckEventID(string eid) { |
| 100 | if (!stringp(eid) || !member(events,eid)) |
| 101 | return 0; |
| 102 | return(sizeof(events[eid])); |
| 103 | } |
| 104 | |
| 105 | // entscheidet, ob ein Objekt fuer einen Event eingetragen werden darf. Zum |
| 106 | // Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle |
| 107 | // Events oeffentlich. Bekommt die Event-ID, das einzutragende Objekt und das |
| 108 | // eintragende Objekt uebergeben. |
| 109 | // 1 fuer Erlaubnis, 0 sonst. |
| 110 | protected int allowed(string eid, object ob, object po) { |
| 111 | return(1); |
| 112 | } |
| 113 | |
| 114 | // entscheidet, ob ein Objekt einen bestimmten Event triggern darf. Zum |
| 115 | // Ueberschreiben in geerbten Dispatchern, in diesem Scheduler sind alle |
| 116 | // Events oeffentlich. Bekommt die Event-ID und das triggernde Objekt |
| 117 | // uebergeben. |
| 118 | // 1 fuer Erlaubnis, 0 sonst. |
| 119 | protected int allowedtrigger(string eid, object trigger) { |
| 120 | return(1); |
| 121 | } |
| 122 | |
| 123 | // registriert sich fuer einen Event. Wenn es den bisher nicht gibt, wird er |
| 124 | // implizit erzeugt. Wenn das Objekt ob schon angemeldet war, wird der |
| 125 | // bisherige Eintrag ueberschrieben. |
| 126 | // 1 bei Erfolg, <=0 bei Misserfolg |
| 127 | int RegisterEvent(string eid, string fun, object ob) { |
| 128 | object po; |
| 129 | if (!stringp(eid) || !stringp(fun) || |
| 130 | !objectp(ob) || !objectp(po=previous_object())) |
| 131 | return -1; |
| 132 | if (!allowed(eid, ob, po)) return -2; |
| 133 | closure cl=symbol_function(fun,ob); |
| 134 | if (!closurep(cl)) |
| 135 | return -3; |
| 136 | if (!mappingp(events[eid])) |
| 137 | events[eid]=m_allocate(1,3); // 3 Werte pro Key |
| 138 | events[eid]+=([object_name(ob):fun;cl;ob]); |
| 139 | // if (find_call_out(#'save_me)==-1) |
| 140 | // call_out(#'save_me,15); |
| 141 | LOGREGISTER(ob,fun,eid,po); |
| 142 | return 1; |
| 143 | } |
| 144 | |
| 145 | // entfernt das Objekt als Listener aus dem Event eid |
| 146 | // Mehr oder weniger optional, wenn ein event verarbeitet wird und das Objekt |
| 147 | // ist nicht mehr auffindbar, wird es ebenfalls geloescht. Bei selten |
| 148 | // getriggerten Events muellt es aber bis dahin den Speicher voll. |
| 149 | // +1 fuer Erfolg, <= 0 fuer Misserfolg |
| 150 | int UnregisterEvent(string eid, object ob) { |
| 151 | object po; |
| 152 | if (!stringp(eid) || !objectp(ob) || |
| 153 | !objectp(po=previous_object()) || !mappingp(events[eid])) |
| 154 | return -1; |
| 155 | string oname=object_name(ob); |
| 156 | if (!member(events[eid],oname)) |
| 157 | return -2; |
| 158 | m_delete(events[eid],oname); |
| 159 | if (!sizeof(events[eid])) |
| 160 | m_delete(events,eid); |
| 161 | // aus aktivem Event austragen, falls es drin sein sollte. |
| 162 | if (sizeof(active) && active[A_EID] == eid) |
| 163 | { |
| 164 | m_delete(active[A_LISTENERS], object_name(ob)); |
| 165 | } |
| 166 | // if (find_call_out(#'save_me)==-1) |
| 167 | // call_out(#'save_me,15); |
| 168 | LOGUNREGISTER(ob, eid, po); |
| 169 | return 1; |
| 170 | } |
| 171 | |
| 172 | // Loest einen Event aus. |
| 173 | // 1 fuer Erfolg, <= 0 fuer Misserfolg |
| 174 | varargs int TriggerEvent(string eid, mixed args) { |
| 175 | object trigger; |
| 176 | if (!stringp(eid) || |
| 177 | !objectp(trigger=previous_object())) |
| 178 | return -1; |
| 179 | if (!allowedtrigger(eid, trigger)) return -2; |
| 180 | if (!member(events,eid)) return -3; |
| 181 | if (sizeof(pending) > __MAX_ARRAY_SIZE__/5) |
| 182 | return -4; |
| 183 | pending+=({ ({eid,trigger,object_name(trigger), args, time()}) }); |
| 184 | if (find_call_out(#'process_events) == -1) |
| 185 | call_out(#'process_events,0); |
| 186 | LOGEVENT(eid,trigger,args); |
| 187 | //DEBUG(sprintf("%O",pending)); |
| 188 | return 1; |
| 189 | } |
| 190 | |
| 191 | protected void process_events() { |
| 192 | // erstmal wieder nen call_out eintragen. |
| 193 | call_out(#'process_events, 1); |
| 194 | |
| 195 | // solange ueber active und pending laufen, bis keine Ticks mehr da sind, |
| 196 | // bzw. in der Schleife abgebrochen wird, weil keine Events mehr da sind. |
| 197 | while(get_eval_cost() > TICK_RESERVE) { |
| 198 | // HB abschalten, wenn nix zu tun ist. |
| 199 | if (!sizeof(active)) { |
| 200 | if (!sizeof(pending)) { |
| 201 | remove_call_out(#'process_events); |
| 202 | break; |
| 203 | } |
| 204 | // scheint noch min. ein Eintrag in pending zu sein, nach active kopieren, |
| 205 | // plus die Callback-Infos aus events |
| 206 | active=({pending[0][P_EID], |
| 207 | pending[0][P_TRIGOB],pending[0][P_TRIGOBNAME], |
| 208 | pending[0][P_ARGS], |
| 209 | deep_copy(events[pending[0][P_EID]]), |
| 210 | pending[0][P_TIME] }); |
| 211 | |
| 212 | if (sizeof(pending)>1) |
| 213 | pending=pending[1..]; // und aus pending erstmal loeschen. ;-) |
| 214 | else |
| 215 | pending=({}); |
| 216 | //DEBUG(sprintf("Pending: %O",pending)); |
| 217 | //DEBUG(sprintf("Active: %O",active)); |
| 218 | } |
| 219 | // jetzte den aktiven Event abarbeiten. |
| 220 | // Infos aus active holen... |
| 221 | string eid=active[A_EID]; |
| 222 | object trigob=active[A_TRIGOB]; |
| 223 | string trigobname=active[A_TRIGOBNAME]; |
| 224 | mixed args=active[A_ARGS]; |
| 225 | mapping listeners=active[A_LISTENERS]; |
| 226 | // und ueber alle Listener iterieren |
| 227 | foreach(string obname, string fun, closure cl, object listener: |
| 228 | listeners) { |
| 229 | // erst pruefen, ob noch genug Ticks da sind. wenn nicht, gehts im |
| 230 | // naechsten Zyklus weiter. |
| 231 | if (get_eval_cost() < TICK_RESERVE) { |
| 232 | return; |
| 233 | } |
| 234 | // wenn Closure und/oder zugehoeriges Objekt nicht existieren, versuchen |
| 235 | // wir erstmal, es wiederzufinden. ;-) |
| 236 | if (!objectp(query_closure_object(cl))) { |
| 237 | if (objectp(listener=find_object(obname)) && |
| 238 | closurep(cl=symbol_function(fun,listener))) { |
| 239 | //geklappt, auch in events wieder ergaenzen |
| 240 | events[eid][obname,CB_CLOSURE]=cl; |
| 241 | events[eid][obname,CB_OBJECT]=listener; |
| 242 | } |
| 243 | else { |
| 244 | // Objekt nicht gefunden oder Closure nicht erzeugbar, austragen |
| 245 | m_delete(listeners,obname); |
| 246 | // und aus events austragen. |
| 247 | m_delete(events[eid],obname); |
| 248 | // und naechster Durchgang |
| 249 | continue; |
| 250 | } |
| 251 | } |
| 252 | // Objekt noch da, Closure wird als ausfuehrbar betrachtet. |
| 253 | catch(limited(#'funcall,({TICKSPERCALLBACK}),cl,eid,trigob,args);publish); |
| 254 | // fertig mit diesem Objekt. |
| 255 | m_delete(listeners,obname); |
| 256 | } |
| 257 | // Statistik? Differenzen zwische Erledigungszeit und Eintragszeit bilden |
| 258 | // die Keys, die Values werden einfach hochgezaehlt. |
| 259 | #ifdef STATISTICS |
| 260 | stats[time()-active[A_TIME]]++; |
| 261 | #endif // STATISTICS |
| 262 | // ok, fertig mit active. |
| 263 | active=({}); |
| 264 | //DEBUG(sprintf("Fertig: %O %O",eid, trigobname)); |
| 265 | LOGEVENTFINISH(eid,trigobname); |
| 266 | } // while(get_eval_cost() > TICK_RESERVE) |
| 267 | |
| 268 | // Soll dies Ding neugeladen werden? Wenn ja, Selbstzerstoerung, wenn keine |
| 269 | // Events mehr da sind. |
| 270 | if (updateme && !sizeof(active) && !sizeof(pending)) { |
| 271 | DEBUG(sprintf("Update requested\n")); |
| 272 | remove(1); |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | protected void create() { |
| 277 | seteuid(getuid(ME)); |
| 278 | restore_object(STORE); |
| 279 | if (lastboot != __BOOT_TIME__) { |
| 280 | // Oh. Reboot war... Alle Events wegschmeissen (es koennten zwar die |
| 281 | // Eventanmeldungen von BPs ueberleben, aber auch die sollen sich lieber |
| 282 | // im create() anmelden.) |
| 283 | events=([]); |
| 284 | lastboot=__BOOT_TIME__;; |
| 285 | } |
| 286 | LOG("Event-Dispatcher loaded"); |
| 287 | } |
| 288 | |
| 289 | |
| 290 | protected void save_me() { |
| 291 | save_object(STORE); |
| 292 | } |
| 293 | |
| 294 | varargs int remove(int silent) { |
| 295 | save_me(); |
| 296 | DEBUG(sprintf("remove() called by %O - destructing\n", previous_object())); |
| 297 | LOG(sprintf("remove called by %O - destructing",previous_object())); |
| 298 | destruct(ME); |
| 299 | return 1; |
| 300 | } |
| 301 | |
| 302 | public void reset() { |
| 303 | if (updateme && !sizeof(active) && !sizeof(pending)) { |
| 304 | DEBUG(sprintf("Update requested\n")); |
| 305 | remove(1); |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | |
| 310 | // fuer Debugzwecke. Interface und Verhalten kann sich jederzeit ohne |
| 311 | // Vorankuendigung aendern. |
| 312 | mapping QueryEvents() { |
| 313 | return(deep_copy(events)); |
| 314 | } |
| 315 | |
| 316 | mixed QueryPending() { |
| 317 | return(deep_copy(pending)); |
| 318 | } |
| 319 | |
| 320 | mixed QueryActive() { |
| 321 | return(deep_copy(active)); |
| 322 | } |
| 323 | |
| 324 | mapping QueryStats() { |
| 325 | return(copy(stats)); |
| 326 | } |
| 327 | |
| 328 | int UpdateMe(int flag) { |
| 329 | updateme=flag; |
| 330 | if (find_call_out(#'process_events)==-1) |
| 331 | reset(); |
| 332 | return flag; |
| 333 | } |
| 334 | |