Skip to content

Instantly share code, notes, and snippets.

@jpf91
Created September 5, 2011 09:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpf91/1194497 to your computer and use it in GitHub Desktop.
Save jpf91/1194497 to your computer and use it in GitHub Desktop.
// Written in the D programming language.
/**
* Signals and Slots are an implementation of the Observer Pattern.
* Essentially, when a Signal is emitted, a list of connected Observers
* (called slots) are called.
*
* There have been several D implementations of Signals and Slots.
* This version makes use of several new features in D, which make
* using it simpler and less error prone. In particular, it is no
* longer necessary to instrument the slots.
*
* References:
* $(LINK2 http://scottcollins.net/articles/a-deeper-look-at-_signals-and-slots.html, A Deeper Look at Signals and Slots)$(BR)
* $(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
* $(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR)
* $(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR)
* $(LINK2 http://doc.trolltech.com/4.1/signalsandslots.html, Qt)$(BR)
*
* There has been a great deal of discussion in the D newsgroups
* over this, and several implementations:
*
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals & Slots for Walter)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties, signals and slots, etc))$(BR)
* $(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR)
*
* Bugs:
* Not safe for multiple threads operating on the same signals
* or slots.
*
* Safety of handlers is not yet enforced
* Macros:
* WIKI = Phobos/StdSignals
* SIGNALS=signals
*
* Copyright: Copyright Digital Mars 2000 - 2009.
* License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
* Authors: $(WEB digitalmars.com, Walter Bright),
* Johannes Pfau
*/
/* Copyright Digital Mars 2000 - 2009.
* 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)
*/
module std.signals;
import std.algorithm; //find
import std.container; //SList
import std.functional; //toDelegate
import std.range; //take
import std.traits; //isPointer, isSafe
//Bug 4536
private template Init(T...)
{
T Init;
}
template isHandlerDelegate(T, Types...)
{
static if(is(T == delegate))
{
enum bool isHandlerDelegate = (is(typeof(T.init(Init!(Types))))
&& (is(ReturnType!(T) == void)
|| is(ReturnType!(T) == bool)));
}
else
{
enum bool isHandlerDelegate = false;
}
}
template isHandlerFunction(T, Types...)
{
static if(isPointer!(T))
{
enum bool isHandlerFunction = (isPointer!(T) && is(pointerTarget!(T) == function)
&& is(typeof(T.init(Init!(Types))))
&& (is(ReturnType!(T) == void)
|| is(ReturnType!(T) == bool)));
}
else
{
enum bool isHandlerFunction = false;
}
}
template isHandlerStruct(T, Types...)
{
static if(isPointer!(T))
{
enum bool isHandlerStruct = (is(pointerTarget!(T) == struct)
//&& (isSafe!(pointerTarget!(T).init))
&& is(typeof(pointerTarget!(T).init.opCall(Init!(Types))))
&& (is(ReturnType!(T) == void)
|| is(ReturnType!(T) == bool)));
}
else
{
enum bool isHandlerStruct = false;
}
}
unittest
{
struct tmp
{
@safe bool opCall() {return false;}
}
tmp* a = new tmp();
assert(isHandlerStruct!(typeof(a)));
tmp b;
assert(!isHandlerStruct!(typeof(b)));
}
//Checking for unsafe handlers is not yet implemented
/*
unittest
{
struct tmp
{
bool opCall() {return false;}
}
tmp* a = new tmp();
assert(!isHandlerStruct!(typeof(a)));
tmp b;
assert(!isHandlerStruct!(typeof(b)));
}
*/
template isHandlerClass(T, Types...)
{
enum bool isHandlerClass = (is(T == class)
&& is(typeof(T.init.opCall(Init!(Types))))
&& (is(ReturnType!(T) == void)
|| is(ReturnType!(T) == bool)));
}
template isHandler(T, Types...)
{
enum bool isHandler = isHandlerDelegate!(T, Types) || isHandlerFunction!(T, Types)
|| isHandlerClass!(T, Types) || isHandlerStruct!(T, Types);
}
unittest
{
struct tmp
{
@safe void opCall(){}
}
struct tmp2
{
@safe char opCall(){ return 'c';}
}
struct tmp3
{
@safe bool opCall(){ return true;}
}
tmp* a = new tmp();
assert(isHandler!(typeof(a)));
assert(!isHandler!(typeof(a), int));
assert(!isHandler!(typeof(a), int, bool));
assert(!isHandler!(typeof(a), int, bool, string));
tmp b;
assert(!isHandler!(typeof(b)));
assert(!isHandler!(typeof(b), int));
assert(!isHandler!(typeof(b), int, bool));
assert(!isHandler!(typeof(b), int, bool, string));
tmp2 c;
assert(!isHandler!(typeof(c)));
assert(!isHandler!(typeof(c), int));
assert(!isHandler!(typeof(c), int, bool));
assert(!isHandler!(typeof(c), int, bool, string));
tmp3* d = new tmp3();
assert(isHandler!(typeof(d)));
assert(!isHandler!(typeof(d), int));
assert(!isHandler!(typeof(d), int, bool));
assert(!isHandler!(typeof(d), int, bool, string));
}
unittest
{
class tmp
{
@safe void opCall(int i){}
}
class tmp2
{
@safe char opCall(string a, bool b){ return 'c';}
}
class tmp3
{
@safe bool opCall(char b){ return true;}
}
tmp a = new tmp();
assert(!isHandler!(typeof(a)));
assert(isHandler!(typeof(a), int));
assert(!isHandler!(typeof(a), int, bool));
assert(!isHandler!(typeof(a), int, bool, string));
tmp2 b = new tmp2();
assert(!isHandler!(typeof(b)));
assert(!isHandler!(typeof(b), string, bool));
assert(!isHandler!(typeof(b), int, bool));
assert(!isHandler!(typeof(b), int, bool, string));
tmp3 c = new tmp3();
assert(!isHandler!(typeof(c)));
assert(isHandler!(typeof(c), char));
assert(!isHandler!(typeof(c), int, bool));
assert(!isHandler!(typeof(c), int, bool, string));
}
unittest
{
static @safe void test(int a, int b) {};
static @safe void test2(int b) {};
static @safe bool test3(int a, int b) {return true;};
static @safe bool test4(int b) {return true;};
assert(isHandler!(typeof(&test), int, int));
assert(!isHandler!(typeof(&test)));
assert(isHandler!(typeof(&test2), int));
assert(!isHandler!(typeof(&test2, string)));
assert(isHandler!(typeof(&test3), int, int));
assert(!isHandler!(typeof(&test3), bool));
assert(isHandler!(typeof(&test4), int));
assert(!isHandler!(typeof(&test4)));
}
unittest
{
@safe void test(int a, int b) {};
@safe void test2(int b) {};
@safe bool test3(int a, int b) {return true;};
@safe bool test4(int b) {return true;};
assert(isHandler!(typeof(&test), int, int));
assert(!isHandler!(typeof(&test)));
assert(isHandler!(typeof(&test2), int));
assert(!isHandler!(typeof(&test2, string)));
assert(isHandler!(typeof(&test3), int, int));
assert(!isHandler!(typeof(&test3), bool));
assert(isHandler!(typeof(&test4), int));
assert(!isHandler!(typeof(&test4)));
}
/**
* This Signal struct is an implementation of the Observer pattern.
*
* All D callable types (functions, delegates, structs with opCall,
* classes with opCall) can be registered with a signal. When the signal
* occurs all assigned callables are called.
*
* Structs with opCall are only supported if they're passed by pointer. These
* structs are then expected to be allocated on the heap.
*
* Delegates to struct instances or nested functions are supported. You
* have to make sure to disconnect these delegates from the Signal before
* they go out of scope though.
*
* The return type of the handlers must be void or bool. If the return
* type is bool and the handler returns false the remaining handlers are
* not called. If true is returned or the type is void the remaining
* handlers are called.
*
* SafeD:
* This Signal template can be used in safeD; all public functions
* are @safe or @trusted. All handlers connected to
* a signal must be @safe or @trusted. It's currently not possible to
* enforce the safety of the handlers, but it will be enforced as soon
* as possible.
*
* Examples:
* -------------------------------------------------------------------
* import std.stdio;
* import std.signals;
*
* //same for classes
* struct A
* {
* string payload;
* @safe bool opCall(float f, string s)
* {
* writefln("A: %f:%s:%s", f, s, payload);
* return true;
* }
* }
*
* @safe void testFunc(float f, string s)
* {
* writefln("Function: %f:%s", f, s);
* }
*
* Signal!(float, string) onTest;
*
* void main()
* {
* A* a = new A();
* a.payload = "test payload";
* onTest.connect(a);
* onTest ~= &testFunc;
* onTest(0.123f, "first call");
* }
* -------------------------------------------------------------------
*/
public struct Signal(Types...)
{
private:
//A slot is implemented as a delegate. The slot_t is the type of the delegate.
alias bool delegate(Types) slot_t;
//Same as slot_t but with void return type
alias void delegate(Types) void_slot_t;
/* This struct stores one delegate and information whether the
* delegate returns a bool or void */
static struct Callable
{
//The void_slot_t delegate
slot_t deleg;
bool returnsBool = true;
this(void_slot_t del)
{
this.deleg = cast(slot_t)del;
this.returnsBool = false;
}
this(slot_t del)
{
this.deleg = del;
}
}
SList!(Callable) handlers;
/*
* Get a Callable for the handler.
* Handler can be a void function, void delegate, bool
* function, bool delegate, class with opCall or a pointer to
* a struct with opCall.
*/
@trusted Callable getCallable(T)(T handler) if(isHandler!(T, Types))
{
static if(isHandlerDelegate!(T, Types) && is(ReturnType!(T) == void))
{
return Callable(cast(void_slot_t)handler);
}
else static if(isHandlerFunction!(T, Types) && is(ReturnType!(T) == void))
{
void delegate(Types) call = toDelegate(cast(void function(Types))handler);
return Callable(call);
}
else static if(isHandlerDelegate!(T, Types) && is(ReturnType!(T) == bool))
{
return Callable(cast(slot_t)handler);
}
else static if(isHandlerFunction!(T, Types) && is(ReturnType!(T) == bool))
{
return Callable(toDelegate(cast(bool function(Types))handler));
}
else static if(isHandlerStruct!(T, Types))
{
static if(is(ReturnType!(T) == void))
{
return Callable(cast(void_slot_t)&handler.opCall);
}
else static if(is(ReturnType!(T) == bool))
{
return Callable(cast(slot_t)&handler.opCall);
}
else
{
static assert(false, "BUG: Internal error");
}
}
else static if(isHandlerClass!(T, Types))
{
static if(is(ReturnType!(T) == void))
{
return Callable(cast(void_slot_t)&handler.opCall);
}
else static if(is(ReturnType!(T) == bool))
{
return Callable(cast(slot_t)&handler.opCall);
}
else
{
static assert(false, "BUG: Internal error");
}
}
else
{
static assert(false, "BUG: Input type not supported. "
"Please file a bug report.");
}
}
public:
/**
* Set to false to disable signal emission
*
* Examples:
* --------------------------------
* bool called = false;
* @safe void handler() { called = true; }
* Signal!() onTest;
* onTest ~= &handler;
* onTest();
* assert(called);
* called = false;
* onTest.enabled = false;
* onTest();
* assert(!called);
* onTest.enabled = true;
* onTest();
* assert(called);
* --------------------------------
*/
@safe bool enabled = true;
/**
* Check whether a handler is already connected
*
* Examples:
* --------------------------------
* @safe void handler() {};
* Signal!() onTest;
* assert(!onTest.isConnected(&handler));
* onTest ~= &handler;
* assert(onTest.isConnected(&handler));
* onTest();
* --------------------------------
*/
@trusted bool isConnected(T)(T handler) if(isHandler!(T, Types))
{
Callable call = getCallable(handler);
return !find(handlers[], call).empty;
}
/**
* Add a handler to the list of handlers to be called when emit() is called.
* The handler is added at the end of the list.
*
* Throws:
* Exception if handler is already registered
* (Only if asserts are enabled! Does not throw
* in release mode!)
*
* Returns:
* The handler that was passed in as a paramter
*
* Examples:
* --------------------------------
* int val;
* string text;
* @safe void handler(int i, string t)
* {
* val = i;
* text = t;
* }
*
* Signal!(int, string) onTest;
* onTest.connect(&handler);
* onTest(1, "test");
* assert(val == 1);
* assert(text == "test");
* --------------------------------
*/
@trusted T connect(T)(T handler) if(isHandler!(T, Types))
{
Callable call = getCallable(handler);
assert(find(handlers[], call).empty, "Handler is already registered!");
handlers.stableInsertAfter(handlers[], call);
return handler;
}
/**
* Add a handler to the list of handlers to be called when emit() is called.
* Add this handler at the top of the list, so it will be called before all
* other handlers.
*
* Throws:
* Exception if handler is already registered
* (Only if asserts are enabled! Does not throw
* in release mode!)
*
* Returns:
* The handler that was passed in as a paramter
*
* --------------------------------
* bool firstCalled, secondCalled;
* @safe void handler1() {firstCalled = true;}
* @safe void handler2()
* {
* secondCalled = true;
* assert(firstCalled);
* }
* Signal!() onTest;
* onTest ~= &handler2;
* onTest.connectFirst(&handler1);
* onTest();
* assert(firstCalled && secondCalled);
* --------------------------------
*/
@trusted T connectFirst(T)(T handler) if(isHandler!(T, Types))
{
Callable call = getCallable(handler);
assert(find(handlers[], call).empty, "Handler is already registered!");
handlers.stableInsertFront(call);
return handler;
}
/**
* Add a handler to be called after another handler.
* Params:
* afterThis = The new attached handler will be called after this handler
* handler = The handler to be attached
*
* Throws:
* Exception if handler is already registered
* (Only if asserts are enabled! Does not throw
* in release mode!)
*
* Exception if afterThis is not registered
* (Always, even if asserts are disabled)
*
* Returns:
* The handler that has been connected
*
* Examples:
* --------------------------------
* bool firstCalled, secondCalled, thirdCalled;
* @safe void handler1() {firstCalled = true;}
* @safe void handler2()
* {
* secondCalled = true;
* assert(firstCalled);
* assert(thirdCalled);
* }
* @safe void handler3()
* {
* thirdCalled = true;
* assert(firstCalled);
* assert(!secondCalled);
* }
* Signal!() onTest;
* onTest ~= &handler1;
* onTest ~= &handler2;
* auto h = onTest.connectAfter(&handler1, &handler3);
* assert(h == &handler3);
* onTest();
* assert(firstCalled && secondCalled && thirdCalled);
* --------------------------------
*/
@trusted T connectAfter(T, U)(T afterThis, U handler)
if(isHandler!(T, Types) && isHandler!(U, Types))
{
Callable after = getCallable(afterThis);
Callable call = getCallable(handler);
auto location = find(handlers[], after);
if(location.empty)
{
throw new Exception("Handler 'afterThis' is not registered!");
}
assert(find(handlers[], call).empty, "Handler is already registered!");
handlers.stableInsertAfter(take(location, 1), call);
return handler;
}
/**
* Add a handler to be called before another handler.
* Params:
* beforeThis = The new attached handler will be called after this handler
* handler = The handler to be attached
*
* Throws:
* Exception if handler is already registered
* (Only if asserts are enabled! Does not throw
* in release mode!)
*
* Returns:
* The handler that has been connected
*
* Exception if beforeThis is not registered
* (Always, even if asserts are disabled)
*
* Examples:
* --------------------------------
* bool firstCalled, secondCalled, thirdCalled;
* @safe void handler1() {firstCalled = true;}
* @safe void handler2()
* {
* secondCalled = true;
* assert(firstCalled);
* assert(!thirdCalled);
* }
* @safe void handler3()
* {
* thirdCalled = true;
* assert(firstCalled);
* assert(secondCalled);
* }
* Signal!() onTest;
* onTest ~= &handler1;
* onTest ~= &handler3;
* onTest.connectBefore(&handler3, &handler2);
* onTest();
* assert(firstCalled && secondCalled && thirdCalled);
* --------------------------------
*/
@trusted T connectBefore(T, U)(T beforeThis, U handler)
if(isHandler!(T, Types) && isHandler!(U, Types))
{
Callable before = getCallable(beforeThis);
Callable call = getCallable(handler);
auto location = find(handlers[], before);
if(location.empty)
{
throw new Exception("Handler 'beforeThis' is not registered!");
}
assert(find(handlers[], call).empty, "Handler is already registered!");
//not exactly fast
uint length = walkLength(handlers[]);
uint pos = walkLength(location);
uint new_location = length - pos;
location = handlers[];
if(new_location == 0)
handlers.stableInsertFront(call);
else
handlers.stableInsertAfter(take(location, new_location), call);
return handler;
}
/**
* Remove a handler from the list of handlers to be called when emit() is called.
*
* Throws:
* Exception if handler is not registered
* (Always, even if asserts are disabled)
*
* Returns:
* The handler that has been disconnected
*
* Examples:
* --------------------------------
* @safe void handler() {};
* Signal!() onTest;
* onTest.connect(&handler);
* onTest.disconnect(&handler);
* onTest.connect(&handler);
* onTest();
* --------------------------------
*/
@trusted T disconnect(T)(T handler) if(isHandler!(T, Types))
{
Callable call = getCallable(handler);
auto pos = find(handlers[], call);
if(pos.empty)
{
throw new Exception("Handler is not connected");
}
handlers.stableLinearRemove(take(pos, 1));
return handler;
}
/**
* Remove all handlers from the signal
*
* Examples:
* --------------------------------
* @safe void handler() {};
* Signal!() onTest;
* assert(onTest.calculateLength() == 0);
* onTest.connect(&handler);
* assert(onTest.calculateLength() == 1);
* onTest.clear();
* assert(onTest.calculateLength() == 0);
* onTest();
* --------------------------------
*/
@trusted void clear()
{
handlers.clear();
}
/**
* Calculate the number of registered handlers
*
* Complexity: $(BIGOH n)
*
* Examples:
* --------------------------------
* @safe void handler() {};
* @safe void handler2() {};
* Signal!() onTest;
* assert(onTest.calculateLength() == 0);
* onTest.connect(&handler);
* assert(onTest.calculateLength() == 1);
* onTest.connect(&handler2);
* assert(onTest.calculateLength() == 2);
* onTest.clear();
* assert(onTest.calculateLength() == 0);
* onTest();
* --------------------------------
*/
@trusted uint calculateLength()
{
return walkLength(handlers[]);
}
/**
* Just like Signal.connect()
*/
@safe T opOpAssign(string op, T)(T rhs) if(op == "~" && isHandler!(T, Types))
{
return connect!(T)(rhs);
}
/**
* Call the connected handlers as explained in the documentation
* for the signal struct.
*
* Throws:
* Exceptions thrown in the signal handlers
*
* Examples:
* --------------------------------
* @safe void handler() {};
* Signal!() onTest;
* onTest.connect(&handler);
* onTest.emit();
* --------------------------------
*/
@trusted void emit(Types params)
{
if(this.enabled)
{
foreach(callable; handlers[])
{
if(callable.returnsBool)
{
slot_t del = cast(slot_t)callable.deleg;
if(!del(params))
return;
}
else
{
void_slot_t del = cast(void_slot_t)callable.deleg;
del(params);
}
}
}
}
/**
* Just like emit()
*/
@trusted void opCall(Types params)
{
emit(params);
}
}
//unit tests
unittest
{
int val;
string text;
@safe void handler(int i, string t)
{
val = i;
text = t;
}
@safe static void handler2(int i, string t)
{
}
Signal!(int, string) onTest;
onTest.connect(&handler);
onTest.connect(&handler2);
onTest(1, "test");
assert(val == 1);
assert(text == "test");
onTest(99, "te");
assert(val == 99);
assert(text == "te");
}
unittest
{
@safe void handler() {}
Signal!() onTest;
onTest.connect(&handler);
bool thrown = false;
try
onTest.connect(&handler);
catch(Throwable)
thrown = true;
assert(thrown);
}
unittest
{
@safe void handler() {};
Signal!() onTest;
onTest.connect(&handler);
onTest.disconnect(&handler);
onTest.connect(&handler);
onTest();
}
unittest
{
bool called = false;
@safe void handler() { called = true; };
Signal!() onTest;
onTest ~= &handler;
onTest.disconnect(&handler);
onTest ~= &handler;
onTest();
assert(called);
}
unittest
{
class handler
{
@safe void opCall(int i) {}
}
struct handler2
{
@safe void opCall(int i) {}
}
Signal!(int) onTest;
onTest ~= new handler;
auto h = onTest ~= new handler2;
onTest(0);
onTest.disconnect(h);
}
unittest
{
__gshared bool called = false;
struct A
{
string payload;
@trusted void opCall(float f, string s)
{
assert(payload == "payload");
assert(f == 0.1234f);
assert(s == "test call");
called = true;
}
}
A* a = new A();
a.payload = "payload";
Signal!(float, string) onTest;
onTest.connect(a);
onTest(0.1234f, "test call");
assert(called);
}
unittest
{
__gshared bool called;
struct A
{
string payload;
@trusted void opCall(float f, string s)
{
assert(payload == "payload 2");
called = true;
}
}
A* a = new A();
a.payload = "payload";
Signal!(float, string) onTest;
onTest.connect(a);
A* b = new A();
b.payload = "payload 2";
onTest.connect(b);
onTest.disconnect(a);
onTest(0.1234f, "test call");
assert(called);
}
unittest
{
struct A
{
@safe void opCall() {}
}
A* a = new A();
Signal!() onTest;
onTest.connect(a);
bool thrown = false;
try
onTest.connect(a);
catch(Throwable)
thrown = true;
assert(thrown);
}
unittest
{
struct A
{
@safe void opCall() {}
}
A* a = new A();
Signal!() onTest;
onTest.connect(a);
onTest.disconnect(a);
bool thrown = false;
try
onTest.disconnect(a);
catch(Throwable)
thrown = true;
assert(thrown);
}
unittest
{
struct A
{
@safe void opCall() {}
}
A* a = new A();
Signal!() onTest;
bool thrown = false;
try
onTest.disconnect(a);
catch(Throwable)
thrown = true;
assert(thrown);
}
unittest
{
bool secondCalled = false;
@safe bool first(int i) {return false;}
@safe void second(int i) {secondCalled = true;}
Signal!(int) onTest;
onTest ~= &first;
onTest ~= &second;
onTest(0);
assert(!secondCalled);
onTest.disconnect(&first);
onTest ~= &first;
onTest(0);
assert(secondCalled);
}
unittest
{
@safe void second(int i) {}
Signal!(int) onTest;
auto t1 = onTest.getCallable(&second);
auto t2 = onTest.getCallable(&second);
auto t3 = onTest.getCallable(&second);
assert(t1 == t2);
assert(t2 == t3);
}
unittest
{
bool called = false;
@safe void handler() { called = true; };
Signal!() onTest;
onTest ~= &handler;
onTest();
assert(called);
called = false;
onTest.enabled = false;
onTest();
assert(!called);
onTest.enabled = true;
onTest();
assert(called);
}
unittest
{
@safe void handler() {};
Signal!() onTest;
assert(!onTest.isConnected(&handler));
onTest ~= &handler;
assert(onTest.isConnected(&handler));
onTest();
assert(onTest.isConnected(&handler));
onTest.disconnect(&handler);
assert(!onTest.isConnected(&handler));
onTest();
assert(!onTest.isConnected(&handler));
}
unittest
{
bool firstCalled, secondCalled, thirdCalled;
@safe void handler1() {firstCalled = true;}
@safe void handler2()
{
secondCalled = true;
assert(firstCalled);
assert(thirdCalled);
}
@safe void handler3()
{
thirdCalled = true;
assert(firstCalled);
assert(!secondCalled);
}
Signal!() onTest;
onTest ~= &handler1;
onTest ~= &handler2;
auto h = onTest.connectAfter(&handler1, &handler3);
assert(h == &handler3);
onTest();
assert(firstCalled && secondCalled && thirdCalled);
}
unittest
{
bool firstCalled, secondCalled;
@safe void handler1() {firstCalled = true;}
@safe void handler2()
{
secondCalled = true;
assert(firstCalled);
}
Signal!() onTest;
onTest ~= &handler2;
onTest.connectFirst(&handler1);
onTest();
assert(firstCalled && secondCalled);
}
unittest
{
bool firstCalled, secondCalled, thirdCalled;
@safe void handler1() {firstCalled = true;}
@safe void handler2()
{
secondCalled = true;
assert(firstCalled);
assert(!thirdCalled);
}
@safe void handler3()
{
thirdCalled = true;
assert(firstCalled);
assert(secondCalled);
}
Signal!() onTest;
onTest ~= &handler2;
auto h = onTest.connectAfter(&handler2, &handler3);
assert(h == &handler3);
auto h2 = onTest.connectBefore(&handler2, &handler1);
assert(h2 == &handler1);
onTest();
assert(firstCalled && secondCalled && thirdCalled);
firstCalled = secondCalled = thirdCalled = false;
onTest.disconnect(h);
onTest.disconnect(h2);
onTest.disconnect(&handler2);
onTest ~= &handler1;
onTest ~= &handler3;
onTest.connectBefore(&handler3, &handler2);
onTest();
assert(firstCalled && secondCalled && thirdCalled);
}
unittest
{
@safe void handler() {};
Signal!() onTest;
assert(onTest.calculateLength() == 0);
onTest.connect(&handler);
assert(onTest.calculateLength() == 1);
onTest.clear();
assert(onTest.calculateLength() == 0);
onTest();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment