// Written in the D programming language.
/**
* Signals and Slots are an implementation of the $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
* Essentially, when a Signal is emitted, a list of connected Observers
* (called slots) are called.
*
* This implementation supersedes the former std.signals.
*
* Copyright: Copyright Robert Klotzner 2012 - 2013.
* License: Boost License 1.0.
* Authors: Robert Klotzner
*/
/* Copyright Robert Klotzner 2012 - 2013.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* Based on the original implementation written by Walter Bright. (std.signals)
* I shamelessly stole some ideas of: http://forum.dlang.org/thread/jjote0$1cql$1@digitalmars.com
* written by Alex Rønne Petersen.
*
* Also thanks to Denis Shelomovskij who made me aware of some
* deficiencies in the concurrent part of WeakRef.
*/
module stdx.signals;
import core.atomic;
import core.memory;
// Hook into the GC to get informed about object deletions.
private alias void delegate(Object) DisposeEvt;
private extern (C) void rt_attachDisposeEvent( Object obj, DisposeEvt evt );
private extern (C) void rt_detachDisposeEvent( Object obj, DisposeEvt evt );
/**
* string mixin for creating a signal.
*
* It creates a Signal instance named "_name", where name is given
* as first parameter with given protection and an accessor method
* with the current context protection named "name" returning either a
* ref RestrictedSignal or ref Signal depending on the given
* protection.
*
* Bugs:
* This mixin generator does not work with templated types right now because of:
* $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=10502, 10502)$(BR)
* You might want to use the Signal struct directly in this
* case. Ideally you write the code, the mixin would generate, manually
* to ensure an easy upgrade path when the above bug gets fixed:
---
* ref RestrictedSignal!(SomeTemplate!int) mysig() { return _mysig;}
* private Signal!(SomeTemplate!int) _mysig;
---
*
* Params:
* name = How the signal should be named. The ref returning function
* will be named like this, the actual struct instance will have an
* underscore prefixed.
*
* protection = Specifies how the full functionality (emit) of the
* signal should be protected. Default is private. If
* Protection.none is given, private is used for the Signal member
* variable and the ref returning accessor method will return a
* Signal instead of a RestrictedSignal. The protection of the
* accessor method is specified by the surrounding protection scope:
---
* public: // Everyone can access mysig now:
* // Result of mixin(signal!int("mysig", Protection.none))
* ref Signal!int mysig() { return _mysig;}
* private Signal!int _mysig;
---
*
* Example:
---
import std.stdio;
class MyObject
{
// Mixin signal named valueChanged, with default "private" protection.
// (Only MyObject is allowed to emit the signal)
mixin(signal!(string, int)("valueChanged"));
int value() @property { return _value; }
int value(int v) @property
{
if (v != _value)
{
_value = v;
// call all the connected slots with the two parameters
_valueChanged.emit("setting new value", v);
}
return v;
}
private:
int _value;
}
class Observer
{ // our slot
void watch(string msg, int i)
{
writefln("Observed msg '%s' and value %s", msg, i);
}
}
void watch(string msg, int i)
{
writefln("Globally observed msg '%s' and value %s", msg, i);
}
void main()
{
auto a = new MyObject;
Observer o = new Observer;
a.value = 3; // should not call o.watch()
a.valueChanged.connect!"watch"(o); // o.watch is the slot
a.value = 4; // should call o.watch()
a.valueChanged.disconnect!"watch"(o); // o.watch is no longer a slot
a.value = 5; // so should not call o.watch()
a.valueChanged.connect!"watch"(o); // connect again
// Do some fancy stuff:
a.valueChanged.connect!Observer(o, (obj, msg, i) => obj.watch("Some other text I made up", i+1));
a.valueChanged.strongConnect(&watch);
a.value = 6; // should call o.watch()
destroy(o); // destroying o should automatically disconnect it
a.value = 7; // should not call o.watch()
}
---
* which should print:
*
* Observed msg 'setting new value' and value 4
* Observed msg 'setting new value' and value 6
* Observed msg 'Some other text I made up' and value 7
* Globally observed msg 'setting new value' and value 6
* Globally observed msg 'setting new value' and value 7
*
*/
string signal(Args...)(string name, Protection protection=Protection.private_) @trusted // trusted necessary because of to!string
{
import std.conv;
string argList="(";
import std.traits : fullyQualifiedName;
foreach (arg; Args)
{
argList~=fullyQualifiedName!(arg)~", ";
}
if (argList.length>"(".length)
argList = argList[0 .. $-2];
argList ~= ")";
string output = (protection == Protection.none ? "private" : to!string(protection)[0..$-1]) ~
" Signal!" ~ argList ~ " _" ~ name ~ ";\n";
string rType = protection == Protection.none ? "Signal!" : "RestrictedSignal!";
output ~= "ref " ~ rType ~ argList ~ " " ~ name ~ "() { return _" ~ name ~ ";}\n";
return output;
}
/**
* Protection to use for the signal string mixin.
*/
enum Protection
{
none, /// No protection at all, the wrapping function will return a ref Signal instead of a ref RestrictedSignal
private_, /// The Signal member variable will be private.
protected_, /// The signal member variable will be protected.
package_ /// The signal member variable will have package protection.
}
/**
* Full signal implementation.
*
* It implements the emit function for all other functionality it has
* this aliased to RestrictedSignal.
*
* A signal is a way to couple components together in a very loose
* way. The receiver does not need to know anything about the sender
* and the sender does not need to know anything about the
* receivers. The sender will just call emit when something happens,
* the signal takes care of notifing all interested parties. By using
* wrapper delegates/functions, not even the function signature of
* sender/receiver need to match.
*
* Another consequence of this very loose coupling is, that a
* connected object will be freed by the GC if all references to it
* are dropped, even if it was still connected to a signal, the
* connection will simply be dropped. This way the developer is freed of
* manually keeping track of connections.
*
* If in your application the connections made by a signal are not
* that loose you can use strongConnect(), in this case the GC won't
* free your object until it was disconnected from the signal or the
* signal got itself destroyed.
*
* This struct is not thread-safe in general, it just handles the
* concurrent parts of the GC.
*
* Bugs: The code probably won't compile with -profile because of bug:
* $(LINK2 http://d.puremagic.com/issues/show_bug.cgi?id=10260, 10260)
*/
struct Signal(Args...)
{
alias restricted this;
/**
* Emit the signal.
*
* All connected slots which are still alive will be called. If
* any of the slots throws an exception, the other slots will
* still be called. You'll receive a chained exception with all
* exceptions that were thrown. Thus slots won't influence each
* others execution.
*
* The slots are called in the same sequence as they were registered.
*
* emit also takes care of actually removing dead connections. For
* concurrency reasons they are set just to an invalid state by the GC.
*
* If you remove a slot during emit() it won't be called in the
* current run if it wasn't already.
*
* If you add a slot during emit() it will be called in the
* current emit() run. Note however Signal is not thread-safe, "called
* during emit" basically means called from within a slot.
*/
void emit( Args args ) @trusted
{
_restricted._impl.emit(args);
}
/**
* Get access to the rest of the signals functionality.
*/
ref RestrictedSignal!(Args) restricted() @property @trusted
{
return _restricted;
}
private:
RestrictedSignal!(Args) _restricted;
}
/**
* The signal implementation, not providing an emit method.
*
* The idea is to instantiate a Signal privately and provide a
* public accessor method for accessing the contained
* RestrictedSignal. You can use the signal string mixin, which does
* exactly that.
*/
struct RestrictedSignal(Args...)
{
/**
* Direct connection to an object.
*
* Use this method if you want to connect directly to an object's
* method matching the signature of this signal. The connection
* will have weak reference semantics, meaning if you drop all
* references to the object the garbage collector will collect it
* and this connection will be removed.
*
* Preconditions: obj must not be null. mixin("&obj."~method)
* must be valid and compatible.
* Params:
* obj = Some object of a class implementing a method
* compatible with this signal.
*/
void connect(string method, ClassType)(ClassType obj) @trusted
if (is(ClassType == class) && __traits(compiles, {void delegate(Args) dg = mixin("&obj."~method);}))
in
{
assert(obj);
}
body
{
_impl.addSlot(obj, cast(void delegate())mixin("&obj."~method));
}
/**
* Indirect connection to an object.
*
* Use this overload if you want to connect to an objects method
* which does not match the signal's signature. You can provide
* any delegate to do the parameter adaption, but make sure your
* delegates' context does not contain a reference to the target
* object, instead use the provided obj parameter, where the
* object passed to connect will be passed to your delegate.
* This is to make weak ref semantics possible, if your delegate
* contains a ref to obj, the object won't be freed as long as
* the connection remains.
*
* Preconditions: obj and dg must not be null (dgs context
* may). dg's context must not be equal to obj.
*
* Params:
* obj = The object to connect to. It will be passed to the
* delegate when the signal is emitted.
*
* dg = A wrapper delegate which takes care of calling some
* method of obj. It can do any kind of parameter adjustments
* necessary.
*/
void connect(ClassType)(ClassType obj, void delegate(ClassType obj, Args) dg) @trusted
if (is(ClassType == class))
in
{
assert(obj);
assert(dg);
assert(cast(void*)obj !is dg.ptr);
}
body
{
_impl.addSlot(obj, cast(void delegate()) dg);
}
/**
* Connect with strong ref semantics.
*
* Use this overload if you either really, really want strong ref
* semantics for some reason or because you want to connect some
* non-class method delegate. Whatever the delegates' context
* references, will stay in memory as long as the signals
* connection is not removed and the signal gets not destroyed
* itself.
*
* Preconditions: dg must not be null. (Its context may.)
*
* Params:
* dg = The delegate to be connected.
*/
void strongConnect(void delegate(Args) dg) @trusted
in
{
assert(dg);
}
body
{
_impl.addSlot(null, cast(void delegate()) dg);
}
/**
* Disconnect a direct connection.
*
* After issuing this call, methods of obj won't be triggered any
* longer when emit is called.
* Preconditions: Same as for direct connect.
*/
void disconnect(string method, ClassType)(ClassType obj) @trusted
if (is(ClassType == class) && __traits(compiles, {void delegate(Args) dg = mixin("&obj."~method);}))
in
{
assert(obj);
}
body
{
void delegate(Args) dg = mixin("&obj."~method);
_impl.removeSlot(obj, cast(void delegate()) dg);
}
/**
* Disconnect an indirect connection.
*
* For this to work properly, dg has to be exactly the same as
* the one passed to connect. So if you used a lamda you have to
* keep a reference to it somewhere if you want to disconnect
* the connection later on. If you want to remove all
* connections to a particular object use the overload which only
* takes an object paramter.
*/
void disconnect(ClassType)(ClassType obj, void delegate(ClassType, T1) dg) @trusted
if (is(ClassType == class))
in
{
assert(obj);
assert(dg);
}
body
{
_impl.removeSlot(obj, cast(void delegate())dg);
}
/**
* Disconnect all connections to obj.
*
* All connections to obj made with calls to connect are removed.
*/
void disconnect(ClassType)(ClassType obj) @trusted if (is(ClassType == class))
in
{
assert(obj);
}
body
{
_impl.removeSlot(obj);
}
/**
* Disconnect a connection made with strongConnect.
*
* Disconnects all connections to dg.
*/
void strongDisconnect(void delegate(Args) dg) @trusted
in
{
assert(dg);
}
body
{
_impl.removeSlot(null, cast(void delegate()) dg);
}
private:
SignalImpl _impl;
}
private struct SignalImpl
{
/**
* Forbid copying.
* Unlike the old implementations, it now is theoretically
* possible to copy a signal. Even different semantics are
* possible. But none of the possible semantics are what the user
* intended in all cases, so I believe it is still the safer
* choice to simply disallow copying.
*/
@disable this(this);
/// Forbid copying
@disable void opAssign(SignalImpl other);
void emit(Args...)( Args args )
{
int emptyCount = 0;
if (!_slots.emitInProgress)
{
_slots.emitInProgress = true;
scope (exit) _slots.emitInProgress = false;
}
else
emptyCount = -1;
doEmit(0, emptyCount, args);
if (emptyCount > 0)
{
_slots.slots = _slots.slots[0 .. $-emptyCount];
_slots.slots.assumeSafeAppend();
}
}
void addSlot(Object obj, void delegate() dg)
{
auto oldSlots = _slots.slots;
if (oldSlots.capacity <= oldSlots.length)
{
auto buf = new SlotImpl[oldSlots.length+1]; // TODO: This growing strategy might be inefficient.
foreach (i, ref slot ; oldSlots)
buf[i].moveFrom(slot);
oldSlots = buf;
}
else
oldSlots.length = oldSlots.length + 1;
oldSlots[$-1].construct(obj, dg);
_slots.slots = oldSlots;
}
void removeSlot(Object obj, void delegate() dg)
{
removeSlot((ref SlotImpl item) => item.wasConstructedFrom(obj, dg));
}
void removeSlot(Object obj)
{
removeSlot((ref SlotImpl item) => item.obj is obj);
}
~this()
{
foreach (ref slot; _slots.slots)
{
debug (signal) { import std.stdio; stderr.writefln("Destruction, removing some slot(%s, weakref: %s), signal: ", &slot, &slot._obj, &this); }
slot.reset(); // This is needed because ATM the GC won't trigger struct
// destructors to be run when within a GC managed array.
}
}
/// Little helper functions:
/**
* Find and make invalid any slot for which isRemoved returns true.
*/
void removeSlot(bool delegate(ref SlotImpl) isRemoved)
{
if (_slots.emitInProgress)
{
foreach (ref slot; _slots.slots)
if (isRemoved(slot))
slot.reset();
}
else // It is save to do immediate cleanup:
{
int emptyCount = 0;
auto mslots = _slots.slots;
foreach (int i, ref slot; mslots)
{
// We are retrieving obj twice which is quite expensive because of GC lock:
if (!slot.isValid || isRemoved(slot))
{
emptyCount++;
slot.reset();
}
else if (emptyCount)
mslots[i-emptyCount].moveFrom(slot);
}
if (emptyCount > 0)
{
mslots = mslots[0..$-emptyCount];
mslots.assumeSafeAppend();
_slots.slots = mslots;
}
}
}
/**
* Helper method to allow all slots being called even in case of an exception.
* All exceptions that occur will be chained.
* Any invalid slots (GC collected or removed) will be dropped.
*/
void doEmit(Args...)(int offset, ref int emptyCount, Args args )
{
int i=offset;
auto myslots = _slots.slots;
scope (exit) if (i+10)
{
myslots[i-emptyCount].reset();
myslots[i-emptyCount].moveFrom(myslots[i]);
}
}
}
}
SlotArray _slots;
}
// Simple convenience struct for signal implementation.
// Its is inherently unsafe. It is not a template so SignalImpl does
// not need to be one.
private struct SlotImpl
{
@disable this(this);
@disable void opAssign(SlotImpl other);
/// Pass null for o if you have a strong ref delegate.
/// dg.funcptr must not point to heap memory.
void construct(Object o, void delegate() dg)
in { assert(this is SlotImpl.init); }
body
{
_obj.construct(o);
_dataPtr = dg.ptr;
_funcPtr = dg.funcptr;
assert(GC.addrOf(_funcPtr) is null, "Your function is implemented on the heap? Such dirty tricks are not supported with std.signal!");
if (o)
{
if (_dataPtr is cast(void*) o)
_dataPtr = directPtrFlag;
hasObject = true;
}
}
/**
* Check whether this slot was constructed from object o and delegate dg.
*/
bool wasConstructedFrom(Object o, void delegate() dg)
{
if ( o && dg.ptr is cast(void*) o)
return obj is o && _dataPtr is directPtrFlag && funcPtr is dg.funcptr;
else
return obj is o && _dataPtr is dg.ptr && funcPtr is dg.funcptr;
}
/**
* Implement proper explict move.
*/
void moveFrom(ref SlotImpl other)
in { assert(this is SlotImpl.init); }
body
{
auto o = other.obj;
_obj.construct(o);
_dataPtr = other._dataPtr;
_funcPtr = other._funcPtr;
other.reset(); // Destroy original!
}
@property Object obj()
{
return _obj.obj;
}
/**
* Whether or not _obj should contain a valid object. (We have a weak connection)
*/
bool hasObject() @property const
{
return cast(ptrdiff_t) _funcPtr & 1;
}
/**
* Check whether this is a valid slot.
*
* Meaning opCall will call something and return true;
*/
bool isValid() @property
{
return funcPtr && (!hasObject || obj !is null);
}
/**
* Call the slot.
*
* Returns: True if the call was successful (the slot was valid).
*/
bool opCall(Args...)(Args args)
{
auto o = obj;
void* o_addr = cast(void*)(o);
if (!funcPtr || (hasObject && !o_addr))
return false;
if (_dataPtr is directPtrFlag || !hasObject)
{
void delegate(Args) mdg;
mdg.funcptr=cast(void function(Args)) funcPtr;
debug (signal) { import std.stdio; writefln("hasObject: %s, o_addr: %s, dataPtr: %s", hasObject, o_addr, _dataPtr);}
assert((hasObject && _dataPtr is directPtrFlag) || (!hasObject && _dataPtr !is directPtrFlag));
if (hasObject)
mdg.ptr = o_addr;
else
mdg.ptr = _dataPtr;
mdg(args);
}
else
{
void delegate(Object, Args) mdg;
mdg.ptr = _dataPtr;
mdg.funcptr = cast(void function(Object, Args)) funcPtr;
mdg(o, args);
}
return true;
}
/**
* Reset this instance to its intial value.
*/
void reset() {
_funcPtr = SlotImpl.init._funcPtr;
_dataPtr = SlotImpl.init._dataPtr;
_obj.reset();
}
private:
void* funcPtr() @property const
{
return cast(void*)( cast(ptrdiff_t)_funcPtr & ~cast(ptrdiff_t)1);
}
void hasObject(bool yes) @property
{
if (yes)
_funcPtr = cast(void*)(cast(ptrdiff_t) _funcPtr | 1);
else
_funcPtr = cast(void*)(cast(ptrdiff_t) _funcPtr & ~cast(ptrdiff_t)1);
}
void* _funcPtr;
void* _dataPtr;
WeakRef _obj;
enum directPtrFlag = cast(void*)(~0);
}
// Provides a way of holding a reference to an object, without the GC seeing it.
private struct WeakRef
{
/**
* As struct must be relocatable, it is not even possible to
* provide proper copy support for WeakRef. rt_attachDisposeEvent
* is used for registering unhook. D's move semantics assume
* relocatable objects, which results in this(this) being called
* for one instance and the destructor for another, thus the wrong
* handlers are deregistered. D's assumption of relocatable
* objects is not matched, so move() for example will still simply
* swap contents of two structs, resulting in the wrong unhook
* delegates being unregistered.
* Unfortunately the runtime still blindly copies WeakRefs if they
* are in a dynamic array and reallocation is needed. This case
* has to be handled separately.
*/
@disable this(this);
@disable void opAssign(WeakRef other);
void construct(Object o)
in { assert(this is WeakRef.init); }
body
{
debug (signal) createdThis=&this;
debug (signal) { import std.stdio; writefln("WeakRef.construct for %s and object: %s", &this, o); }
if (!o)
return;
_obj.construct(cast(void*)o);
rt_attachDisposeEvent(o, &unhook);
}
Object obj() @property
{
return cast(Object) _obj.address;
}
/**
* Reset this instance to its intial value.
*/
void reset()
{
auto o = obj;
debug (signal) { import std.stdio; writefln("WeakRef.reset for %s and object: %s", &this, o); }
if (o)
rt_detachDisposeEvent(o, &unhook);
unhook(o); // unhook has to be done unconditionally, because in case the GC
//kicked in during toggleVisibility(), obj would contain -1
//so the assertion of SlotImpl.moveFrom would fail.
debug (signal) createdThis = null;
}
~this()
{
reset();
}
private:
debug (signal)
{
invariant()
{
import std.conv : text;
assert(createdThis is null || &this is createdThis,
text("We changed address! This should really not happen! Orig address: ",
cast(void*)createdThis, " new address: ", cast(void*)&this));
}
WeakRef* createdThis;
}
void unhook(Object o)
{
_obj.reset();
}
shared(InvisibleAddress) _obj;
}
// Do all the dirty stuff, WeakRef is only a thin wrapper completing
// the functionality by means of rt_ hooks.
private shared struct InvisibleAddress
{
/// Initialize with o, state is set to invisible immediately.
/// No precautions regarding thread safety are necessary because
/// obviously a live reference exists.
void construct(void* o)
{
auto tmp = cast(ptrdiff_t) o;
_addr = makeInvisible(cast(ptrdiff_t) o);
}
void reset()
{
atomicStore(_addr, 0L);
}
void* address() @property
{
makeVisible();
scope (exit) makeInvisible();
GC.addrOf(cast(void*)atomicLoad(_addr)); // Just a dummy call to the GC
// in order to wait for any possible running
// collection to complete (have unhook called).
auto buf = atomicLoad(_addr);
if ( isNull(buf) )
return null;
assert(isVisible(buf));
return cast(void*) buf;
}
debug(signal) string toString()
{
import std.conv : text;
return text(address);
}
private:
long _addr;
void makeVisible()
{
long buf, wbuf;
do
{
buf = atomicLoad(_addr);
wbuf = makeVisible(buf);
}
while(!cas(&_addr, buf, wbuf));
}
void makeInvisible()
{
long buf, wbuf;
do
{
buf = atomicLoad(_addr);
wbuf = makeInvisible(buf);
}
while(!cas(&_addr, buf, wbuf));
}
version(D_LP64)
{
static long makeVisible(long addr)
{
return ~addr;
}
static long makeInvisible(long addr)
{
return ~addr;
}
static bool isVisible(long addr)
{
return !(addr & (1L << (ptrdiff_t.sizeof*8-1)));
}
static bool isNull(long addr)
{
return ( addr == 0 || addr == (~0) );
}
}
else
{
static long makeVisible(long addr)
{
auto addrHigh = (addr >> 32) & 0xffff;
auto addrLow = addr & 0xffff;
return addrHigh << 16 | addrLow;
}
static long makeInvisible(long addr)
{
auto addrHigh = ((addr >> 16) & 0x0000ffff) | 0xffff0000;
auto addrLow = (addr & 0x0000ffff) | 0xffff0000;
return (cast(long)addrHigh << 32) | addrLow;
}
static bool isVisible(long addr)
{
return !((addr >> 32) & 0xffffffff);
}
static bool isNull(long addr)
{
return ( addr == 0 || addr == ((0xffff0000L << 32) | 0xffff0000) );
}
}
}
/**
* Provides a way of storing flags in unused parts of a typical D array.
*
* By unused I mean the highest bits of the length.
* (We don't need to support 4 billion slots per signal with int
* or 10^19 if length gets changed to 64 bits.)
*/
private struct SlotArray {
// Choose int for now, this saves 4 bytes on 64 bits.
alias int lengthType;
import std.bitmanip : bitfields;
enum reservedBitsCount = 3;
enum maxSlotCount = lengthType.max >> reservedBitsCount;
SlotImpl[] slots() @property
{
return _ptr[0 .. length];
}
void slots(SlotImpl[] newSlots) @property
{
_ptr = newSlots.ptr;
version(assert)
{
import std.conv : text;
assert(newSlots.length <= maxSlotCount, text("Maximum slots per signal exceeded: ", newSlots.length, "/", maxSlotCount));
}
_blength.length &= ~maxSlotCount;
_blength.length |= newSlots.length;
}
size_t length() @property
{
return _blength.length & maxSlotCount;
}
bool emitInProgress() @property
{
return _blength.emitInProgress;
}
void emitInProgress(bool val) @property
{
_blength.emitInProgress = val;
}
private:
SlotImpl* _ptr;
union BitsLength {
mixin(bitfields!(
bool, "", lengthType.sizeof*8-1,
bool, "emitInProgress", 1
));
lengthType length;
}
BitsLength _blength;
}
unittest {
SlotArray arr;
auto tmp = new SlotImpl[10];
arr.slots = tmp;
assert(arr.length == 10);
assert(!arr.emitInProgress);
arr.emitInProgress = true;
assert(arr.emitInProgress);
assert(arr.length == 10);
assert(arr.slots is tmp);
arr.slots = tmp;
assert(arr.emitInProgress);
assert(arr.length == 10);
assert(arr.slots is tmp);
debug (signal){ import std.stdio;
writeln("Slot array tests passed!");
}
}
unittest
{ // Check that above example really works ...
debug (signal) import std.stdio;
class MyObject
{
mixin(signal!(string, int)("valueChanged"));
int value() @property { return _value; }
int value(int v) @property
{
if (v != _value)
{
_value = v;
// call all the connected slots with the two parameters
_valueChanged.emit("setting new value", v);
}
return v;
}
private:
int _value;
}
class Observer
{ // our slot
void watch(string msg, int i)
{
debug (signal) writefln("Observed msg '%s' and value %s", msg, i);
}
}
void watch(string msg, int i)
{
debug (signal) writefln("Globally observed msg '%s' and value %s", msg, i);
}
auto a = new MyObject;
Observer o = new Observer;
a.value = 3; // should not call o.watch()
a.valueChanged.connect!"watch"(o); // o.watch is the slot
a.value = 4; // should call o.watch()
a.valueChanged.disconnect!"watch"(o); // o.watch is no longer a slot
a.value = 5; // so should not call o.watch()
a.valueChanged.connect!"watch"(o); // connect again
// Do some fancy stuff:
a.valueChanged.connect!Observer(o, (obj, msg, i) => obj.watch("Some other text I made up", i+1));
a.valueChanged.strongConnect(&watch);
a.value = 6; // should call o.watch()
destroy(o); // destroying o should automatically disconnect it
a.value = 7; // should not call o.watch()
}
unittest
{
debug (signal) import std.stdio;
class Observer
{
void watch(string msg, int i)
{
//writefln("Observed msg '%s' and value %s", msg, i);
captured_value = i;
captured_msg = msg;
}
int captured_value;
string captured_msg;
}
class SimpleObserver
{
void watchOnlyInt(int i) {
captured_value = i;
}
int captured_value;
}
class Foo
{
@property int value() { return _value; }
@property int value(int v)
{
if (v != _value)
{ _value = v;
_extendedSig.emit("setting new value", v);
//_simpleSig.emit(v);
}
return v;
}
mixin(signal!(string, int)("extendedSig"));
//Signal!(int) simpleSig;
private:
int _value;
}
Foo a = new Foo;
Observer o = new Observer;
SimpleObserver so = new SimpleObserver;
// check initial condition
assert(o.captured_value == 0);
assert(o.captured_msg == "");
// set a value while no observation is in place
a.value = 3;
assert(o.captured_value == 0);
assert(o.captured_msg == "");
// connect the watcher and trigger it
a.extendedSig.connect!"watch"(o);
a.value = 4;
assert(o.captured_value == 4);
assert(o.captured_msg == "setting new value");
// disconnect the watcher and make sure it doesn't trigger
a.extendedSig.disconnect!"watch"(o);
a.value = 5;
assert(o.captured_value == 4);
assert(o.captured_msg == "setting new value");
//a.extendedSig.connect!Observer(o, (obj, msg, i) { obj.watch("Hahah", i); });
a.extendedSig.connect!Observer(o, (obj, msg, i) => obj.watch("Hahah", i) );
a.value = 7;
debug (signal) stderr.writeln("After asignment!");
assert(o.captured_value == 7);
assert(o.captured_msg == "Hahah");
a.extendedSig.disconnect(o); // Simply disconnect o, otherwise we would have to store the lamda somewhere if we want to disconnect later on.
// reconnect the watcher and make sure it triggers
a.extendedSig.connect!"watch"(o);
a.value = 6;
assert(o.captured_value == 6);
assert(o.captured_msg == "setting new value");
// destroy the underlying object and make sure it doesn't cause
// a crash or other problems
debug (signal) stderr.writefln("Disposing");
destroy(o);
debug (signal) stderr.writefln("Disposed");
a.value = 7;
}
unittest {
class Observer
{
int i;
long l;
string str;
void watchInt(string str, int i)
{
this.str = str;
this.i = i;
}
void watchLong(string str, long l)
{
this.str = str;
this.l = l;
}
}
class Bar
{
@property void value1(int v) { _s1.emit("str1", v); }
@property void value2(int v) { _s2.emit("str2", v); }
@property void value3(long v) { _s3.emit("str3", v); }
mixin(signal!(string, int) ("s1"));
mixin(signal!(string, int) ("s2"));
mixin(signal!(string, long)("s3"));
}
void test(T)(T a)
{
auto o1 = new Observer;
auto o2 = new Observer;
auto o3 = new Observer;
// connect the watcher and trigger it
a.s1.connect!"watchInt"(o1);
a.s2.connect!"watchInt"(o2);
a.s3.connect!"watchLong"(o3);
assert(!o1.i && !o1.l && !o1.str);
assert(!o2.i && !o2.l && !o2.str);
assert(!o3.i && !o3.l && !o3.str);
a.value1 = 11;
assert(o1.i == 11 && !o1.l && o1.str == "str1");
assert(!o2.i && !o2.l && !o2.str);
assert(!o3.i && !o3.l && !o3.str);
o1.i = -11; o1.str = "x1";
a.value2 = 12;
assert(o1.i == -11 && !o1.l && o1.str == "x1");
assert(o2.i == 12 && !o2.l && o2.str == "str2");
assert(!o3.i && !o3.l && !o3.str);
o2.i = -12; o2.str = "x2";
a.value3 = 13;
assert(o1.i == -11 && !o1.l && o1.str == "x1");
assert(o2.i == -12 && !o1.l && o2.str == "x2");
assert(!o3.i && o3.l == 13 && o3.str == "str3");
o3.l = -13; o3.str = "x3";
// disconnect the watchers and make sure it doesn't trigger
a.s1.disconnect!"watchInt"(o1);
a.s2.disconnect!"watchInt"(o2);
a.s3.disconnect!"watchLong"(o3);
a.value1 = 21;
a.value2 = 22;
a.value3 = 23;
assert(o1.i == -11 && !o1.l && o1.str == "x1");
assert(o2.i == -12 && !o1.l && o2.str == "x2");
assert(!o3.i && o3.l == -13 && o3.str == "x3");
// reconnect the watcher and make sure it triggers
a.s1.connect!"watchInt"(o1);
a.s2.connect!"watchInt"(o2);
a.s3.connect!"watchLong"(o3);
a.value1 = 31;
a.value2 = 32;
a.value3 = 33;
assert(o1.i == 31 && !o1.l && o1.str == "str1");
assert(o2.i == 32 && !o1.l && o2.str == "str2");
assert(!o3.i && o3.l == 33 && o3.str == "str3");
// destroy observers
destroy(o1);
destroy(o2);
destroy(o3);
a.value1 = 41;
a.value2 = 42;
a.value3 = 43;
}
test(new Bar);
class BarDerived: Bar
{
@property void value4(int v) { _s4.emit("str4", v); }
@property void value5(int v) { _s5.emit("str5", v); }
@property void value6(long v) { _s6.emit("str6", v); }
mixin(signal!(string, int) ("s4"));
mixin(signal!(string, int) ("s5"));
mixin(signal!(string, long)("s6"));
}
auto a = new BarDerived;
test!Bar(a);
test!BarDerived(a);
auto o4 = new Observer;
auto o5 = new Observer;
auto o6 = new Observer;
// connect the watcher and trigger it
a.s4.connect!"watchInt"(o4);
a.s5.connect!"watchInt"(o5);
a.s6.connect!"watchLong"(o6);
assert(!o4.i && !o4.l && !o4.str);
assert(!o5.i && !o5.l && !o5.str);
assert(!o6.i && !o6.l && !o6.str);
a.value4 = 44;
assert(o4.i == 44 && !o4.l && o4.str == "str4");
assert(!o5.i && !o5.l && !o5.str);
assert(!o6.i && !o6.l && !o6.str);
o4.i = -44; o4.str = "x4";
a.value5 = 45;
assert(o4.i == -44 && !o4.l && o4.str == "x4");
assert(o5.i == 45 && !o5.l && o5.str == "str5");
assert(!o6.i && !o6.l && !o6.str);
o5.i = -45; o5.str = "x5";
a.value6 = 46;
assert(o4.i == -44 && !o4.l && o4.str == "x4");
assert(o5.i == -45 && !o4.l && o5.str == "x5");
assert(!o6.i && o6.l == 46 && o6.str == "str6");
o6.l = -46; o6.str = "x6";
// disconnect the watchers and make sure it doesn't trigger
a.s4.disconnect!"watchInt"(o4);
a.s5.disconnect!"watchInt"(o5);
a.s6.disconnect!"watchLong"(o6);
a.value4 = 54;
a.value5 = 55;
a.value6 = 56;
assert(o4.i == -44 && !o4.l && o4.str == "x4");
assert(o5.i == -45 && !o4.l && o5.str == "x5");
assert(!o6.i && o6.l == -46 && o6.str == "x6");
// reconnect the watcher and make sure it triggers
a.s4.connect!"watchInt"(o4);
a.s5.connect!"watchInt"(o5);
a.s6.connect!"watchLong"(o6);
a.value4 = 64;
a.value5 = 65;
a.value6 = 66;
assert(o4.i == 64 && !o4.l && o4.str == "str4");
assert(o5.i == 65 && !o4.l && o5.str == "str5");
assert(!o6.i && o6.l == 66 && o6.str == "str6");
// destroy observers
destroy(o4);
destroy(o5);
destroy(o6);
a.value4 = 44;
a.value5 = 45;
a.value6 = 46;
}
unittest
{
import std.stdio;
struct Property
{
alias value this;
mixin(signal!(int)("signal"));
@property int value()
{
return value_;
}
ref Property opAssign(int val)
{
debug (signal) writeln("Assigning int to property with signal: ", &this);
value_ = val;
_signal.emit(val);
return this;
}
private:
int value_;
}
void observe(int val)
{
debug (signal) writefln("observe: Wow! The value changed: %s", val);
}
class Observer
{
void observe(int val)
{
debug (signal) writefln("Observer: Wow! The value changed: %s", val);
debug (signal) writefln("Really! I must know I am an observer (old value was: %s)!", observed);
observed = val;
count++;
}
int observed;
int count;
}
Property prop;
void delegate(int) dg = (val) => observe(val);
prop.signal.strongConnect(dg);
assert(prop.signal._impl._slots.length==1);
Observer o=new Observer;
prop.signal.connect!"observe"(o);
assert(prop.signal._impl._slots.length==2);
debug (signal) writeln("Triggering on original property with value 8 ...");
prop=8;
assert(o.count==1);
assert(o.observed==prop);
}
unittest
{
debug (signal) import std.stdio;
import std.conv;
Signal!() s1;
void testfunc(int id)
{
throw new Exception(to!string(id));
}
s1.strongConnect(() => testfunc(0));
s1.strongConnect(() => testfunc(1));
s1.strongConnect(() => testfunc(2));
try s1.emit();
catch(Exception e)
{
Throwable t=e;
int i=0;
while (t)
{
debug (signal) stderr.writefln("Caught exception (this is fine)");
assert(to!int(t.msg)==i);
t=t.next;
i++;
}
assert(i==3);
}
}
unittest
{
class A
{
mixin(signal!(string, int)("s1"));
}
class B : A
{
mixin(signal!(string, int)("s2"));
}
}
unittest
{
struct Test
{
mixin(signal!int("a", Protection.package_));
mixin(signal!int("ap", Protection.private_));
mixin(signal!int("app", Protection.protected_));
mixin(signal!int("an", Protection.none));
}
static assert(signal!int("a", Protection.package_)=="package Signal!(int) _a;\nref RestrictedSignal!(int) a() { return _a;}\n");
static assert(signal!int("a", Protection.protected_)=="protected Signal!(int) _a;\nref RestrictedSignal!(int) a() { return _a;}\n");
static assert(signal!int("a", Protection.private_)=="private Signal!(int) _a;\nref RestrictedSignal!(int) a() { return _a;}\n");
static assert(signal!int("a", Protection.none)=="private Signal!(int) _a;\nref Signal!(int) a() { return _a;}\n");
debug (signal)
{
pragma(msg, signal!int("a", Protection.package_));
pragma(msg, signal!(int, string, int[int])("a", Protection.private_));
pragma(msg, signal!(int, string, int[int], float, double)("a", Protection.protected_));
pragma(msg, signal!(int, string, int[int], float, double, long)("a", Protection.none));
}
}
unittest // Test nested emit/removal/addition ...
{
Signal!() sig;
bool doEmit = true;
int counter = 0;
int slot3called = 0;
int slot3shouldcalled = 0;
void slot1()
{
doEmit = !doEmit;
if (!doEmit)
sig.emit();
}
void slot3()
{
slot3called++;
}
void slot2()
{
debug (signal) { import std.stdio; writefln("\nCALLED: %s, should called: %s", slot3called, slot3shouldcalled);}
assert (slot3called == slot3shouldcalled);
if ( ++counter < 100)
slot3shouldcalled += counter;
if ( counter < 100 )
sig.strongConnect(&slot3);
}
void slot4()
{
if ( counter == 100 )
sig.strongDisconnect(&slot3); // All connections dropped
}
sig.strongConnect(&slot1);
sig.strongConnect(&slot2);
sig.strongConnect(&slot4);
for (int i=0; i<1000; i++)
sig.emit();
debug (signal)
{
import std.stdio;
writeln("slot3called: ", slot3called);
}
}
/* vim: set ts=4 sw=4 expandtab : */