egeditor: more VaVoom C highlighting
[iv.d.git] / eventbus.d
blobc24a0a9e56e285df47820bb188426cc23dee309d
1 /* Invisible Vector Library
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module iv.eventbus /*is aliced*/;
17 private:
19 import core.time : MonoTime;
20 import std.datetime : Clock, SysTime;
22 import iv.alice;
23 import iv.weakref;
26 // ////////////////////////////////////////////////////////////////////////// //
27 ///WARNING! changing this is NOT thread-safe!
28 /// you should set this up at program startup and don't touch after that
29 __gshared void delegate (const(char)[] msg) ebusLogError;
32 /// objects should implement this interface to make sinking/bubbling work.
33 /// if this interface is not implemented, targeted message will be dropped on the floor.
34 public interface EventTarget {
35 /// this should return parent object or null
36 abstract @property Object eventbusParent ();
37 /// this will be called on sinking and bubbling
38 abstract void eventbusOnEvent (Event evt);
42 // ////////////////////////////////////////////////////////////////////////// //
43 /** note that when event reached it's destination, it will be switched to bubbling
44 * phase before calling the appropriate event handler. */
45 public class Event {
46 public:
47 /// propagation flags
48 enum PFlags : ubyte {
49 Eaten = 1U<<0, /// event is processed, but not cancelled
50 Cancelled = 1U<<1, /// event is cancelled (it may be *both* processed and cancelled!)
51 Bubbling = 1U<<2, /// event is in bubbling phase
52 Posted = 1U<<7, /// event is posted
54 protected Object osource; /// event source, can be null
55 protected Object odest; /// event destination, can be null for broadcast events
56 protected ubyte flags; /// see PFlags
57 protected SysTime etime; /// emiting time
59 this () { etime = Clock.currTime; } ///
60 this (Object asrc) { etime = Clock.currTime; osource = asrc; } ///
61 this (Object asrc, Object adest) { etime = Clock.currTime; osource = asrc; odest = adest; } ///
63 /// post event: put it into queue to process at next iteration.
64 final void post () { this.postpone(0); }
66 /// schedule event to be processed later (after `msecs` msecs passed)
67 final void later (int msecs) { this.postpone(msecs); }
69 final pure nothrow @safe @nogc:
70 void eat () { pragma(inline, true); flags |= PFlags.Eaten; } /// "eat" event (set "eaten" flag)
71 void cancel () { pragma(inline, true); flags |= PFlags.Cancelled; } /// "cancel" event (set "cancelled" flag)
73 inout(Object) source () inout { pragma(inline, true); return osource; } /// source object
74 inout(Object) dest () inout { pragma(inline, true); return odest; } /// destination object
75 inout(SysTime) time () inout { pragma(inline, true); return etime; } /// emitting time
77 const @property:
78 bool eaten () { pragma(inline, true); return ((flags&PFlags.Eaten) != 0); } ///
79 bool cancelled () { pragma(inline, true); return ((flags&PFlags.Cancelled) != 0); } ///
80 bool processed () { pragma(inline, true); return ((flags&(PFlags.Eaten|PFlags.Cancelled)) != 0); } ///
81 bool posted () { pragma(inline, true); return ((flags&PFlags.Posted) != 0); } ///
82 bool sinking () { pragma(inline, true); return ((flags&PFlags.Bubbling) == 0); } ///
83 bool bubbling () { pragma(inline, true); return ((flags&PFlags.Bubbling) != 0); } ///
87 // ////////////////////////////////////////////////////////////////////////// //
88 /** register listener for all events (both targeted and not, and for anonymous too).
89 * returns event id that can be used to remove listener. */
90 public uint addEventListener(E:Event) (void delegate (E evt) dg, bool oneshot=false) {
91 return addEventListener!E(null, dg, oneshot);
94 /** register event listener for *source* object `obj`.
95 * returns event id that can be used to remove listener. */
96 public uint addEventListener(E:Event) (Object obj, void delegate (E evt) dg, bool oneshot=false) {
97 if (dg is null) return 0;
98 synchronized (Event.classinfo) {
99 if (!oneshot) {
100 foreach (ref EventListenerInfo eli; llist) {
101 if (typeid(E) == eli.ti && eli.dg is cast(EventListenerInfo.DgType)dg) return eli.id;
104 llist ~= EventListenerInfo(obj, typeid(E), cast(EventListenerInfo.DgType)dg, oneshot);
105 return lastid;
110 // ////////////////////////////////////////////////////////////////////////// //
111 /// remove event listener with the given id. returns `true` if some listener was removed.
112 public bool removeEventListener (uint id) {
113 synchronized (Event.classinfo) {
114 if (id !in usedIds) return false;
115 usedIds.remove(id);
116 foreach (ref EventListenerInfo eli; llist) {
117 if (eli.id == id) {
118 needListenerCleanup = true;
119 eli.id = 0;
120 eli.dg = null;
121 eli.dobj = null;
122 eli.xsrc = null;
123 return true;
126 return false;
131 // ////////////////////////////////////////////////////////////////////////// //
132 enum SBDispatchMixin = q{
133 static if (is(typeof(&__traits(getMember, this, "onMyEvent")) == delegate) ||
134 is(typeof(&__traits(getMember, this, "onMyEvent")) == function))
137 //if (this !is evt.odest) return; // not our
138 void doit (TypeInfo_Class ti) {
139 import std.traits;
140 if (typeid(Event) != ti) doit(ti.base);
141 if (evt.processed) return;
142 foreach (immutable oidx, /*auto*/ over; __traits(getOverloads, this, "onMyEvent")) {
143 alias tt = typeof(over);
144 static if (is(ReturnType!tt == void)) {
145 alias pars = Parameters!tt;
146 static if (pars.length == 1) {
147 static if (is(pars[0] : Event)) {
148 if (typeid(pars[0]) == ti) {
149 if (auto ev = cast(pars[0])evt) {
150 __traits(getOverloads, this, "onMyEvent")[oidx](ev);
151 return;
159 if (evt !is null && !evt.processed && evt.bubbling && evt.odest is this) {
160 doit(typeid(evt));
167 private class ObjDispatcher {
168 enum Type { Broadcast, My, Sink, Bubble }
169 abstract bool alive ();
170 abstract Type type ();
171 abstract bool isObject (Object o);
172 abstract void dispatch (Event ev);
176 /** this should be called in object ctor to automatically connect event listening methods.
178 * valid handler methods:
180 * ---------------
181 * void onEvent (EventXXX ev); // will receive any events
182 * void onMyEvent (EventXXX ev); // will receive only events targeted to this object (dest is this), when they sinked
183 * void onSinkEvent (EventXXX ev); // will receive sinking events (but not "my")
184 * void onBubbleEvent (EventXXX ev); // will receive bubbling events (but not "my")
185 * ---------------
187 public void connectListeners(O:Object) (O obj) {
188 if (obj is null) return;
190 void addListener(ObjDispatcher.Type tp) () {
191 static if (tp == ObjDispatcher.Type.Broadcast) enum EvtName = "onEvent";
192 else static if (tp == ObjDispatcher.Type.My) enum EvtName = "onMyEvent";
193 else static if (tp == ObjDispatcher.Type.Sink) enum EvtName = "onSinkEvent";
194 else static if (tp == ObjDispatcher.Type.Bubble) enum EvtName = "onBubbleEvent";
195 else static assert(0, "invalid type");
197 static if (is(typeof(&__traits(getMember, obj, EvtName)) == delegate) ||
198 is(typeof(&__traits(getMember, obj, EvtName)) == function))
200 static class ObjDispatcherX(OT, ObjDispatcher.Type tp) : ObjDispatcher {
201 static if (tp == ObjDispatcher.Type.Broadcast) enum EvtName = "onEvent";
202 else static if (tp == ObjDispatcher.Type.My) enum EvtName = "onMyEvent";
203 else static if (tp == ObjDispatcher.Type.Sink) enum EvtName = "onSinkEvent";
204 else static if (tp == ObjDispatcher.Type.Bubble) enum EvtName = "onBubbleEvent";
205 else static assert(0, "invalid type");
206 Weak!OT obj;
207 this (OT aobj) { obj = new Weak!OT(aobj); /*obj.onDead = &ebusOnDeadCB;*/ }
208 override bool alive () { return !obj.empty; }
209 override Type type () { return tp; }
210 override bool isObject (Object o) {
211 if (o is null || obj.empty) return false;
212 return (obj.object is o);
214 override void dispatch (Event evt) {
215 if (obj.empty) return;
216 auto o = obj.object;
217 scope(exit) o = null; // for GC
218 void doit (TypeInfo_Class ti) {
219 import std.traits;
220 if (typeid(Event) != ti) doit(ti.base);
221 if (evt.processed) return;
222 foreach (immutable oidx, /*auto*/ over; __traits(getOverloads, OT, EvtName)) {
223 alias tt = typeof(over);
224 static if (is(ReturnType!tt == void)) {
225 alias pars = Parameters!tt;
226 static if (pars.length == 1) {
227 static if (is(pars[0] : Event)) {
228 if (typeid(pars[0]) == ti) {
229 if (auto ev = cast(pars[0])evt) {
230 debug(tuing_eventbus) { import iv.vfs.io; VFile("zevtlog.log", "a").writefln("calling %s for %s 0x%08x (%s)", tp, o.classinfo.name, *cast(void**)&o, evt.classinfo.name); }
231 __traits(getOverloads, o, EvtName)[oidx](ev);
232 return;
240 doit(typeid(evt));
243 synchronized (Event.classinfo) {
244 bool found = false;
245 static if (tp == ObjDispatcher.Type.Broadcast) {
246 foreach (ref EventListenerInfo eli; llist) {
247 if (eli.dobj !is null && eli.dobj.type == tp && eli.dobj.isObject(obj)) { found = true; break; }
250 if (!found) {
251 debug(tuing_eventbus) { import iv.vfs.io; VFile("zevtlog.log", "a").writefln("added %s for %s 0x%08x", tp, obj.classinfo.name, *cast(void**)&obj); }
252 auto dp = new ObjDispatcherX!(O, tp)(obj);
253 static if (tp == ObjDispatcher.Type.Broadcast) {
254 llist ~= EventListenerInfo(dp);
255 } else {
256 llistsb ~= EventListenerInfo(dp);
263 addListener!(ObjDispatcher.Type.Broadcast)();
264 addListener!(ObjDispatcher.Type.My)();
265 addListener!(ObjDispatcher.Type.Sink)();
266 addListener!(ObjDispatcher.Type.Bubble)();
270 // ////////////////////////////////////////////////////////////////////////// //
271 private import core.thread : Thread, ThreadID;
273 private __gshared bool nowProcessing = false;
274 private __gshared ThreadID peLastTID = 0;
277 /** process queued events. it is safe to post new events in event handlers.
279 * WARNING! this MUST be called only in main processing thread!
281 public void processEvents () {
282 usize left;
283 synchronized (Event.classinfo) {
284 if (nowProcessing) throw new Exception("recursive calls to `processEvents()` aren't allowed");
285 if (peLastTID == 0) {
286 peLastTID = Thread.getThis.id;
287 } else {
288 if (peLastTID != Thread.getThis.id) throw new Exception("calling `processEvents()` from different threads aren't allowed");
290 nowProcessing = true;
291 cleanupListeners();
292 // process only so much events to avoid endless loop
293 left = ppevents.length;
295 Event evt;
296 scope(exit) evt = null;
297 if (left > 0) {
298 auto tm = MonoTime.currTime;
299 while (left-- > 0) {
300 synchronized (Event.classinfo) {
301 if (tm < ppevents[0].hitTime) break;
302 evt = ppevents.ptr[0].evt;
303 foreach (immutable c; 1..ppevents.length) ppevents.ptr[c-1] = ppevents.ptr[c];
304 ppevents[$-1] = ppevents[0].init;
305 ppevents.length -= 1;
306 ppevents.assumeSafeAppend;
308 try {
309 callEventListeners(evt);
310 } catch (Exception e) {
311 if (ebusLogError !is null) {
312 ebusLogError("******** EVENT PROCESSING ERROR: "~e.msg);
313 ebusLogError("******** EVENT PROCESSING ERROR: "~e.toString);
318 // process only so much events to avoid endless loop
319 synchronized (Event.classinfo) left = events.length;
320 while (left-- > 0) {
321 synchronized (Event.classinfo) {
322 evt = events.ptr[0];
323 foreach (immutable c; 1..events.length) events.ptr[c-1] = events.ptr[c];
324 events[$-1] = null;
325 events.length -= 1;
326 events.assumeSafeAppend;
328 try {
329 callEventListeners(evt);
330 } catch (Exception e) {
331 if (ebusLogError !is null) {
332 ebusLogError("******** EVENT PROCESSING ERROR: "~e.msg);
333 ebusLogError("******** EVENT PROCESSING ERROR: "~e.toString);
337 synchronized (Event.classinfo) {
338 cleanupListeners();
339 nowProcessing = false;
344 // ////////////////////////////////////////////////////////////////////////// //
345 /// returns milliseconds until next postponed event or -1 if there are none
346 public int ebusSafeDelay () {
347 synchronized (Event.classinfo) {
348 if (events.length > 0) return 0; // we have something to process
349 if (ppevents.length == 0) return -1;
350 auto tm = MonoTime.currTime;
351 if (tm >= ppevents[0].hitTime) return 0; // first scheduled event should already be fired
352 auto res = cast(int)((ppevents[0].hitTime-tm).total!"msecs");
353 return res;
358 // ////////////////////////////////////////////////////////////////////////// //
359 private:
360 __gshared bool[uint] usedIds;
361 __gshared uint lastid;
362 __gshared bool wrapped = false;
365 uint getId () {
366 if (!wrapped) {
367 auto res = ++lastid;
368 if (res != 0) { usedIds[res] = true; return res; }
369 wrapped = true;
370 lastid = 1;
372 // wrapped
373 foreach (long count; 0..cast(long)uint.max+1) {
374 if (lastid !in usedIds) { usedIds[lastid] = true; return lastid; }
375 if (++lastid == 0) lastid = 1;
377 assert(0, "too many listeners!");
381 struct PostponedEvent {
382 Event evt;
383 MonoTime hitTime;
386 __gshared Event[] events;
387 __gshared PostponedEvent[] ppevents;
390 void postpone (Event evt, int msecs) {
391 import core.time : dur;
392 if (evt is null) return;
393 synchronized (Event.classinfo) {
394 if (evt.posted) throw new Exception("can't post already posted event");
395 evt.flags |= Event.PFlags.Posted;
396 if (msecs <= 0) { events ~= evt; return; }
397 auto tm = MonoTime.currTime+dur!"msecs"(msecs);
398 ppevents ~= PostponedEvent(evt, tm);
399 if (ppevents.length > 1) {
400 import std.algorithm : sort;
401 ppevents.sort!((in ref i0, in ref i1) => i0.hitTime < i1.hitTime);
407 // ////////////////////////////////////////////////////////////////////////// //
408 // get druntime dynamic cast function
409 private extern(C) void* _d_dynamic_cast (Object o, ClassInfo c);
412 struct EventListenerInfo {
413 alias DgType = void delegate (/*Event*/void* e); // actually, `e` is any `Event` subclass; cheater!
414 TypeInfo_Class ti;
415 DgType dg;
416 uint id;
417 ObjDispatcher dobj;
418 Weak!Object xsrc;
419 bool oneshot;
420 this (TypeInfo_Class ati, DgType adg, bool aoneshot) {
421 id = getId;
422 ti = ati;
423 dg = adg;
424 oneshot = aoneshot;
426 this (Object asrc, TypeInfo_Class ati, DgType adg, bool aoneshot) {
427 id = getId;
428 ti = ati;
429 dg = adg;
430 if (asrc !is null) {
431 xsrc = new Weak!Object(asrc);
432 //xsrc.onDead = &ebusOnDeadCB;
434 oneshot = aoneshot;
436 this (ObjDispatcher adobj) {
437 id = getId;
438 dobj = adobj;
442 __gshared EventListenerInfo[] llist;
443 __gshared EventListenerInfo[] llistsb; // sink/bubble
444 __gshared bool needListenerCleanup = false;
445 shared bool needListenerCleanupSB = false;
448 void cleanupListeners () {
449 if (needListenerCleanup) {
450 needListenerCleanup = false;
451 int pos = 0;
452 while (pos < llist.length) {
453 auto ell = llist.ptr+pos;
454 if (ell.id != 0) {
455 if (ell.dobj !is null && !ell.dobj.alive) ell.id = 0;
456 if (ell.xsrc !is null && ell.xsrc.empty) ell.id = 0;
458 if (ell.id == 0) {
459 foreach (immutable c; pos+1..llist.length) llist.ptr[c-1] = llist.ptr[c];
460 llist[$-1] = EventListenerInfo.init;
461 llist.length -= 1;
462 llist.assumeSafeAppend;
463 } else {
464 ++pos;
468 import core.atomic : cas;
469 if (cas(&needListenerCleanupSB, true, false)) {
470 int pos = 0;
471 while (pos < llistsb.length) {
472 auto ell = llistsb.ptr+pos;
473 if (ell.id != 0) {
474 if (ell.dobj !is null && !ell.dobj.alive) ell.id = 0;
475 if (ell.xsrc !is null && ell.xsrc.empty) ell.id = 0;
477 if (ell.id == 0) {
478 foreach (immutable c; pos+1..llistsb.length) llistsb.ptr[c-1] = llistsb.ptr[c];
479 llistsb[$-1] = EventListenerInfo.init;
480 llistsb.length -= 1;
481 llistsb.assumeSafeAppend;
482 } else {
483 ++pos;
490 __gshared Object[] evchain;
492 void buildEvChain (Object obj) {
493 if (evchain.length) { evchain[] = null; evchain.length = 0; evchain.assumeSafeAppend; }
494 while (obj !is null) {
495 evchain ~= obj;
496 if (auto itf = cast(EventTarget)(obj)) {
497 obj = itf.eventbusParent();
498 } else {
499 break;
505 // with evchain
506 void dispatchSinkBubble (Event evt) {
507 void callOnTypeFor (ObjDispatcher.Type type, Object obj) {
508 usize llen;
509 synchronized (Event.classinfo) llen = llistsb.length;
510 foreach (usize idx; 0..llen) {
511 ObjDispatcher dobj = null;
512 synchronized (Event.classinfo) {
513 auto eli = &llistsb[idx];
514 if (eli.id == 0) continue;
515 if (eli.dobj !is null) {
516 if (!eli.dobj.alive) { import core.atomic; atomicStore(needListenerCleanupSB, true); eli.id = 0; eli.dobj = null; eli.xsrc = null; continue; }
517 dobj = eli.dobj;
518 if (dobj.type != type || !dobj.isObject(obj)) dobj = null;
521 if (dobj !is null) {
522 dobj.dispatch(evt);
523 if (evt.processed) break;
528 if (evchain.length != 0) {
529 // sinking phase, backward
530 foreach_reverse (Object o; evchain[1..$]) {
531 callOnTypeFor(ObjDispatcher.Type.Sink, o);
532 if (evt.processed) return;
533 if (auto itf = cast(EventTarget)o) {
534 itf.eventbusOnEvent(evt);
535 if (evt.processed) return;
538 evt.flags |= Event.PFlags.Bubbling;
539 callOnTypeFor(ObjDispatcher.Type.My, evchain[0]);
540 if (evt.processed) return;
541 if (auto itf = cast(EventTarget)evchain[0]) {
542 itf.eventbusOnEvent(evt);
543 if (evt.processed) return;
545 // bubbling phase, forward
546 foreach (Object o; evchain[1..$]) {
547 callOnTypeFor(ObjDispatcher.Type.Bubble, o);
548 if (evt.processed) return;
549 if (auto itf = cast(EventTarget)o) {
550 itf.eventbusOnEvent(evt);
551 if (evt.processed) return;
554 } else {
555 if (evt.processed) return;
556 evt.flags |= Event.PFlags.Bubbling;
557 callOnTypeFor(ObjDispatcher.Type.My, evt.odest);
562 void callEventListeners (Event evt) {
563 if (evt is null) return;
564 scope(exit) { evt.osource = null; evt.odest = null; }
565 if (evt.processed) return;
566 debug(tuing_eventbus) {{
567 import iv.vfs.io;
568 auto fo = VFile("zevtlog.log", "a");
569 fo.writef("dispatching %s", evt.classinfo.name);
570 if (evt.osource !is null) fo.writef("; src=%s", evt.osource.classinfo.name);
571 if (evt.odest !is null) fo.writef("; dest=%s", evt.odest.classinfo.name);
572 fo.writeln;
574 // broadcast listeners (they will also catch directed events w/o doing sink/bubble)
575 usize llen;
576 synchronized (Event.classinfo) llen = llist.length;
577 foreach (usize idx; 0..llen) {
578 ObjDispatcher dobj = null;
579 EventListenerInfo.DgType dg = null;
580 TypeInfo_Class ti = null;
581 Weak!Object xsrc = null;
582 bool oneshot = false;
583 synchronized (Event.classinfo) {
584 auto eli = &llist[idx];
585 if (eli.id == 0) continue;
586 xsrc = eli.xsrc;
587 if (xsrc !is null && xsrc.empty) { needListenerCleanup = true; eli.id = 0; eli.dobj = null; eli.xsrc = null; continue; }
588 if (eli.dobj !is null) {
589 if (!eli.dobj.alive) { needListenerCleanup = true; eli.id = 0; eli.dobj = null; eli.xsrc = null; continue; }
590 dobj = eli.dobj;
591 if (dobj.type != ObjDispatcher.Type.Broadcast) dobj = null;
592 } else {
593 dg = eli.dg;
594 ti = eli.ti;
596 oneshot = eli.oneshot;
598 if (xsrc !is null) {
599 if (xsrc.empty) continue;
600 if (xsrc.object !is evt.osource) continue;
602 if (dobj !is null) {
603 if (oneshot) synchronized (Event.classinfo) { auto eli = &llist[idx]; needListenerCleanup = true; eli.id = 0; eli.dg = null; eli.dobj = null; eli.xsrc = null; }
604 dobj.dispatch(evt);
605 if (evt.processed) break;
606 } else if (dg !is null && ti !is null) {
607 auto ecc = _d_dynamic_cast(evt, ti);
608 if (ecc !is null) {
609 if (oneshot) synchronized (Event.classinfo) { auto eli = &llist[idx]; needListenerCleanup = true; eli.id = 0; eli.dg = null; eli.dobj = null; eli.xsrc = null; }
610 dg(ecc);
611 if (evt.processed) break;
615 if (evt.processed) return;
616 // targeted message?
617 if (evt.odest !is null) {
618 scope(exit) if (evchain.length) { evchain[] = null; evchain.length = 0; evchain.assumeSafeAppend; }
619 buildEvChain(evt.odest);
620 dispatchSinkBubble(evt);
625 // ////////////////////////////////////////////////////////////////////////// //
626 version(tuing_ebus_test) {
627 import iv.vfs.io;
629 class EventEx : Event { this (Object s, Object d) { super(s, d); } }
631 class Obj : EventTarget {
632 Object parent;
634 // this should return parent object or null
635 override @property Object eventbusParent () { return parent; }
637 // this will be called on sinking and bubbling
638 override void eventbusOnEvent (Event evt) {
639 writefln("eventbusOnEvent: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
640 //mixin(SBDispatchMixin);
643 this () {
644 this.connectListeners();
645 writefln("ctor: 0x%08x", cast(uint)cast(void*)this);
648 void onEvent (Event evt) {
649 writefln("onEvent: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
652 void onMyEvent (Event evt) {
653 writefln("onMy: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
656 void onMyEvent (EventEx evt) {
657 writefln("onMyEx: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
660 void onSinkEvent (Event evt) {
661 writefln("onSink: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
664 void onBubbleEvent (EventEx evt) {
665 writefln("onBubble: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
669 class Obj1 : Obj {
670 alias onMyEvent = super.onMyEvent;
672 this () {
673 this.connectListeners();
674 super();
675 //writefln("ctor: 0x%08x", cast(uint)cast(void*)this);
678 override void onMyEvent (EventEx evt) {
679 writefln("OVERRIDEN onMyEx: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
680 super.onMyEvent(evt);
681 writefln("EXITING OVERRIDEN onMyEx: 0x%08x 0x%08x %s", cast(uint)cast(void*)this, cast(uint)cast(void*)evt.odest, evt.bubbling);
686 void main () {
687 Object o0 = new Obj;
688 Obj o1 = new Obj;
689 Obj o2 = new Obj1;
690 o1.parent = o0;
691 o2.parent = o1;
692 addEventListener((Event evt) { writeln("!!! main handler!"); });
693 (new Event()).post;
694 (new Event(null, o0)).post;
695 (new Event(null, o1)).post;
696 (new Event(null, o2)).post;
697 processEvents();
698 writeln("====");
699 (new EventEx(null, o2)).post;
700 processEvents();