November 06, 2012
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
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
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
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
> 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
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
> _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
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
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.

1 2
Next ›   Last »