August 29, 2017
On Tuesday, 29 August 2017 at 20:27:11 UTC, kinke wrote:
> 
> I like the C# event syntax too and came up with the following D analogon, just to prove that a primitive library-based solution in D is doable in 35 lines and can offer as much comfort as C# here.

My current implementation looks basically the same, which is what prompted me to create this thread. Neither of our implementations address the issues I've stated above - and those issues aren't trivial corner cases either.
August 30, 2017
On Tuesday, 29 August 2017 at 05:10:25 UTC, bitwise wrote:
> I needed some C# style events, so I rolled my own. Long story short, the result was unsatisfactory.
>

>
> `
> Foo foo;
> foo.onEvent += (int n) => writeln(n);
> foo.onEvent += &baz;
> foo.onEvent -= &baz;
>
> if(foo.onEvent)
>     foo.onEvent(1);
> `
>

I implemented one:


bt_ok = new IupButton("&OK");
        bt_ok.padding = Size(10,2);
bt_ok.click += &bt_ok_click;

private void bt_ok_click(Object sender, CallbackEventArgs e)
{
  string v = textBox.text;
}


See also:
https://github.com/Heromyth/Iup4D/blob/master/Examples/SimpleDemo/main.d
https://github.com/Heromyth/Iup4D/blob/master/Iup4D/toolkit/event.d
August 30, 2017
https://dpaste.dzfl.pl/f7c5fc49d80f Like this. If you need locking, write another mixin, it's just a very small convenience wrapper.
August 30, 2017
On Wednesday, 30 August 2017 at 14:46:12 UTC, Kagamin wrote:
> https://dpaste.dzfl.pl/f7c5fc49d80f Like this. If you need locking, write another mixin, it's just a very small convenience wrapper.

I don't understand how this helps.

-What if I want an event to lock a shared mutex of the enclosing object, without storing a pointer to that mutex inside the event itself (and every single other event in the object)?

-What if I want an event to call a method of the enclosing object when a handler is added (without keeping a pointer to it inside the actual event)?

Please let me know if these questions are unclear. I stated these concerns in the original post, and they have yet to even be acknowledged by any of the responses here - so I'm finding this a bit confusing at this point.

August 31, 2017
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;
    }
}

August 31, 2017
On Wednesday, 30 August 2017 at 15:35:57 UTC, bitwise wrote:
> -What if I want an event to lock a shared mutex of the enclosing object, without storing a pointer to that mutex inside the event itself (and every single other event in the object)?
>
> -What if I want an event to call a method of the enclosing object when a handler is added (without keeping a pointer to it inside the actual event)?

So in essence, you'd like something like this to work, right?

struct Event(alias __parent, Handler) {
    enum parentHasLock = __traits(compiles, __parent.lock());
    ...
    void opCall()(Parameters!Handler args)
    {
        static if (parentHasLock)
            __parent.lock();
        ...
    }
}

struct Host1 {
    Event!Handler onChanged;
    Event!Handler onClosed;
}

and have the compiler internally instantiate something like

Event!(/* parent type */ Host1, /* .offsetof in parent in order to deduce the __parent address from Event's &this */ 0, Handler)
Event!(Host1, N, Handler)
August 31, 2017
On Thursday, 31 August 2017 at 19:36:00 UTC, kinke wrote:
> On Wednesday, 30 August 2017 at 15:35:57 UTC, bitwise wrote:
>> -What if I want an event to lock a shared mutex of the enclosing object, without storing a pointer to that mutex inside the event itself (and every single other event in the object)?
>>
>> -What if I want an event to call a method of the enclosing object when a handler is added (without keeping a pointer to it inside the actual event)?
>
> So in essence, you'd like something like this to work, right?
>
> struct Event(alias __parent, Handler) {
>     enum parentHasLock = __traits(compiles, __parent.lock());
>     ...
>     void opCall()(Parameters!Handler args)
>     {
>         static if (parentHasLock)
>             __parent.lock();
>         ...
>     }
> }
>
> struct Host1 {
>     Event!Handler onChanged;
>     Event!Handler onClosed;
> }
>
> and have the compiler internally instantiate something like
>
> Event!(/* parent type */ Host1, /* .offsetof in parent in order to deduce the __parent address from Event's &this */ 0, Handler)
> Event!(Host1, N, Handler)

Something like that ;)

I played around with this idea while trying to create a library implementation of properties for C++. `offsetof` in C++ is unsafe though, which I think was due to how multiple inheritance works. It was only recently allowed by the standard, with limitations that make it all but useless:

See restrictions on "standard layout class"
http://www.cplusplus.com/reference/cstddef/offsetof/

IMO though, this path is still fraught with peril, even if it works in D. The declaration of an event would have to be very noisy, and full of extra template parameters that only existed to supplement the underlying hack. And it still wouldn't allow invocation of host object code, without even more painful bloat.

1 2
Next ›   Last »