Thread overview | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
September 29, 2006 Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Ok, I admit I don't understand S&S. But let's try starting with Qt's canonical example from http://doc.trolltech.com/3.3/signalsandslots.html: class Foo : public QObject { Q_OBJECT public: Foo(); int value() const { return val; } public slots: void setValue( int ); { if ( v != val ) { val = v; emit valueChanged(v); } } signals: void valueChanged( int ); private: int val; }; Foo a, b; connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int))); b.setValue( 11 ); // a == undefined b == 11 a.setValue( 79 ); // a == 79 b == 79 b.value(); // returns It seems that I can do this in D with: class Foo { this(); int value() { return val; } void setValue( int ); { if ( v != val ) { val = v; valueChanged(v); } } void valueChanged( int i ) { foreach (dg; slots) dg(i); } void connect( void delegate(int i) dg) { slots ~= dg; } private: void delegate(int i)[] slots; int val; }; Foo a = new Foo; Foo b = new Foo; a.connect(&b.setValue); b.setValue( 11 ); // a == undefined b == 11 a.setValue( 79 ); // a == 79 b == 79 b.value(); // returns 79 There's no casting, it's statically typesafe. Some of the boilerplate can be eliminated with a mixin. Is that all there is to it, or have I completely missed the boat? |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
> Ok, I admit I don't understand S&S. But let's try starting with Qt's canonical example from http://doc.trolltech.com/3.3/signalsandslots.html:
>
> class Foo : public QObject
> {
> Q_OBJECT
> public:
> Foo();
> int value() const { return val; }
> public slots:
> void setValue( int );
> {
> if ( v != val ) {
> val = v;
> emit valueChanged(v);
> }
> }
> signals:
> void valueChanged( int );
> private:
> int val;
> };
>
> Foo a, b;
> connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
> b.setValue( 11 ); // a == undefined b == 11
> a.setValue( 79 ); // a == 79 b == 79
> b.value(); // returns
>
> It seems that I can do this in D with:
>
> class Foo
> {
> this();
> int value() { return val; }
>
> void setValue( int );
> {
> if ( v != val ) {
> val = v;
> valueChanged(v);
> }
> }
>
> void valueChanged( int i )
> {
> foreach (dg; slots)
> dg(i);
> }
>
> void connect( void delegate(int i) dg)
> {
> slots ~= dg;
> }
>
> private:
> void delegate(int i)[] slots;
>
> int val;
> };
>
> Foo a = new Foo;
> Foo b = new Foo;
> a.connect(&b.setValue);
> b.setValue( 11 ); // a == undefined b == 11
> a.setValue( 79 ); // a == 79 b == 79
> b.value(); // returns 79
>
> There's no casting, it's statically typesafe. Some of the boilerplate can be eliminated with a mixin. Is that all there is to it, or have I completely missed the boat?
Almost, it's very simple, like the plain old function pointer as callback in C. The main difference is:
- when an object which has member functions connected gets detroyed, there is no problem, no segfaults.
- signals and slots are not as tightly coupled to a specific class as in your example. Be it through introspection or ifti, class B needs to know nothing about A except where it can connect, meaning more less coupling between classes. I think thats all there is to it in essence.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Lutger |
Lutger wrote:
> Walter Bright wrote:
>
>> Ok, I admit I don't understand S&S. But let's try starting with Qt's canonical example from http://doc.trolltech.com/3.3/signalsandslots.html:
>>
>> class Foo : public QObject
>> {
>> Q_OBJECT
>> public:
>> Foo();
>> int value() const { return val; }
>> public slots:
>> void setValue( int );
>> {
>> if ( v != val ) {
>> val = v;
>> emit valueChanged(v);
>> }
>> }
>> signals:
>> void valueChanged( int );
>> private:
>> int val;
>> };
>>
>> Foo a, b;
>> connect(&a, SIGNAL(valueChanged(int)), &b, SLOT(setValue(int)));
>> b.setValue( 11 ); // a == undefined b == 11
>> a.setValue( 79 ); // a == 79 b == 79
>> b.value(); // returns
>>
>> It seems that I can do this in D with:
>>
>> class Foo
>> {
>> this();
>> int value() { return val; }
>>
>> void setValue( int );
>> {
>> if ( v != val ) {
>> val = v;
>> valueChanged(v);
>> }
>> }
>>
>> void valueChanged( int i )
>> {
>> foreach (dg; slots)
>> dg(i);
>> }
>>
>> void connect( void delegate(int i) dg)
>> {
>> slots ~= dg;
>> }
>>
>> private:
>> void delegate(int i)[] slots;
>>
>> int val;
>> };
>>
>> Foo a = new Foo;
>> Foo b = new Foo;
>> a.connect(&b.setValue);
>> b.setValue( 11 ); // a == undefined b == 11
>> a.setValue( 79 ); // a == 79 b == 79
>> b.value(); // returns 79
>>
>> There's no casting, it's statically typesafe. Some of the boilerplate can be eliminated with a mixin. Is that all there is to it, or have I completely missed the boat?
>
>
> Almost, it's very simple, like the plain old function pointer as callback in C. The main difference is:
> - when an object which has member functions connected gets detroyed, there is no problem, no segfaults.
> - signals and slots are not as tightly coupled to a specific class as in your example. Be it through introspection or ifti, class B needs to know nothing about A except where it can connect, meaning more less coupling between classes. I think thats all there is to it in essence.
Yeah thats pretty much it, everything can be done with templates etc. What we dont really have yet is a standardized introspection. One of the nice things about qt is it provides a typesafe callback which c++ doesnt have. D has the Wonderful delegate, so at least we get builtin typesafe callbacks. But on my D wishlist is introspection - or a builtin messaging system, or at least a standardized implementation in phobos. But its not a big deal by any means, I just think people who have used qt see how s&s would be Very Cool in D.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Lutger | Lutger wrote: > Almost, it's very simple, like the plain old function pointer as callback in C. The main difference is: > - when an object which has member functions connected gets detroyed, there is no problem, no segfaults. Doesn't garbage collection automatically take care of that? > - signals and slots are not as tightly coupled to a specific class as in your example. Be it through introspection or ifti, class B needs to know nothing about A except where it can connect, meaning more less coupling between classes. I think thats all there is to it in essence. I think that is easily handled with a naming convention - call A.connect(). |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote: > Lutger wrote: >> Almost, it's very simple, like the plain old function pointer as callback in C. The main difference is: >> - when an object which has member functions connected gets detroyed, there is no problem, no segfaults. > > Doesn't garbage collection automatically take care of that? Yes, but you'd have a reference to a dead object in your delegate array when it gets deleted and then you emit a signal. You can of course remove that reference when you so want to delete an object, then you have to track them and miss some ease of use. Or let some language / library do it for you. >> - signals and slots are not as tightly coupled to a specific class as in your example. Be it through introspection or ifti, class B needs to know nothing about A except where it can connect, meaning more less coupling between classes. I think thats all there is to it in essence. > > I think that is easily handled with a naming convention - call A.connect(). Yes, this is also a convention amongst different libraries (I like the ~= syntactic sugar though. C# events use the += operator btw). Given this convention, some boilerplate code and a way to delete objects without needing to manually call A.disconnect(&foo), there are two things missing, but they could be left out: - Signals as a seperate struct or class instead, no need for a signal to be limited to be a member of some class. - It should work for all callable types. |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
> Some of the boilerplate can be eliminated with a mixin.
Here's the mixin. Actually, 3 of them, one each for 0 arguments, 1 argument, and 2 arguments. I added a disconnect() function. Note how trivial it is to use - no need for preprocessing.
import std.stdio;
template Signal() // for 0 arguments
{
void emit()
{
foreach (dg; slots)
dg();
}
void connect( void delegate() dg)
{
slots ~= dg;
}
void disconnect( void delegate() dg)
{
for (size_t i = 0; i < slots.length; i++)
{
if (slots[i] == dg)
{
if (i + 1 == slots.length)
slots = slots[0 .. i];
else
slots = slots[0 .. i] ~ slots[i + 1 .. length];
}
}
}
private:
void delegate()[] slots;
}
template Signal(T1) // for one argument
{
void emit( T1 i )
{
foreach (dg; slots)
dg(i);
}
void connect( void delegate(T1) dg)
{
slots ~= dg;
}
void disconnect( void delegate(T1) dg)
{
for (size_t i = 0; i < slots.length; i++)
{
if (slots[i] == dg)
{
if (i + 1 == slots.length)
slots = slots[0 .. i];
else
slots = slots[0 .. i] ~ slots[i + 1 .. length];
}
}
}
private:
void delegate(T1)[] slots;
}
template Signal(T1, T2) // for two arguments
{
void emit( T1 i, T2 j )
{
foreach (dg; slots)
dg(i, j);
}
void connect( void delegate(T1, T2) dg)
{
slots ~= dg;
}
void disconnect( void delegate(T1, T2) dg)
{
for (size_t i = 0; i < slots.length; i++)
{
if (slots[i] == dg)
{
if (i + 1 == slots.length)
slots = slots[0 .. i];
else
slots = slots[0 .. i] ~ slots[i + 1 .. length];
}
}
}
private:
void delegate(T1, T2)[] slots;
}
class Foo
{
this() { }
int value() { return val; }
void setValue( int v )
{
if ( v != val )
{
val = v;
emit(v);
}
}
mixin Signal!(int); // adds in all the boilerplate to make it work
private:
int val;
}
void main()
{
Foo a = new Foo;
Foo b = new Foo;
a.connect(&b.setValue);
b.setValue( 11 ); // a == 0 b == 11
a.setValue( 79 ); // a == 79 b == 79
writefln(b.value()); // prints 79
a.disconnect(&b.setValue);
a.setValue( 80);
writefln(b.value()); // prints 79
}
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote: > Walter Bright wrote: >> Some of the boilerplate can be eliminated with a mixin. > > Here's the mixin. Actually, 3 of them, one each for 0 arguments, 1 argument, and 2 arguments. I added a disconnect() function. Note how trivial it is to use - no need for preprocessing. Nice, this is also a good option imho, even though it lacks a few features. To be fair, the preprocessor of QT adds a lot more stuff than this. See http://www.scottcollins.net/articles/a-deeper-look-at-signals-and-slots.html for a comparison. I would mix this in a struct, alias emit to opCall, provide a clear function (remove all delegates) and maybe opApply. Then you can have something like this: class Button { Signal!() onClicked; void processInput() { if (/*code to detect clicky*/) onClicked(); } } Button clicky = new Button; Popup hello = new Popup("hello"); clicky.onClicked.connect(&hello.msg); // or clicky.onClicked.connect(&hello.msg, hello) if connections are made safe. // or: clicky.onClicked ~= hello.msg; If and when D function pointers and delegates get to be compatible, (will they?) it will get even better for this simple solution. |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Ok, before anyone jumps on me, this has all been discussed in http://www.digitalmars.com/d/archives/28456.html Looks like the deletion problem is a real issue. Let me think about it a bit. |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote: > Ok, before anyone jumps on me, this has all been discussed in http://www.digitalmars.com/d/archives/28456.html > > Looks like the deletion problem is a real issue. Let me think about it a bit. could something like this work ? // ---- import std.stdio, std.c.stdlib, std.gc; class Observer { this (char[] name) { this.name = name; } void connect(Observee o) { observee = o; o.register(this); } void hear() { writefln("%s hears !", name); } ~this() { writefln("%s goes bye bye", name); if (observee) { observee.unregister(this); } } Observee observee; char[] name; } class Observee { void register(Observer o) { writefln("registering ", o.name); if (observers.length == 0) { observers = (cast(Observer*)malloc(Observer.sizeof))[0..1]; } else { observers = (cast(Observer*)realloc( observers.ptr, Observer.sizeof * (observers.length+1) ))[0..observers.length+1]; } observers[length-1] = o; } void unregister(Observer o) { writefln("unregistering ", o.name); foreach (i, inout x; observers) { if (x is o) { x.observee = null; x = observers[length-1]; observers = observers[0..length-1]; return; } } assert (false); } void shout() { writefln("shouting !"); foreach (o; observers) o.hear(); } ~this() { foreach (o; observers) delete o; } Observer[] observers; } void foo(Observee stuff) { Observer foo1 = new Observer("frisky"); Observer foo2 = new Observer("bob"); foo1.connect(stuff); foo2.connect(stuff); } void main() { Observee stuff = new Observee; foo(stuff); float[100] eraseStack; Observer foo3 = new Observer("pat"); foo3.connect(stuff); Observer foo4 = new Observer("zomg"); foo4.connect(stuff); std.gc.fullCollect(); delete foo4; stuff.shout(); writefln("exiting"); } // ---- basically, the registered observers are stored as weak pointers due to the gc not scanning malloc'd memory blocks. if both sides do the unregistration, it seems to work fine... -- Tomasz Stachowiak |
September 29, 2006 [OT] Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | All this Signals&Slots business (which I also admit to having zero experience with) makes me think of the Actions concept I worked into my hypothetical GUI library, based on a similar concept found (with incomplete implementation, last I checked) in Java's Swing GUI. An 'Action' is an object representing a behavior (or, well, "action" :)) of the program, and has three faculties: storage of metadata, such as a name, associated resources, etc; generation of Presenters, such as toolbar buttons and menu items; binding to Performers -- callbacks that do the work of the Action. Some snips to (hopefully) make it clearer: # // bind this.open(ActionContext) to an appropriate Action # Action["OpenFile"].append(&open); Note that we need only refer to the Action instance by its name, and note also the Context class which is sent as the only parameter. This would encapsulate any additional data needed by the Performer, and can also be subclassed for custom data. (In theory, anyhow.) # // retrieve a menu item Presenter for an Action # auto item = Action["SaveAs"].presenter(new MenuItem); Surely also self-explanatory. In addition to the .append() method for adding Performers, there is a .prepend() -- for completion, but could be useful -- a .clear() which unbinds all Performers, and a .set() which is the same as clearing and then appending. Actions (in my hypothetical GUI, mind you) would be triggered by component objects, usually in response to an event from the underlying system's GUI concept. (Messages in Windows, for example.) All the library user's code need do is bind Performers to Actions, and generate appropriate components by asking Actions for their Presenters. The program then, essentially, runs itself. How does this idea relate to Signals&Slots? I really want to understand what exactly makes S&S so valuable. Is it essentially just a standard for convenience? (Which would be a bad thing, neccessarily, but that's all I can figure it to be.) Or does it inherently open up some new capability I'm not aware of? -- Chris Nicholson-Sauls |
Copyright © 1999-2021 by the D Language Foundation