September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Tom S | Tom S wrote: > 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 ? > > > // ---- > (snip) > > // ---- > due to a popular demand, the dtor in 'Observee' can be changed to: ~this() { foreach (o; observers) o.observee = null; } |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Tom S | Tom S wrote:
> 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
Couldn't you also do weak pointers by XORing them with 0xFFFFFFFF (or, better yet, const size_t weakxor = -1), then XORing again before and after you need to operate on them?
Just thought I'd toss that out there.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Tom S | Tom S wrote:
> 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...
Yes, I was thinking of using malloc/free to avoid having the gc scan the array.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Chad J | Chad J > wrote:
> Couldn't you also do weak pointers by XORing them with 0xFFFFFFFF (or, better yet, const size_t weakxor = -1), then XORing again before and after you need to operate on them?
>
> Just thought I'd toss that out there.
You're very devious! I like that idea. (All you need to do is set the least significant bit to 1.)
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright wrote:
> Chad J > wrote:
>> Couldn't you also do weak pointers by XORing them with 0xFFFFFFFF (or, better yet, const size_t weakxor = -1), then XORing again before and after you need to operate on them?
>>
>> Just thought I'd toss that out there.
>
> You're very devious! I like that idea. (All you need to do is set the least significant bit to 1.)
Spoke too soon. That won't work.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright schrieb:
> Walter Bright wrote:
>> Chad J > wrote:
>>> Couldn't you also do weak pointers by XORing them with 0xFFFFFFFF (or, better yet, const size_t weakxor = -1), then XORing again before and after you need to operate on them?
>>>
>>> Just thought I'd toss that out there.
>>
>> You're very devious! I like that idea. (All you need to do is set the
>> least significant bit to 1.)
>
> Spoke too soon. That won't work.
Inverting the lsb will not, because it is also a valid ptr to the object.
Inverting the msb should always work. Well it should work in the way,
that it does not prevent the object from being deleted.
But how can it be tested, that the ptr is callable?
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | Walter Bright skrev:
> Walter Bright wrote:
>> Some of the boilerplate can be eliminated with a mixin.
> 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;
> }
>
I like what I see. But there is a problem, a signal is hereby identified by it's types only. In a real world scenario many signals will have the same types. Bith a keyUp and a keyDown signal will probably want to send a key code of the same type.
I see that as a minor problem though, it would work just as the target/action mechanism of Cocoa. Where the majority of UI controls have a single "signal". In reality a button rarely need more than "onClick", a text field "onChange", etc.
But still the exceptional events would need to be handled in some way, I would suggest going down the object delegate route just as Cocoa. A simple TextField could be:
interface TextFieldDelagate {
bool shouldChange(TextField, char[]);
void didChange(TextField);
}
And then the TextField class have a delage getter/setter of this interface type. If no delegate is set then the all calls are simply ignored, if set then the TextField will call them when appropriate.
For simplicity you do not want more than a single delegate, but if a control have say 10 delegate methods in it's delegate interface then you would not want to implement dummies for them all.
May I therefor suggest Interfaces with "optional" methods. Something like this:
interface TextFieldDelagate {
optinal bool shouldChange(TextField, char[]);
optional void didChange(TextField);
}
You would then need the ability to query the availability of an optional method. I guess something like this (Somewhere in the TextField class using the delegate interface):
void doStuff() {
if (_delaget && _delegate.implements(void didChange(TextField))) {
_delegate.didChange(this);
}
}
I guess unimplemented methods would have NULL pointers in the method tables. So this test stage could be ignored for most cases as the compiler could simply skip calling the method if it gets a NULL-pointer when fetching the function pointer.
// Fredrik Olsson
|
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? It's close, but check out the signature of trolltech's connect method: bool connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoCompatConnection ); The key difference is that the target method is specified by a *string*. That's the main difference between what Qt has and the S&S implementations people generally come up with for C++ (or D). Every QObject subclass has a QMetaObject member. http://doc.trolltech.com/4.1/qmetaobject.html QMetaObject has interesting methods like int indexOfMethod ( const char * method ) const int indexOfProperty ( const char * name ) const int methodCount () const QMetaMethod method ( int index ) const For looking up parts of the class by name and dynamic introspection. That's the part that requires the running of their "moc" tool, the Meta-Object compiler. It scans through headers and picks out that sort of information. Ok, you're probably now saying, "yeh, but that's not statically typesafe, and my implementation is!". You're right, sometimes you do want static type-safety. But sometimes you'd rather have loose dynamic coupling and runtime type-safety. Here's where I get a little hand-wavy, but this dynamic binding is very useful for writing GUIs (and generally any component system that needs loose coupling). QtDesigner is Trolltech's GUI builder: http://www.trolltech.com/products/qt/features/designer It takes advantage of all the introspection capabilities offered by the QMetaObject that lives in every component. You can point it to a gui widget you wrote, and it immediately can show all that widget's properties, signals, and signalable methods (slots), and you can add that widget to your GUI and start hooking methods together. Also it means that at run-time, you can safely try to connect to slots that may or may not be there. If the target doesn't have that slot, no harm done. And you don't need to know anything about the object at compile time other than it's a QObject. Loose coupling. I think you can get similar results in pure C++ with a lot of templates plus the requirement that users call some sort of method for every function or property they want to have dynamically callable: registerSlot(foo, "foo(int,int)") I think the CEGUI library (www.cegui.org.uk) is now using something like that approach. But obviously it requires a lot less maintenance if that is handled for you automatically, because in C++ the place you call the registerSlot() method always ends up being separated from the place where you actual declare the foo method. Qt's "slot:" decorator keyword basically lets you "register" the method at the place of declaration by tagging it with one word. All this is not to say that Qt S&S is the best way. Qt's design is constrained ultimately by having to work with C++. Hence the separate "moc" compiler. In the end Qt's QMetaObject provides a certain, fairly limited amount of dynamic functionality. But as pointed out in the other thread, something like Objective-C provides a much more general messaging mechanism. From that you can easily build Qt-like S&S or a dozen other loose coupling solutions. I think railroading Qt's S&S into a language is the wrong approach. What goes into the language should be a more general mechanism on top of which schemes like dynamic S&S can be easily built. --bb |
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Frank Benoit | Frank Benoit wrote:
> Inverting the lsb will not, because it is also a valid ptr to the object.
> Inverting the msb should always work. Well it should work in the way,
> that it does not prevent the object from being deleted.
> But how can it be tested, that the ptr is callable?
I don't think msb will work, either, as there's no guarantee the gc pool won't straddle the boundary.
|
September 29, 2006 Re: Signals and Slots in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Fredrik Olsson | Fredrik Olsson wrote:
> I like what I see. But there is a problem, a signal is hereby identified by it's types only. In a real world scenario many signals will have the same types. Bith a keyUp and a keyDown signal will probably want to send a key code of the same type.
I don't understand the problem.
|
Copyright © 1999-2021 by the D Language Foundation