On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
I needed some C# style events, so I rolled my own.

The following is my current event implementation. I was able to make it thread safe by including an optional spin-lock. Of course, that extra spinlock has to be included in every single event, which has a pointlessly high memory cost, even when no handlers are attached to the event. Also, having this event call it's host class back when events are added/removed would require even MORE wasted memory by storing extra delegates. I've thoroughly explored the idea of a library-implemented event, and the downsides are not fixable.


struct Event(Handler, bool atomic = false)
    if(is(Handler == delegate) && is(ReturnType!Handler == void))
{
    Handler[] _handlers;

    static if(atomic)
    {
        Spinlock _lock;
        @disable this(this);
    }

    ref auto opOpAssign(string op, H)(H handler)
        if(op == "+")
    {
        static if(atomic) auto lk = lock(_lock);
        _handlers ~= toDelegate(handler);
        return this;
    }

    ref auto opOpAssign(string op, H)(H handler)
        if(op == "-")
    {
        static if(atomic) auto lk = lock(_lock);

        auto del = toDelegate(handler);
        foreach(ref handler; _handlers)
        {
            if(handler == del) {
_handlers = _handlers.remove(&handler - _handlers.ptr);
                break;
            }
        }

        return this;
    }

    void opCall()(Parameters!Handler args)
    {
        static if(atomic)
        {
            Handler[] tmp;

            if(_handlers.length <= 64)
            {
                auto lk = lock(_lock);
                size_t sz = _handlers.length * Handler.sizeof;
                tmp = cast(Handler[])(alloca(sz)[0..sz]);
                tmp[] = _handlers[];
            }
            else
            {
                auto lk = lock(_lock);
                tmp = _handlers.dup;
            }

            foreach(ref handler; tmp)
                handler(args);
        }
        else
        {
            foreach(ref handler; _handlers)
                handler(args);
        }
    }

    bool opCast(T : bool)() {
        static if(atomic) auto lk = lock(_lock);
        return _handlers.length != 0;
    }

    void clear() {
        static if(atomic) auto lk = lock(_lock);
        _handlers.length = 0;
    }

    bool empty() {
        static if(atomic) auto lk = lock(_lock);
        return _handlers.length == 0;
    }
}

Reply via email to