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*/;
19 import core
.time
: MonoTime
;
20 import std
.datetime
: Clock
, SysTime
;
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. */
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
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
) {
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
);
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;
116 foreach (ref EventListenerInfo eli
; llist
) {
118 needListenerCleanup
= true;
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
) {
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
);
159 if (evt
!is null && !evt
.processed
&& evt
.bubbling
&& evt
.odest
is this) {
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:
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")
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");
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;
217 scope(exit
) o
= null; // for GC
218 void doit (TypeInfo_Class ti
) {
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
);
243 synchronized (Event
.classinfo
) {
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; }
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
);
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 () {
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
;
288 if (peLastTID
!= Thread
.getThis
.id
) throw new Exception("calling `processEvents()` from different threads aren't allowed");
290 nowProcessing
= true;
292 // process only so much events to avoid endless loop
293 left
= ppevents
.length
;
296 scope(exit
) evt
= null;
298 auto tm
= MonoTime
.currTime
;
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
;
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
;
321 synchronized (Event
.classinfo
) {
323 foreach (immutable c
; 1..events
.length
) events
.ptr
[c
-1] = events
.ptr
[c
];
326 events
.assumeSafeAppend
;
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
) {
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");
358 // ////////////////////////////////////////////////////////////////////////// //
360 __gshared
bool[uint] usedIds
;
361 __gshared
uint lastid
;
362 __gshared
bool wrapped
= false;
368 if (res
!= 0) { usedIds
[res
] = true; return res
; }
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
{
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!
420 this (TypeInfo_Class ati
, DgType adg
, bool aoneshot
) {
426 this (Object asrc
, TypeInfo_Class ati
, DgType adg
, bool aoneshot
) {
431 xsrc
= new Weak
!Object(asrc
);
432 //xsrc.onDead = &ebusOnDeadCB;
436 this (ObjDispatcher 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;
452 while (pos
< llist
.length
) {
453 auto ell
= llist
.ptr
+pos
;
455 if (ell
.dobj
!is null && !ell
.dobj
.alive
) ell
.id
= 0;
456 if (ell
.xsrc
!is null && ell
.xsrc
.empty
) ell
.id
= 0;
459 foreach (immutable c
; pos
+1..llist
.length
) llist
.ptr
[c
-1] = llist
.ptr
[c
];
460 llist
[$-1] = EventListenerInfo
.init
;
462 llist
.assumeSafeAppend
;
468 import core
.atomic
: cas
;
469 if (cas(&needListenerCleanupSB
, true, false)) {
471 while (pos
< llistsb
.length
) {
472 auto ell
= llistsb
.ptr
+pos
;
474 if (ell
.dobj
!is null && !ell
.dobj
.alive
) ell
.id
= 0;
475 if (ell
.xsrc
!is null && ell
.xsrc
.empty
) ell
.id
= 0;
478 foreach (immutable c
; pos
+1..llistsb
.length
) llistsb
.ptr
[c
-1] = llistsb
.ptr
[c
];
479 llistsb
[$-1] = EventListenerInfo
.init
;
481 llistsb
.assumeSafeAppend
;
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) {
496 if (auto itf
= cast(EventTarget
)(obj
)) {
497 obj
= itf
.eventbusParent();
506 void dispatchSinkBubble (Event evt
) {
507 void callOnTypeFor (ObjDispatcher
.Type type
, Object obj
) {
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; }
518 if (dobj
.type
!= type ||
!dobj
.isObject(obj
)) dobj
= null;
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;
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
) {{
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
);
574 // broadcast listeners (they will also catch directed events w/o doing sink/bubble)
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;
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; }
591 if (dobj
.type
!= ObjDispatcher
.Type
.Broadcast
) dobj
= null;
596 oneshot
= eli
.oneshot
;
599 if (xsrc
.empty
) continue;
600 if (xsrc
.object
!is evt
.osource
) continue;
603 if (oneshot
) synchronized (Event
.classinfo
) { auto eli
= &llist
[idx
]; needListenerCleanup
= true; eli
.id
= 0; eli
.dg
= null; eli
.dobj
= null; eli
.xsrc
= null; }
605 if (evt
.processed
) break;
606 } else if (dg
!is null && ti
!is null) {
607 auto ecc
= _d_dynamic_cast(evt
, ti
);
609 if (oneshot
) synchronized (Event
.classinfo
) { auto eli
= &llist
[idx
]; needListenerCleanup
= true; eli
.id
= 0; eli
.dg
= null; eli
.dobj
= null; eli
.xsrc
= null; }
611 if (evt
.processed
) break;
615 if (evt
.processed
) return;
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
) {
629 class EventEx
: Event
{ this (Object s
, Object d
) { super(s
, d
); } }
631 class Obj
: EventTarget
{
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);
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
);
670 alias onMyEvent
= super.onMyEvent
;
673 this.connectListeners();
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
);
692 addEventListener((Event evt
) { writeln("!!! main handler!"); });
694 (new Event(null, o0
)).post
;
695 (new Event(null, o1
)).post
;
696 (new Event(null, o2
)).post
;
699 (new EventEx(null, o2
)).post
;