Jump to page: 1 2
Thread overview
[phobos] Improving std.signals
Sep 29, 2010
Johannes Pfau
Sep 30, 2010
Robert Jacques
Oct 07, 2010
Johannes Pfau
Oct 09, 2010
Sean Kelly
Oct 09, 2010
Robert Jacques
Oct 10, 2010
Johannes Pfau
Oct 11, 2010
Yao G.
Oct 11, 2010
Robert Jacques
Oct 11, 2010
Yao G.
Jan 03, 2011
Johannes Pfau
September 29, 2010
 Hi,
I needed signals for a simple test program some time ago and because I
wanted to learn D templates anyway I coded my own signal implementation.
I then saw phobos already had an implementation of signals and slot in
std.signals, but I was kinda disappointed by the implementation in
std.signals. D offers the possibilities to enhance it in many ways.

The limitations of std.signals:
*Only supports delegates, not even functions. And it should also support
objects/structs with opCall
*Only supports delegates of class objects.
The std.signal implementation attaches a DisposeEvent to the object of
the delegate. This only works for classes. It first sounds like a good
idea to automatically remove the delegate when the object is destroyed,
but: explicit delete is deprecated; the garbage collector should not
collect the object while the delegate is still registered; it limits
delegate support to class delegates; requiring the user to explicitly
remove the delegate is ok. Most(all?) signal implementations require
explicitly removing the signal handlers.
*Delegates return void. Most signal implementations have handlers return
bool. When a handler returns false, the remaining handlers are not called.
*Uses template mixins. Template structs work as well and give a nicer
syntax.

So I merged my signals implementation with std.signals. All problems
mentioned above have been fixed. I additionally added the following things:
*Support all handlers with void or bool return type. Using bool return
type is preferred though, because it's faster.
*Overloads for += and -= like in C#

I also came up with a pretty nice use case for structs with opCall:
Consider this common c# code:
public delegate void ChangedEventHandler(object sender, EventArgs e);
The sender object is part of the Signal type. Now, this won't work for
D, because structs aren't objects and signals can be at module level as
well.
But the struct + opcall support allows us to do this in a even better way:
struct Handler
{
    object _sender;
    bool opCall(string s)
    {
         //use _sender and s
    }
    this(object sender)
    {
        this._sender = sender;
    }
}
obj.onDatareceived.connect(new Handler(obj));

Using this technique we can pass any number of additional arguments.
This could also be used to build an signal->message passing dispatcher:
obj.onDatareceived.connect(new EventMessage!string(tid));

I then looked into the glib api documentation to see what additional
functionality it's signal handling offers.
It's basically:
 * Handler IDs.
this would allow us to add the same handler to a signal multiple times.
It also makes addafter/before easier, but it's not required for that.
I wonder whether phobos already has something like an IDPool, something
like this: http://ideone.com/xs80c
 * block/unblock complete signal (easy to do)
 * block/unblock handlers (shouldn't be too hard)
 * threading? (Is this useful/ needed?)
 * addafter / before (Adding handlers before a specific handler)
 * is handler connected? (easy)
 * stop signal emission (only useful when used with threading)

If the new code was accepted into phobos, I'd offer to implement the features above as well, if desired.

The proposed new std.signals code is located here: http://ideone.com/NHS5C

-- 
Johannes Pfau

September 30, 2010
On Wed, 29 Sep 2010 10:16:32 -0400, Johannes Pfau <johannespfau at googlemail.com> wrote:
>  Hi,
> I needed signals for a simple test program some time ago and because I
> wanted to learn D templates anyway I coded my own signal implementation.
> I then saw phobos already had an implementation of signals and slot in
> std.signals, but I was kinda disappointed by the implementation in
> std.signals. D offers the possibilities to enhance it in many ways.
>
> The limitations of std.signals:
> *Only supports delegates, not even functions. And it should also support
> objects/structs with opCall
> *Only supports delegates of class objects.
> The std.signal implementation attaches a DisposeEvent to the object of
> the delegate. This only works for classes. It first sounds like a good
> idea to automatically remove the delegate when the object is destroyed,
> but: explicit delete is deprecated; the garbage collector should not
> collect the object while the delegate is still registered; it limits
> delegate support to class delegates; requiring the user to explicitly
> remove the delegate is ok. Most(all?) signal implementations require
> explicitly removing the signal handlers.
> *Delegates return void. Most signal implementations have handlers return
> bool. When a handler returns false, the remaining handlers are not
> called.
> *Uses template mixins. Template structs work as well and give a nicer
> syntax.
>
> So I merged my signals implementation with std.signals. All problems
> mentioned above have been fixed. I additionally added the following
> things:
> *Support all handlers with void or bool return type. Using bool return
> type is preferred though, because it's faster.
> *Overloads for += and -= like in C#
>
> I also came up with a pretty nice use case for structs with opCall:
> Consider this common c# code:
> public delegate void ChangedEventHandler(object sender, EventArgs e);
> The sender object is part of the Signal type. Now, this won't work for
> D, because structs aren't objects and signals can be at module level as
> well.
> But the struct + opcall support allows us to do this in a even better
> way:
> struct Handler
> {
>     object _sender;
>     bool opCall(string s)
>     {
>          //use _sender and s
>     }
>     this(object sender)
>     {
>         this._sender = sender;
>     }
> }
> obj.onDatareceived.connect(new Handler(obj));
>
> Using this technique we can pass any number of additional arguments.
> This could also be used to build an signal->message passing dispatcher:
> obj.onDatareceived.connect(new EventMessage!string(tid));
>
> I then looked into the glib api documentation to see what additional
> functionality it's signal handling offers.
> It's basically:
>  * Handler IDs.
> this would allow us to add the same handler to a signal multiple times.
> It also makes addafter/before easier, but it's not required for that.
> I wonder whether phobos already has something like an IDPool, something
> like this: http://ideone.com/xs80c
>  * block/unblock complete signal (easy to do)
>  * block/unblock handlers (shouldn't be too hard)
>  * threading? (Is this useful/ needed?)
>  * addafter / before (Adding handlers before a specific handler)
>  * is handler connected? (easy)
>  * stop signal emission (only useful when used with threading)
>
> If the new code was accepted into phobos, I'd offer to implement the features above as well, if desired.
>
> The proposed new std.signals code is located here: http://ideone.com/NHS5C
>

Hi Johannes,
The signals code base, as far as I know, hasn't been updated since the D1
days, so I'm glad someone is looking at it. I can't really comment on the
implementation itself, since I'm not a user of signals, but there is an
API issue I'd like to point out. I consider operator overload abuse,
particularly of the "+" for concatenation kind, verboten in D. In fact, we
have "~" as a dedicated concatenation operator specifically to avoid this
issue. I'd recommend changing the "+=" function to "~=" and dropping "-=".
Also, although I don't use signals, I have used DFL's events which are
very similar. One of my major grips about them was the inability to use
std.algorithm, etc., to modify the underlying array of delegates. Since
signals is really a specialized container, I would strongly recommend
adding support for ranges and containers to it. For example, I may want to
add my new handler before all the existing handlers, which I can't do
currently using connect.
October 07, 2010
 I'm sorry it took me so long to respond, it has been a busy week ;-)

On 30.09.2010 17:21, Robert Jacques wrote:
> Hi Johannes,
> The signals code base, as far as I know, hasn't been updated since the
> D1 days, so I'm glad someone is looking at it. I can't really comment
> on the implementation itself, since I'm not a user of signals, but
> there is an API issue I'd like to point out. I consider operator
> overload abuse, particularly of the "+" for concatenation kind,
> verboten in D. In fact, we have "~" as a dedicated concatenation
> operator specifically to avoid this issue. I'd recommend changing the
> "+=" function to "~=" and dropping "-=".
OK, I've done that. I just adopted the syntax from C#, but yeah ~= fits better into D signals. And ~= overloading can be used with pointers (+= can't) so ~= is definitely the only choice in this case.
> Also, although I don't use signals, I have used DFL's events which are very similar. One of my major grips about them was the inability to use std.algorithm, etc., to modify the underlying array of delegates. Since signals is really a specialized container, I would strongly recommend adding support for ranges and containers to it. For example, I may want to add my new handler before all the existing handlers, which I can't do currently using connect.
I'm not sure about that. I changed the internal implementation from an array to std.containers SList now, so just publishing the range interface is easy. But Signal internally only stores delegates. There's no way to get the original representation of the handler(function, class with opCall) from the delegate. So publishing forward ranges of these delegates isn't that useful. To make it useful, I'd have to make getDelegate public, but I fear that would break encapsulation as getDelegate is an implementation detail. On the other hand the range interface could be quite useful so I'm not really sure what to do about that.

I also dropped the idea of handler ids, so the only way to refer to an handler is the function address / delegate / object / that was used to register the handler. This simplifies the implementation and also saves some runtime memory. The only limitation this causes is that the same handler can be connected only once per signal, but I don't think that's a problem.

I will implement some more features (is handler registered, ...) and post the updated code on the weekend.

-- 
Johannes Pfau

October 09, 2010
What are the limitations of this for multithreaded use and reentrancy?  Can I modify the handler list from within a handler, etc?

Sent from my iPhone

On Oct 7, 2010, at 10:39 AM, Johannes Pfau <johannespfau at googlemail.com> wrote:

> I'm sorry it took me so long to respond, it has been a busy week ;-)
> 
> On 30.09.2010 17:21, Robert Jacques wrote:
>> Hi Johannes,
>> The signals code base, as far as I know, hasn't been updated since the
>> D1 days, so I'm glad someone is looking at it. I can't really comment
>> on the implementation itself, since I'm not a user of signals, but
>> there is an API issue I'd like to point out. I consider operator
>> overload abuse, particularly of the "+" for concatenation kind,
>> verboten in D. In fact, we have "~" as a dedicated concatenation
>> operator specifically to avoid this issue. I'd recommend changing the
>> "+=" function to "~=" and dropping "-=".
> OK, I've done that. I just adopted the syntax from C#, but yeah ~= fits better into D signals. And ~= overloading can be used with pointers (+= can't) so ~= is definitely the only choice in this case.
>> Also, although I don't use signals, I have used DFL's events which are very similar. One of my major grips about them was the inability to use std.algorithm, etc., to modify the underlying array of delegates. Since signals is really a specialized container, I would strongly recommend adding support for ranges and containers to it. For example, I may want to add my new handler before all the existing handlers, which I can't do currently using connect.
> I'm not sure about that. I changed the internal implementation from an array to std.containers SList now, so just publishing the range interface is easy. But Signal internally only stores delegates. There's no way to get the original representation of the handler(function, class with opCall) from the delegate. So publishing forward ranges of these delegates isn't that useful. To make it useful, I'd have to make getDelegate public, but I fear that would break encapsulation as getDelegate is an implementation detail. On the other hand the range interface could be quite useful so I'm not really sure what to do about that.
> 
> I also dropped the idea of handler ids, so the only way to refer to an handler is the function address / delegate / object / that was used to register the handler. This simplifies the implementation and also saves some runtime memory. The only limitation this causes is that the same handler can be connected only once per signal, but I don't think that's a problem.
> 
> I will implement some more features (is handler registered, ...) and post the updated code on the weekend.
> 
> -- 
> Johannes Pfau
> 
> _______________________________________________
> phobos mailing list
> phobos at puremagic.com
> http://lists.puremagic.com/mailman/listinfo/phobos
October 09, 2010
On Sat, 09 Oct 2010 14:48:29 -0400, Sean Kelly <sean at invisibleduck.org> wrote:
> What are the limitations of this for multithreaded use and reentrancy? Can I modify the handler list from within a handler, etc?

Delegates as currently not safe for multithreaded use as delegate modifiers (pure, shared, immutable, etc) don't work yet, IIRC. The library appears to be designed for signal thread use only. Currently, modifying the handler list from within a handler would result in undefined behavior.
October 10, 2010
 On 09.10.2010 23:20, Robert Jacques wrote:
> On Sat, 09 Oct 2010 14:48:29 -0400, Sean Kelly
> <sean at invisibleduck.org> wrote:
>> What are the limitations of this for multithreaded use and reentrancy?  Can I modify the handler list from within a handler, etc?
>
> Delegates as currently not safe for multithreaded use as delegate modifiers (pure, shared, immutable, etc) don't work yet, IIRC. The library appears to be designed for signal thread use only. Currently, modifying the handler list from within a handler would result in undefined behavior.
Like the old implementation this implementation doesn't support multithreaded use (yet). I thought about it a little and I think making it thread safe using mutexes shouldn't be hard. I know message passing is supposed to be used whenever possible, but I don't think it works well in this case.

With the new implementation SList is used internally. SLists stableInsertAfter and stableLinearRemove do not invalidate ranges, so at least for single thread use modifying the handler list from within a handler should be fine. The emit call's reentrancy depends on SLists opSlice reentrancy, but I guess it is reentrant.

If delegate modifiers really don't work yet, this could be a problem.

-- 
Johannes Pfau

October 11, 2010
Robert, what license will you use for your signals module?

Thanks.

-- 
Yao G.
October 11, 2010
On Mon, 11 Oct 2010 13:12:36 -0400, Yao G. <yao.gomez at gmail.com> wrote:

> Robert, what license will you use for your signals module?
>
> Thanks.
>

Hi Yao,
I don't have a signal's module; the module under discussion is Johannes's.
The file Johannes linked to doesn't have a license, but since it's a
derivative of the phobos code base and Johannes is submitting it to a
phobos review, I'd assume Boost.
-Robert
October 11, 2010
Ups. Sorry for the confusion.

-- 
Yao G.
January 02, 2011
Hi Johannes,


The code looks very promising. Could you please generate documentation from it and make a proposal to digitalmars.d?


Andrei

On 9/29/10 9:16 AM, Johannes Pfau wrote:
>   Hi,
> I needed signals for a simple test program some time ago and because I
> wanted to learn D templates anyway I coded my own signal implementation.
> I then saw phobos already had an implementation of signals and slot in
> std.signals, but I was kinda disappointed by the implementation in
> std.signals. D offers the possibilities to enhance it in many ways.
>
> The limitations of std.signals:
> *Only supports delegates, not even functions. And it should also support
> objects/structs with opCall
> *Only supports delegates of class objects.
> The std.signal implementation attaches a DisposeEvent to the object of
> the delegate. This only works for classes. It first sounds like a good
> idea to automatically remove the delegate when the object is destroyed,
> but: explicit delete is deprecated; the garbage collector should not
> collect the object while the delegate is still registered; it limits
> delegate support to class delegates; requiring the user to explicitly
> remove the delegate is ok. Most(all?) signal implementations require
> explicitly removing the signal handlers.
> *Delegates return void. Most signal implementations have handlers return
> bool. When a handler returns false, the remaining handlers are not called.
> *Uses template mixins. Template structs work as well and give a nicer
> syntax.
>
> So I merged my signals implementation with std.signals. All problems
> mentioned above have been fixed. I additionally added the following things:
> *Support all handlers with void or bool return type. Using bool return
> type is preferred though, because it's faster.
> *Overloads for += and -= like in C#
>
> I also came up with a pretty nice use case for structs with opCall:
> Consider this common c# code:
> public delegate void ChangedEventHandler(object sender, EventArgs e);
> The sender object is part of the Signal type. Now, this won't work for
> D, because structs aren't objects and signals can be at module level as
> well.
> But the struct + opcall support allows us to do this in a even better way:
> struct Handler
> {
>      object _sender;
>      bool opCall(string s)
>      {
>           //use _sender and s
>      }
>      this(object sender)
>      {
>          this._sender = sender;
>      }
> }
> obj.onDatareceived.connect(new Handler(obj));
>
> Using this technique we can pass any number of additional arguments.
> This could also be used to build an signal->message passing dispatcher:
> obj.onDatareceived.connect(new EventMessage!string(tid));
>
> I then looked into the glib api documentation to see what additional
> functionality it's signal handling offers.
> It's basically:
>   * Handler IDs.
> this would allow us to add the same handler to a signal multiple times.
> It also makes addafter/before easier, but it's not required for that.
> I wonder whether phobos already has something like an IDPool, something
> like this: http://ideone.com/xs80c
>   * block/unblock complete signal (easy to do)
>   * block/unblock handlers (shouldn't be too hard)
>   * threading? (Is this useful/ needed?)
>   * addafter / before (Adding handlers before a specific handler)
>   * is handler connected? (easy)
>   * stop signal emission (only useful when used with threading)
>
> If the new code was accepted into phobos, I'd offer to implement the features above as well, if desired.
>
> The proposed new std.signals code is located here: http://ideone.com/NHS5C
>
« First   ‹ Prev
1 2