2 * Signals and Slots are an implementation of the $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
3 * Essentially, when a Signal is emitted, a list of connected Observers
4 * (called slots) are called.
6 * They were first introduced in the
7 * $(LINK2 http://en.wikipedia.org/wiki/Qt_%28framework%29, Qt GUI toolkit), alternate implementations are
8 * $(LINK2 http://libsigc.sourceforge.net, libsig++) or
9 * $(LINK2 http://www.boost.org/doc/libs/1_55_0/doc/html/signals2.html, Boost.Signals2)
10 * similar concepts are implemented in other languages than C++ too.$(BR)
11 * $(LINK2 https://github.com/phobos-x/phobosx.git, original)
13 * Copyright: Copyright Robert Klotzner 2012 - 2014; Ketmar Dark 2015
14 * License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
15 * Authors: Robert Klotzner, Ketmar Dark
18 /* Copyright Robert Klotzner 2012 - 2014.
19 * Distributed under the Boost Software License, Version 1.0.
20 * (See accompanying file LICENSE_1_0.txt or copy at
21 * http://www.boost.org/LICENSE_1_0.txt)
23 * Based on the original implementation written by Walter Bright. (std.signals)
24 * I shamelessly stole some ideas of: http://forum.dlang.org/thread/jjote0$1cql$1@digitalmars.com
25 * written by Alex Rønne Petersen.
27 * Also thanks to Denis Shelomovskij who made me aware of some
28 * deficiencies in the concurrent part of WeakRef.
30 module iv
.signal
/*is aliced*/;
33 // Hook into the GC to get informed about object deletions.
34 private alias void delegate (Object
) DisposeEvt
;
35 private extern (C
) void rt_attachDisposeEvent (Object obj
, DisposeEvt evt
);
36 private extern (C
) void rt_detachDisposeEvent (Object obj
, DisposeEvt evt
);
40 * Full signal implementation.
42 * It implements the emit function, for all other functionality it has
43 * this aliased to RestrictedSignal.
45 * A signal is a way to couple components together in a very loose
46 * way. The receiver does not need to know anything about the sender
47 * and the sender does not need to know anything about the
48 * receivers. The sender will just call emit when something happens,
49 * the signal takes care of notifying all interested parties. By using
50 * wrapper delegates/functions, not even the function signature of
51 * sender/receiver need to match.
53 * Another consequence of this very loose coupling is, that a
54 * connected object will be freed by the GC if all references to it
55 * are dropped, even if it was still connected to a signal. The
56 * connection will simply be removed. This way the developer is freed of
57 * manually keeping track of connections.
59 * If in your application the connections made by a signal are not
60 * that loose you can use strongConnect(), in this case the GC won't
61 * free your object until it was disconnected from the signal or the
62 * signal got itself destroyed.
64 * This struct is not thread-safe in general, it just handles the
65 * concurrent parts of the GC.
76 // Public accessor method returning a RestrictedSignal, thus restricting
77 // the use of emit to this module. See the signal() string mixin below
79 ref RestrictedSignal!(string, int) valueChanged () { return valueChangedSg.restricted; }
80 private Signal!(string, int) valueChangedSg;
82 @property int value () => mValue;
83 @property int value (int v) {
86 // call all the connected slots with the two parameters
87 valueChangedSg.emit("setting new value", v);
95 void watch (string msg, int i) {
96 writefln("Observed msg '%s' and value %s", msg, i);
99 void watch (string msg, int i) {
100 writefln("Globally observed msg '%s' and value %s", msg, i);
103 auto a = new MyObject;
104 Observer o = new Observer;
106 a.value = 3; // should not call o.watch()
107 a.valueChanged.connect!"watch"(o); // o.watch is the slot
108 a.value = 4; // should call o.watch()
109 a.valueChanged.disconnect!"watch"(o); // o.watch is no longer a slot
110 a.value = 5; // should not call o.watch()
111 a.valueChanged.connect!"watch"(o); // connect again
112 // Do some fancy stuff:
113 a.valueChanged.connect!Observer(o, (obj, msg, i) => obj.watch("Some other text I made up", i+1));
114 a.valueChanged.connect(&watch);
115 a.value = 6; // should call o.watch()
116 destroy(o); // destroying o should automatically disconnect it
117 a.value = 7; // should not call o.watch()
120 * which should print:
122 * Observed msg 'setting new value' and value 4
123 * Observed msg 'setting new value' and value 6
124 * Observed msg 'Some other text I made up' and value 7
125 * Globally observed msg 'setting new value' and value 6
126 * Globally observed msg 'setting new value' and value 7
129 struct Signal(Args
...) {
131 RestrictedSignal
!(Args
) mRestricted
;
134 alias restricted
this;
139 * All connected slots which are still alive will be called. If
140 * any of the slots throws an exception, the other slots will
141 * still be called. You'll receive a chained exception with all
142 * exceptions that were thrown. Thus slots won't influence each
145 * The slots are called in the same sequence as they were registered.
147 * emit also takes care of actually removing dead connections. For
148 * concurrency reasons they are just set to an invalid state by the GC.
150 * If you remove a slot during emit() it won't be called in the
151 * current run if it was not already.
153 * If you add a slot during emit() it will be called in the
154 * current emit() run. Note however, Signal is not thread-safe, "called
155 * during emit" basically means called from within a slot.
157 void emit (Args args
) @trusted => mRestricted
.mImpl
.emit(args
);
160 * Get access to the rest of the signals functionality.
162 * By only granting your users access to the returned RestrictedSignal
163 * reference, you are preventing your users from calling emit on their
166 @property ref RestrictedSignal
!(Args
) restricted () @trusted => mRestricted
;
171 * The signal implementation, not providing an emit method.
173 * A RestrictedSignal reference is returned by Signal.restricted,
174 * it can safely be passed to users of your API, without
175 * allowing them to call emit().
177 struct RestrictedSignal(Args
...) {
183 * Direct connection to an object.
185 * Use this method if you want to connect directly to an object's
186 * method matching the signature of this signal. The connection
187 * will have weak reference semantics, meaning if you drop all
188 * references to the object the garbage collector will collect it
189 * and this connection will be removed.
191 * Preconditions: mixin("&obj."~method) must be valid and compatible.
194 * obj = Some object of a class implementing a method
195 * compatible with this signal.
197 void connect(string method
, ClassType
) (ClassType obj
) @trusted
198 if (is(ClassType
== class) && __traits(compiles
, {void delegate (Args
) dg
= mixin("&obj."~method
);}))
200 if (obj
) mImpl
.addSlot(obj
, cast(void delegate ())mixin("&obj."~method
));
204 * Indirect connection to an object.
206 * Use this overload if you want to connect to an objects method
207 * which does not match the signal's signature. You can provide
208 * any delegate to do the parameter adaption, but make sure your
209 * delegates' context does not contain a reference to the target
210 * object, instead use the provided obj parameter, where the
211 * object passed to connect will be passed to your delegate.
212 * This is to make weak ref semantics possible, if your delegate
213 * contains a ref to obj, the object won't be freed as long as
214 * the connection remains.
216 * Preconditions: dg's context must not be equal to obj.
219 * obj = The object to connect to. It will be passed to the
220 * delegate when the signal is emitted.
222 * dg = A wrapper delegate which takes care of calling some
223 * method of obj. It can do any kind of parameter adjustments
226 void connect(ClassType
) (ClassType obj
, void delegate (ClassType obj
, Args
) dg
) @trusted
227 if (is(ClassType
== class))
229 if (obj
!is null && dg
) {
230 if (cast(void*)obj
is dg
.ptr
) assert(0, "iv.signal connect: invalid delegate");
231 mImpl
.addSlot(obj
, cast(void delegate ())dg
);
236 * Connect with strong ref semantics.
238 * Use this overload if you either want strong ref
239 * semantics for some reason or because you want to connect some
240 * non-class method delegate. Whatever the delegates' context
241 * references will stay in memory as long as the signals'
242 * connection is not removed and the signal gets not destroyed
246 * dg = The delegate to be connected.
248 void strongConnect (void delegate (Args
) dg
) @trusted {
249 if (dg
!is null) mImpl
.addSlot(null, cast(void delegate ())dg
);
253 * Connect a free function to this signal.
256 * fn = The free function to be connected.
258 void connect (void function (Args
) fn
) @trusted {
260 import std
.functional
: toDelegate
;
261 auto dg
= toDelegate(fn
);
262 mImpl
.addSlot(null, cast(void delegate ())dg
);
267 * Disconnect a direct connection.
269 * After issuing this call, the connection to method of obj is lost
270 * and obj.method() will no longer be called on emit.
271 * Preconditions: Same as for direct connect.
273 void disconnect(string method
, ClassType
) (ClassType obj
) @trusted
274 if (is(ClassType
== class) && __traits(compiles
, {void delegate (Args
) dg
= mixin("&obj."~method
);}))
277 void delegate (Args
) dg
= mixin("&obj."~method
);
278 mImpl
.removeSlot(obj
, cast(void delegate ()) dg
);
283 * Disconnect an indirect connection.
285 * For this to work properly, dg has to be exactly the same as
286 * the one passed to connect. So if you used a lamda you have to
287 * keep a reference to it somewhere if you want to disconnect
288 * the connection later on. If you want to remove all
289 * connections to a particular object, use the overload which only
290 * takes an object parameter.
292 void disconnect(ClassType
) (ClassType obj
, void delegate (ClassType
, T1
) dg
) @trusted
293 if (is(ClassType
== class))
295 if (obj
!is null && dg
!is null) mImpl
.removeSlot(obj
, cast(void delegate ())dg
);
299 * Disconnect all connections to obj.
301 * All connections to obj made with calls to connect are removed.
303 void disconnect(ClassType
) (ClassType obj
) @trusted
304 if (is(ClassType
== class))
306 if (obj
!is null) mImpl
.removeSlot(obj
);
310 * Disconnect a connection made with strongConnect.
312 * Disconnects all connections to dg.
314 void strongDisconnect (void delegate (Args
) dg
) @trusted {
315 if (dg
!is null) mImpl
.removeSlot(null, cast(void delegate ())dg
);
319 * Disconnect a free function.
322 * fn = The function to be disconnected.
324 void disconnect (void function (Args
) fn
) @trusted {
326 import std
.functional
: toDelegate
;
327 auto dg
= toDelegate(fn
);
328 mImpl
.removeSlot(null, cast(void delegate ())dg
);
335 * string mixin for creating a signal.
337 * If you found the above:
339 ref RestrictedSignal!(string, int) valueChanged () { return valueChangedSg.restricted; }
340 private Signal!(string, int) valueChangedSg;
342 a bit tedious, but still want to restrict the use of emit, you can use this
343 string mixin. The following would result in exactly the same code:
345 mixin(signal!(string, int)("valueChanged"));
347 * Additional flexibility is provided by the protection parameter,
348 * where you can change the protection of sgValueChanged to protected
352 * name = How the signal should be named. The ref returning function
353 * will be named like this, the actual struct instance will have an
354 * underscore prefixed.
356 * protection = Specifies how the full functionality (emit) of the
357 * signal should be protected. Default is private. If
358 * Protection.None is given, private is used for the Signal member
359 * variable and the ref returning accessor method will return a
360 * Signal instead of a RestrictedSignal. The protection of the
361 * accessor method is specified by the surrounding protection scope:
363 * public: // Everyone can access mysig now:
364 * // Result of mixin(signal!int("mysig", Protection.None))
365 * Signal!int* mysig () { return mysigSg; }
366 * private Signal!int mysigSg;
369 string
signal(Args
...) (string name
, Protection protection
=Protection
.Private
) @trusted { // trusted necessary because of to!string
370 static string
pnorm() (string n
) => ""~cast(char)(n
[0]+32)~n
[1..$];
372 import std
.conv
: to
;
374 string argList
= "(";
375 foreach (/*auto*/ arg
; Args
) {
376 import std
.traits
: fullyQualifiedName
;
377 argList
~= fullyQualifiedName
!(arg
)~", ";
379 if (argList
.length
> "(".length
) argList
= argList
[0..$-2];
382 string fieldName
= name
~"Sg";
384 string output
= (protection
== Protection
.None ?
"private" : pnorm(to
!string(protection
)))~
385 " Signal!"~argList
~" "~fieldName
~";\n";
386 if (protection
== Protection
.None
) {
387 output
~= "ref Signal!"~argList
~" "~name
~" () { return "~fieldName
~"; }\n";
389 output
~= "ref RestrictedSignal!"~argList
~" "~name
~" () { return "~fieldName
~".restricted; }\n";
396 * Protection to use for the signal string mixin.
399 None
, /// No protection at all, the wrapping function will return a ref Signal instead of a ref RestrictedSignal
400 Private
, /// The Signal member variable will be private.
401 Protected
, /// The signal member variable will be protected.
402 Package
/// The signal member variable will have package protection.
406 private struct SignalImpl
{
413 * Unlike the old implementations, it would now be theoretically
414 * possible to copy a signal. Even different semantics are
415 * possible. But none of the possible semantics are what the user
416 * intended in all cases, so I believe it is still the safer
417 * choice to simply disallow copying.
419 @disable this (this);
421 @disable void opAssign (SignalImpl other
);
424 foreach (ref slot
; mSlots
.slots
) {
427 errwritefln
!"Destruction, removing some slot(%08X, weakref: %08X), signal: %08X"(&slot
, &slot
.mObj
, &this);
429 slot
.reset(); // This is needed because ATM the GC won't trigger struct
430 // destructors to be run when within a GC managed array.
434 void emit(Args
...) (Args args
) {
436 if (!mSlots
.emitInProgress
) {
437 mSlots
.emitInProgress
= true;
438 scope (exit
) mSlots
.emitInProgress
= false;
442 doEmit(0, emptyCount
, args
);
443 if (emptyCount
> 0) {
444 mSlots
.slots
= mSlots
.slots
[0..$-emptyCount
];
445 mSlots
.slots
.assumeSafeAppend();
449 void addSlot (Object obj
, void delegate () dg
) {
450 auto oldSlots
= mSlots
.slots
;
451 if (oldSlots
.capacity
<= oldSlots
.length
) {
452 auto buf
= new SlotImpl
[oldSlots
.length
+1]; // TODO: This growing strategy might be inefficient.
453 foreach (immutable i
, ref slot
; oldSlots
) buf
[i
].moveFrom(slot
);
456 oldSlots
.length
= oldSlots
.length
+1;
458 oldSlots
[$-1].construct(obj
, dg
);
459 mSlots
.slots
= oldSlots
;
462 void removeSlot (Object obj
, void delegate () dg
) => removeSlot((ref SlotImpl item
) => item
.wasConstructedFrom(obj
, dg
));
463 void removeSlot (Object obj
) => removeSlot((ref SlotImpl item
) => item
.obj
is obj
);
465 /// Little helper functions:
468 * Find and make invalid any slot for which isRemoved returns true.
470 void removeSlot (bool delegate (ref SlotImpl
) isRemoved
) {
471 if (mSlots
.emitInProgress
) {
472 foreach (ref slot
; mSlots
.slots
) if (isRemoved(slot
)) slot
.reset();
474 // It is save to do immediate cleanup:
476 auto mslots
= mSlots
.slots
;
477 foreach (int i
, ref slot
; mslots
) {
478 // We are retrieving obj twice which is quite expensive because of GC lock:
479 if (!slot
.isValid ||
isRemoved(slot
)) {
482 } else if (emptyCount
) {
483 mslots
[i
-emptyCount
].moveFrom(slot
);
486 if (emptyCount
> 0) {
487 mslots
= mslots
[0..$-emptyCount
];
488 mslots
.assumeSafeAppend();
489 mSlots
.slots
= mslots
;
495 * Helper method to allow all slots being called even in case of an exception.
496 * All exceptions that occur will be chained.
497 * Any invalid slots (GC collected or removed) will be dropped.
499 void doEmit(Args
...) (int offset
, ref int emptyCount
, Args args
) {
501 auto myslots
= mSlots
.slots
;
502 scope (exit
) if (i
+1 < myslots
.length
) doEmit(i
+1, emptyCount
, args
); // Carry on.
503 if (emptyCount
== -1) {
504 for (; i
< myslots
.length
; ++i
) {
506 myslots
= mSlots
.slots
; // Refresh because addSlot might have been called.
509 for (; i
< myslots
.length
; ++i
) {
510 bool result
= myslots
[i
](args
);
511 myslots
= mSlots
.slots
; // Refresh because addSlot might have been called.
514 } else if (emptyCount
> 0) {
515 myslots
[i
-emptyCount
].reset();
516 myslots
[i
-emptyCount
].moveFrom(myslots
[i
]);
524 // Simple convenience struct for signal implementation.
525 // Its is inherently unsafe. It is not a template so SignalImpl does
526 // not need to be one.
527 private struct SlotImpl
{
533 enum directPtrFlag
= cast(void*)(~0);
534 enum HasObjectFlag
= 1uL<<(sptrdiff
.sizeof
*8-1);
537 @disable this (this);
538 @disable void opAssign (SlotImpl other
);
540 /// Pass null for o if you have a strong ref delegate.
541 /// dg.funcptr must not point to heap memory.
542 void construct (Object o
, void delegate () dg
)
543 in { assert(this is SlotImpl
.init
); }
545 import core
.memory
: GC
;
548 mFuncPtr
= dg
.funcptr
;
549 // as high addresses are reserved for kernel almost everywhere,
550 // i'll use highest bit for keeping "hasObject" flag instead of lowest bit
551 if ((cast(usize
)mFuncPtr
)&HasObjectFlag
) assert(0, "iv.signals internal error 001");
552 assert(GC
.addrOf(mFuncPtr
) is null, "Your function is implemented on the heap? Such dirty tricks are not supported with iv.signal!");
554 if (mDataPtr
is cast(void*)o
) mDataPtr
= directPtrFlag
;
560 * Check whether this slot was constructed from object o and delegate dg.
562 bool wasConstructedFrom (Object o
, void delegate () dg
) {
563 if (o
&& dg
.ptr
is cast(void*)o
) {
564 return (obj
is o
&& mDataPtr
is directPtrFlag
&& funcPtr
is dg
.funcptr
);
566 return (obj
is o
&& mDataPtr
is dg
.ptr
&& funcPtr
is dg
.funcptr
);
571 * Implement proper explicit move.
573 void moveFrom (ref SlotImpl other
)
574 in { assert(this is SlotImpl
.init
); }
578 mDataPtr
= other
.mDataPtr
;
579 mFuncPtr
= other
.mFuncPtr
;
580 other
.reset(); // Destroy original!
583 @property Object
obj () => mObj
.obj
;
586 * Whether or not mObj should contain a valid object. (We have a weak connection)
588 @property bool hasObject () const => (cast(usize
)mFuncPtr
&HasObjectFlag
) != 0;
591 * Check whether this is a valid slot.
593 * Meaning opCall will call something and return true;
595 @property bool isValid () => funcPtr
&& (!hasObject || obj
!is null);
600 * Returns: True if the call was successful (the slot was valid).
602 bool opCall(Args
...) (Args args
) {
604 void* o_addr
= cast(void*)(o
);
605 if (!funcPtr ||
(hasObject
&& !o_addr
)) return false;
606 if (mDataPtr
is directPtrFlag ||
!hasObject
) {
607 void delegate (Args
) mdg
;
608 mdg
.funcptr
= cast(void function(Args
))funcPtr
;
609 assert((hasObject
&& mDataPtr
is directPtrFlag
) ||
(!hasObject
&& mDataPtr
!is directPtrFlag
));
610 mdg
.ptr
= (hasObject ? o_addr
: mDataPtr
);
613 void delegate (Object
, Args
) mdg
;
615 mdg
.funcptr
= cast(void function(Object
, Args
))funcPtr
;
622 * Reset this instance to its initial value.
625 mFuncPtr
= SlotImpl
.init
.mFuncPtr
;
626 mDataPtr
= SlotImpl
.init
.mDataPtr
;
631 @property void* funcPtr () const => cast(void*)(cast(usize
)mFuncPtr
&~HasObjectFlag
);
632 @property void hasObject (bool yes
) {
634 mFuncPtr
= cast(void*)(cast(usize
)mFuncPtr|HasObjectFlag
);
636 mFuncPtr
= cast(void*)(cast(usize
)mFuncPtr
&~HasObjectFlag
);
642 // Provides a way of holding a reference to an object, without the GC seeing it.
643 private struct WeakRef
{
645 shared(InvisibleAddress
) mObj
;
649 * As struct must be relocatable, it is not even possible to
650 * provide proper copy support for WeakRef. rt_attachDisposeEvent
651 * is used for registering unhook. D's move semantics assume
652 * relocatable objects, which results in this(this) being called
653 * for one instance and the destructor for another, thus the wrong
654 * handlers are deregistered. D's assumption of relocatable
655 * objects is not matched, so move() for example will still simply
656 * swap contents of two structs, resulting in the wrong unhook
657 * delegates being unregistered.
659 * Unfortunately the runtime still blindly copies WeakRefs if they
660 * are in a dynamic array and reallocation is needed. This case
661 * has to be handled separately.
663 @disable this (this);
664 @disable void opAssign (WeakRef other
);
668 void construct (Object o
)
669 in { assert(this is WeakRef
.init
); }
671 debug(signal
) createdThis
= &this;
672 debug(signal
) { import iv
.writer
; writefln
!"WeakRef.construct for %08X and object: %08X"(&this, cast(void*)o
); }
674 mObj
.construct(cast(void*)o
);
675 rt_attachDisposeEvent(o
, &unhook
);
678 @property Object
obj () => cast(Object
)mObj
.address
;
681 * Reset this instance to its intial value.
685 debug(signal
) { import iv
.writer
; writefln
!"WeakRef.reset for %08X and object: %08x"(&this, cast(void*)o
); }
686 if (o
) rt_detachDisposeEvent(o
, &unhook
);
687 unhook(o
); // unhook has to be done unconditionally, because in case the GC
688 // kicked in during toggleVisibility(), obj would contain -1
689 // so the assertion of SlotImpl.moveFrom would fail.
690 debug(signal
) createdThis
= null;
696 import std
.conv
: text
;
697 assert(createdThis
is null ||
&this is createdThis
,
698 text("We changed address! This should really not happen! Orig address: ",
699 cast(void*)createdThis
, " new address: ", cast(void*)&this));
701 WeakRef
* createdThis
;
704 void unhook (Object o
) => mObj
.reset();
708 // Do all the dirty stuff, WeakRef is only a thin wrapper completing
709 // the functionality by means of rt_ hooks.
710 private shared struct InvisibleAddress
{
711 debug(signal
) string
toString () {
712 import std
.conv
: text
;
713 return text(address
);
717 /// Initialize with o, state is set to invisible immediately.
718 /// No precautions regarding thread safety are necessary because
719 /// obviously a live reference exists.
720 void construct (void* o
) @nogc {
721 mAddr
= makeInvisible(cast(usize
)o
);
724 void reset () @nogc {
725 import core
.atomic
: atomicStore
;
726 atomicStore(mAddr
, 0L);
729 @property void* address () {
730 import core
.atomic
: atomicLoad
;
731 import core
.memory
: GC
;
733 scope (exit
) makeInvisible();
734 GC
.addrOf(cast(void*)atomicLoad(mAddr
)); // Just a dummy call to the GC
735 // in order to wait for any possible running
736 // collection to complete (have unhook called).
737 auto buf
= atomicLoad(mAddr
);
738 if (isNull(buf
)) return null;
739 assert(isVisible(buf
));
740 return cast(void*)buf
;
747 void makeVisible () {
748 import core
.atomic
: cas
;
751 import core
.atomic
: atomicLoad
;
752 buf
= atomicLoad(mAddr
);
753 wbuf
= makeVisible(buf
);
754 } while (!cas(&mAddr
, buf
, wbuf
));
757 void makeInvisible () {
758 import core
.atomic
: cas
;
761 import core
.atomic
: atomicLoad
;
762 buf
= atomicLoad(mAddr
);
763 wbuf
= makeInvisible(buf
);
764 } while (!cas(&mAddr
, buf
, wbuf
));
768 static ulong makeVisible() (ulong addr
) => ~addr
;
769 static ulong makeInvisible() (ulong addr
) => ~addr
;
770 static bool isVisible() (ulong addr
) => !(addr
&(1uL<<(sptrdiff
.sizeof
*8-1)));
771 static bool isNull() (ulong addr
) => (addr
== 0 || addr
== ~0);
773 static ulong makeVisible() (ulong addr
) {
775 immutable addrHigh = (addr>>32)&0xffff;
776 immutable addrLow = addr&0xffff;
777 return (addrHigh<<16)|addrLow;
779 return (addr
&0xffff)|
((addr
>>16)&0xffff0000u
);
781 static ulong makeInvisible() (ulong addr
) {
783 immutable addrHigh = ((addr>>16)&0x0000ffff)|0xffff0000;
784 immutable addrLow = (addr&0x0000ffff)|0xffff0000;
785 return (cast(ulong)addrHigh<<32)|addrLow;
789 ((addr
&0xffff0000u
)<<16)|
790 0xffff0000_ffff
0000uL;
792 static bool isVisible() (ulong addr
) => !((addr
>>32)&0xffffffff);
793 static bool isNull() (ulong addr
) => (addr
== 0 || addr
== ((0xffff0000L
<<32)|
0xffff0000));
799 * Provides a way of storing flags in unused parts of a typical D array.
801 * By unused I mean the highest bits of the length.
802 * (We don't need to support 4 billion slots per signal with int
803 * or 10^19 if length gets changed to 64 bits.)
805 private struct SlotArray
{
810 bool, "", lengthType
.sizeof
*8-1,
811 bool, "emitInProgress", 1
818 // Choose uint for now, this saves 4 bytes on 64 bits.
819 alias uint lengthType
;
820 import std
.bitmanip
: bitfields
;
821 enum reservedBitsCount
= 3;
822 enum maxSlotCount
= lengthType
.max
>>reservedBitsCount
;
823 @property SlotImpl
[] slots() => mPtr
[0..length
];
824 @property void slots (SlotImpl
[] newSlots
) {
827 import std
.conv
: text
;
828 assert(newSlots
.length
<= maxSlotCount
, text("Maximum slots per signal exceeded: ", newSlots
.length
, "/", maxSlotCount
));
830 mBLength
.length
&= ~maxSlotCount
;
831 mBLength
.length |
= newSlots
.length
;
833 @property usize
length () const => mBLength
.length
&maxSlotCount
;
834 @property bool emitInProgress() const => mBLength
.emitInProgress
;
835 @property void emitInProgress (bool val
) => mBLength
.emitInProgress
= val
;
838 version(unittest_signal
)
841 auto tmp
= new SlotImpl
[10];
843 assert(arr
.length
== 10);
844 assert(!arr
.emitInProgress
);
845 arr
.emitInProgress
= true;
846 assert(arr
.emitInProgress
);
847 assert(arr
.length
== 10);
848 assert(arr
.slots
is tmp
);
850 assert(arr
.emitInProgress
);
851 assert(arr
.length
== 10);
852 assert(arr
.slots
is tmp
);
853 debug(signal
) { import iv
.writer
; writeln("Slot array tests passed!"); }
856 version(unittest_signal
)
858 // Check that above example really works ...
859 import std
.functional
;
860 debug(signal
) import iv
.writer
;
865 mixin(signal
!(string
, int)("valueChanged"));
866 //pragma(msg, signal!(string, int)("valueChanged"));
868 @property int value () => mValue
;
869 @property int value (int v
) {
872 // call all the connected slots with the two parameters
873 valueChangedSg
.emit("setting new value", v
);
881 void watch (string msg
, int i
) {
882 debug(signal
) writefln
!"Observed msg '%s' and value %s"(msg
, i
);
886 static void watch (string msg
, int i
) {
887 debug(signal
) writefln
!"Globally observed msg '%s' and value %s"(msg
, i
);
890 auto a
= new MyObject
;
891 Observer o
= new Observer
;
893 a
.value
= 3; // should not call o.watch()
894 a
.valueChanged
.connect
!"watch"(o
); // o.watch is the slot
895 a
.value
= 4; // should call o.watch()
896 a
.valueChanged
.disconnect
!"watch"(o
); // o.watch is no longer a slot
897 a
.value
= 5; // so should not call o.watch()
898 a
.valueChanged
.connect
!"watch"(o
); // connect again
899 // Do some fancy stuff:
900 a
.valueChanged
.connect
!Observer(o
, (obj
, msg
, i
) => obj
.watch("Some other text I made up", i
+1));
901 a
.valueChanged
.strongConnect(toDelegate(&watch
));
902 a
.value
= 6; // should call o.watch()
903 destroy(o
); // destroying o should automatically disconnect it
904 a
.value
= 7; // should not call o.watch()
907 version(unittest_signal
)
909 debug(signal
) import iv
.writer
;
912 void watch (string msg
, int i
) {
913 //debug(signal) writeln("Observed msg '", msg, "' and value ", i);
921 class SimpleObserver
{
922 void watchOnlyInt (int i
) => captured_value
= i
;
931 @property int value() => mValue
;
932 @property int value (int v
) {
935 extendedSigSg
.emit("setting new value", v
);
941 mixin(signal
!(string
, int)("extendedSig"));
942 //pragma(msg, signal!(string, int)("extendedSig"));
943 //Signal!(int) simpleSig;
947 Observer o
= new Observer
;
948 SimpleObserver so
= new SimpleObserver
;
949 // check initial condition
950 assert(o
.captured_value
== 0);
951 assert(o
.captured_msg
== "");
953 // set a value while no observation is in place
955 assert(o
.captured_value
== 0);
956 assert(o
.captured_msg
== "");
958 // connect the watcher and trigger it
959 a
.extendedSig
.connect
!"watch"(o
);
961 //debug(signal) { writeln("o.captured_value=", o.captured_value, " (must be 4)"); }
962 assert(o
.captured_value
== 4);
963 assert(o
.captured_msg
== "setting new value");
965 // disconnect the watcher and make sure it doesn't trigger
966 a
.extendedSig
.disconnect
!"watch"(o
);
968 assert(o
.captured_value
== 4);
969 assert(o
.captured_msg
== "setting new value");
970 //a.extendedSig.connect!Observer(o, (obj, msg, i) { obj.watch("Hahah", i); });
971 a
.extendedSig
.connect
!Observer(o
, (obj
, msg
, i
) => obj
.watch("Hahah", i
) );
974 debug(signal
) errwriteln("After asignment!");
975 //debug(signal) { writeln("o.captured_value=", o.captured_value, " (must be 7)"); }
976 assert(o
.captured_value
== 7);
977 assert(o
.captured_msg
== "Hahah");
978 a
.extendedSig
.disconnect(o
); // Simply disconnect o, otherwise we would have to store the lamda somewhere if we want to disconnect later on.
979 // reconnect the watcher and make sure it triggers
980 a
.extendedSig
.connect
!"watch"(o
);
982 assert(o
.captured_value
== 6);
983 assert(o
.captured_msg
== "setting new value");
985 // destroy the underlying object and make sure it doesn't cause
986 // a crash or other problems
987 debug(signal
) errwriteln("Disposing");
989 debug(signal
) errwriteln("Disposed");
993 version(unittest_signal
)
1000 void watchInt (string
str, int i
) {
1005 void watchLong (string
str, long l
) {
1012 @property void value1 (int v
) => s1Sg
.emit("str1", v
);
1013 @property void value2 (int v
) => s2Sg
.emit("str2", v
);
1014 @property void value3 (long v
) => s3Sg
.emit("str3", v
);
1016 mixin(signal
!(string
, int) ("s1"));
1017 mixin(signal
!(string
, int) ("s2"));
1018 mixin(signal
!(string
, long)("s3"));
1021 void test(T
) (T a
) {
1022 auto o1
= new Observer
;
1023 auto o2
= new Observer
;
1024 auto o3
= new Observer
;
1026 // connect the watcher and trigger it
1027 a
.s1
.connect
!"watchInt"(o1
);
1028 a
.s2
.connect
!"watchInt"(o2
);
1029 a
.s3
.connect
!"watchLong"(o3
);
1031 assert(!o1
.i
&& !o1
.l
&& !o1
.str.length
);
1032 assert(!o2
.i
&& !o2
.l
&& !o2
.str.length
);
1033 assert(!o3
.i
&& !o3
.l
&& !o3
.str.length
);
1036 assert(o1
.i
== 11 && !o1
.l
&& o1
.str == "str1");
1037 assert(!o2
.i
&& !o2
.l
&& !o2
.str.length
);
1038 assert(!o3
.i
&& !o3
.l
&& !o3
.str.length
);
1039 o1
.i
= -11; o1
.str = "x1";
1042 assert(o1
.i
== -11 && !o1
.l
&& o1
.str == "x1");
1043 assert(o2
.i
== 12 && !o2
.l
&& o2
.str == "str2");
1044 assert(!o3
.i
&& !o3
.l
&& !o3
.str.length
);
1045 o2
.i
= -12; o2
.str = "x2";
1048 assert(o1
.i
== -11 && !o1
.l
&& o1
.str == "x1");
1049 assert(o2
.i
== -12 && !o1
.l
&& o2
.str == "x2");
1050 assert(!o3
.i
&& o3
.l
== 13 && o3
.str == "str3");
1051 o3
.l
= -13; o3
.str = "x3";
1053 // disconnect the watchers and make sure it doesn't trigger
1054 a
.s1
.disconnect
!"watchInt"(o1
);
1055 a
.s2
.disconnect
!"watchInt"(o2
);
1056 a
.s3
.disconnect
!"watchLong"(o3
);
1061 assert(o1
.i
== -11 && !o1
.l
&& o1
.str == "x1");
1062 assert(o2
.i
== -12 && !o1
.l
&& o2
.str == "x2");
1063 assert(!o3
.i
&& o3
.l
== -13 && o3
.str == "x3");
1065 // reconnect the watcher and make sure it triggers
1066 a
.s1
.connect
!"watchInt"(o1
);
1067 a
.s2
.connect
!"watchInt"(o2
);
1068 a
.s3
.connect
!"watchLong"(o3
);
1073 assert(o1
.i
== 31 && !o1
.l
&& o1
.str == "str1");
1074 assert(o2
.i
== 32 && !o1
.l
&& o2
.str == "str2");
1075 assert(!o3
.i
&& o3
.l
== 33 && o3
.str == "str3");
1077 // destroy observers
1088 class BarDerived
: Bar
{
1089 @property void value4 (int v
) => s4Sg
.emit("str4", v
);
1090 @property void value5 (int v
) => s5Sg
.emit("str5", v
);
1091 @property void value6 (long v
) => s6Sg
.emit("str6", v
);
1093 mixin(signal
!(string
, int) ("s4"));
1094 mixin(signal
!(string
, int) ("s5"));
1095 mixin(signal
!(string
, long)("s6"));
1098 auto a
= new BarDerived
;
1103 auto o4
= new Observer
;
1104 auto o5
= new Observer
;
1105 auto o6
= new Observer
;
1107 // connect the watcher and trigger it
1108 a
.s4
.connect
!"watchInt"(o4
);
1109 a
.s5
.connect
!"watchInt"(o5
);
1110 a
.s6
.connect
!"watchLong"(o6
);
1112 assert(!o4
.i
&& !o4
.l
&& !o4
.str.length
);
1113 assert(!o5
.i
&& !o5
.l
&& !o5
.str.length
);
1114 assert(!o6
.i
&& !o6
.l
&& !o6
.str.length
);
1117 assert(o4
.i
== 44 && !o4
.l
&& o4
.str == "str4");
1118 assert(!o5
.i
&& !o5
.l
&& !o5
.str.length
);
1119 assert(!o6
.i
&& !o6
.l
&& !o6
.str.length
);
1120 o4
.i
= -44; o4
.str = "x4";
1123 assert(o4
.i
== -44 && !o4
.l
&& o4
.str == "x4");
1124 assert(o5
.i
== 45 && !o5
.l
&& o5
.str == "str5");
1125 assert(!o6
.i
&& !o6
.l
&& !o6
.str.length
);
1126 o5
.i
= -45; o5
.str = "x5";
1129 assert(o4
.i
== -44 && !o4
.l
&& o4
.str == "x4");
1130 assert(o5
.i
== -45 && !o4
.l
&& o5
.str == "x5");
1131 assert(!o6
.i
&& o6
.l
== 46 && o6
.str == "str6");
1132 o6
.l
= -46; o6
.str = "x6";
1134 // disconnect the watchers and make sure it doesn't trigger
1135 a
.s4
.disconnect
!"watchInt"(o4
);
1136 a
.s5
.disconnect
!"watchInt"(o5
);
1137 a
.s6
.disconnect
!"watchLong"(o6
);
1142 assert(o4
.i
== -44 && !o4
.l
&& o4
.str == "x4");
1143 assert(o5
.i
== -45 && !o4
.l
&& o5
.str == "x5");
1144 assert(!o6
.i
&& o6
.l
== -46 && o6
.str == "x6");
1146 // reconnect the watcher and make sure it triggers
1147 a
.s4
.connect
!"watchInt"(o4
);
1148 a
.s5
.connect
!"watchInt"(o5
);
1149 a
.s6
.connect
!"watchLong"(o6
);
1154 assert(o4
.i
== 64 && !o4
.l
&& o4
.str == "str4");
1155 assert(o5
.i
== 65 && !o4
.l
&& o5
.str == "str5");
1156 assert(!o6
.i
&& o6
.l
== 66 && o6
.str == "str6");
1158 // destroy observers
1167 version(unittest_signal
)
1177 mixin(signal
!(int)("signal"));
1178 @property int value () => mValue
;
1179 ref Property
opAssign (int val
) {
1180 debug(signal
) writefln
!"Assigning int to property with signal: %08X"(&this);
1187 void observe (int val
) {
1188 debug(signal
) writeln("observe: Wow! The value changed: ", val
);
1192 void observe (int val
) {
1193 debug(signal
) writeln("Observer: Wow! The value changed: ", val
);
1194 debug(signal
) writeln("Really! I must know I am an observer (old value was: ", observed
, ")!");
1202 void delegate (int) dg
= (val
) => observe(val
);
1203 prop
.signal
.strongConnect(dg
);
1204 assert(prop
.signal
.mImpl
.mSlots
.length
==1);
1205 Observer o
= new Observer
;
1206 prop
.signal
.connect
!"observe"(o
);
1207 assert(prop
.signal
.mImpl
.mSlots
.length
==2);
1208 debug(signal
) writeln("Triggering on original property with value 8 ...");
1211 assert(o
.observed
==prop
);
1214 version(unittest_signal
)
1216 debug(signal
) import iv
.writer
;
1219 void testfunc (int id
) { throw new Exception(to
!string(id
)); }
1220 s1
.strongConnect(() => testfunc(0));
1221 s1
.strongConnect(() => testfunc(1));
1222 s1
.strongConnect(() => testfunc(2));
1225 } catch (Exception e
) {
1229 debug(signal
) errwriteln("*** Caught exception (this is fine); i=", i
, "; msg=", t
.msg
);
1230 version(DigitalMars
) assert(to
!int(t
.msg
) == i
);
1234 debug(signal
) errwriteln("+++");
1235 version(DigitalMars
) assert(i
== 3);
1239 version(unittest_signal
)
1242 mixin(signal
!(string
, int)("s1"));
1246 mixin(signal
!(string
, int)("s2"));
1250 version(unittest_signal
)
1253 mixin(signal
!int("a", Protection
.Package
));
1254 mixin(signal
!int("ap", Protection
.Private
));
1255 mixin(signal
!int("app", Protection
.Protected
));
1256 mixin(signal
!int("an", Protection
.None
));
1260 pragma(msg, signal!int("a", Protection.Package));
1261 pragma(msg, signal!int("a", Protection.Protected));
1262 pragma(msg, signal!int("a", Protection.Private));
1263 pragma(msg, signal!int("a", Protection.None));
1266 static assert(signal
!int("a", Protection
.Package
) == "package Signal!(int) aSg;\nref RestrictedSignal!(int) a () { return aSg.restricted; }\n");
1267 static assert(signal
!int("a", Protection
.Protected
) == "protected Signal!(int) aSg;\nref RestrictedSignal!(int) a () { return aSg.restricted; }\n");
1268 static assert(signal
!int("a", Protection
.Private
) == "private Signal!(int) aSg;\nref RestrictedSignal!(int) a () { return aSg.restricted; }\n");
1269 static assert(signal
!int("a", Protection
.None
) == "private Signal!(int) aSg;\nref Signal!(int) a () { return aSg; }\n");
1272 pragma(msg
, signal
!int("a", Protection
.Package
));
1273 pragma(msg
, signal
!(int, string
, int[int])("a", Protection
.Private
));
1274 pragma(msg
, signal
!(int, string
, int[int], float, double)("a", Protection
.Protected
));
1275 pragma(msg
, signal
!(int, string
, int[int], float, double, long)("a", Protection
.None
));
1279 // Test nested emit/removal/addition ...
1280 version(unittest_signal
)
1285 int slot3called
= 0;
1286 int slot3shouldcalled
= 0;
1289 if (!doEmit
) sig
.emit();
1291 void slot3 () => ++slot3called
;
1293 debug(signal
) { import iv
.writer
; writefln
!"CALLED: %s, should called: %s"(slot3called
, slot3shouldcalled
); }
1294 assert(slot3called
== slot3shouldcalled
);
1295 if (++counter
< 100) slot3shouldcalled
+= counter
;
1296 if (counter
< 100) sig
.strongConnect(&slot3
);
1299 if (counter
== 100) sig
.strongDisconnect(&slot3
); // All connections dropped
1301 sig
.strongConnect(&slot1
);
1302 sig
.strongConnect(&slot2
);
1303 sig
.strongConnect(&slot4
);
1304 foreach (; 0..1000) sig
.emit();
1307 writeln("slot3called: ", slot3called
);
1311 version(unittest_signal
)
1313 import iv
.writer
; errwriteln("tests passed!");
1317 // parse signal definition, return mixin string
1318 public template Signals(string sstr
) {
1319 static string
doIt() (string sstr
) {
1320 usize
skipSpaces() (usize pos
) {
1321 while (pos
< sstr
.length
) {
1322 if (pos
+1 < sstr
.length
&& sstr
[pos
] == '/') {
1323 if (sstr
[pos
+1] == '/') {
1324 while (pos
< sstr
.length
&& sstr
[pos
] != '\n') ++pos
;
1325 } else if (sstr
[pos
+1] == '*' || sstr
[pos
+1] == '+') {
1326 //FIXME: "+" should nest
1327 char ech
= sstr
[pos
+1];
1329 while (pos
< sstr
.length
-1) {
1330 if (sstr
[pos
+1] == '/' && sstr
[pos
] == ech
) { ++pos
; break; }
1337 } else if (sstr
[pos
] <= ' ') {
1347 while (sstr
.length
) {
1350 usize pos
= skipSpaces(0);
1351 if (pos
>= sstr
.length
) break;
1354 while (end
< sstr
.length
) {
1355 if (sstr
[end
] <= ' ' || sstr
[end
] == '(' || sstr
[end
] == '/') break;
1358 string id
= sstr
[pos
..end
];
1359 end
= skipSpaces(end
);
1360 if (end
>= sstr
.length || sstr
[end
] != '(') assert(0, "Signals: '(' expected");
1361 sstr
= sstr
[end
+1..$];
1362 //assert(0, "*** "~sstr);
1363 res
~= "mixin(signal!(";
1365 while (sstr
.length
) {
1366 pos
= skipSpaces(0);
1367 if (pos
>= sstr
.length
) assert(0, "Signals: ')' expected");
1368 if (sstr
[pos
] == ')') {
1369 pos
= skipSpaces(pos
+1);
1370 sstr
= sstr
[pos
..$];
1375 //usize lastSpace = usize.max;
1378 while (end
< sstr
.length
) {
1379 if (sstr
[end
] == '(') {
1381 } else if (sstr
[end
] == ')') {
1382 if (bcnt
-- == 0) break;
1383 } else if (sstr
[end
] == ',') {
1384 if (bcnt
!= 0) assert(0, "Signals: unbalanced parens: "~sstr
[pos
..end
]);
1389 if (end
>= sstr
.length || end
== pos
) assert(0, "Signals: ')' expected");
1391 string def
= sstr
[pos
..end
];
1392 end
= skipSpaces(end
);
1393 if (sstr
[end
] == ',') ++end
;
1394 sstr
= sstr
[end
..$];
1395 // strip trailing spaces
1396 for (end
= def
.length
; end
> 0; --end
) if (def
[end
] > ' ') break;
1397 //if (end < def.length) def = def[0..end];
1398 // now cut out the last word
1400 while (end
> 0 && def
[end
] > ' ') --end
;
1402 // only one word, wtf?!
1403 assert(0, "Signals: argument name expected: "~def
);
1405 while (end
> 0 && def
[end
] <= ' ') --end
;
1406 res
~= def
[0..end
+1]~",";
1409 if (!sstr
.length || sstr
[0] != ';') assert(0, "Signals: ';' expected: "~sstr
);
1411 if (res
[$-1] == ',') res
= res
[0..$-1];
1412 res
~= ")(`"~id
~"`));\n";
1416 enum Signals
= doIt(sstr
);
1420 version(unittest_signal
)
1422 pragma(msg
, Signals
!q
{
1423 onBottomLineChange (uint id
, uint newln
);
1424 onWriteBytes (uint id
, const(char)[] buf
);
1434 public template AutoConnect(string srcobj
, T
) if (is(T
== class) ||
is(T
== struct)) {
1435 private import iv
.udas
;
1436 template doMember(MB
...) {
1437 static if (MB
.length
== 0) {
1439 } else static if (is(typeof(__traits(getMember
, T
, MB
[0])))) {
1440 static if (hasUDA
!(__traits(getMember
, T
, MB
[0]), slot
)) {
1441 //pragma(msg, MB[0]);
1442 static if (is(typeof(getUDA
!(__traits(getMember
, T
, MB
[0]), slot
))))
1443 enum slt
= getUDA
!(__traits(getMember
, T
, MB
[0]), slot
).signalName
;
1447 srcobj
~"."~(slt
.length ? slt
: MB
[0].stringof
[1..$-1])~
1448 ".connect!"~MB
[0].stringof
~"(this);\n"~
1449 doMember
!(MB
[1..$]);
1451 enum doMember
= doMember
!(MB
[1..$]);
1454 enum doMember
= doMember
!(MB
[1..$]);
1457 //private enum mems = __traits(T, getMembers);
1458 enum AutoConnect
= doMember
!(__traits(allMembers
, T
));
1461 // to allow calling `mixin(AutoConnect!("term", this));` from class/struct methods
1462 public template AutoConnect(string srcobj
, alias obj
) if (is(typeof(obj
) == class) ||
is(typeof(obj
) == struct)) {
1463 enum AutoConnect
= AutoConnect
!(srcobj
, typeof(obj
));
1467 version(unittest_signal
)
1470 @slot void onFuck () {}
1471 @slot("onShit") void crap () {}
1475 pragma(msg
, AutoConnect
!("term", A
));