JuliaGizmos / Interact.jl

Interactive widgets to play with your Julia code
Other
519 stars 76 forks source link

Button instability when displaying images #173

Closed rand5 closed 6 years ago

rand5 commented 7 years ago

When I am using Interact and the TestImages package, I seem to be getting variable behavior of a button.

For example, the following code executes as expected, printing "Hello World" once for each button click: function Test1() A = button("Click Here") map(v->println("Hello World"),signal(A),init=nothing) @manipulate for a=A img = testimage("cameraman"); t=1; end end

However, if I execute the following (the line t=1 has been removed), clicking the button prints only "Hello World" a few times (2-5) before seeming to stop working: function Test2() A = button("Click Here") map(v->println("Hello World"),signal(A),init=nothing) @manipulate for a=A img = testimage("cameraman"); end end

I've also tested this with the Images package and loading a local image and I get the same results.

JobJob commented 7 years ago

I think the problem is (probably surprisingly) that your map is being garbage collected.

Fixes:

  1. assign it to a variable that doesn't go out of scope
  2. use preserve(map(...))
  3. or use foreach(...) which is shorthand for 2.

2 & 3 are useful in cases like the above where your aim is to produce a side effect (e.g. printing stuff), and you don't care about the value of the map signal, and thus you don't assign it to a variable (nothing in this case since println returns nothing)

I'm guessing the images possibly just increased the chance of garbage collection because they use more memory, that's why you saw the different behaviour

This seems to be a common problem btw. I'm just curious did you notice anything about garbage collection in the Reactive.jl docs? It is mentioned somewhere, but I don't think it's clear enough, and we should probably add it to the docstring for Reactive.map.

rand5 commented 7 years ago

@JobJob thanks for the solutions. After reading your comment I went back and looked at the Reactive.jl docs and found only the following reference to garbage collection: "Note that, signal nodes that do not have any reference in Reactive are admissible to garbage collection and subsequent termination of updates." As someone who is new to this sort of thing, that sentence didn't mean a whole lot to me and it never crossed my mind that garbage collection could be the issue.

I like the way Interact.jl has put some notebooks in the doc folder to serve as a tutorial to the package. I think it may be worth adding one on garbage collection.

timholy commented 7 years ago

I think this is easily the biggest "gotcha" in using Reactive, especially if you avoid preserve (e.g., if you want to make sure that each GUI window holds references to its own resources, and gets cleaned up after you close the window). More discussion about a possible alternative here.

JobJob commented 7 years ago

@rand5 thank you, that is helpful. A GC notebook sounds like a good idea.

I think this is easily the biggest "gotcha" in using Reactive...

yeah completely, it keeps coming up. We should definitely try make the default behaviour more friendly to new users, or at the very least make it much more obvious that preserve and/or foreach needs to be used in cases like the above.

I only skimmed what you wrote in Observables - is it fair to say that trigger is somewhat analogous to foreach? Is there more there that should perhaps be adapted to Reactive? Are the defaults different?

timholy commented 7 years ago

I only skimmed what you wrote in Observables - is it fair to say that trigger is somewhat analogous to foreach?

Yes and no. The real difference is that it doesn't create a new object, so there's nothing to garbage-collect. It's the collection of the object that removes the callback.

piever commented 6 years ago

Closing as this is no longer an issue in latest (unreleased) Interact, because the map information is stored in the Observable observe(A). Btw, the new syntax would be:

function Test2()
    A = button("Click Here")
    on(v->println("Hello World"),observe(A))
    @manipulate for a=A
        WebIO.render(testimage("cameraman"))
    end
end

I'm not sure why WebIO.render is needed, but have opened an issue about it.