digitalmars.D - std.signals2 proposal
- Robert (96/96) Nov 05 2012 Hi there!
- Zhenya (3/137) Nov 05 2012 Hi!Could you write some examples for struct and non-object
- Robert (29/32) Nov 05 2012 Sure!
- Zhenya (7/52) Nov 05 2012 I am embarrassed a little that a member function of the structure
- Ellery Newcomer (38/96) Nov 05 2012 Not sure I understand why such hatred is rational?
- Robert Klotzner (62/150) Nov 06 2012 I don't know where you saw hate. I just believe that a strong type
- Jacob Carlborg (4/9) Nov 06 2012 And why would it be bad to pass a delegate literal like this?
- eskimo (16/23) Nov 06 2012 Because the connect method in std.signal interprets the context pointer
- Jacob Carlborg (5/9) Nov 06 2012 I've not read the code and I'm not 100% sure of the intentions of
- eskimo (38/41) Nov 06 2012 Signals are a way of a very loose coupling of components. This loose
- Jacob Carlborg (5/21) Nov 06 2012 Aha, I see, that was the small detail I had missed. Thanks for the
- Kagamin (8/19) Nov 07 2012 As long as you keep strong reference to the observer, it won't
- Ellery Newcomer (22/57) Nov 06 2012 // slot[i].indirect.ptr == null
Hi there! I just developed a proof-of-concept implementation of an improved std.signals. Things I did not like about the current implementation: 1. Passing a delegate to connect, which basically is an extended (void*) and assuming it is an object delegate, does not feel quite right in D, where we usually try to guarantee correctness by the compiler wherever possible. 2. The restriction that only objects can be connected. Point 2 does not really bother me, because from my little experience I never really had connected anything else than objects to a signal. But the restriction that the connected delegate must not be some wrapper, is quite a restriction I came to dislike about Qt and even more so with this implementation because unlike Qt the signatures have to match exactly, you can't omit parameters, like: // Qt code connect(button, SIGNAL(clicked(bool)), this, SLOT(buttonClicked()); -> In the current implementation buttonClicked would have to take a bool. In addition boost::signals together with boost::bind offered even more comfort like passing additional parameters to a slot, which is really very, very useful: for(int i=0; i<buttons.length(); i++) { buttons[i].clicked.connect(boost::bind(&MyObj::addNumber, this, i)); } So I tried to improve std.signals, code: (warning at least one bug is remaining, explained below) https://github.com/eskimor/phobos/tree/new_signal You can easily connect to an object's method with: obj.signal.connect!"someMethod"(obj); instead of the old implementation: obj.signal.connect(&obj.someMethod); -> The interface is clean and type safe, all the ugly details are hidden from the user. And it is just one character more typing. Simple things stay simple. In addition a method allowing wrapper delegates was added: class Observer { void addNumber(int i) { sum+=i; } int sum; } class Button { Signal!(bool) clicked; // ... } void main() { auto b=new Button; auto o=new Observer; // Ignore boolean parameter and pass some int: b.connect!Observer(o, (o, p) { o.addNumber(7); }); // or even: b.connect!Observer(o, (o, p) => o.addNumber(7)); // For some reason the compiler is not able to deduce "o" being // Observer, so the !Observer is needed, but it is still very // neat and readable. } Thanks to D's lamdas the syntax is even more concise as boost::bind and far more powerful. By passing the object explicitly to the delegate, it is possible to maintain the 'weak ref' semantics to the target object, while ensuring that the delegates context won't be freed. As a side effect it is now even possible to use struct delegates or even any non object delegate. Simply pass null for the obj parameter. It is completely safe, the only drawback is that the struct won't be deleted until the Button gets destroyed. (Because it holds a reference to the struct, by means of the delegate.) But for long lived structs this usually is perfectly acceptable. Implementation: In my implementation I changed the Signal mixin to be a simple template struct, because I hit strange compiler errors with it being a mixin. The most prominent: std/signals.d(443): Error: no overload matches for connect(string method,T2) if (is(T2 : Object)) You can find the version triggering these errors at: https://github.com/eskimor/phobos/tree/new_signal_mixin Also I did not really get the point why a mixin was used in the first place, it does not really gain us anything? What was the reasoning about it? I almost thought I found the reason, because my implementations suffers from unhook not being called, although it was properly registered with "rt_attachDisposeEvent(obj, &unhook);", thus causing a segmentation fault when the observer gets deleted. I did not really find any difference from the original version that could explain this behavior, despite the original implementation being a mixin. So I thought, well maybe the delegate passed to "rt_attachDisposeEvent(obj, &unhook);" must be an object delegate (that's would be why the mixin was needed), but after digging in object_.d I did not find any code assuming that the delegate was an object delegate. Any ideas on this? Another thing I'd like to ask Walter, is what the "L1:" label is for in connect(), is it just some left over or has it some special internal compiler thing meaning? What do you think? Best regards, Robert
Nov 05 2012
On Monday, 5 November 2012 at 13:35:26 UTC, Robert wrote:Hi there! I just developed a proof-of-concept implementation of an improved std.signals. Things I did not like about the current implementation: 1. Passing a delegate to connect, which basically is an extended (void*) and assuming it is an object delegate, does not feel quite right in D, where we usually try to guarantee correctness by the compiler wherever possible. 2. The restriction that only objects can be connected. Point 2 does not really bother me, because from my little experience I never really had connected anything else than objects to a signal. But the restriction that the connected delegate must not be some wrapper, is quite a restriction I came to dislike about Qt and even more so with this implementation because unlike Qt the signatures have to match exactly, you can't omit parameters, like: // Qt code connect(button, SIGNAL(clicked(bool)), this, SLOT(buttonClicked()); -> In the current implementation buttonClicked would have to take a bool. In addition boost::signals together with boost::bind offered even more comfort like passing additional parameters to a slot, which is really very, very useful: for(int i=0; i<buttons.length(); i++) { buttons[i].clicked.connect(boost::bind(&MyObj::addNumber, this, i)); } So I tried to improve std.signals, code: (warning at least one bug is remaining, explained below) https://github.com/eskimor/phobos/tree/new_signal You can easily connect to an object's method with: obj.signal.connect!"someMethod"(obj); instead of the old implementation: obj.signal.connect(&obj.someMethod); -> The interface is clean and type safe, all the ugly details are hidden from the user. And it is just one character more typing. Simple things stay simple. In addition a method allowing wrapper delegates was added: class Observer { void addNumber(int i) { sum+=i; } int sum; } class Button { Signal!(bool) clicked; // ... } void main() { auto b=new Button; auto o=new Observer; // Ignore boolean parameter and pass some int: b.connect!Observer(o, (o, p) { o.addNumber(7); }); // or even: b.connect!Observer(o, (o, p) => o.addNumber(7)); // For some reason the compiler is not able to deduce "o" being // Observer, so the !Observer is needed, but it is still very // neat and readable. } Thanks to D's lamdas the syntax is even more concise as boost::bind and far more powerful. By passing the object explicitly to the delegate, it is possible to maintain the 'weak ref' semantics to the target object, while ensuring that the delegates context won't be freed. As a side effect it is now even possible to use struct delegates or even any non object delegate. Simply pass null for the obj parameter. It is completely safe, the only drawback is that the struct won't be deleted until the Button gets destroyed. (Because it holds a reference to the struct, by means of the delegate.) But for long lived structs this usually is perfectly acceptable. Implementation: In my implementation I changed the Signal mixin to be a simple template struct, because I hit strange compiler errors with it being a mixin. The most prominent: std/signals.d(443): Error: no overload matches for connect(string method,T2) if (is(T2 : Object)) You can find the version triggering these errors at: https://github.com/eskimor/phobos/tree/new_signal_mixin Also I did not really get the point why a mixin was used in the first place, it does not really gain us anything? What was the reasoning about it? I almost thought I found the reason, because my implementations suffers from unhook not being called, although it was properly registered with "rt_attachDisposeEvent(obj, &unhook);", thus causing a segmentation fault when the observer gets deleted. I did not really find any difference from the original version that could explain this behavior, despite the original implementation being a mixin. So I thought, well maybe the delegate passed to "rt_attachDisposeEvent(obj, &unhook);" must be an object delegate (that's would be why the mixin was needed), but after digging in object_.d I did not find any code assuming that the delegate was an object delegate. Any ideas on this? Another thing I'd like to ask Walter, is what the "L1:" label is for in connect(), is it just some left over or has it some special internal compiler thing meaning? What do you think? Best regards, RobertHi!Could you write some examples for struct and non-object delegates?
Nov 05 2012
Hi!Could you write some examples for struct and non-object delegates?Sure! Something like: struct Observer { void observe(int a, int b) { // ... } } void main() { Signal!(int, int) s1; Signal!int s2 Observer o; s1.connect!Object(null, (null_object, a, b) => o.observe(a, b)); s2.connect!Object(null, (null_object, a) => o.observe(7, a)); } Having the delegate accept a null parameter might not be pretty, but I consider this a good thing, because of the changed semantics: The signal will keep a reference to the struct now, so the signals weak reference semantics are no longer in place. (If struct is allocated on the heap, it won't be freed as long as the signal is alive.) But it is possible and safe. And if you know what you are doing also very reasonable. But the main benefit is not that you can connect to structs (which is a side effect), but that you can use wrapping delegates which do parameter adoptions. That's the killer feature that proved to be so indispensable and neat for me and others. If really required it would not be to hard to provide an overload of connect() which takes a struct pointer directly, just like the one taking an object, but because of the changed semantics and the rare uses I'd expect, probably not worthwhile. But comments are appreciated.
Nov 05 2012
On Monday, 5 November 2012 at 19:07:13 UTC, Robert wrote:I am embarrassed a little that a member function of the structure looks like a static function,maybe it would be better if connect took struct pointer directly.I think if struct become immortal it will not be a big trouble,in other case we have disconnect,that will help us. Sorry for my awful englishHi!Could you write some examples for struct and non-object delegates?Sure! Something like: struct Observer { void observe(int a, int b) { // ... } } void main() { Signal!(int, int) s1; Signal!int s2 Observer o; s1.connect!Object(null, (null_object, a, b) => o.observe(a, b)); s2.connect!Object(null, (null_object, a) => o.observe(7, a)); } Having the delegate accept a null parameter might not be pretty, but I consider this a good thing, because of the changed semantics: The signal will keep a reference to the struct now, so the signals weak reference semantics are no longer in place. (If struct is allocated on the heap, it won't be freed as long as the signal is alive.) But it is possible and safe. And if you know what you are doing also very reasonable. But the main benefit is not that you can connect to structs (which is a side effect), but that you can use wrapping delegates which do parameter adoptions. That's the killer feature that proved to be so indispensable and neat for me and others. If really required it would not be to hard to provide an overload of connect() which takes a struct pointer directly, just like the one taking an object, but because of the changed semantics and the rare uses I'd expect, probably not worthwhile. But comments are appreciated.
Nov 05 2012
On 11/05/2012 05:36 AM, Robert wrote:I just developed a proof-of-concept implementation of an improved std.signals.Cool.1. Passing a delegate to connect, which basically is an extended (void*) and assuming it is an object delegate, does not feel quite right in D, where we usually try to guarantee correctness by the compiler wherever possible.Not sure I understand why such hatred is rational?Point 2 does not really bother me,It bothers me.So I tried to improve std.signals, code: (warning at least one bug is remaining, explained below) https://github.com/eskimor/phobos/tree/new_signalin emit: if(slot.indirect.ptr) { <stuff> }else{ <otherstuff> slot.indirect.ptr = <thing> slot.indirect(i); } <stuff> will not be executed more than once? in addSlot: you use malloc &friends to allocate objs, but the gc to allocate slots? The correct thing to do here is use allocators, which we will totally have fleshed out by Christmas (right, Andrei?). in connect [Object]: <Tony Shalhoub voice> It's not formatted right! </Tony Shalhoub voice> You might s/is(T2 : Object)/is(T2 == class)/ Also, it looks like it will not delegate to any overriding methods, but I don't know if that is desired. I doubt it differs from existing std.signal. invariant:assert(slots_idx==slots.length); // I probably even remove slots_idx all together.Yes you should. Also, you should assert that slots.length == objs.length. private:union DelegateTypes { void delegate(void*, T1) indirect; void delegate(T1) direct; }Could you explain why indirect must have its first param void* ?You can easily connect to an object's method with: obj.signal.connect!"someMethod"(obj); instead of the old implementation: obj.signal.connect(&obj.someMethod);The improvement is where?In addition a method allowing wrapper delegates was added: class Observer { void addNumber(int i) { sum+=i; } int sum; } class Button { Signal!(bool) clicked; // ... } void main() { auto b=new Button; auto o=new Observer; // Ignore boolean parameter and pass some int: b.connect!Observer(o, (o, p) { o.addNumber(7); }); // or even: b.connect!Observer(o, (o, p) => o.addNumber(7)); // For some reason the compiler is not able to deduce "o" being // Observer, so the !Observer is needed, but it is still very // neat and readable. }Nice. Should that be b.clicked.connect?By passing the object explicitly to the delegate, it is possible to maintain the 'weak ref' semantics to the target object, while ensuring that the delegates context won't be freed.When would the delegate's context be freed? I think it wouldn't.As a side effect it is now even possible to use struct delegates or even any non object delegate. Simply pass null for the obj parameter. It is completely safe, the only drawback is that the struct won't be deleted until the Button gets destroyed. (Because it holds a reference to the struct, by means of the delegate.) But for long lived structs this usually is perfectly acceptable.I like this capability, but the api could handle the case of non-object delegates better. <nitpick> Not completely safe if the struct was allocated on the stack </nitpick>In my implementation I changed the Signal mixin to be a simple template struct, because I hit strange compiler errors with it being a mixin. The most prominent:Welcome to D.Also I did not really get the point why a mixin was used in the first place, it does not really gain us anything? What was the reasoning about it?So you can do b.connect in the simplest case?What do you think?I like the way this is headed.
Nov 05 2012
First a more direct link to the code: https://github.com/eskimor/phobos/blob/new_signal/std/signals.dNot sure I understand why such hatred is rational?I don't know where you saw hate. I just believe that a strong type system is a good idea to avoid many errors, that's one of the reason I am a C++ and D developer and not use some clumsy scripting language where everything blows at run time. I believe that you should have a type safe public API where ever possible and if you need a delegate pointing to an object, you should state that, if it is possible. The only reason Walter probably has chosen this way, is that he most likely had something in mind to lift this restriction in the future. Which would be, in my opinion the only valid reason to leave it the way it is. In the current situation it would not be possible to use signals from safe D, in my version you can mark it as trusted. I wrote my version to show that very powerful signals are possible within the current language and without proper weak references or something. I also don't know how Walter would have intended to support wrapper delegates for parameter matching in the current design. I can't think of any language feature that would make this possible. (You would only have an opaque pointer to a delegates context, which somehow has a reference to the target object. You need a weak ref to the target object, but a strong ref to the delegates context. If the target ref is contained in an unknown way in the delegates context, there is no way to achieve this.)in emit: if(slot.indirect.ptr) { <stuff> }else{ <otherstuff> slot.indirect.ptr = <thing> slot.indirect(i); } <stuff> will not be executed more than once?<stuff> is: "slot.indirect(cast(void*)(objs[index]), i);" and gets executed for every slot. I am not sure I understand your question.in addSlot: you use malloc &friends to allocate objs, but the gc to allocate slots? The correct thing to do here is use allocators, which we will totally have fleshed out by Christmas (right, Andrei?).Yes, because I need the references in slots to be considered by the gc, I could equally well have used malloc and afterwards add it to gc roots, but the current implementation is just proof of concept, not a perfectly tweaked one. If people want it in phobos I would most certainly try to make it as good as possible first.in connect [Object]: <Tony Shalhoub voice> It's not formatted right! </Tony Shalhoub voice> You might s/is(T2 : Object)/is(T2 == class)/well yes, because every class derives from Object. But the requirement I need is that T2 is derived from Object, so it makes the intent more clear.Also, it looks like it will not delegate to any overriding methods, but I don't know if that is desired. I doubt it differs from existing std.signal. invariant:-> No I should not, at least in the current implementation this would be wrong. I did not yet made up my mind how I want to implement it in the end, so I left it for now.assert(slots_idx==slots.length); // I probably even remove slots_idx all together.Yes you should. Also, you should assert that slots.length == objs.length.private:Well it does not have to, but I know for sure that a cast to void* is just a reinterpretation. I could equally well have used Object, because at least in my understanding of a non-multiple inheritance hierarchy, the cast to Object should not change the pointer value either. But in fact it does not really matter as you don't do anything with it, other than passing it on. It would avoid some casts though, so I will consider it.union DelegateTypes { void delegate(void*, T1) indirect; void delegate(T1) direct; }Could you explain why indirect must have its first param void* ?That you can not pass some arbitrary slot like: obj.signal.connect(() { /* my very funny delegate. */ }) which would compile with the standard implementation but would fail badly at runtime.You can easily connect to an object's method with: obj.signal.connect!"someMethod"(obj); instead of the old implementation: obj.signal.connect(&obj.someMethod);The improvement is where?b.connect!Observer(o, (o, p) => o.addNumber(7));Nice. Should that be b.clicked.connect?Yes, it should be. Thanks.If you keep the delegate references in non gc managed memory, the gc will not see any references to the delegates' context if client code does not keep a reference, which is very likely for lamdas and thus will free it in the next collection cycle. If the delegates context is not the object itself, you need a strong reference to it.By passing the object explicitly to the delegate, it is possible to maintain the 'weak ref' semantics to the target object, while ensuring that the delegates context won't be freed.When would the delegate's context be freed? I think it wouldn't.I knew this was coming. Well I haven't done anything with closures yet, the usage is an escaping of a reference so the compiler should allocate it on the heap, if not than you got a problem, but its not specific to signals -> So I still consider the signal API safe ;-)As a side effect it is now even possible to use struct delegates or even any non object delegate. Simply pass null for the obj parameter. It is completely safe, the only drawback is that the struct won't be deleted until the Button gets destroyed. (Because it holds a reference to the struct, by means of the delegate.) But for long lived structs this usually is perfectly acceptable.I like this capability, but the api could handle the case of non-object delegates better. <nitpick> Not completely safe if the struct was allocated on the stack </nitpick>Yes, that's the only reason that comes to mind. I personally think that a struct is a cleaner solution, that also avoids cluttering the containing objects namespace.Also I did not really get the point why a mixin was used in the first place, it does not really gain us anything? What was the reasoning about it?So you can do b.connect in the simplest case?Thanks, and thanks for you review.What do you think?I like the way this is headed.
Nov 06 2012
On 2012-11-06 13:10, Robert Klotzner wrote:And why would it be bad to pass a delegate literal like this? -- /Jacob CarlborgThe improvement is where?That you can not pass some arbitrary slot like: obj.signal.connect(() { /* my very funny delegate. */ }) which would compile with the standard implementation but would fail badly at runtime.
Nov 06 2012
On Tue, 2012-11-06 at 14:13 +0100, Jacob Carlborg wrote:Because the connect method in std.signal interprets the context pointer of the delegate to be an Object. If it is not, your program crashes. To quote the author of _d_toObject (which is used in the original std.signal): /****************************************** * Given a pointer: * If it is an Object, return that Object. * If it is an interface, return the Object implementing the interface. * If it is null, return null. * Else, undefined crash */ Object _d_toObject(void* p); Also check out the bugs section in the html documentation: http://dlang.org/phobos/std_signals.htmlobj.signal.connect(() { /* my very funny delegate. */ }) which would compile with the standard implementation but would fail badly at runtime.And why would it be bad to pass a delegate literal like this?
Nov 06 2012
On 2012-11-06 14:39, eskimo wrote:Because the connect method in std.signal interprets the context pointer of the delegate to be an Object. If it is not, your program crashes. To quote the author of _d_toObject (which is used in the original std.signal):I've not read the code and I'm not 100% sure of the intentions of std.signal but why not just call the delegate as is? -- /Jacob Carlborg
Nov 06 2012
I've not read the code and I'm not 100% sure of the intentions of std.signal but why not just call the delegate as is?Signals are a way of a very loose coupling of components. This loose coupling is the reason why people usually expect weak reference semantics from signals. So people expect a signal connection to simply vanish when the observer object dies, instead of keeping it alive because it holds a reference to it. The solution at the moment is to hold a reference to the object in memory not seen by the gc. So it gets destroyed if no one else holds a reference. But to avoid calling a dead object's method the signal needs to be notified about this, which is currently only possible for objects. (so no generic delegate support) The only reason why a simple delegate is not enough, is the weak reference semantics. If it was not for that, a signal would just be a simple array of delegates. Things get even more complex if you allow wrapper delegates. That's because you have to keep the wrapper delegates context in memory even if nobody else references it. Thus you need a strong ref to the delegates context and a weak ref to the target object, that's why the target object is passed to the delegate, instead of simply containing a ref in the context pointer. The new implementation supports wrapper delegates, the old one does not and can't with the current API. If we had generic weak references and you would pass a lamda to connect, (which would be syntactically correct), the connection would be gone on the next gc collect cycle. The reason I think wrapper delegates are really needed is that signals, as already mentioned, are a way of loose coupling. If the signatures of signal and final receiver have to match exactly, the usefulness is seriously limited. And very powerful and elegant techniques are possible if you can pass arbitrary parameters to a slot via the delegate. A very simple example with button.clicked & addNumber was already presented in may first e-mail on this topic, but it is really just the top of the iceberg. Almost every signal/slot implementation has weak reference semantics, the only one I found not having weak reference semantics was the implementation in Tango, but only because D has no weak references. In some occasions weak reference semantics might not needed (if you have objects connected together with similar life times), but a general working approach should support weak coupling, otherwise you could simply use a delegate array.
Nov 06 2012
On 2012-11-06 16:32, eskimo wrote:Aha, I see, that was the small detail I had missed. Thanks for the explanation. -- /Jacob CarlborgI've not read the code and I'm not 100% sure of the intentions of std.signal but why not just call the delegate as is?Signals are a way of a very loose coupling of components. This loose coupling is the reason why people usually expect weak reference semantics from signals. So people expect a signal connection to simply vanish when the observer object dies, instead of keeping it alive because it holds a reference to it. The solution at the moment is to hold a reference to the object in memory not seen by the gc. So it gets destroyed if no one else holds a reference. But to avoid calling a dead object's method the signal needs to be notified about this, which is currently only possible for objects. (so no generic delegate support) The only reason why a simple delegate is not enough, is the weak reference semantics. If it was not for that, a signal would just be a simple array of delegates.
Nov 06 2012
On Tuesday, 6 November 2012 at 15:31:42 UTC, eskimo wrote:As long as you keep strong reference to the observer, it won't die. Having signals with weak reference semantics can be surprising reference semantics for observers. On the other hand one may want strong reference semantics: if you have e.g. a button.click listener, you don't want it to die prematurely, do you?I've not read the code and I'm not 100% sure of the intentions of std.signal but why not just call the delegate as is?Signals are a way of a very loose coupling of components. This loose coupling is the reason why people usually expect weak reference semantics from signals. So people expect a signal connection to simply vanish when the observer object dies, instead of keeping it alive because it holds a reference to it.
Nov 07 2012
On Wednesday, 7 November 2012 at 13:59:53 UTC, Kagamin wrote:On Tuesday, 6 November 2012 at 15:31:42 UTC, eskimo wrote:sources of memory leaks. It's likely the most common memory leak period, but because in most situations you don't have tens of thousands of subscribers people don't notice. When you do though, things get messy.Signals are a way of a very loose coupling of components. This loose coupling is the reason why people usually expect weak reference semantics from signals. So people expect a signal connection to simply vanish when the observer object dies, instead of keeping it alive because it holds a reference to it.As long as you keep strong reference to the observer, it won't die. Having signals with weak reference semantics can be surprising reference semantics for observers. On the other hand one may want strong reference semantics: if you have e.g. a button.click listener, you don't want it to die prematurely, do you?
Nov 07 2012
Having signals with weak reference semantics can be surprising reference semantics for observers. On the other hand one may want strong reference semantics: if you have e.g. a button.click listener, you don't want it to die prematurely, do you?Well I don't think it is a common pattern to create an object, connect it to some signal and drop every reference to it. On the other hand, if a signal kept a a strong reference to every object and you are done with it, you manually have to disconnect it from every signal in order not to have a memory leak, if the signals are long lived. Which comes pretty close to manual memory management (You don't get dangling pointers when doing things wrong, but signals keeping objects alive nobody cares about). So for me, especially in a garbage collected environment I would expect not to have to worry about such things. At least in my understanding it is very unintuitive to have a valid object whose only reference is a delegate contained in some signal. Having said that, there might be good reasons someone wants strong refs, so I will support in an easy and clean way, also because it comes at essentially no additional cost. I will just add a method like: void strongConnnect(void delegate(T1) dg) with dg being any delegate. (struct member function, lambda, class member, ...) with obvious semantics. This way you can use strongConnect if you have an observer you don't need any reference to, with its lifetime dependent on the signal and use the normal connect for carefree loose coupling. Best regards, Robert
Nov 07 2012
On Wednesday, 7 November 2012 at 23:26:46 UTC, eskimo wrote:Well I don't think it is a common pattern to create an object, connect it to some signal and drop every reference to it.Ok, example: suppose we have a tabbed interface and on closing a tab we want to free model data, displayed in the tab and we already have standard IDisposable.Dispose() method, so: _tab.closed.connect((sender,args)=>this.Dispose()); If the closure dies prematurely, it won't free resources at all or at the right time. Although you currently keep a strong reference to closures, you claim it's a bug rather than feature. You fix deterministic sloppiness of memory leaks at the cost of undeterministic sloppiness of prematurely dying event handlers (depending on the state of the heap).
Nov 08 2012
_tab.closed.connect((sender,args)=>this.Dispose()); If the closure dies prematurely, it won't free resources at all or at the right time. Although you currently keep a strong reference to closures, you claim it's a bug rather than feature. You fix deterministic sloppiness of memory leaks at the cost of undeterministic sloppiness of prematurely dying event handlers (depending on the state of the heap).Now I see where this is coming from, you got that wrong. It is an absolute must to have a strong ref to the closure. Otherwise it would not work at all, but the signal should not keep "this" from your example alive, which is obviously not possible, because it would break the closure, also the signal has no way to find out that this.Dispose() is eventually invoked. The trick that solved both problems is that I pass the object to the delegate, instead of hiding it in its context. This way I don't have a strong ref from the delegate, which would keep the object alive and the signal can tell the runtime to get informed when the connected object gets deleted. The thing I claimed a side effect (not a bug, it really is not) is that you can create a strong ref to the object easily, by issuing connect with null for the object and simply contain the object in the delegates context. This way also struct methods and other delegates can be connected to a signal, but with strong ref semantics. Maybe this misunderstanding was caused by this thread unfortunately being split up in two threads, so you might have missed half of my explanation and examples: One is starting with "std.signals2 proposal" and one staring with "RE: std.signals2 proposal". Best regards, Robert
Nov 08 2012
Huh? I don't get it. Didn't you want weak ref semantics for signals? Why do you want strong ref semantics now?
Nov 09 2012
On Fri, 2012-11-09 at 19:28 +0100, Kagamin wrote: Huh? I don't get it. Didn't you want weak ref semantics forsignals? Why do you want strong ref semantics now?There is a distinction between the context of a delegate, which is used for parameter transformation or other advanced stuff and the final destination object. The first one is very likely that only the signal has a reference to it (think of lamdas), and thus the signal holds a strong ref to it. For the object, which method gets eventually invoked, the signal does not hold a strong ref, instead it simply drops the slot when the object gets deleted. In your example, to make it work with weak ref semantics with the new signal implementation: _tab.closed.connect(this, (obj, sender,args)=>obj.Dispose()); instead of: _tab.closed.connect((sender,args)=>this.Dispose()); (obj, sender,args)=>obj.Dispose() is in this case just a function or a delegate with null ptr as context. But if there were a context the signal would keep it in memory. The object which gets explicitly passed to the delegate via obj, is only weakly referenced from the signal. The whole purpose is to make indirect connections to an objects method possible, for parameter transformations, parameter omissions, for providing additional parameters, ... If you want a direct connection you would use the simpler: signal.connect!"Dispose"(this); as explained in my initial post.
Nov 10 2012
On 11/06/2012 04:10 AM, Robert Klotzner wrote:nvmNot sure I understand why such hatred is rational?// slot[i].indirect.ptr == null thing.emit(params); // slot[i].indirect.ptr != null thing.emit(params);in emit: if(slot.indirect.ptr) { <stuff> }else{ <otherstuff> slot.indirect.ptr = <thing> slot.indirect(i); } <stuff> will not be executed more than once?<stuff> is: "slot.indirect(cast(void*)(objs[index]), i);" and gets executed for every slot. I am not sure I understand your question.well yes, because every class derives from Object. But the requirement I need is that T2 is derived from Object, so it makes the intent more clear.IsExpression rarely does what you want it to, especially with that colon. I have come across many such IsExpression usages. Off the top of my head in your case: is(shared(Object) : Object); is(const(Object) : Object); is(immutable(Object) : Object); is(AnyInterface : Object); std.traits.BaseTypeTuple might be the way to go if you want clarity. Also, this brings us to the spectre of const correctness, which I will drop just as quickly (but keep it in mind - phobos already has too much code that isn't const correct)-> No I should not, at least in the current implementation this would be wrong. I did not yet made up my mind how I want to implement it in the end, so I left it for now.Ah. Well, do something so I stop double-taking every time I see objs[slot_idx-1] or somesuch.That you can not pass some arbitrary slot like:Ok, I'm starting to see what you're doing here.I knew this was coming. Well I haven't done anything with closures yet, the usage is an escaping of a reference so the compiler should allocate it on the heap, if not than you got a problem, but its not specific to signals -> So I still consider the signal API safe ;-)Heh. Perhaps trusted is a more apt description.I think you're right.Yes, that's the only reason that comes to mind. I personally think that a struct is a cleaner solution, that also avoids cluttering the containing objects namespace.Also I did not really get the point why a mixin was used in the first place, it does not really gain us anything? What was the reasoning about it?So you can do b.connect in the simplest case?
Nov 06 2012