Jump to page: 1 26  
Page
Thread overview
Signals and Slots in D
Sep 29, 2006
Walter Bright
Sep 29, 2006
Lutger
Sep 29, 2006
J Duncan
Sep 29, 2006
Walter Bright
Sep 29, 2006
Lutger
Sep 29, 2006
Sean Kelly
Sep 29, 2006
Walter Bright
Sep 29, 2006
Lutger
Sep 29, 2006
Walter Bright
Sep 29, 2006
Tom S
Sep 29, 2006
Tom S
Sep 29, 2006
Chad J
Sep 29, 2006
Walter Bright
Sep 29, 2006
Walter Bright
Sep 29, 2006
Frank Benoit
Sep 29, 2006
Walter Bright
Sep 29, 2006
Frits van Bommel
Sep 29, 2006
Walter Bright
Sep 29, 2006
Frits van Bommel
Sep 29, 2006
Thomas Kuehne
Sep 29, 2006
Lionello Lunesu
Sep 29, 2006
Thomas Kuehne
Sep 29, 2006
Lionello Lunesu
Sep 29, 2006
Lionello Lunesu
Sep 29, 2006
xs0
Sep 29, 2006
Lionello Lunesu
Sep 29, 2006
Lionello Lunesu
Sep 29, 2006
Sean Kelly
Oct 02, 2006
Lionello Lunesu
Oct 02, 2006
Sean Kelly
Sep 29, 2006
Chad J
Sep 29, 2006
Miles
Sep 29, 2006
Walter Bright
Sep 29, 2006
Frits van Bommel
Sep 29, 2006
Chad J
Sep 29, 2006
Georg Wrede
Sep 29, 2006
Chad J
Sep 29, 2006
Walter Bright
Sep 29, 2006
Georg Wrede
Sep 29, 2006
Walter Bright
Sep 29, 2006
Fredrik Olsson
Sep 29, 2006
Walter Bright
Sep 29, 2006
Fredrik Olsson
Sep 29, 2006
Fredrik Olsson
Sep 29, 2006
Walter Bright
Sep 29, 2006
Fredrik Olsson
Sep 29, 2006
Georg Wrede
Sep 29, 2006
Walter Bright
Sep 29, 2006
Georg Wrede
Sep 30, 2006
Walter Bright
[OT] Re: Signals and Slots in D
Sep 29, 2006
Bill Baxter
Sep 29, 2006
Walter Bright
Sep 29, 2006
Don Clugston
Sep 29, 2006
Walter Bright
Sep 29, 2006
Fredrik Olsson
Sep 29, 2006
Georg Wrede
Sep 29, 2006
Josh Stern
Sep 29, 2006
Sean Kelly
September 29, 2006
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
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

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
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
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
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
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
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
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
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
« First   ‹ Prev
1 2 3 4 5 6