Interest in non-intrusive signal/slot lib?
Hello, I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well. Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid. The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object. Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/. Thanks, Emil P.S. Thanks to Peter Dimov for his feedback and for coming up with the name Synapse. :)
On 12/05/2015 14:25, Emil Dotchevski wrote:
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
This looks interesting, though I've only looked at the docs so far. The incomplete struct return type seems a bit peculiar. I assume it was required for some implementation-based reason? It might be a blocker if you later do want to add support for signals with return values. The docs for connect seem to state that when either weak_ptr expires it is considered a disconnection but meta::disconnected is not called. If this is intended it seems a little confusing. Perhaps a different word should be used ("inactive", maybe?). It seems like a non-uniformity to have connect() and other methods take a weak_ptr<emitter> while emit() and emitter_connection_count() take a bare pointer. Looking at the header docs you do have an emit_weak() as well but maybe these should be the other way around? (I assume current emit() is a convenience so that you can emit(this, ...) without needing shared_from_this() or similar.) In the docs for translate(), I assume the Effects code: connect<OriginalSignal>(original_emitter,bind(&emit<TranslatedSignal>,_1,_2,...)) was intended to be this instead: connect<OriginalSignal>(original_emitter,bind(&emit<TranslatedSignal>,translated_emitter,_1,_2,...)) The logging system example doesn't seem to include a way to actually generate log messages, unless you're expecting callers to just emit(severity(n), string) directly, which seems like a leaking abstraction.
On Tue, May 12, 2015 at 12:49 AM, Gavin Lambert
On 12/05/2015 14:25, Emil Dotchevski wrote:
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
This looks interesting, though I've only looked at the docs so far.
The incomplete struct return type seems a bit peculiar. I assume it was required for some implementation-based reason? It might be a blocker if you later do want to add support for signals with return values.
In my opinion the main use case for such a lib is when the caller doesn't care if there are 0, 1 or many connected functions; and allowing return values makes zero/many quite tricky to deal with. On the other hand, passing a reference or a pointer to emit<> to return/accumulate values from any number of connected functions fits nicely. So I think this is the best compromise between expressive power and simplicity.
The docs for connect seem to state that when either weak_ptr expires it is considered a disconnection but meta::disconnected is not called. If this is intended it seems a little confusing. Perhaps a different word should be used ("inactive", maybe?).
Yes, thank you for this comment. You're correct, meta::disconnected is called when the connection object expires, not when the emitter or the target expire.
It seems like a non-uniformity to have connect() and other methods take a weak_ptr<emitter> while emit() and emitter_connection_count() take a bare pointer. Looking at the header docs you do have an emit_weak() as well but maybe these should be the other way around? (I assume current emit() is a convenience so that you can emit(this, ...) without needing shared_from_this() or similar.)
You're right, this allows emitting from a member function (this), but also when working with 3rd party APIs (which is the most important use case for me) it's quite common that you don't have weak/shared_ptr when calling emit. For example, in http://www.revergestudios.com/boost-synapse/using_Boost_Synapse_to_handle_ev... emit is called directly with the HWND object; at that point there's no way to get your hands on a weak_ptr. :) Another consideration is that emit guarantees that the connected functions are called (assuming the target weak_ptr hasn't expired). This guarantee is quite useful sometimes. So actually I consider emit_weak just a convenience function, syntactic sugar for if( shared_ptr<void const> sp=wp.lock() ) emit<signal>(sp.get(),....);
In the docs for translate(), I assume the Effects code:
connect<OriginalSignal>(original_emitter,bind(&emit<TranslatedSignal>,_1,_2,...)) was intended to be this instead:
connect<OriginalSignal>(original_emitter,bind(&emit<TranslatedSignal>,translated_emitter,_1,_2,...))
Yes, thank you.
The logging system example doesn't seem to include a way to actually generate log messages, unless you're expecting callers to just emit(severity(n), string) directly, which seems like a leaking abstraction.
Well, sure. The point of the example is to illustrate the use of connect and translate, most definitely it isn't a complete logging system. Thanks! Emil
On Tue, May 12, 2015 at 4:25 AM, Emil Dotchevski
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the
included
examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
Interesting. Thanks for sharing. One thing that surprised me in the example is that you store the QPushButton in a shared_ptr, which from shared_ptr point-of-view has sole ownership, while also setting the QDialog as the parent of that button. http://doc.qt.io/qt-4.8/qwidget.html#QWidget states "The new widget is deleted when its parent is deleted". The shared_ptr goes out of scope before the QDialog, so obviously Qt has a way to avoid the double-delete (I suspect via its internal/intrusive tracking of children), but the other way around (longer lifetime for the shared_ptr) would crash, no? And the shared_ptr seems to be required by the API? Also the fact that you must static_cast from a void* to get back the QPushButton is a little disturbing, when the single type itself never "said" in its signature the emitter has to be a QPushButton. Wouldn't such casting also be fraught with dangers in case of multiple or virtual inheritance? Thanks, --DD
On Tue, May 12, 2015 at 1:34 AM, Dominique Devienne
On Tue, May 12, 2015 at 4:25 AM, Emil Dotchevski
wrote:
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the
included
examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
Interesting. Thanks for sharing.
One thing that surprised me in the example is that you store the QPushButton in a shared_ptr, which from shared_ptr point-of-view has sole ownership, while also setting the QDialog as the parent of that button.
http://doc.qt.io/qt-4.8/qwidget.html#QWidget states "The new widget is deleted when its parent is deleted". The shared_ptr goes out of scope before the QDialog, so obviously Qt has a way to avoid the double-delete (I suspect via its internal/intrusive tracking of children), but the other way around (longer lifetime for the shared_ptr) would crash, no?
Yes, if you delete a child it unregisters from the parent and that's fine. If the parent is deleted before the shared_ptr to the child that would be a double delete, but in the example that doesn't happen.
And the shared_ptr seems to be required by the API?
Yes it is, however shared_ptr is quite flexible, e.g. you can use it with null_deleter if needed, etc. Also, Qt sends signals before it deletes an object, which can be used to at least detect the double delete.
Also the fact that you must static_cast from a void* to get back the QPushButton is a little disturbing, when the single type itself never "said" in its signature the emitter has to be a QPushButton. Wouldn't such casting also be fraught with dangers in case of multiple or virtual inheritance? Thanks, --DD
Yes, one has to be careful with casting. I'm wondering if it's worth storing in the connection object the type of the emitter, which can be captured at the time connect is called, if available. Thanks for the feedback! Emil
On Tue, May 12, 2015 at 11:51 AM, Emil Dotchevski
On Tue, May 12, 2015 at 1:34 AM, Dominique Devienne
Also the fact that you must static_cast from a void* to get back the QPushButton is a little disturbing, when the single type itself never "said" in its signature the emitter has to be a QPushButton. Wouldn't such casting also be fraught with dangers in case of multiple or virtual inheritance? Thanks, --DD
Yes, one has to be careful with casting. I'm wondering if it's worth storing in the connection object the type of the emitter, which can be captured at the time connect is called, if available.
In your example, the emiter type is already lost, but yes, being able to do at least a "safe cast" (i.e. dynamic_cast like) in the meta "slot", or even better enforce a required emiter type in the signal itself (perhaps optionally) would go a long way to make Synapse more typesafe, and thus safer IMHO. My $0.02. Thanks again, --DD
On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
What do you mean by non-intrusive versus the signals2 library ?
On Tue, May 12, 2015 at 8:49 AM, Edward Diener
On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
What do you mean by non-intrusive versus the signals2 library ?
As far as I can tell in Signals2 to emit a signal you must have access to
the signal object, while synapse::emit takes an emitter object. For
example, with Synapse you could write:
QPushButton * b;
......
synapse::emit
On 5/12/2015 2:14 PM, Emil Dotchevski wrote:
On Tue, May 12, 2015 at 8:49 AM, Edward Diener
wrote: On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
What do you mean by non-intrusive versus the signals2 library ?
As far as I can tell in Signals2 to emit a signal you must have access to the signal object, while synapse::emit takes an emitter object. For example, with Synapse you could write:
QPushButton * b; ...... synapse::emit
(b); (Of course emit<> has no use for the type of its emitter argument -- the above would work just as well with a void pointer.)
It looks interesting but without a documented explanation I cannot figure out how it works. I don't think that one page of introduction, some examples and a reference is adequate. You should write something about what the parts of your system are, how they relate, and how they are used. I assume they form some sort of signal/slot system.
On Wed, May 13, 2015 at 12:38 PM, Edward Diener
On 5/12/2015 2:14 PM, Emil Dotchevski wrote:
On Tue, May 12, 2015 at 8:49 AM, Edward Diener
wrote: On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
What do you mean by non-intrusive versus the signals2 library ?
As far as I can tell in Signals2 to emit a signal you must have access to the signal object, while synapse::emit takes an emitter object. For example, with Synapse you could write:
QPushButton * b; ...... synapse::emit
(b); (Of course emit<> has no use for the type of its emitter argument -- the above would work just as well with a void pointer.)
It looks interesting but without a documented explanation I cannot figure out how it works. I don't think that one page of introduction, some examples and a reference is adequate. You should write something about what the parts of your system are, how they relate, and how they are used. I assume they form some sort of signal/slot system.
Thanks for the feedback -- I'll write a tutorial soon, though it's a very simple lib so you might find http://www.revergestudios.com/boost-synapse/Index_of_Functions.html and http://www.revergestudios.com/boost-synapse/Examples.html sufficiently helpful. Thanks, Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
The documentation says that a signal emitter is a void pointer. Doesn't it need to be a shared_ptr<void> instead, since it must be passed to emit as a weak_pointer ? You might clarify this in the documentation. Instead of: "Boost Synapse does not define its own signal emitter type. Instead, signal emitters are identified by void pointers." you might say: "Boost Synapse does not define its own signal emitter type. Instead, signal emitters are identified by pointers to any type specified as a shared_ptr<void>." It looks like shared_ptr, weak_ptr, and function are C++ standard types and not Boost types, but this is not clarified. I assume that the purpose of having the signal type be a pointer to a function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
On Sat, May 16, 2015 at 7:09 AM, Edward Diener
On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
The documentation says that a signal emitter is a void pointer. Doesn't it need to be a shared_ptr<void> instead, since it must be passed to emit as a weak_pointer ? You might clarify this in the documentation. Instead of:
emit<> takes a void pointer, emit_weak<> takes a weak pointer. I have updated the documentation, I believe the new introduction and the simple example it presents are much more understandable now, thanks to the initial feedback I'm getting.
I assume that the purpose of having the signal type be a pointer to a function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have: typedef struct button_down_(*button_down)(int x, int y); you can tell it apart from typedef struct button_up_(*button_up)(int x, int y); -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/16/2015 2:20 PM, Emil Dotchevski wrote:
On Sat, May 16, 2015 at 7:09 AM, Edward Diener
wrote: On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
The documentation says that a signal emitter is a void pointer. Doesn't it need to be a shared_ptr<void> instead, since it must be passed to emit as a weak_pointer ? You might clarify this in the documentation. Instead of:
emit<> takes a void pointer, emit_weak<> takes a weak pointer. I have updated the documentation, I believe the new introduction and the simple example it presents are much more understandable now, thanks to the initial feedback I'm getting.
I will take a look.
I assume that the purpose of having the signal type be a pointer to a function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have:
typedef struct button_down_(*button_down)(int x, int y);
you can tell it apart from
typedef struct button_up_(*button_up)(int x, int y);
I understand that the types are different but when would you ever use that knowledge in code ? The signal handler knows nothing about the type of the signal except that it's signature matches the parameters of the signal.
On Sat, May 16, 2015 at 11:50 AM, Edward Diener
I assume that the purpose of having the signal type be a pointer to a
function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have:
typedef struct button_down_(*button_down)(int x, int y);
you can tell it apart from
typedef struct button_up_(*button_up)(int x, int y);
I understand that the types are different but when would you ever use that knowledge in code ?
It seems you're asking why are different signals necessary, e.g. why would
I need to discriminate between a "button down" and a "button up" event --
which is puzzling. Anyway, the answer is that you need different signals so
that you can tell connect<> which signal you're connecting (and emit<>
which signal you're emitting) from the specified emitter object:
void handle_button_down( int x, int y );
void handle_button_up( int x, int y );
auto c1=connect
The signal handler knows nothing about the type of the signal except that it's signature matches the parameters of the signal.
The signal handling code knows nothing about signals at all. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/16/2015 3:04 PM, Emil Dotchevski wrote:
On Sat, May 16, 2015 at 11:50 AM, Edward Diener
wrote: I assume that the purpose of having the signal type be a pointer to a
function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have:
typedef struct button_down_(*button_down)(int x, int y);
you can tell it apart from
typedef struct button_up_(*button_up)(int x, int y);
I understand that the types are different but when would you ever use that knowledge in code ?
It seems you're asking why are different signals necessary, e.g. why would I need to discriminate between a "button down" and a "button up" event -- which is puzzling. Anyway, the answer is that you need different signals so that you can tell connect<> which signal you're connecting (and emit<> which signal you're emitting) from the specified emitter object:
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up);
typedef void (*button_down)(int x, int y);
typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y );
void handle_button_up( int x, int y );
auto c1=connect
The signal handler knows nothing about the type of the signal except that it's signature matches the parameters of the signal.
The signal handling code knows nothing about signals at all.
On Sat, May 16, 2015 at 2:35 PM, Edward Diener
On 5/16/2015 3:04 PM, Emil Dotchevski wrote:
On Sat, May 16, 2015 at 11:50 AM, Edward Diener
wrote: I assume that the purpose of having the signal type be a pointer to a
function taking some data and returning an incomplete type instead of
returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have:
typedef struct button_down_(*button_down)(int x, int y);
you can tell it apart from
typedef struct button_up_(*button_up)(int x, int y);
I understand that the types are different but when would you ever use that knowledge in code ?
It seems you're asking why are different signals necessary, e.g. why would I need to discriminate between a "button down" and a "button up" event -- which is puzzling. Anyway, the answer is that you need different signals so that you can tell connect<> which signal you're connecting (and emit<> which signal you're emitting) from the specified emitter object:
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ?
Yes, the reason is that in that case button_down and button_up are the same type void(*)(int,int), so they'd be the same signal. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 17/05/2015 09:35, Edward Diener wrote:
typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ? The fact that button_down and button_up are the same types above but different types the way synapse is currently designed seems irrelevant. I am enquiring whether there is an internal reason for that.
I had the same confusion at first. The issue is that there is no "signal container" instance within the emitter itself, which is how you'd normally distinguish between signals with the same signature. This library stores all signals centrally in the management object, indexed by type, rather than having members of the emitter object (that's the "non-intrusive" part). As a result the types need to be unique as they're the only differentiating feature. I am curious whether alternate designs have been considered, such as using strings to locate signals (as in Gtk) or using arbitrary atoms returned from a registration function. I'm also curious if there are any performance consequences of, say, registering a "click" event on hundreds of buttons simultaneously, with different actions required for each, since this model will have a single list with hundreds of entries to search through on each emit, whereas more "traditional" designs will have hundreds of single-entry lists and a much more trivial emit.
On Sun, May 17, 2015 at 7:40 PM, Gavin Lambert
On 17/05/2015 09:35, Edward Diener wrote:
typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ? The fact that button_down and button_up are the same types above but different types the way synapse is currently designed seems irrelevant. I am enquiring whether there is an internal reason for that.
I had the same confusion at first. The issue is that there is no "signal container" instance within the emitter itself, which is how you'd normally distinguish between signals with the same signature.
This library stores all signals centrally in the management object, indexed by type, rather than having members of the emitter object (that's the "non-intrusive" part). As a result the types need to be unique as they're the only differentiating feature.
I now understand where the confusion comes from, too. In Boost Signals 2, a signal (emitter) is an actual object, and it is the address of that object that identifies different signals independently from their signature. Indeed, in Synapse signals are *not* objects, they're types.
I am curious whether alternate designs have been considered, such as using strings to locate signals (as in Gtk) or using arbitrary atoms returned from a registration function.
The reason why I've settled on using types is that this way the connections are separated "by signal" at compile time, e.g. adding button_down connections does not add to the time it takes to emit button_up signals.
I'm also curious if there are any performance consequences of, say, registering a "click" event on hundreds of buttons simultaneously, with different actions required for each, since this model will have a single list with hundreds of entries to search through on each emit, whereas more "traditional" designs will have hundreds of single-entry lists and a much more trivial emit.
The current implementation uses a single list per signal type, meaning that if there are multiple emitters emitting the same signal they will contribute to the time it takes to emit the signal from a specific emitter. In my experience so far this has never showed up in profiling. It is of course possible to order by emitter (weak_ptr<void const>::op<) but at this point this would be premature. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/18/2015 12:35 AM, Emil Dotchevski wrote:
On Sun, May 17, 2015 at 7:40 PM, Gavin Lambert
wrote: On 17/05/2015 09:35, Edward Diener wrote:
typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ? The fact that button_down and button_up are the same types above but different types the way synapse is currently designed seems irrelevant. I am enquiring whether there is an internal reason for that.
I had the same confusion at first. The issue is that there is no "signal container" instance within the emitter itself, which is how you'd normally distinguish between signals with the same signature.
This library stores all signals centrally in the management object, indexed by type, rather than having members of the emitter object (that's the "non-intrusive" part). As a result the types need to be unique as they're the only differentiating feature.
I now understand where the confusion comes from, too. In Boost Signals 2, a signal (emitter) is an actual object, and it is the address of that object that identifies different signals independently from their signature. Indeed, in Synapse signals are *not* objects, they're types.
That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ). BTW you could provide a fairly simple variadic macro for creating signals which might be a little easier on the end-user: #define SYNAPSE_SIGNAL(name,...) typedef struct name ## _ (*name)(__VA_ARGS__); In your documentation I think you should explain that when you emit a signal you pass a pointer to the object doing the emitting. Also when you connect you are passing a shared_ptr ( or weak_ptr ) to the object doing the emitting. I think I have this correct. Finally i mentioned this before but you did not reply. Is the connected object designated as a std::function or a boost::function ? I am guessing the former unless you have overloaded for both. On the 'connect' page your code overruns the edge of the page.
I am curious whether alternate designs have been considered, such as using strings to locate signals (as in Gtk) or using arbitrary atoms returned from a registration function.
The reason why I've settled on using types is that this way the connections are separated "by signal" at compile time, e.g. adding button_down connections does not add to the time it takes to emit button_up signals.
I'm also curious if there are any performance consequences of, say, registering a "click" event on hundreds of buttons simultaneously, with different actions required for each, since this model will have a single list with hundreds of entries to search through on each emit, whereas more "traditional" designs will have hundreds of single-entry lists and a much more trivial emit.
The current implementation uses a single list per signal type, meaning that if there are multiple emitters emitting the same signal they will contribute to the time it takes to emit the signal from a specific emitter. In my experience so far this has never showed up in profiling. It is of course possible to order by emitter (weak_ptr<void const>::op<) but at this point this would be premature.
On Mon, May 18, 2015 at 8:42 AM, Edward Diener
On 5/18/2015 12:35 AM, Emil Dotchevski wrote:
On Sun, May 17, 2015 at 7:40 PM, Gavin Lambert
wrote: On 17/05/2015 09:35, Edward Diener wrote:
typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ? The fact that button_down and button_up are the same types above but different types the way synapse is currently designed seems irrelevant. I am enquiring whether there is an internal reason for that.
I had the same confusion at first. The issue is that there is no "signal container" instance within the emitter itself, which is how you'd normally distinguish between signals with the same signature.
This library stores all signals centrally in the management object, indexed by type, rather than having members of the emitter object (that's the "non-intrusive" part). As a result the types need to be unique as they're the only differentiating feature.
I now understand where the confusion comes from, too. In Boost Signals 2, a signal (emitter) is an actual object, and it is the address of that object that identifies different signals independently from their signature. Indeed, in Synapse signals are *not* objects, they're types.
That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ).
The signal *is* the event though. So I guess I still don't understand. If you have two events, "button up" and "button down", then you need two Synapse signals or else you wouldn't be able to tell them apart. BTW you could provide a fairly simple variadic macro for creating signals
which might be a little easier on the end-user:
#define SYNAPSE_SIGNAL(name,...) typedef struct name ## _ (*name)(__VA_ARGS__);
Maybe I'll add something like this, since I've had a couple of instances where through copy&paste I ended up with: typedef struct foo_(*foo)(); typedef struct foo_(*bar)(); BTW I don't think I'd need vaargs even.
In your documentation I think you should explain that when you emit a signal you pass a pointer to the object doing the emitting. Also when you connect you are passing a shared_ptr ( or weak_ptr ) to the object doing the emitting. I think I have this correct.
You mean more than I have already in http://www.revergestudios.com/boost-synapse/connect.html and http://www.revergestudios.com/boost-synapse/emit.html? -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/18/2015 1:39 PM, Emil Dotchevski wrote:
On Mon, May 18, 2015 at 8:42 AM, Edward Diener
wrote: On 5/18/2015 12:35 AM, Emil Dotchevski wrote:
On Sun, May 17, 2015 at 7:40 PM, Gavin Lambert
wrote: On 17/05/2015 09:35, Edward Diener wrote:
typedef void (*button_down)(int x, int y); typedef void (*button_up)(int x, int y);
void handle_button_down( int x, int y ); void handle_button_up( int x, int y );
auto c1=connect
(e,&handle_button_down); auto c2=connect (e,&handle_button_up); Is there some reason why the above would not work ? The fact that button_down and button_up are the same types above but different types the way synapse is currently designed seems irrelevant. I am enquiring whether there is an internal reason for that.
I had the same confusion at first. The issue is that there is no "signal container" instance within the emitter itself, which is how you'd normally distinguish between signals with the same signature.
This library stores all signals centrally in the management object, indexed by type, rather than having members of the emitter object (that's the "non-intrusive" part). As a result the types need to be unique as they're the only differentiating feature.
I now understand where the confusion comes from, too. In Boost Signals 2, a signal (emitter) is an actual object, and it is the address of that object that identifies different signals independently from their signature. Indeed, in Synapse signals are *not* objects, they're types.
That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ).
The signal *is* the event though. So I guess I still don't understand. If you have two events, "button up" and "button down", then you need two Synapse signals or else you wouldn't be able to tell them apart.
Your synapse code evidently needs to tell them apart but from the end-user's perspective it isn't necessary, since the only thing he cares about is the parameter types being specified. If he has a signal of: typedef void (*button_signal)(int,int) ; why wouldn't he use it for both a button_up and a button_down event, since it has the correct number and type of parameters for both ? I don't mind your specifying that the above must be something like: typedef button_up_ (*button_up)(int,int) ; typedef button_down_ (*button_down)(int,int) ; so that the types are distinct. But from the end-user's point of view having each signal be a distinct type seems irrelevant. I am not questioning your internal need for each signal to be a distinct type, only pointing out that it isn't needed AFIACS from the end-user's point of view.
BTW you could provide a fairly simple variadic macro for creating signals
which might be a little easier on the end-user:
#define SYNAPSE_SIGNAL(name,...) typedef struct name ## _ (*name)(__VA_ARGS__);
Maybe I'll add something like this, since I've had a couple of instances where through copy&paste I ended up with:
typedef struct foo_(*foo)(); typedef struct foo_(*bar)();
If your signal can take no parameters then the macro I proposed has to get more clever. If that is the case I will let you work it out.
BTW I don't think I'd need vaargs even.
Really ? Well if you don't want to use a variadic macro, or any macro at all, that is up to you.
In your documentation I think you should explain that when you emit a signal you pass a pointer to the object doing the emitting. Also when you connect you are passing a shared_ptr ( or weak_ptr ) to the object doing the emitting. I think I have this correct.
You mean more than I have already in http://www.revergestudios.com/boost-synapse/connect.html and http://www.revergestudios.com/boost-synapse/emit.html?
In the 'coonect' doc there is nothing which specifies from where 'weak_ptr<emitter const>' is supposed to come. In the 'emit' doc there is nothing which specifies what 'signal_emitter" is supposed to be. There is a value to making things clear on the conceptual level rather than relying on the details you specify elsewhere to be interpreted correctly.
On Mon, May 18, 2015 at 2:36 PM, Edward Diener
On 5/18/2015 1:39 PM, Emil Dotchevski wrote:
On Mon, May 18, 2015 at 8:42 AM, Edward Diener
wrote: That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ).
The signal *is* the event though. So I guess I still don't understand. If you have two events, "button up" and "button down", then you need two Synapse signals or else you wouldn't be able to tell them apart.
Your synapse code evidently needs to tell them apart but from the end-user's perspective it isn't necessary, since the only thing he cares about is the parameter types being specified. If he has a signal of:
typedef void (*button_signal)(int,int) ;
why wouldn't he use it for both a button_up and a button_down event, since it has the correct number and type of parameters for both ?
I'm still trying to figure out what's the misunderstanding. Perhaps an
example will help, here is how you could use synapse::emit<> from a
WindowProc:
typedef struct button_down_(*button_down)(int,int);
typedef struct button_up_(*button_up)(int,int);
LRESULT CALLBACK
WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch( uMsg )
{
case WM_LBUTTONDOWN:
synapse::emit
On 19/05/2015 09:36, Edward Diener wrote:
On 5/18/2015 1:39 PM, Emil Dotchevski wrote:
On Mon, May 18, 2015 at 8:42 AM, Edward Diener
wrote: That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ).
The signal *is* the event though. So I guess I still don't understand. If you have two events, "button up" and "button down", then you need two Synapse signals or else you wouldn't be able to tell them apart.
Your synapse code evidently needs to tell them apart but from the end-user's perspective it isn't necessary, since the only thing he cares about is the parameter types being specified. If he has a signal of:
typedef void (*button_signal)(int,int) ;
why wouldn't he use it for both a button_up and a button_down event, since it has the correct number and type of parameters for both ?
The key issue is that the list of connected handlers is associated with the type (which I did say before, but evidently not clearly enough). This means that if you use the same type and the same emitter, it's the same "signal" and it's not possible to differentiate between them -- you can call them "button_down" and "button_up" if you like, but they're just aliases for the same thing and using either of them will result in the same set of handlers being invoked (which is the union of connections made using either name, because again they're just aliases). If you want separate handler lists (ie. a separate signal) then you have to have a separate type, and that requires that the type be unique in some way. The author chose to use the return value for that purpose. (The implementation stores all connected handlers in a list indexed by type, and then at emit time calls only the ones with the correct emitter.)
On Mon, May 18, 2015 at 4:22 PM, Gavin Lambert
On 19/05/2015 09:36, Edward Diener wrote:
On 5/18/2015 1:39 PM, Emil Dotchevski wrote:
On Mon, May 18, 2015 at 8:42 AM, Edward Diener
wrote: That's not where the confusion existed for me. Gavin Lambert explained why each signal must be a different type. I realized that a signal is a type, but not why the same signal could not be used for more than one event ( button down, button up etc. ).
The signal *is* the event though. So I guess I still don't understand. If you have two events, "button up" and "button down", then you need two Synapse signals or else you wouldn't be able to tell them apart.
Your synapse code evidently needs to tell them apart but from the end-user's perspective it isn't necessary, since the only thing he cares about is the parameter types being specified. If he has a signal of:
typedef void (*button_signal)(int,int) ;
why wouldn't he use it for both a button_up and a button_down event, since it has the correct number and type of parameters for both ?
The key issue is that the list of connected handlers is associated with the type (which I did say before, but evidently not clearly enough).
This means that if you use the same type and the same emitter, it's the same "signal" and it's not possible to differentiate between them -- you can call them "button_down" and "button_up" if you like, but they're just aliases for the same thing and using either of them will result in the same set of handlers being invoked (which is the union of connections made using either name, because again they're just aliases).
If you want separate handler lists (ie. a separate signal) then you have to have a separate type, and that requires that the type be unique in some way. The author chose to use the return value for that purpose.
(The implementation stores all connected handlers in a list indexed by type, and then at emit time calls only the ones with the correct emitter.)
Gavin, you got it and explained it very well, thank you. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/16/2015 2:20 PM, Emil Dotchevski wrote:
On Sat, May 16, 2015 at 7:09 AM, Edward Diener
wrote: On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
The documentation says that a signal emitter is a void pointer. Doesn't it need to be a shared_ptr<void> instead, since it must be passed to emit as a weak_pointer ? You might clarify this in the documentation. Instead of:
emit<> takes a void pointer, emit_weak<> takes a weak pointer. I have updated the documentation, I believe the new introduction and the simple example it presents are much more understandable now, thanks to the initial feedback I'm getting.
The link in the documentation in 'Questions and Answers" to: Read "Smart Pointer Programming Techniques" to learn more is broken. I can find it in the Boost doc but others might not.
I assume that the purpose of having the signal type be a pointer to a function taking some data and returning an incomplete type instead of returning void is to make each signal type unique. But is this really necessary ?
It is necessary so that if you have:
typedef struct button_down_(*button_down)(int x, int y);
you can tell it apart from
typedef struct button_up_(*button_up)(int x, int y);
On Sat, May 16, 2015 at 12:19 PM, Edward Diener
The link in the documentation in 'Questions and Answers" to:
Read "Smart Pointer Programming Techniques" to learn more
is broken. I can find it in the Boost doc but others might not.
Works for me?? Are you talking about this page: http://revergestudios.com/boost-synapse/questions_and_answers.html ? -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/16/2015 3:25 PM, Emil Dotchevski wrote:
On Sat, May 16, 2015 at 12:19 PM, Edward Diener
wrote: The link in the documentation in 'Questions and Answers" to:
Read "Smart Pointer Programming Techniques" to learn more
is broken. I can find it in the Boost doc but others might not.
Works for me?? Are you talking about this page: http://revergestudios.com/boost-synapse/questions_and_answers.html ?
Click on the Smart Pointer Programming Techniques link on that page.
Emil Dotchevski wrote:
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :)
You don't get to make belittling claims like that unless you link to it. I searched the correct place and found nothing from you: http://search.gmane.org/search.php?group=gmane.comp.lib.qt.devel&query=Dotchevski+ Thanks, Steve.
On Sun, May 17, 2015 at 2:12 AM, Stephen Kelly
Emil Dotchevski wrote:
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :)
You don't get to make belittling claims like that unless you link to it.
I searched the correct place and found nothing from you:
http://search.gmane.org/search.php?group=gmane.comp.lib.qt.devel&query=Dotchevski+
I didn't post there, I asked on their IRC channel. I don't know if that's archived somewhere. People there were actually very helpful in getting me up and running with Qt, but in my experience the no-mocing thing is a hot button issue, and probably for good reasons. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On May 11, 2015, at 7:26 PM, Emil Dotchevski
wrote: Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses.
Have you looked at CopperSpice? It's basically Qt 4.8 but in C++11 and without MOC. It's also probably the reason for the negative responses on the list, but who knows.
Ahmed Charles wrote:
On May 11, 2015, at 7:26 PM, Emil Dotchevski
wrote: Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses.
Have you looked at CopperSpice? It's basically Qt 4.8 but in C++11 and without MOC.
It's also probably the reason for the negative responses on the list, but who knows.
What negative response on what list are you talking about? As I showed in my other mail, Emil didn't ask the Qt community anything. I'm sure he asked someone something (maybe Joe Bloggs at the water cooler?), but either he wrote nothing to the Qt devel list, or it somehow does not show up in a GMane search. So, please don't repeat unsupported claims of a negative response. Thanks, Steve.
On Sun, May 17, 2015 at 1:15 PM, Ahmed Charles
On May 11, 2015, at 7:26 PM, Emil Dotchevski
wrote: Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses.
Have you looked at CopperSpice? It's basically Qt 4.8 but in C++11 and without MOC.
Perhaps I shouldn't have emphasized that Synapse makes it possible to add custom Qt signals without mocing and without having to derive from their types. Synapse is not about Qt, it's much more generic. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
Le 12/05/15 04:25, Emil Dotchevski a écrit :
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
Hi,
as others I was surprised with the design, in particular the signal
declaration form
typedef struct report_progress_(*report_progress)( int );
What is wrong with the usual way
struct report_progress = signal
On Mon, May 18, 2015 at 2:34 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Le 12/05/15 04:25, Emil Dotchevski a écrit :
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.
Hi,
as others I was surprised with the design, in particular the signal declaration form
typedef struct report_progress_(*report_progress)( int );
What is wrong with the usual way
struct report_progress = signal
; If you want to make two signals with the same signature two different signal types you could provide something like
struct report_progress : signal
{}; The fist parameter would be the signal name.
This is definitely possible but I think a simple function pointer typedef is better because in that case you can define Synapse signals without having to include any headers, reducing physical coupling.
I find weird also the emmiter name. In my opinion you are disguising in this emitter parameter the signal instance instead of the signal sender. I don't believe the signal sender is interesting enough in synchronous signal programming as it is with asynchronous one (as asynchronous has often associated agents that send and receive signals).
It's unconventional, yes, but the ability to emit a signal from any object whatsoever (including 3rd-party objects) is what makes it possible to use Synapse in situations where other similar libraries would be very difficult or impossible to use.
BTW, I don't see how the signal handler can retrieve the emitter (e.g. to disconnect from).
In synapse the only way to disconnect a signal is to destroy the connection object you receive from connect<>, although emit<> won't call functions if the corresponding emitter has expired even if the connection object is still afloat.
This mean that the user would need to have two different signal handlers to manage two different emitters (two different instances). What is the rationale to don't providing this information on the to the signal handler.
You can bind whatever you want and pass it to the signal handler.
What is the advantage of your design respect to working with signals instances? What am I missing?
See the example on the main page ( http://revergestudios.com/boost-synapse/index.html) and http://revergestudios.com/boost-synapse/using_Boost_Synapse_to_handle_events... and possibly http://revergestudios.com/boost-synapse/questions_and_answers.html. -- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On Mon, May 18, 2015 at 2:34 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
Hi,
as others I was surprised with the design, in particular the signal declaration form
typedef struct report_progress_(*report_progress)( int );
What is wrong with the usual way
struct report_progress = signal
; If you want to make two signals with the same signature two different signal types you could provide something like
struct report_progress : signal
{}; The fist parameter would be the signal name.
This is definitely possible but I think a simple function pointer typedef is better because in that case you can define Synapse signals without having to include any headers, reducing physical coupling. I understand, but I suspect that the the server or the application would need to use an include for your library, isn't it?.
I find weird also the emmiter name. In my opinion you are disguising in this emitter parameter the signal instance instead of the signal sender. I don't believe the signal sender is interesting enough in synchronous signal programming as it is with asynchronous one (as asynchronous has often associated agents that send and receive signals).
It's unconventional, yes, but the ability to emit a signal from any object whatsoever (including 3rd-party objects) is what makes it possible to use Synapse in situations where other similar libraries would be very difficult or impossible to use. Please, could you elaborate. How having signal instances forbids the use of 3pp objects?
BTW, I don't see how the signal handler can retrieve the emitter (e.g. to disconnect from).
In synapse the only way to disconnect a signal is to destroy the connection object you receive from connect<>, although emit<> won't call functions if the corresponding emitter has expired even if the connection object is still afloat. Ok, I see for disconnect.
This mean that the user would need to have two different signal handlers to manage two different emitters (two different instances). What is the rationale to don't providing this information on the to the signal handler.
You can bind whatever you want and pass it to the signal handler. I see. It would be good to see an example of how your library handles
Le 19/05/15 01:40, Emil Dotchevski a écrit : this signal sender pattern.
What is the advantage of your design respect to working with signals instances? What am I missing?
See the example on the main page ( http://revergestudios.com/boost-synapse/index.html) and http://revergestudios.com/boost-synapse/using_Boost_Synapse_to_handle_events... and possibly http://revergestudios.com/boost-synapse/questions_and_answers.html.
These links give me differences not the advantages. Could you be more precise? Could you show how things are done with each library and the associated performances? What can not be done with Boost.Signal2 that can be done with your library? Vicente
On 20/05/2015 18:21, Vicente J. Botet Escriba wrote:
These links give me differences not the advantages. Could you be more precise? Could you show how things are done with each library and the associated performances? What can not be done with Boost.Signal2 that can be done with your library?
The main distinguishing feature is the non-intrusiveness, ie. the ability to emit an event that claims a raw OS/library handle as its source. It means that at least with regard to event-handling, there is no need to wrap this handle in a representative object that includes the signal support. (ie. the way you'd do an HWND-with-events with Signals2 is by creating a "Window" class that contains an HWND and the signal instances. But then you also usually need the plumbing to look up an existing Window by HWND or create a new one -- whereas none of that is needed in Synapse.) TBH though in the presented examples I would doubt that signal/event handling is the only concern (in a real application), so you might end up creating those Window objects anyway, in which case the appeal lessens. So I can't personally think of any cases where I would use this instead of Signals2 or libsigc++ or similar. (BTW a better comparison would be against Signal, not Signal2; Synapse is non-thread-safe like the former and unlike the latter.)
Le 20/05/15 08:35, Gavin Lambert a écrit :
On 20/05/2015 18:21, Vicente J. Botet Escriba wrote:
These links give me differences not the advantages. Could you be more precise? Could you show how things are done with each library and the associated performances? What can not be done with Boost.Signal2 that can be done with your library?
The main distinguishing feature is the non-intrusiveness, ie. the ability to emit an event that claims a raw OS/library handle as its source.
It means that at least with regard to event-handling, there is no need to wrap this handle in a representative object that includes the signal support. (ie. the way you'd do an HWND-with-events with Signals2 is by creating a "Window" class that contains an HWND and the signal instances. But then you also usually need the plumbing to look up an existing Window by HWND or create a new one -- whereas none of that is needed in Synapse.)
Oh, I think that I understand what you mean. Dissociating the signal type and the emitter gives a degree of freedom. A function having an emitter can emit several signals. Having signal instances would mean that the function would need as parameter all the signals that can be emitted. I don't like the emitter name, but I like the separation. Thanks for the explanation, Vicente
On Tue, May 19, 2015 at 11:35 PM, Gavin Lambert
TBH though in the presented examples I would doubt that signal/event handling is the only concern (in a real application), so you might end up creating those Window objects anyway, in which case the appeal lessens. So I can't personally think of any cases where I would use this instead of Signals2 or libsigc++ or similar.
An example when the non-intrusiveness is important is when you load up an entire hierarchy of UI objects from a file and you want to add custom signals to it. Other solutions to this problem are less elegant. Another useful non-intrusive feature is the meta signals system. It lets you manage connections to 3rd party objects automatically. For example it can be used to add support for C++ functional objects to any C-style callback API. Emil
On Wed, May 20, 2015 at 10:40 AM, Emil Dotchevski
On Tue, May 19, 2015 at 11:35 PM, Gavin Lambert
wrote: TBH though in the presented examples I would doubt that signal/event handling is the only concern (in a real application), so you might end up creating those Window objects anyway, in which case the appeal lessens. So I can't personally think of any cases where I would use this instead of Signals2 or libsigc++ or similar.
An example when the non-intrusiveness is important is when you load up an entire hierarchy of UI objects from a file and you want to add custom signals to it. Other solutions to this problem are less elegant.
Another useful non-intrusive feature is the meta signals system. It lets you manage connections to 3rd party objects automatically. For example it can be used to add support for C++ functional objects to any C-style callback API.
I figured my second point above would be difficult to understand without an
example, so I've added it here:
http://revergestudios.com/boost-synapse/using_Boost_Synapse_meta_signals_to_...
.
Emil
On Wed, May 20, 2015 at 10:40 AM, Emil Dotchevski
On Tue, May 19, 2015 at 11:35 PM, Gavin Lambert
wrote: TBH though in the presented examples I would doubt that signal/event handling is the only concern (in a real application), so you might end up creating those Window objects anyway, in which case the appeal lessens. So I can't personally think of any cases where I would use this instead of Signals2 or libsigc++ or similar.
An example when the non-intrusiveness is important is when you load up an entire hierarchy of UI objects from a file and you want to add custom signals to it. Other solutions to this problem are less elegant.
Another useful non-intrusive feature is the meta signals system. It lets you manage connections to 3rd party objects automatically. For example it can be used to add support for C++ functional objects to any C-style callback API.
Emil
-- Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On 5/11/2015 10:25 PM, Emil Dotchevski wrote:
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
Specifically I was motivated by wanting to use Qt without MOCing. I asked the Qt community if that was possible which generated several rather annoyed negative responses. :) The issue is that while in Qt it's possible to use any function as a Slot, to define a new Qt signal for an existing Qt type one needs to derive from the Qt type, define the signal (as a member function) and do the MOC dance -- which I wanted to avoid.
The result is a small, non-intrusive signals/slots library that allows objects of any type to be used as signal emitters, e.g. one of the included examples shows how signals can be emitted by a Windows HWND object.
Documentation and source code released under the Boost license can be found here: http://www.revergestudios.com/boost-synapse/.+
It would be nicer if Synapse used git and possibly Github. My understanding of the 'connect' function is that weak_ptr must be of the same object that emitted the signal in order for the signal to be received. In other words that the emitter/signal combination is what ties together a signal and a function for handling the signal. Do I have that right ? This means that in your system the handler of a signal has to have some way to access the emitter of the signal in order to setup the connection. This is similar to the way in the signals2 library that one has to have access to a boost::signals2::signal in order to setup a connection to a slot. So while I like your library I do not immediately understand the advantage it has over signals2. Could you discuss that more, either here or in your documentation ?
Edward Diener wrote:
This is similar to the way in the signals2 library that one has to have access to a boost::signals2::signal in order to setup a connection to a slot. So while I like your library I do not immediately understand the advantage it has over signals2.
When you already have an existing third-party object (say, XButton) it's
more convenient to not have to think where to place the corresponding signal
object. Non-intrusive dispatch is kind of like the library containing the
equivalent of map
On 5/20/2015 11:32 AM, Peter Dimov wrote:
Edward Diener wrote:
This is similar to the way in the signals2 library that one has to have access to a boost::signals2::signal in order to setup a connection to a slot. So while I like your library I do not immediately understand the advantage it has over signals2.
When you already have an existing third-party object (say, XButton) it's more convenient to not have to think where to place the corresponding signal object. Non-intrusive dispatch is kind of like the library containing the equivalent of map
, automatically managing the signal objects corresponding to an external, third-party, object.
I understand that with Synapse you are merely making a call at a convenient point in order to emit a signal, whereas with signals2 you need to have a boost::signals2::signal object somewhere for the particular signal triggering before you can emit a signal or have connections to handle the signal. But in Synapse you also must have a signal emitter which is accesible for the event handlers making the connection to the event. This to me is equivalent to boost::signals2 having a signal object.
In the use case in which you define your buttons, there's not much to gain, you make them contain your signal objects. But third-party buttons don't contain your signal objects, they contain their own functionality for notifying you of events, sometimes that's (their own) signal implementation, sometimes a callback/std::function, sometimes you need to override a virtual method, sometimes they send you messages. With a non-intrusive signal library, you just attach an emit call to the third-party notification mechanism and the rest of the code can connect to the non-intrusive signal. This avoids the need to wrap the external objects in your own types that contain signal objects.
The rest of the code can connect to the non-intrusive signal as long as it has access to the signal emitter. It is not as if when the signal emitter goes out of scope you can make a connection to that signal anymore. Or have I misunderstood this ? In both cases whatever is emitting the signal has to be accessible. I do understand that conceptually in Synapse have the signal emission be disconnected from the signal connection appears a less dependent design but this seems to me to be only on the surface. This does not mean I do not like Synapse. Its syntax for emitting a signal from any object is clearer than signals2, but under the surface the separation between the signal and signal handler seems much the same.
Edward Diener wrote:
I understand that with Synapse you are merely making a call at a convenient point in order to emit a signal, whereas with signals2 you need to have a boost::signals2::signal object somewhere for the particular signal triggering before you can emit a signal or have connections to handle the signal. But in Synapse you also must have a signal emitter which is accesible for the event handlers making the connection to the event. This to me is equivalent to boost::signals2 having a signal object.
You can use an already existing object as the emitter, such as a HWND, or a third-party UI button object.
On Wed, May 20, 2015 at 3:20 PM, Peter Dimov
Edward Diener wrote:
I understand that with Synapse you are merely making a call at a
convenient point in order to emit a signal, whereas with signals2 you need to have a boost::signals2::signal object somewhere for the particular signal triggering before you can emit a signal or have connections to handle the signal. But in Synapse you also must have a signal emitter which is accesible for the event handlers making the connection to the event. This to me is equivalent to boost::signals2 having a signal object.
You can use an already existing object as the emitter, such as a HWND, or a third-party UI button object.
Yes, for example it is not at all trivial to pass some kind of (emitter) object down to a WindowProc. With Synapse you don't have to, you can call emit<> on the HWND object directly, as in http://revergestudios.com/boost-synapse/using_Boost_Synapse_to_handle_events... . Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
On Wed, May 20, 2015 at 3:00 PM, Edward Diener
In the use case in which you define your buttons, there's not much to
gain, you make them contain your signal objects. But third-party buttons don't contain your signal objects, they contain their own functionality for notifying you of events, sometimes that's (their own) signal implementation, sometimes a callback/std::function, sometimes you need to override a virtual method, sometimes they send you messages. With a non-intrusive signal library, you just attach an emit call to the third-party notification mechanism and the rest of the code can connect to the non-intrusive signal. This avoids the need to wrap the external objects in your own types that contain signal objects.
The rest of the code can connect to the non-intrusive signal as long as it has access to the signal emitter. It is not as if when the signal emitter goes out of scope you can make a connection to that signal anymore. Or have I misunderstood this ?
No, you got that right.
In both cases whatever is emitting the signal has to be accessible.
On the surface that's not a big deal but typically the code that is emitting the signal already has an object that it did something to, or received a notification from. If emitting the signal requires another emitter object then in addition it must have access to that too -- which sometimes is problematic, not just inconvenient. Emil Dotchevski Reverge Studios, Inc. http://www.revergestudios.com/reblog/index.php?n=ReCode
Le 12/05/15 04:25, Emil Dotchevski a écrit :
Hello,
I realize that Boost has Signals library already, but I've implemented a non-intrusive one which approaches the problem differently, and I'm pretty sure that there is no overlap between the two. It turned out more generic than I originally anticipated, so I thought I'd ask if others would find it useful as well.
I have some concerns about the lifetime of the emitters.
E.g. an application can have a class that contains a several possible
emitters. The lifetime of all the emitters depends on the lifetime of
the C instance.
struct C {
T1 e1;
T2 e2;
};
int main() {
shared_ptr<C> c = make_shared<C>();
// ...
The lifetime of emitters c->e1 and c->e2 is managed by c.
Now I would like to connect to c->e1 and/or c->e2. But I would need to
change the class C, as I don't have a shared_ptr to &(c->e1) or &(c->e2).
Do you have a solution without changing C? If not, could your library
take care of this use case? IMO this will reduce the need for more
shared_ptr than really needed.
Maybe a connect_member
auto c1 = connect_member
On 20 May 2015 14:13, Vicente J. Botet Escriba wrote:
I have some concerns about the lifetime of the emitters.
E.g. an application can have a class that contains a several possible emitters. The lifetime of all the emitters depends on the lifetime of the C instance.
struct C { T1 e1; T2 e2; };
int main() {
shared_ptr<C> c = make_shared<C>(); // ...
The lifetime of emitters c->e1 and c->e2 is managed by c.
Now I would like to connect to c->e1 and/or c->e2. But I would need to change the class C, as I don't > have a shared_ptr to &(c->e1) or &(c->e2).
Do you have a solution without changing C? If not, could your library take care of this use case? IMO this will reduce the need for more shared_ptr than really needed.
Doesn't an aliasing shared_ptr work for this? I.e. boost::shared_ptr<T>(c, &c->e1) Best regards, Gareth ************************************************************************ The information contained in this message or any of its attachments may be confidential and is intended for the exclusive use of the addressee(s). Any disclosure, reproduction, distribution or other dissemination or use of this communication is strictly prohibited without the express permission of the sender. The views expressed in this email are those of the individual and not necessarily those of Sony or Sony affiliated companies. Sony email is for business use only. This email and any response may be monitored by Sony to be in compliance with Sony's global policies and standards
Le 20/05/15 15:25, Sylvester-Bradley, Gareth a écrit :
On 20 May 2015 14:13, Vicente J. Botet Escriba wrote:
I have some concerns about the lifetime of the emitters.
E.g. an application can have a class that contains a several possible emitters. The lifetime of all the emitters depends on the lifetime of the C instance.
struct C { T1 e1; T2 e2; };
int main() {
shared_ptr<C> c = make_shared<C>(); // ...
The lifetime of emitters c->e1 and c->e2 is managed by c.
Now I would like to connect to c->e1 and/or c->e2. But I would need to change the class C, as I don't > have a shared_ptr to &(c->e1) or &(c->e2).
Do you have a solution without changing C? If not, could your library take care of this use case? IMO this will reduce the need for more shared_ptr than really needed. Doesn't an aliasing shared_ptr work for this?
I.e. boost::shared_ptr<T>(c, &c->e1)
Wow, I missed this functionality. Thanks a lot for sharing, Vicente
Vicente J. Botet Escriba wrote: Le 20/05/15 15:25, Sylvester-Bradley, Gareth a écrit :
On 20 May 2015 14:13, Vicente J. Botet Escriba wrote: ...
struct C { T1 e1; T2 e2; }; ... Doesn't an aliasing shared_ptr work for this?
I.e. boost::shared_ptr<T>(c, &c->e1)
Wow, I missed this functionality.
Thanks a lot for sharing,
Yes, this is the motivating example for the aliasing constructor. It's also useful when you have some tree-like data structure in which the inner, say, XWidgets, are owned by the root one, and you have a shared_ptr to the root, but the root does not use shared_ptr to hold the children. So you use the aliasing constructor to create a shared_ptr that points to the inner XWidget, but shares ownership with the root shared_ptr. You can then make a weak_ptr from that, and then use that weak_ptr as an emitter. Something like, in more or less pseudocode shared_ptr<XWidget> p = load_UI_from_file( "description.xml" ); XWidget * inner = find_by_id( p.get(), "ID_OK" ); shared_ptr<XWidget> p2( p, inner ); // connect to p2's button_down (or whatever) signal Here load_UI_from_file is a third-party function from the XWidget library that creates a tree of XWidgets based on the description in the XML. To tie this with the other question, note that it's not possible to tell this function to use MyWidgetWrapper objects instead, into which we could have put signal objects. So if we had to use signal objects, we'd have needed to figure out where to place them.
participants (9)
-
Ahmed Charles
-
Dominique Devienne
-
Edward Diener
-
Emil Dotchevski
-
Gavin Lambert
-
Peter Dimov
-
Stephen Kelly
-
Sylvester-Bradley, Gareth
-
Vicente J. Botet Escriba