View mode: basic / threaded / horizontal-split · Log in · Help
November 06, 2012
Re: std.signals2 proposal
On 2012-11-06 16:32, eskimo wrote:
>
>> 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.

Aha, I see, that was the small detail I had missed. Thanks for the 
explanation.

-- 
/Jacob Carlborg
November 07, 2012
Re: std.signals2 proposal
On 11/06/2012 04:10 AM, Robert Klotzner wrote:
>> Not sure I understand why such hatred is rational?

nvm

>
>> 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.

// slot[i].indirect.ptr == null
thing.emit(params);
// slot[i].indirect.ptr != null
thing.emit(params);

>
> 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.

>
>>
>>> 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?
> 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.

I think you're right.
November 07, 2012
Re: std.signals2 proposal
On Tuesday, 6 November 2012 at 15:31:42 UTC, eskimo wrote:
>
>> 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.

As long as you keep strong reference to the observer, it won't 
die.
Having signals with weak reference semantics can be surprising 
for a garbage collected language: AFAIK Java and C# use strong 
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?
November 07, 2012
Re: std.signals2 proposal
On Wednesday, 7 November 2012 at 13:59:53 UTC, Kagamin wrote:
> On Tuesday, 6 November 2012 at 15:31:42 UTC, eskimo wrote:
>>
>> 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 
> for a garbage collected language: AFAIK Java and C# use strong 
> 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?

Note that in C#, event subscribers are one of the most common 
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.
November 07, 2012
Re: std.signals2 proposal
> Having signals with weak reference semantics can be surprising 
> for a garbage collected language: AFAIK Java and C# use strong 
> 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
November 08, 2012
Re: std.signals2 proposal
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).
November 08, 2012
Re: std.signals2 proposal
> _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
November 09, 2012
Re: std.signals2 proposal
Huh? I don't get it. Didn't you want weak ref semantics for 
signals? Why do you want strong ref semantics now?
November 10, 2012
Re: std.signals2 proposal
On Fri, 2012-11-09 at 19:28 +0100, Kagamin wrote:
Huh? I don't get it. Didn't you want weak ref semantics for 
> signals? 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.
Next ›   Last »
1 2
Top | Discussion index | About this forum | D home