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

trigger update on modification of sub-array in Observable{Array} #67

Open
gszep opened this issue Jan 26, 2021 · 10 comments
Open

trigger update on modification of sub-array in Observable{Array} #67

gszep opened this issue Jan 26, 2021 · 10 comments

Comments

@gszep
Copy link

gszep commented Jan 26, 2021

currently you have to manually trigger the change by re-assigning the whole array, seems wasteful?

x = Observable([1,2,3,4,5,6,7,8,9,10])
x[][1:2:end] .= -42 #modify subarray
x[] = x[] # manually trigger change
@gszep
Copy link
Author

gszep commented Jan 26, 2021

alternatively this would probably work

x[] = map( x-> iseven(x) ? x : -42, x[] )

@timholy
Copy link
Member

timholy commented Jan 26, 2021

On master (which will hopefully be released soon) it's just notify(x).

@gszep
Copy link
Author

gszep commented Jan 26, 2021

presumably notify sends the entire array again though? This becomes very in efficient if I just want to update one element

x = Observable(randn(100000))
x[][12] = 42
notify(x)

@timholy
Copy link
Member

timholy commented Jan 26, 2021

It notifies all dependents that x has a new value. What they do with that information is up to the f.

EDIT: if the updates were going to be minimal, you could use a LastModifiedIndexArray rather than a plain Vector. Of course you have to write LastModifiedIndexArray yourself, but it could keep track of the last index that got modified.

@twavv
Copy link
Member

twavv commented Jan 26, 2021

I don't think reassigning the array is actually that wasteful (no more so than using notify). Julia doesn't have pass-by-value semantics, so reassigning the array doesn't actually allocate a new array.

EDIT: Though if you're using something like WebIO which sends observable updates to the browser, it would send the entire array over the wire. In that case you'd probably want to create something like the above which whose observable updates are only the changed indices.

@gszep
Copy link
Author

gszep commented Jan 26, 2021

Interesting.. thanks for the feedback :) so I'm using WGLMakie.jl to render plots in Pluto.jl would it be too much overhead to create a Array{Observable} type that only sends updated values to the brpwser?

@timholy
Copy link
Member

timholy commented Jan 26, 2021

I don't think reassigning the array is actually that wasteful (no more so than using notify)

That's exactly right. Still, it's weird to need to apparently mutate just to send a notification. #58 flipped the way of doing it to the more logical order. But as you say, the performance impact is basically nil; this is a "conceptual cleanup" rather than a serious improvement.

would it be too much overhead to create a Array{Observable} type that only sends updated values to the brpwser?

That was basically my suggestion for LastModifiedIndexArray above. But you'd have to write that yourself.

@hhaensel
Copy link

For Stipple.jl we recently defined a new Observable type Reactive that defines getindex() and notifying setindex!() for non-missing arguments by passing it on to the referenced object.

function Base.getindex(r::Reactive{T}, arg1, args...) where T
  Base.getindex(r.o.val, arg1, args...)
end
function Base.setindex!(r::Reactive{T}, val, arg1, args...) where T
  setindex!(r.o.val, val, arg1, args...)
  Observables.notify!(r)
end

With that we can do

julia> r = Reactive([1, 2, 3])
Reactive{Vector{Int64}}(Observable{Vector{Int64}} with 0 listeners. Value:
[1, 2, 3], 1, false, false)

julia> on(r) do r
         @info(r)
       end
(::Observables.ObserverFunction) (generic function with 0 methods)

julia> #non-notifying
julia> r[][3] = 4
4
julia> @show r[][3]
(r[])[3] = 4
4
julia> #notifying
julia> r[3] = 4
[ Info: [1, 2, 4]
4
julia> @show r[3]
r[3] = 4
4

Does that sound like an API enhancement for Observables as well?

@hhaensel
Copy link

Meanwhile, we also forward getproperties() and setproperties() to the underlying object, the latter one being notifying.

@travigd , @timholy Do you think that it is reasonable to integrate this behaviour into Observables?
If so, please have a detailed look, how we set it up.

We also defined a non-notifying notation in order to change the content of Observables without calling notify():

r = Reactive([1, 2, 3])
r[!] = [2, 3, 4]

The idea behind using the !-symbol was to combine both meanings of it; modification of the variable and NOT notifying.

I personally found, that code using this syntax is much more reader-friendly, but I also understand that it hides the underlying mechanism. For our use case that was perfect, but from a general perspective it might look different. Moreover, this syntax does not cover nested structures.

I'd be interested in hearing you opinion.

@hhaensel
Copy link

EDIT: if the updates were going to be minimal, you could use a LastModifiedIndexArray rather than a plain Vector. Of course you have to write LastModifiedIndexArray yourself, but it could keep track of the last index that got modified.

I've implemented something like this for the package Stipple.
I've enhanced notify(r) by notify(r, args...) and send the value and the index of an array or dict with it. (As we need a converted key array for a js client, we convert the indices to be 0-based, but that could be easily changed).

Furthermore, I have come up with an indexing scheme that digs through nested objects and thus can trigger updates at any level.

If this sounds interesting to you, I'd be happy to submit a PR.

Our code is currently in the hh-push branch of Stipple.jl

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

4 participants