flexlay2: respect maximum box size
[iv.d.git] / _obsolete_dont_use / signal.d
blob351072b9bdf5a7f0ab6a0922173da982c74696ac
1 /**
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*/;
31 import iv.alice;
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);
39 /**
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.
67 * Example:
68 ---
69 import iv.signal;
70 import std.stdio;
72 class MyObject {
73 private:
74 int mValue;
75 public:
76 // Public accessor method returning a RestrictedSignal, thus restricting
77 // the use of emit to this module. See the signal() string mixin below
78 // for a simpler way.
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) {
84 if (v != mValue) {
85 mValue = v;
86 // call all the connected slots with the two parameters
87 valueChangedSg.emit("setting new value", v);
89 return v;
93 class Observer {
94 // our slot
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);
102 void main () {
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:
121 * <pre>
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
127 * </pre>
129 struct Signal(Args...) {
130 private:
131 RestrictedSignal!(Args) mRestricted;
133 public:
134 alias restricted this;
137 * Emit the signal.
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
143 * others execution.
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
164 * own.
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...) {
178 private:
179 SignalImpl mImpl;
181 public:
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.
193 * Params:
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.
218 * Params:
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
224 * necessary.
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
243 * itself.
245 * Params:
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.
255 * Params:
256 * fn = The free function to be connected.
258 void connect (void function (Args) fn) @trusted {
259 if (fn !is null) {
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);}))
276 if (obj !is null) {
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.
321 * Params:
322 * fn = The function to be disconnected.
324 void disconnect (void function (Args) fn) @trusted {
325 if (fn !is null) {
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
349 * for example.
351 * Params:
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];
380 argList ~= ")";
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";
388 } else {
389 output ~= "ref RestrictedSignal!"~argList~" "~name~" () { return "~fieldName~".restricted; }\n";
391 return output;
396 * Protection to use for the signal string mixin.
398 enum Protection {
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 {
407 private:
408 SlotArray mSlots;
410 public:
412 * Forbid copying.
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);
420 /// Forbid copying
421 @disable void opAssign (SignalImpl other);
423 ~this () {
424 foreach (ref slot; mSlots.slots) {
425 debug(signal) {
426 import iv.writer;
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) {
435 int emptyCount = 0;
436 if (!mSlots.emitInProgress) {
437 mSlots.emitInProgress = true;
438 scope (exit) mSlots.emitInProgress = false;
439 } else {
440 emptyCount = -1;
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);
454 oldSlots = buf;
455 } else {
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();
473 } else {
474 // It is save to do immediate cleanup:
475 int emptyCount = 0;
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)) {
480 ++emptyCount;
481 slot.reset();
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) {
500 int i = offset;
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) {
505 myslots[i](args);
506 myslots = mSlots.slots; // Refresh because addSlot might have been called.
508 } else {
509 for (; i < myslots.length; ++i) {
510 bool result = myslots[i](args);
511 myslots = mSlots.slots; // Refresh because addSlot might have been called.
512 if (!result) {
513 ++emptyCount;
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 {
528 private:
529 void* mFuncPtr;
530 void* mDataPtr;
531 WeakRef mObj;
533 enum directPtrFlag = cast(void*)(~0);
534 enum HasObjectFlag = 1uL<<(sptrdiff.sizeof*8-1);
536 public:
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); }
544 body {
545 import core.memory : GC;
546 mObj.construct(o);
547 mDataPtr = dg.ptr;
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!");
553 if (o) {
554 if (mDataPtr is cast(void*)o) mDataPtr = directPtrFlag;
555 hasObject = true;
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);
565 } else {
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); }
575 body {
576 auto o = other.obj;
577 mObj.construct(o);
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);
598 * Call the slot.
600 * Returns: True if the call was successful (the slot was valid).
602 bool opCall(Args...) (Args args) {
603 auto o = obj;
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);
611 mdg(args);
612 } else {
613 void delegate (Object, Args) mdg;
614 mdg.ptr = mDataPtr;
615 mdg.funcptr = cast(void function(Object, Args))funcPtr;
616 mdg(o, args);
618 return true;
622 * Reset this instance to its initial value.
624 void reset () {
625 mFuncPtr = SlotImpl.init.mFuncPtr;
626 mDataPtr = SlotImpl.init.mDataPtr;
627 mObj.reset();
630 private:
631 @property void* funcPtr () const => cast(void*)(cast(usize)mFuncPtr&~HasObjectFlag);
632 @property void hasObject (bool yes) {
633 if (yes) {
634 mFuncPtr = cast(void*)(cast(usize)mFuncPtr|HasObjectFlag);
635 } else {
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 {
644 private:
645 shared(InvisibleAddress) mObj;
647 public:
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);
666 ~this () => reset();
668 void construct (Object o)
669 in { assert(this is WeakRef.init); }
670 body {
671 debug(signal) createdThis = &this;
672 debug(signal) { import iv.writer; writefln!"WeakRef.construct for %08X and object: %08X"(&this, cast(void*)o); }
673 if (!o) return;
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.
683 void reset () {
684 auto o = obj;
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;
693 private:
694 debug(signal) {
695 invariant() {
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);
716 nothrow:
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;
732 makeVisible();
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;
743 private:
744 @nogc:
745 ulong mAddr;
747 void makeVisible () {
748 import core.atomic : cas;
749 ulong buf, wbuf;
750 do {
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;
759 ulong buf, wbuf;
760 do {
761 import core.atomic : atomicLoad;
762 buf = atomicLoad(mAddr);
763 wbuf = makeInvisible(buf);
764 } while (!cas(&mAddr, buf, wbuf));
767 version(D_LP64) {
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);
772 } else {
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;
787 return
788 (addr&0xffff)|
789 ((addr&0xffff0000u)<<16)|
790 0xffff0000_ffff0000uL;
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 {
806 private:
807 SlotImpl* mPtr;
808 union BitsLength {
809 mixin(bitfields!(
810 bool, "", lengthType.sizeof*8-1,
811 bool, "emitInProgress", 1
813 lengthType length;
815 BitsLength mBLength;
817 public:
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) {
825 mPtr = newSlots.ptr;
826 version(assert) {
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)
839 unittest {
840 SlotArray arr;
841 auto tmp = new SlotImpl[10];
842 arr.slots = tmp;
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);
849 arr.slots = 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)
857 unittest {
858 // Check that above example really works ...
859 import std.functional;
860 debug(signal) import iv.writer;
861 class MyObject {
862 private:
863 int mValue;
864 public:
865 mixin(signal!(string, int)("valueChanged"));
866 //pragma(msg, signal!(string, int)("valueChanged"));
868 @property int value () => mValue;
869 @property int value (int v) {
870 if (v != mValue) {
871 mValue = v;
872 // call all the connected slots with the two parameters
873 valueChangedSg.emit("setting new value", v);
875 return v;
879 class Observer {
880 // our slot
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)
908 unittest {
909 debug(signal) import iv.writer;
911 class Observer {
912 void watch (string msg, int i) {
913 //debug(signal) writeln("Observed msg '", msg, "' and value ", i);
914 captured_value = i;
915 captured_msg = msg;
917 int captured_value;
918 string captured_msg;
921 class SimpleObserver {
922 void watchOnlyInt (int i) => captured_value = i;
923 int captured_value;
926 class Foo {
927 private:
928 int mValue;
930 public:
931 @property int value() => mValue;
932 @property int value (int v) {
933 if (v != mValue) {
934 mValue = v;
935 extendedSigSg.emit("setting new value", v);
936 //simpleSig.emit(v);
938 return v;
941 mixin(signal!(string, int)("extendedSig"));
942 //pragma(msg, signal!(string, int)("extendedSig"));
943 //Signal!(int) simpleSig;
946 Foo a = new Foo;
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
954 a.value = 3;
955 assert(o.captured_value == 0);
956 assert(o.captured_msg == "");
958 // connect the watcher and trigger it
959 a.extendedSig.connect!"watch"(o);
960 a.value = 4;
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);
967 a.value = 5;
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) );
973 a.value = 7;
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);
981 a.value = 6;
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");
988 destroy(o);
989 debug(signal) errwriteln("Disposed");
990 a.value = 7;
993 version(unittest_signal)
994 unittest {
995 class Observer {
996 int i;
997 long l;
998 string str;
1000 void watchInt (string str, int i) {
1001 this.str = str;
1002 this.i = i;
1005 void watchLong (string str, long l) {
1006 this.str = str;
1007 this.l = l;
1011 class Bar {
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);
1035 a.value1 = 11;
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";
1041 a.value2 = 12;
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";
1047 a.value3 = 13;
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);
1058 a.value1 = 21;
1059 a.value2 = 22;
1060 a.value3 = 23;
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);
1070 a.value1 = 31;
1071 a.value2 = 32;
1072 a.value3 = 33;
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
1078 destroy(o1);
1079 destroy(o2);
1080 destroy(o3);
1081 a.value1 = 41;
1082 a.value2 = 42;
1083 a.value3 = 43;
1086 test(new Bar);
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;
1100 test!Bar(a);
1101 test!BarDerived(a);
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);
1116 a.value4 = 44;
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";
1122 a.value5 = 45;
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";
1128 a.value6 = 46;
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);
1139 a.value4 = 54;
1140 a.value5 = 55;
1141 a.value6 = 56;
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);
1151 a.value4 = 64;
1152 a.value5 = 65;
1153 a.value6 = 66;
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
1159 destroy(o4);
1160 destroy(o5);
1161 destroy(o6);
1162 a.value4 = 44;
1163 a.value5 = 45;
1164 a.value6 = 46;
1167 version(unittest_signal)
1168 unittest {
1169 import iv.writer;
1171 struct Property {
1172 private:
1173 int mValue;
1175 public:
1176 alias value this;
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);
1181 mValue = val;
1182 signalSg.emit(val);
1183 return this;
1187 void observe (int val) {
1188 debug(signal) writeln("observe: Wow! The value changed: ", val);
1191 class Observer {
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, ")!");
1195 observed = val;
1196 ++count;
1198 int observed;
1199 int count;
1201 Property prop;
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 ...");
1209 prop=8;
1210 assert(o.count==1);
1211 assert(o.observed==prop);
1214 version(unittest_signal)
1215 unittest {
1216 debug(signal) import iv.writer;
1217 import std.conv;
1218 Signal!() s1;
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));
1223 try {
1224 s1.emit();
1225 } catch (Exception e) {
1226 Throwable t = e;
1227 int i = 0;
1228 while (t) {
1229 debug(signal) errwriteln("*** Caught exception (this is fine); i=", i, "; msg=", t.msg);
1230 version(DigitalMars) assert(to!int(t.msg) == i);
1231 t = t.next;
1232 ++i;
1234 debug(signal) errwriteln("+++");
1235 version(DigitalMars) assert(i == 3);
1239 version(unittest_signal)
1240 unittest {
1241 class A {
1242 mixin(signal!(string, int)("s1"));
1245 class B : A {
1246 mixin(signal!(string, int)("s2"));
1250 version(unittest_signal)
1251 unittest {
1252 struct Test {
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");
1271 debug(signal) {
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)
1281 unittest {
1282 Signal!() sig;
1283 bool doEmit = true;
1284 int counter = 0;
1285 int slot3called = 0;
1286 int slot3shouldcalled = 0;
1287 void slot1 () {
1288 doEmit = !doEmit;
1289 if (!doEmit) sig.emit();
1291 void slot3 () => ++slot3called;
1292 void slot2 () {
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);
1298 void slot4 () {
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();
1305 debug(signal) {
1306 import iv.writer;
1307 writeln("slot3called: ", slot3called);
1311 version(unittest_signal)
1312 unittest {
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];
1328 pos += 2;
1329 while (pos < sstr.length-1) {
1330 if (sstr[pos+1] == '/' && sstr[pos] == ech) { ++pos; break; }
1331 ++pos;
1333 ++pos;
1334 } else {
1335 break;
1337 } else if (sstr[pos] <= ' ') {
1338 ++pos;
1339 } else {
1340 break;
1343 return pos;
1346 string res;
1347 while (sstr.length) {
1348 // get signal name
1349 // skip spaces
1350 usize pos = skipSpaces(0);
1351 if (pos >= sstr.length) break;
1352 // skip id
1353 usize end = pos;
1354 while (end < sstr.length) {
1355 if (sstr[end] <= ' ' || sstr[end] == '(' || sstr[end] == '/') break;
1356 ++end;
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!(";
1364 // parse args
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..$];
1371 break;
1373 // find ')' or ','
1374 end = pos;
1375 //usize lastSpace = usize.max;
1376 usize bcnt = 0;
1377 //TODO: comments
1378 while (end < sstr.length) {
1379 if (sstr[end] == '(') {
1380 ++bcnt;
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]);
1385 break;
1387 ++end;
1389 if (end >= sstr.length || end == pos) assert(0, "Signals: ')' expected");
1390 // get definition
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
1399 usize xxend = end;
1400 while (end > 0 && def[end] > ' ') --end;
1401 if (end == 0) {
1402 // only one word, wtf?!
1403 assert(0, "Signals: argument name expected: "~def);
1404 } else {
1405 while (end > 0 && def[end] <= ' ') --end;
1406 res ~= def[0..end+1]~",";
1409 if (!sstr.length || sstr[0] != ';') assert(0, "Signals: ';' expected: "~sstr);
1410 sstr = sstr[1..$];
1411 if (res[$-1] == ',') res = res[0..$-1];
1412 res ~= ")(`"~id~"`));\n";
1414 return res;
1416 enum Signals = doIt(sstr);
1420 version(unittest_signal)
1421 unittest {
1422 pragma(msg, Signals!q{
1423 onBottomLineChange (uint id, uint newln);
1424 onWriteBytes (uint id, const(char)[] buf);
1429 struct slot {
1430 string signalName;
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) {
1438 enum doMember = "";
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;
1444 else
1445 enum slt = "";
1446 enum doMember =
1447 srcobj~"."~(slt.length ? slt : MB[0].stringof[1..$-1])~
1448 ".connect!"~MB[0].stringof~"(this);\n"~
1449 doMember!(MB[1..$]);
1450 } else {
1451 enum doMember = doMember!(MB[1..$]);
1453 } else {
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)
1468 unittest {
1469 static class A {
1470 @slot void onFuck () {}
1471 @slot("onShit") void crap () {}
1472 void piss () {};
1475 pragma(msg, AutoConnect!("term", A));