Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

listeners as Vector{WeakRef} #1

Closed
shashi opened this issue Apr 8, 2017 · 13 comments
Closed

listeners as Vector{WeakRef} #1

shashi opened this issue Apr 8, 2017 · 13 comments

Comments

@shashi
Copy link
Member

shashi commented Apr 8, 2017

My concern with using Vector{WeakRef} is, in

foo(x,y)
    on(x) do a
        ...
    end
end

the handler goes out of scope when the function finishes.

ping @timholy

@timholy
Copy link
Member

timholy commented Apr 8, 2017

As a purely technical solution, I guess we could have on return f?

But it's an interesting problem. One doesn't mind holding on to the functions, it's the derived Observables you'd rather not hold. I wonder if map needs to create a slightly different type, one that finalizes references to the output if the derived observable goes out of scope.

@shashi
Copy link
Member Author

shashi commented Apr 8, 2017

As a purely technical solution, I guess we could have on return f?

Good idea!

It would help if someone wants to use off as it is. I'm thinking for now off is a good solution since packages that use this package will have more information about when a handler can be cleaned up. (e.g. window closed, code-cell re executed).

@timholy
Copy link
Member

timholy commented Apr 8, 2017

IMO the GC management is one of the less intuitive things about Reactive. If you don't mind, I'll spend a little time to see if I can design something that might make it simpler for the user. Most likely, there will be some cost in making the internals a little more complex 😦

@shashi
Copy link
Member Author

shashi commented Apr 9, 2017

I agree that this is the least intuitive part of Reactive. It does come in handy though.

Yes, you're welcome to tackle this! Hopefully it won't make things too complex.

@timholy
Copy link
Member

timholy commented Apr 9, 2017

OK, check out this gist: https://gist.github.com/timholy/eb224bce573e37b31456eed789bb8f37

The basic idea is that one can define actions that don't create a new Observable (trigger), and these actions are protected from garbage collection. This might be appropriate for actions that "just" do stuff (e.g., draw on a canvas in response to updates to a signal, but don't otherwise produce "output"). If you do want to create a new Observable out of other inputs (some/all of which may be Observables), like any other julia variable, the user is responsible for holding a reference to the new Observable for as long as you want it to last. If that new Observable goes out of scope and gets finalized, the action won't run anymore.

I made listener a Vector{Pair{WeakRef,UInt}} rather than a WeakKeyDict to preserve the order of actions.

EDIT: I should add that of course this may have serious flaws (it needs much more stringent testing), but at least it hopefully illustrates one possible direction for making working with Observables (and defining actions on them) a little bit more like "regular" julia rules: variables are GCed, functions are not.

@shashi
Copy link
Member Author

shashi commented Apr 9, 2017

@timholy Very interesting! Thanks for putting this together! I like the distinction created by SelfUpdate and the way listeners and actions fields work together to give the desired effect. 👍

I will have to take a more detailed look when I'm not this (very) sleepy. 😅

@shashi
Copy link
Member Author

shashi commented Apr 13, 2017

Awesome. This looks quite good!

The only concern I have is this

julia> function inc!(x)
           global counter
           counter::Int += 1
       end
inc! (generic function with 1 method)

julia> o = Obs.Observable(0);
julia> Obs.trigger(inc!, o);
julia> o[] = 1
1
julia> counter
1
julia> o[] = 1
1
julia> counter
1
julia> o[] = 2
2
julia> counter
2

I'd expect the counter to go 1,2,3... no matter what updates it got (imagine counting the number of mouse clicks)

Besides it's quite easy to still have a cycle:

julia> o = Obs.Observable(0.);
julia> Obs.trigger(evil, o);
julia> o = Obs.Observable(0.);
julia> evil(x) = o[] = rand();
julia> Obs.trigger(evil, o);
julia> o[]=1.0
ERROR: StackOverflowError:
 in setindex!(::Obs.Observable{Float64}, ::Float64) at /home/shashi/code/observable.jl:85
 in evil(::Float64) at ./REPL[13]:1
 in setindex!(::Obs.Observable{Float64}, ::Float64) at /home/shashi/code/observable.jl:85
 in evil(::Float64) at ./REPL[13]:1
 in setindex!(::Obs.Observable{Float64}, ::Float64) at /home/shashi/code/observable.jl:85
 in evil(::Float64) at ./REPL[13]:1
 in setindex!(::Obs.Observable{Float64}, ::Float64) at /home/shashi/code/observable.jl:85
...

Also, are you not a fan of the name on ? 😆 It reads quite nicely with the do syntax. on(clicks) do btn; counter += 1 end

@timholy
Copy link
Member

timholy commented Apr 13, 2017

True, that cycle detection isn't perfect, we could leave it out and reconsider if it becomes a problem.

And actually, I do like on, that would be fine.

I'm not certain that I have all the memory-management stuff correct. In particular, a multi-argument action should be removed if even one of the observables gets GCed, and I'm not certain I got that part right. But if you like this overall, I could turn it into a PR.

@timholy
Copy link
Member

timholy commented Apr 13, 2017

One other issue is that, while I'm a fan of synchronous execution by default, we should come up with a scheme that allows users to schedule asynchronously when desired.

@shashi
Copy link
Member Author

shashi commented Apr 14, 2017

Would @async x[] = y work for your purposes?

It's easy to do throttle as it is. You just need to keep creating a new timer after executing an update and stopping it if another update came in too quickly.

a multi-argument action should be removed if even one of the observables gets GCed

I'm not sure if that's the correct thing expected by a casual user. But it seems like this does happen currently?

Sure! It would be good to turn this into a PR.

@non-Jedi
Copy link

non-Jedi commented Aug 2, 2019

I'm experimenting with creating a discrete event simulation package
based on Observables. Am I reading this right that if I have a
function that uses the do notation to create callbacks on several
observables, those callbacks will all be lost unless I maintain a
reference to that inline function somehow?

EDIT: I guess this isn't the current state but a proposal. Sorry for
the noise.

@rapus95
Copy link

rapus95 commented Jan 23, 2020

Bump, why storing listening Observables as WeakRef isn't a sane thing to do? That way changes won't be pushed into unused listening Observables anymore. Since Observables are used for Makie I'm quite sure especially when prototyping there's a whole bunch of unused Observables which are blocked to be gc'd by the listener reference.

@twavv
Copy link
Member

twavv commented Jan 30, 2020

I think this issue is too old to be useful. Feel free to open a new issue.

The main feature of any proposal would have to be

  1. not breaking existing stuff
  2. behaving in a "sane" manner

I'm actually not sure that this can be fixed in a way that satisfies 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants