view qt/d2/qt/Signal.d @ 282:256ab6cb8e85

Signals look-up andNew syntax for connect. The old one will not work from now on. This will allow for the signals overload. Although changes are done for both D1 and D2 versions, D1 won't work because of compiler bugs. I am tired of waiting for fixes.
author eldar
date Fri, 16 Oct 2009 02:43:59 +0000
parents 519befd5a5d1
children 1f6923c8cba0
line wrap: on
line source

/**
 *
 *  Copyright: Copyright QtD Team, 2008-2009
 *  Authors: Max Samukha, Eldar Insafutdinov
 *  License: <a href="http://www.boost.org/LICENSE_1_0.txt>Boost License 1.0</a>
 *
 *  Copyright QtD Team, 2008-2009
 *  Distributed under the Boost Software License, Version 1.0.
 *  (See accompanying file boost-license-1.0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 *
 */
module qt.Signal;

public import qt.QGlobal;
public import
    std.metastrings,
    std.typetuple;
import core.stdc.stdlib : crealloc = realloc, cfree = free;
import core.stdc.string : memmove;
import
    std.traits,
    core.thread,
    core.exception;

private: // private by default

alias void delegate(Object) DEvent;

extern(C) void rt_attachDisposeEvent(Object o, DEvent e);
extern(C) void rt_detachDisposeEvent(Object o, DEvent e);
extern(C) Object _d_toObject(void* p);

void realloc(T)(ref T[] a, size_t length)
{
    a = (cast(T*)crealloc(a.ptr, length * T.sizeof))[0..length];
    if (!a.ptr)
        new OutOfMemoryError(__FILE__, __LINE__);
}


void append(T)(ref T[] a, T element)
{
    auto newLen = a.length + 1;
    a = (cast(T*)crealloc(a.ptr, newLen * T.sizeof))[0..newLen];
    if (!a.ptr)
        new OutOfMemoryError(__FILE__, __LINE__);
    a[newLen - 1] = element;
}

void move(T)(ref T[] a, size_t src, size_t dest, size_t length)
{
    if (a.length > 1)
        memmove(a.ptr + dest, a.ptr + src, length * T.sizeof);
}

// COMPILER BUG: Though this is private cannot name it 'remove' because of conflicts
// with Array.remove
void erase(T)(ref T[] a, size_t i) 
{
    auto newLen = a.length - 1;
    move(a, i + 1, i, newLen);
    realloc(a, newLen);
}

version (QtdUnittest)
{
    unittest
    {
        int[] a;
        realloc(a, 16);
        assert(a.length == 16);
        foreach (i, ref e; a)
            e = i;
        realloc(a, 4096);
        assert(a.length == 4096);
        foreach (i, e; a[0..16])
            assert(e == i);
        cfree(a.ptr);
    }
}

//TODO: should be in the standard library
struct STuple(A...)
{
    static string genSTuple()
    {
        string r = "";
        foreach (i, e; A)
            r ~= A[i].stringof ~ " _" ~ ToString!(i) ~ ";";
        return r;
    }

    mixin (genSTuple);
    template at(size_t i) { mixin("alias _" ~ ToString!(i) ~ " at;"); };
}

enum SignalEventId
{
    firstSlotConnected,
    lastSlotDisconnected
}

public class SignalException : Exception
{
    this(string msg)
    {
        super(msg);
    }
}

struct Fn
{
    void* funcptr;

    static typeof(this) opCall(R, A...)(R function(A) fn)
    {
        typeof(this) r;
        r.funcptr = fn;
        return r;
    }

    template call(R)
    {
        R call(A...)(A args)
        {
            alias R function(A) Fn;
            return (cast(Fn)funcptr)(args);
        }
    }

    S get(S)()
    {
        static assert (is(typeof(*S.init) == function));
        return cast(S)funcptr;
    }
}

struct Dg
{
    void* context;
    void* funcptr;

    static typeof(this) opCall(R, A...)(R delegate(A) dg)
    {
        typeof(this) r;
        r.context = dg.ptr;
        r.funcptr = dg.funcptr;
        return r;
    }

    template call(R)
    {
        R call(A...)(A args)
        {
            R delegate(A) dg; // BUG: parameter storage classes are ignored
            dg.ptr = context;
            dg.funcptr = cast(typeof(dg.funcptr))funcptr;
            return dg(args);
        }
    }

    S get(S)()
    {
        static assert (is(S == delegate));
        S r;
        r.ptr = context;
        r.funcptr = cast(typeof(r.funcptr))funcptr;
        return r;
    }
}

struct Slot(R)
{
    alias R Receiver;

    Receiver receiver;
    Dg invoker;
    ConnectionFlags flags;
    
    static if (is(Receiver == Dg))
    {
        static const isDelegate = true;
                
        bool isDisposed()
        {
            return !receiver.funcptr;
        }
        
        void dispose()
        {
            receiver.funcptr = null;
            receiver.context = null;
        }

        Object getObject()
        {           
            return flags & ConnectionFlags.NoObject || !receiver.context
                ? null : _d_toObject(receiver.context);
        }
    }
    else
        static const isDelegate = false;
}

enum SlotListId
{
    Func, // function pointers
    Weak, // object delegates stored in C heap
    Strong // delegates stored in GC heap
}

/**
    Used to specify the type of a signal-to-slot connection.

    Examples:
----
class Sender
{
    mixin Signal!("changed");
    void change()
    {
        changed.emit;
    }
}


class Receiver
{
    void alarm() {}
}

void main()
{
    auto s = new Sender;
    auto r = new Receiver;
    s.changed.connect(&r.alarm); // now s weakly references r

    r = null;
    // collect garbage (assume there is no more reachable pointers
    // to the receiver and it gets finalized)
    ...

    s.change;
    // weak reference to the receiving object
    // has been removed from the sender's connection lists.

    r = new Receiver;
    s.changed.connect(&r.alarm, ConnectionFlags.Strong);

    r = null;
    // collect garbage
    ...
    // the receiving object has not been finalized because s strongly references it.

    s.change; // the receiver is called.
    delete r;
    s.change; // the receiver is disconnected from the sender.

    static void foo()
    {
    }

    s.changed.connect(&foo);
    s.changed.emit; // foo is called.
    s.changed.disconnect(&foo); // must be explicitly disconnected.

    void bar()
    {
    }

    // ConnectionFlags.NoObject must be specified for delegates
    // to non-static local functions or struct member functions.
    s.changed.connect(&bar, ConnectionFlags.NoObject);
    s.changed.emit; // bar is called.
    s.changed.disconnect(&bar); // must be explicitly disconnected.
}
----
*/
public enum ConnectionFlags : ubyte
{
    ///
    None,
    /**
        The receiver will be stored as weak reference (implied if ConnectionFlags.NoObject is not specified).
        If the signal receiver is not a function pointer or a delegate referencing a D class instance.
        the sender will not be notified when the receiving object is deleted and emitting the signal
        connected to that receiving object will result in undefined behavior.
    */
    Weak                = 0x0001,
    /**
        The receiver is stored as strong reference (implied if ConnectionFlags.NoObject is specified).
    */
    Strong              = 0x0002,
    /**
        Must be specified if the receiver is not a function pointer or a delegate referencing a D class instance.
    */
    NoObject            = 0x0004

    // Queued           = 0x0004,
    // BlockingQueued   = 0x0008
}


struct SlotList(SlotT, bool strong = false)
{
    alias SlotT SlotType;
    SlotType[] data;

    void length(size_t length)
    {
        static if (strong)
            data.length = length;
        else
            realloc(data, length);
    }

    SlotType* add(SlotType slot)
    {
        auto oldLen = data.length;
        length = oldLen + 1;
        auto p = &data[oldLen];
        *p = slot;
        return p;
    }

    SlotType* get(int slotId)
    {
        return &data[slotId];
    }

    void remove(int slotId)
    {
        move(data, slotId, slotId + 1, data.length - slotId - 1);
        data = data[0..$ - 1];
    }

    size_t length()
    {
        return data.length;
    }

    void free()
    {
        static if (!strong)
            cfree(data.ptr);
    }
}

public alias void delegate(int signalId, SignalEventId event) SignalEvent;

struct Receivers
{
    struct Data
    {
        Object object;
        int refs;
    }
    
    Data[] data;
    void add(Object receiver, DEvent disposeEvent)
    {        
        foreach (ref d; data)
        {
            if (d.object is receiver)
            {               
                d.refs++;              
                return;
            }
        }
        
        append(data, Data(receiver, 1));
        rt_attachDisposeEvent(receiver, disposeEvent);
    }
    
    void remove(Object receiver, DEvent disposeEvent)
    {
        foreach (i, ref d; data)
        {
            if (d.object is receiver)
            {
                assert (d.refs);
                d.refs--;
                if (!d.refs)
                {
                    .erase(data, i);                    
                    rt_detachDisposeEvent(receiver, disposeEvent);
                }
                return;
            }
        }
        
        assert (false);
    }
    
    // remove all refarences for receiver, receiver has been disposed
    void removeAll(Object receiver)
    {
        foreach (i, ref d; data)
        {
            if (d.object is receiver) 
            {
                .erase(data, i);
                return;
            }
        }
    }
    
    // remove all references for all receivers, detaching dispose events
    void free(DEvent disposeEvent)
    {
        foreach (i, ref d; data)
            rt_detachDisposeEvent(d.object, disposeEvent);
        cfree(data.ptr);
        data = null;
    }
}

struct SignalConnections
{
    bool isInUse;

    STuple!(
        SlotList!(Slot!(Fn)),
        SlotList!(Slot!(Dg)),
        SlotList!(Slot!(Dg), true)
    ) slotLists;

    STuple!(
        Fn[],
        Dg[]
    ) delayedDisconnects;

    void addDelayedDisconnect(Fn r)
    {
        delayedDisconnects.at!(0) ~= r;
    }

    void addDelayedDisconnect(Dg r)
    {
        delayedDisconnects.at!(1) ~= r;
    }

    SlotListType!(slotListId)* getSlotList(int slotListId)()
    {
        return &slotLists.tupleof[slotListId];
    }

    bool hasSlots()
    {
        foreach(i, e; slotLists.tupleof)
        {
            if (slotLists.tupleof[i].length)
                return true;
        }
        return false;
    }

    int slotCount()
    {
        int count;
        foreach(i, e; slotLists.tupleof)
            count += slotLists.at!(i).length;
        return count;
    }

    void slotListLengths(int[] lengths)
    {
        foreach(i, e; slotLists.tupleof)
             lengths[i] = slotLists.at!(i).length;
    }

    SlotType!(slotListId)* addSlot(int slotListId)(SlotType!(slotListId) slot)
    {
        return getSlotList!(slotListId).add(slot);
    }

    void removeSlot(int slotListId)(int slotId)
    {
        slotLists.at!(slotListId).remove(slotId);
    }

    void free()
    {
        foreach(i, e; slotLists.tupleof)
        {
            static if (is(typeof(slotLists.at!(i).free)))
                slotLists.at!(i).free;
        }
    }
    
    void onReceiverDisposed(Object receiver)
    {
        foreach (i, e; slotLists.tupleof)
        {
            static if (slotLists.at!(i).SlotType.isDelegate)
            {
                foreach (ref slot; slotLists.at!(i).data)
                {
                    if (slot.getObject is receiver)
                        slot.dispose;
                }
            }
        }
    }

    template SlotListType(int slotListId)
    {
        alias typeof(slotLists.tupleof)[slotListId] SlotListType;
    }

    template SlotType(int slotListId)
    {
        alias SlotListType!(slotListId).SlotType SlotType;
    }

    template ReceiverType(int slotListId)
    {
        alias SlotType!(slotListId).Receiver ReceiverType;
    }

    static const slotListCount = slotLists.tupleof.length;
}


private Object signalSender_;

/**
    If called from a slot, returns the object
    that is emitting the signal. Otherwise, returns null.
*/
public Object signalSender() {
    return signalSender_;
}

public final class SignalHandler
{
    SignalConnections[] connections;
    Receivers receivers;
    Object owner;
    int blocked;
    
    SignalEvent signalEvent;
       
    alias SignalConnections.SlotType SlotType;
    alias SignalConnections.ReceiverType ReceiverType;

    public this(Object owner_) {
        owner = owner_;
    }

    private SignalConnections* getConnections(int signalId)
    {
        if (signalId < connections.length)
            return &connections[signalId];
        return null;
    }

    private SlotType!(slotListId)* addSlot(int slotListId)(int signalId, ReceiverType!(slotListId) receiver,
        Dg invoker, ConnectionFlags flags)
    {
        if (signalId >= connections.length)
            connections.length = signalId + 1;
        auto slot = connections[signalId].addSlot!(slotListId)(SlotType!(slotListId)(receiver, invoker));
        
        static if (slot.isDelegate)
        {
            if (!(flags & ConnectionFlags.NoObject))
                receivers.add(_d_toObject(receiver.context), &onReceiverDisposed);
        }

        if (signalEvent && connections[signalId].slotCount == 1)
            signalEvent(signalId, SignalEventId.firstSlotConnected);

        return slot;
    }
    
    void onReceiverDisposed(Object receiver)
    {
        synchronized(this)
        {
            foreach(ref c; connections)
                c.onReceiverDisposed(receiver);
            receivers.removeAll(receiver);
        }
    }

    private void removeSlot(int slotListId)(int signalId, int slotId)
    {
        auto slot = connections[signalId].getSlotList!(slotListId).get(slotId);
        static if (slot.isDelegate)
        {
            if (auto obj = slot.getObject)
                receivers.remove(obj, &onReceiverDisposed);
        }
        
        connections[signalId].removeSlot!(slotListId)(slotId);

        if (signalEvent && !connections[signalId].slotCount)
            signalEvent(signalId, SignalEventId.lastSlotDisconnected);
    }

    size_t slotCount(int signalId)
    {
        synchronized(this)
        {
            auto con = getConnections(signalId);
            if (con)
                return con.slotCount;
            return 0;
        }
    }

    void connect(Receiver)(int signalId, Receiver receiver,
        Dg invoker, ConnectionFlags flags)
    {
        synchronized(this)
        {
            static if (is(typeof(receiver.context)))
            {
                Object obj;
                if ((flags & ConnectionFlags.NoObject))
                {
                    // strong by default
                    if (flags & ConnectionFlags.Weak)
                        addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
                    else
                        addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
                }
                else
                {
                    // weak by default
                    if (flags & ConnectionFlags.Strong)
                        addSlot!(SlotListId.Strong)(signalId, receiver, invoker, flags);
                    else
                        addSlot!(SlotListId.Weak)(signalId, receiver, invoker, flags);
                }
            }
            else
                addSlot!(SlotListId.Func)(signalId, receiver, invoker, flags);
        }
    }

    void disconnect(Receiver)(int signalId, Receiver receiver)
    {
        synchronized(this)
        {
            auto cons = getConnections(signalId);
            if (!cons)
                return;

            // if called from a slot being executed by this signal, delay disconnection
            // until all slots has been called.
            if (cons.isInUse)
            {
                cons.addDelayedDisconnect(receiver);
                return;
            }

        TOP:
            foreach (slotListId, e; cons.slotLists.tupleof)
            {
                /// COMPILER BUG: ReceiverType is evaluated to expression instead of type.
                static if (is(typeof(cons.ReceiverType!(slotListId)) == Receiver))
                {
                    auto slotList = cons.getSlotList!(slotListId);
                    for (int slotId; slotId < slotList.length;)
                    {
                        auto slot = slotList.get(slotId);
                        static if (slot.isDelegate)
                        {
                            if (slot.isDisposed)
                            {
                                removeSlot!(slotListId)(signalId, slotId);
                                continue;
                            }
                        }

                        if (slot.receiver == receiver)
                        {
                            removeSlot!(slotListId)(signalId, slotId);
                            break TOP;
                        }

                        slotId++;
                    }
                }
            }
        }
    }

    void emit(A...)(size_t signalId, A args)
    {
        synchronized(this)
        {
            if (signalId >= connections.length || blocked)
                return;
            auto cons = &connections[signalId];

            if (cons.hasSlots)
            {
                {
                    cons.isInUse = true;
                    signalSender_ = owner;
                    scope(exit)
                    {
                        cons.isInUse = false;
                        signalSender_ = null;
                    }

                    // Store the lengths to avoid calling new slots
                    // connected in the slots being called.
                    // dmd bug: int[cons.slotListCount] fails
                    static const c = cons.slotListCount;
                    int[c] lengths = void;
                    cons.slotListLengths(lengths);

                    foreach (slotListId, e; cons.slotLists.tupleof)
                    {
                        auto slotList = cons.getSlotList!(slotListId);
                        for (size_t slotId; slotId < lengths[slotListId];)
                        {
                            auto slot = slotList.get(slotId);
                            static if (slot.isDelegate)
                            {
                                if (slot.isDisposed)
                                {
                                    removeSlot!(slotListId)(signalId, slotId);
                                    lengths[slotListId]--;
                                    continue;
                                }
                            }

                            slot.invoker.call!(void)(slot.receiver, args);
                            ++slotId;
                        }
                    }
                }


                // process delayed disconnects if any
                foreach(i, e; cons.delayedDisconnects.tupleof)
                {
                    if (cons.delayedDisconnects.at!(i).length)
                    {
                        foreach (d; cons.delayedDisconnects.at!(i))
                            disconnect(signalId, d);
                        cons.delayedDisconnects.at!(i).length = 0;
                    }
                }
            }
        }
    }

    // Adjusts signal arguments and calls the slot. S - slot signature, A - signal arguments
    private void invokeSlot(S, Receiver, A...)(Receiver r, A args)
    {
        r.get!(S)()(args[0..ParameterTypeTuple!(S).length]);
    }

    void blockSignals()
    {
        synchronized(this)
            blocked++;
    }

    void unblockSignals()
    {
        synchronized(this)
        {
            if(!blocked)
                throw new SignalException("Signals are not blocked");
            blocked--;
        }
    }

    ~this()
    {
        receivers.free(&onReceiverDisposed);
        foreach(ref c; connections)
            c.free;
    }
}

//TODO: this could be avoided if named mixins didn't suck.
public struct SignalOps(int sigId, A...)
{
    private SignalHandler sh;
    enum { signalId = sigId }

    void emit(A args)
    {
        sh.emit(signalId, args);
    }

    debug size_t slotCount()
    {
        return sh.slotCount(signalId);
    }
}

public template SignalHandlerOps()
{
    static assert (is(typeof(this.signalHandler)),
        "SignalHandlerOps is already instantiated in " ~ typeof(this).stringof ~ " or one of its base classes");

protected:
    SignalHandler signalHandler_; // manages signal-to-slot connections

    final SignalHandler signalHandler()
    {
        if (!signalHandler_)
        {
            signalHandler_ = new SignalHandler(this);
            onSignalHandlerCreated(signalHandler_);
        }
        return signalHandler_;
    }

    void onSignalHandlerCreated(ref SignalHandler sh)
    {
    }

public:
    final void blockSignals()
    {
        signalHandler.blockSignals();
    }

    final void unblockSignals()
    {
        signalHandler.unblockSignals();
    }

    template connect(string signalName, A...)
    {
        static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
            if (isFnOrDg!(Func))
        {
            alias findSignal!(T, signalName, Func, A).result sig;
            auto sh = sender.signalHandler();
            static if (isFn!(Func))
                alias Fn Callable;
            else
                alias Dg Callable;
            auto invoker = Dg(&sh.invokeSlot!(typeof(func), Callable, sig[2..$]));
            sh.connect(sig[1], Callable(func), invoker, flags);
        }
    }

    template disconnect(string signalName, A...)
    {
        static void connect(T, Func)(T sender, Func func, ConnectionFlags flags = ConnectionFlags.None)
            if (isFnOrDg!(Func))
        {
            alias findSignal!(T, signalName, Func, A).result sig;
            auto sh = sender.signalHandler();
            static if (isFn!(Func))
                alias Fn Callable;
            else
                alias Dg Callable;
            sh.disconnect(sig[1], Callable(func));
        }
    }
/*
    template slotCount(string signalName, A...)
    {
        debug static void slotCount(T)(T sender)
        {
            alias findSignal!(T, signalName, Func, A).result sig;
            auto sh = sender.signalHandler();
            return sh.slotCount(sig[1]);
        }
    }
    */
}

/**
    New implementation.
*/

const string signalPrefix = "__signal";

template TupleWrapper(A...) { alias A at; }

template isDg(Dg)
{
    enum isDg = is(Dg == delegate);
}

template isFn(Fn)
{
    enum isFn = is(typeof(*Fn.init) == function);
}

template isFnOrDg(Dg)
{
    enum isFnOrDg = isFn!(Dg) || isDg!(Dg);
}

string joinArgs(A...)()
{
    string res = "";
    static if(A.length)
    {
        res = A[0].stringof;
        foreach(k; A[1..$])
            res ~= "," ~ k.stringof;
    }
    return res;
}

template SlotPred(T1, T2)
{
    enum SlotPred = is(T1 : T2);
}

template CheckSlot(alias Needle, alias Source)
{
    static if(Needle.at.length <= Source.at.length)
        enum CheckSlot = CheckArgs!(Needle, Source, SlotPred, 0).value;
    else
        enum CheckSlot = false;
}

template SignalPred(T1, T2)
{
    enum SignalPred = is(T1 == T2);
}

template CheckSignal(alias Needle, alias Source)
{
    static if(Needle.at.length == Source.at.length)
        enum CheckSignal = CheckArgs!(Needle, Source, SignalPred, 0).value;
    else
        enum CheckSignal = false;
}

template CheckArgs(alias Needle, alias Source, alias pred, int i)
{
    static if (i < Needle.at.length)
    {
        static if (pred!(Needle.at[i], Source.at[i]))
            enum value = CheckArgs!(Needle, Source, pred, i + 1).value;
        else
            enum value = false;
    }
    else
    {
        enum value = true;
    }
}

template SigByNamePred(string name, SlotArgs...)
{
    template SigByNamePred(source...)
    {
        static if (source[0] == name) // only instantiate CheckSlot if names match
            enum SigByNamePred = CheckSlot!(TupleWrapper!(SlotArgs), TupleWrapper!(source[2 .. $]));
        else
            enum SigByNamePred = false;
    }
}

template SigBySignPred(string name, SigArgs...)
{
    template SigBySignPred(source...)
    {
        static if (source[0] == name) // only instantiate CheckSignal if names match
            enum SigBySignPred = CheckSignal!(TupleWrapper!(SigArgs), TupleWrapper!(source[2 .. $]));
        else
            enum SigBySignPred = false;
    }
}

template staticSymbolName(string prefix, int id)
{
    const string staticSymbolName = prefix ~ ToString!(id);
}

template signatureString(string name, A...)
{
    const string signatureString = name ~ "(" ~ joinArgs!(A) ~ ")";
}

template findSymbolImpl(string prefix, C, int id, alias pred)
{
    static if ( is(typeof(mixin("C." ~ staticSymbolName!(prefix, id)))) )
    {
        mixin ("alias C." ~ staticSymbolName!(prefix, id) ~ " current;");
        static if (pred!current)
            alias current result;
        else
            alias findSymbolImpl!(prefix, C, id + 1, pred).result result;
    }
    else
    {
        alias void result;
    }
}

template findSymbol(string prefix, C, alias pred)
{
    alias findSymbolImpl!(prefix, C, 0, pred).result findSymbol;
}

template findSignal(C, string name, Receiver, SigArgs...)
{
    alias TupleWrapper!(ParameterTypeTuple!Receiver) SlotArgsWr;
    static if (SigArgs.length > 0)
    {
        alias findSymbol!(signalPrefix, C, SigBySignPred!(name, SigArgs)) result;
        static if (is(result == void))
            static assert(0, "Signal " ~ name ~ "(" ~ joinArgs!SigArgs() ~ ") was not found.");
        else
            static if (!CheckSlot!(SlotArgsWr, TupleWrapper!(result[2 .. $])))
                static assert(0, "Signature of slot is incompatible with signal " ~ name ~ ".");
    }
    else
    {
        alias findSymbol!(signalPrefix, C, SigByNamePred!(name, SlotArgsWr.at)) result;
        static if (is(result == void))
            static assert(0, "Signal " ~ name ~ " was not found.");
    }
}

/** ---------------- */


/**
    Examples:
----
struct Args
{
    bool cancel;
}

class C
{
    private int _x;
    // reference parameters are not supported yet,
    // so we pass arguments by pointer.
    mixin Signal!("xChanging", int, Args*);
    mixin Signal!("xChanged");

    void x(int v)
    {
        if (v != _x)
        {
            Args args;
            xChanging.emit(v, &args);
            if (!args.cancel)
            {
                _x = v;
                xChanged.emit;
            }
        }
    }
}
----
*/
template Signal(string name, A...)
{
    mixin SignalImpl!(0, name, A);
}

template SignalImpl(int index, string name, A...)
{
    static if (is(typeof(mixin(typeof(this).stringof ~ ".__signal" ~ ToString!(index)))))
        mixin SignalImpl!(index + 1, name, A);
    else
    {
        // mixed-in once
        static if (!is(typeof(this.signalHandler)))
        {
            mixin SignalHandlerOps;
        }
        mixin("public alias TypeTuple!(\"" ~ name ~ "\", index, A) __signal" ~ ToString!(index) ~ ";");
        mixin("SignalOps!(" ~ ToString!(index) ~ ", A) " ~ name ~ "(){ return SignalOps!("
            ~ ToString!(index) ~ ", A)(signalHandler); }");
    }
}

extern(C) alias void function(void*) SlotConnector;

debug (UnitTest)
{
    class A
    {
        mixin Signal!("scorched", int);

        int signalId1 = -1;
        int signalId2 = -1;

        void onFirstConnect(int sId)
        {
            signalId1 = sId;
        }

        void onLastDisconnect(int sId)
        {
            signalId2 = sId;
        }

        this()
        {
            signalHandler.firstSlotConnected = &onFirstConnect;
            signalHandler.lastSlotDisconnected = &onLastDisconnect;
        }
    }

    class B : A
    {
        mixin Signal!("booed", int);

        int bazSum;
        void baz(int i)
        {
            bazSum += i;
        }
    }

    class C : A
    {
        mixin Signal!("cooked");
    }
}

unittest
{
    static int fooSum;
    static int barSum;

    static void foo(int i)
    {
        fooSum += i;
    }

    void bar(long i)
    {
        barSum += i;
    }

    auto a = new A;
    auto b = new B;
    auto c = new C;
    assert(b.scorched.signalId == 0);
    assert(b.booed.signalId == 1);
    assert(c.cooked.signalId == 1);

    auto sh = b.signalHandler;

    b.scorched.connect(&foo);
    assert(sh.connections.length == 1);
    assert(b.signalId1 == 0);
    auto scCons = &sh.connections[0];

    assert(scCons.getSlotList!(SlotListId.Func).length == 1);
    b.scorched.emit(1);
    assert(fooSum == 1);

    b.scorched.connect(&bar, ConnectionFlags.NoObject);
    assert(sh.connections.length == 1);
    assert(scCons.getSlotList!(SlotListId.Strong).length == 1);
    b.scorched.emit(1);
    assert (fooSum == 2 && barSum == 1);

    b.scorched.connect(&b.baz);
    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
    b.scorched.emit(1);
    assert (fooSum == 3 && barSum == 2 && b.bazSum == 1);

    b.scorched.disconnect(&bar);
    assert(scCons.slotCount == 2);
    b.scorched.disconnect(&b.baz);
    assert(scCons.slotCount == 1);
    b.scorched.disconnect(&foo);
    assert(scCons.slotCount == 0);
    assert(b.signalId2 == 0);

    fooSum = 0;
    void connectFoo()
    {
        b.scorched.connect(&foo);
        b.scorched.disconnect(&connectFoo);
    }

    b.scorched.connect(&connectFoo, ConnectionFlags.NoObject);
    b.scorched.emit(1);
    assert(scCons.getSlotList!(SlotListId.Func).length == 1);
    assert(scCons.getSlotList!(SlotListId.Strong).length == 0);
    assert(!fooSum);

    auto r = new B();
    b.scorched.connect(&r.baz);
    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
    b.scorched.emit(1);
    assert(r.bazSum == 1);
    assert(fooSum == 1);

    delete(r);
    assert(scCons.getSlotList!(SlotListId.Weak).length == 1);
    b.scorched.emit(1);
    assert(scCons.getSlotList!(SlotListId.Weak).length == 0);
}