Observables are like Ref
s but you can listen to changes.
juliagizmos / observables.jl Goto Github PK
View Code? Open in Web Editor NEWobservable refs
License: Other
observable refs
License: Other
During use use and investigate LyceumMuJoCoViz.jl, I found this Lyceum/LyceumMuJoCoViz.jl#30 , I'm not sure it is related to Observerables.jl or not
Very useful function :)
I need to investigate further, but I'm observing that if I add a work-heavy handler to an Observable
, subsequent handlers for the same Observable
may never run.
This issue is used to trigger TagBot; feel free to unsubscribe.
If you haven't already, you should update your TagBot.yml
to include issue comment triggers.
Please see this post on Discourse for instructions and more details.
If you'd like for me to do this for you, comment TagBot fix
on this issue.
I'll open a PR within a few hours, please be patient!
Currently, Observables throws an error when trying to map!(any, r, o)
where o is a normal Observable and r is of a different observable type R{T} <: AbstractObservable{T}
. The root cause is the definition of MapUpdate:
struct MapUpdater{F, T} <: Function
f::F
observable::Observable{T}
end
map!()
, map()
and connect!()
all support AbstractObservables.
Probably, the only thing that would need to be adpated is to change Observable{T}
to AbstractObservable{T}
.
For my use case that worked, but I may be overlooking something.
Are there plans to support distributed Observable
s, cross-process? E.g. creating an observable on one process and listening to it from another process by automatically introducing a channel when the observable is serialized? Or is this beyond the intended scope of Observables.jl?
I have performed a really complicated plot using GraphMakie.jl and GLMakie.jl, then replot it when I change the underlying graph. It seems the following error is introduced by Observables.jl v0.5.4. When I fix the version of Observables.jl at v0.5.2, the error is gone. It seems the error is related to adjustlimits!
of an existing axis.
julia> Error in callback:
Binding freed GLBuffer{Vec{4, Float32}}
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:35
[2] bind(buffer::GLMakie.GLAbstraction.GLBuffer{Vec{4, Float32}})
@ GLMakie.GLAbstraction ~/.julia/packages/GLMakie/x68HK/src/GLAbstraction/GLBuffer.jl:24
[3] gpu_setindex!
@ ~/.julia/packages/GLMakie/x68HK/src/GLAbstraction/GLBuffer.jl:141 [inlined]
[4] setindex!(A::GLMakie.GLAbstraction.GLBuffer{Vec{4, Float32}}, value::Vector{Vec{4, Float32}}, ranges::UnitRange{Int64})
@ GLMakie.GLAbstraction ~/.julia/packages/GLMakie/x68HK/src/GLAbstraction/AbstractGPUArray.jl:51
[5] update!
@ ~/.julia/packages/GLMakie/x68HK/src/GLAbstraction/AbstractGPUArray.jl:73 [inlined]
[6] (::GLMakie.GLAbstraction.var"#10#11"{GLMakie.GLAbstraction.GLBuffer{Vec{4, Float32}}})(x::Vector{Vec{4, Float32}})
@ GLMakie.GLAbstraction ~/.julia/packages/GLMakie/x68HK/src/GLAbstraction/AbstractGPUArray.jl:196
[7] #invokelatest#2
@ ./essentials.jl:729 [inlined]
[8] invokelatest
@ ./essentials.jl:726 [inlined]
[9] notify
@ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
[10] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
[11] (::Observables.MapCallback)(value::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:431
--- the last 5 lines are repeated 1 more time ---
[17] #invokelatest#2
@ ./essentials.jl:729 [inlined]
[18] invokelatest
@ ./essentials.jl:726 [inlined]
[19] notify
@ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
[20] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
[21] (::Observables.SetindexCallback)(x::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:111
--- the last 5 lines are repeated 1 more time ---
[27] #invokelatest#2
@ ./essentials.jl:729 [inlined]
[28] invokelatest
@ ./essentials.jl:726 [inlined]
[29] notify
@ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
[30] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
[31] (::Observables.MapCallback)(value::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:431
--- the last 5 lines are repeated 4 more times ---
[52] #invokelatest#2
@ ./essentials.jl:729 [inlined]
[53] invokelatest
@ ./essentials.jl:726 [inlined]
[54] notify
@ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
[55] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
[56] adjustlimits!(la::Axis)
@ Makie ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:982
[57] (::Makie.var"#1295#1325"{Axis})(pxa::GeometryBasics.HyperRectangle{2, Int64}, lims::GeometryBasics.HyperRectangle{2, Float32})
@ Makie ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:503
[58] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ Base ./essentials.jl:729
[59] invokelatest(::Any, ::Any, ::Vararg{Any})
@ Base ./essentials.jl:726
[60] (::Observables.OnAny)(value::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:415
[61] #invokelatest#2
@ ./essentials.jl:729 [inlined]
[62] invokelatest
@ ./essentials.jl:726 [inlined]
[63] notify
@ ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:169 [inlined]
[64] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/PHGQ8/src/Observables.jl:86
[65] reset_limits!(ax::Axis; xauto::Bool, yauto::Bool, zauto::Bool)
@ Makie ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:628
[66] reset_limits!
@ ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:552 [inlined]
[67] plot!(la::Axis, P::Type{Combined{GraphMakie.graphplot}}, attributes::Attributes, args::PolymerArchitecture.BlockCopolymerGraph; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
@ Makie ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:785
[68] plot!
@ ~/.julia/packages/Makie/iY5BJ/src/makielayout/blocks/axis.jl:765 [inlined]
Observables.jl/src/Observables.jl
Line 20 in 40243b2
const name_dict = Dict{Int, String}()
Observable(x, name = nothing) = (...; name != nothing && name_dict[id] = name)
name(x::Observable) = get(name_dict, x.id) do
error("Observable doesn't have a name. You may call it numer: $(x.id), though")
end
Could do a PR if this is appreciated
At present, your Observable values report on every assignment. There are plenty of use cases where that all that activity could be an encumbrance. A second serving of observing, one that reports on changed value (an assignment of a value that is not equal to the current value held) should be considered. One might use isapprox
with Floats. The third kind would report on change of value which exceeds an initialization time safe range.
I'd like some input on my redesign idea https://github.com/jkrumbiegel/Observables2.jl
I've noticed that in Makie, it gets quite problematic that observables don't tell you who they are listening to. That means you can't disconnect easily and that means often resources are never garbage collected until the whole Scene is destroyed.
I've written my reasoning down in the README and it would be great if those who are interested take a look.
As far as I can tell there is no automatic way to remove a child observable and detach it from all its parents. For example
O1 = Observable(1)
O2 = map(identity, O1)
O2 = nothing
GC.gc()
will leave you with a listener in O1
and O2
will remain somewhere in memory. The same happens with weak = true
- the mapped observable and the listeners remain. In these cases one can explicitly remove listener with off.(O2.inputs)
, which then allows them to be gced.
With on
it's a bit different. There are no references being kept in inputs
for on
, so the above doesn't work. The gc will also happily remove the observable when it goes out of scope, causing this to error:
O1 = Observable(1)
O2 = Observable(1)
on(x -> O2[] = x, O1)
O2 = nothing
O1[] = 1
So in this case one would need to keep track of observer functions themselves to correctly remove a child. It would be nice if this was easier to handle.
I find the following behavior a bit confusing (from Makie examples):
points = Observable(Point2f0[(0, 0), (0.5, 0.5), (1.0, 0.0)]) # AbstractPlotting renames Observables to Nodes it seems
later, in a function
points[][idx[]] = pos
points[] = points[]
The example does not work if the last line is excluded, even though it seems to be doing nothing. Is the issue that points[][idx[]] = pos
somehow does not send a change signal?
how about make a new function and keep connect! as 0.3.x?
[0.3.x] connect!(x,y) means x->y
[0.4.x] plugin!(x,y)/update!(x,y)/transfer!(x,y) means y->x
I'm looking at
mutable struct Observable{T} <: AbstractObservable{T}
listeners::Vector{Any}
val::T
...
end
and wondering if it would make sense to make that
struct Observable{T} <: AbstractObservable{T}
listeners::Vector{Any}
val::Ref{T}
...
end
so that Observable
could be immutable and then
function Base.setindex!(observable::Observable, val)
observable.val = val
...
end
becomes
function Base.setindex!(observable::Observable, val)
observable.val[] = val
...
end
and
Base.getindex(observable::Observable) = observable.val
becomes
Base.getindex(observable::Observable) = observable.val[]
etc.
The first example in the Multiply updates section in documentation works in v0.4 but doesn't in v0.5
I read some more and tried some stuff, but my brain still hurts 🤕... I don't understand the difference between Observables and Signals.
Could you guys improve/elaborate on the explanation for how Observables are different from Signals?
Thanks!
From the CI log:
│ This can be caused by a DOCUMENTER_KEY variable that is not correctly set up.
│ Make sure that the environment variable is properly set up as a Base64-encoded string
│ of the SSH private key. You may need to re-generate the keys with DocumenterTools.
└ @ Documenter ~/.julia/packages/Documenter/pjwqp/src/Documenter.jl:546
I created a super simple app with Oxygen that has various routes. In this, I created an HTML page using HypertextLiteral.jl (HTL) and a counter using HTL and the <script>
tag inside the @htl
macro.
I now want to recreate this simple functionality using Observables.jl (and maybe WebIO or whatever) inside this Oxygen app. Is there a simple way to go about this? Here is the app.jl
file and if someone could direct me on how to make a route like /counter
but using Observables, that would be a helpful starting place!
using Oxygen
using HTTP
using HypertextLiteral
@get "/greet" function (req::HTTP.Request)
return "hello world!"
end
@get "/multiply/{a}/{b}" function (req, a::Float64, b::Float64)
return a * b
end
@get "/getdata" function ()
return Dict("message" => "hello2!")
end
@get "/htl" function ()
string(@htl """
<h1>Hello test</h1>
<p>This is a simple frontend using HypertextLiteral.jl.</p>
""")
end
@get "/counter" function ()
string(@htl """
<h1>Counter</h1>
<button onclick="incrementCounter()">Increment</button>
<p>Counter: <span id="counter">0</span></p>
<script>
let counter = 0;
function incrementCounter() {
counter += 1;
document.getElementById("counter").innerText = counter;
}
</script>
""")
end
serve()
In the little app below, s1
sets the upper limit of s2
, but the label on the second slider does not update when the position of s2
gets changed through s1
.
I have tried notifying s2
separately but it didn't work.
App() do session::Session
s1 = Slider(0:0.01:1)
l1 = Labeled(s1, s1.value)
s2 = Slider{Float64}(@lift(collect(0:0.01:$s1)),
Observable(0.0),
Observable(1),
Dict{Symbol, Any}())
l2 = Labeled(s2, s2.value)
return DOM.div(l1, l2)
end
Hi,
first time I thought Observables.jl are just the right thing for my task, however it seems they are not. I want to listen on updates of on object, however then I also want to compute more detailed update information. For this I would need the previous value.
As far I understood, I would need to create an Observable of a Pair (or Tuple) prev => current
so that I can access the previous element.
It would be much nicer if the Observerable could just bear the current value and the prev
will be passed to the ObserveFunction on an update.
In the implementation of map
function Base.map(f, o::AbstractObservable, os...; init=f(o[], map(_val, os)...))
map!(f, Observable{Any}(init), o, os...)
end
https://github.com/JuliaGizmos/Observables.jl/blob/master/src/Observables.jl#L172
Why is there an Any
as a construction parameter? Shouldn't this type be inferred by the output of f
here https://github.com/JuliaGizmos/Observables.jl/blob/master/src/Observables.jl#L25?
This changelog is missing making it hard to figure out what changed between v0.4 and v0.5
i believe this is a bug, but declaring types like this is not something i do often.
julia> using GLMakie
julia> x = Observable(1)
Observable(1)
julia> y::Observable{Union{Int,AbstractFloat}} = lift(x) do x
x==1 ? 2 : NaN
end
Observable(2)
0 => (sc::Observables.SetindexCallback)(x) @ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:148
julia> y[]
2
julia> x[]=3 ### y can not be set to NaN via the lift
ERROR: InexactError: Int64(NaN)
Stacktrace:
[1] Int64
@ ./float.jl:912 [inlined]
[2] convert(::Type{Int64}, x::Float64)
@ Base ./number.jl:7
[3] setproperty!(x::Observable{Int64}, f::Symbol, v::Float64)
@ Base ./Base.jl:40
[4] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:122
[5] (::Observables.MapCallback)(value::Any)
@ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:436
[6] #invokelatest#2
@ ./essentials.jl:892 [inlined]
[7] invokelatest
@ ./essentials.jl:889 [inlined]
[8] notify
@ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
[9] setindex!(observable::Observable, val::Any)
@ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123
[10] top-level scope
@ REPL[7]:1
julia> y[]=NaN ### but can be set to NaN directly
NaN
the workaround, which is more verbose, is:
julia> X = Observable(1)
Observable(1)
julia> Y = Observable{Union{Int,AbstractFloat}}(2)
Observable{Union{Int64, AbstractFloat}}(2)
julia> on(X) do X
Y[] = X==1 ? 2 : NaN
end
ObserverFunction defined at REPL[13]:2 operating on Observable(1)
julia> Y[]
2
julia> X[]=3
3
julia> Y[]
NaN
julia> X[]=1
1
julia> Y[]
2
let me know if i should have rather filed an issue with Makie.
julia> versioninfo()
Julia Version 1.10.2
Commit bd47eca2c8a (2024-03-01 10:14 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: macOS (arm64-apple-darwin22.4.0)
CPU: 12 × Apple M2 Max
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-15.0.7 (ORCJIT, apple-m1)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
Environment:
JULIA_PROJECT = @.
JULIA_EDITOR = vi
(jl_PdQxLH) pkg> st
Status `/private/var/folders/s5/8d629n5d7nsf37f60_91wzr40000gq/T/jl_PdQxLH/Project.toml`
[e9467ef8] GLMakie v0.9.9
I saw that version 5 introduced ignore_equal_values
.
In the past I already thought it would be nice to have a general acceptance test for incoming updates.
So while version 5 is not officially released I propose to change the architecture to have a field isinvalid
which accepts a function as validy check. In case of ignore_equal_values
isinvalid
would be set to isequal
I'll submit a draft PR how that could look like.
Just from a slack discussion with @mschauer, which hopelessly nerd-sniped me, since I've always wanted static, no overhead observable graphs as well...
A possible implementation could be this:
struct StaticObservable{T, Listeners} <: Observables.AbstractObservable
listeners::Listeners
value::Base.RefValue{T}
end
Base.getindex(obs::StaticObservable) = obs.value[]
Observables.listeners(obs::StaticObservable) = obs.listeners
_notify(val, funcs::Tuple{}) where F = nothing
function _notify(val, funcs::Tuple)
f = first(funcs)
f(val)
_notify(val, Base.tail(funcs))
end
function Base.notify(obs::StaticObservable)
val = observable[]
_notify(val, obs.listeners)
return
end
function Base.setindex!(observable::StaticObservable, val)
observable.val[] = val
notify(observable)
return val
end
The question is how to construct the graph... Since the current API relies on lots of dynamic behavior, the normal API can't be used to construct the observables.
I had the idea to use static_obs = make_static(observables...)
, but the Observable api doesn't really allow to walk over the graph and convert the nodes to StaticObservable
... So is the only way to use something like SoonToBeStaticObservable
,
which works like Observable
but keeps more information about the graph, and can then be converted via make_static
to a static graph?
ERROR (unhandled task failure): MethodError: no method matching (::##272#273)(::Array{Widgets.Widget{:slider},1})
The applicable method may be too new: running in world age 24284, while current world is 24288.
I got this by running async_latest in this function where sliders are rebuilt when I swap to a different model, and they are remapped for sending back to the model.
I think the key issue is that map!(x, y, async_lates(z))
ocurrs within another map!()
.
function make_sliders(model)
params = flatten(Vector, model)
rnge = metaflatten(Vector, model, range)
labels = metaflatten(Vector, model, fieldname_meta)
sliders = broadcast((x,l,v) -> InteractBase.slider(x[1]:(x[2]-x[1])/200:x[2], label=string(l), value=v), rnge, labels, params)
map!(make_plot, plt, model, async_latest.(observe.(sliders))...)
sliders
end
map!(make_sliders, sliders, model)
Swapping async_latest
to throttle
resolves the issue, but thought I should document it here because it took a while to track down the problem.
To have a better integration with the Widgets package and ecosystem:
AbstractObservable
interface (setindex!
, getindex
, listeners
, setexcludinghandlers!
I think)flatten
to turn Observable{Observable{T}}
into Observable{T}
@map
, @map!
and @on
macro as syntactic sugar: @map &a+2(&b)
stands for map((x,y) -> x+2y, a, b)
I've often come across the need to selectively trigger handlers of observables, e.g. only trigger the first one.
In the past I've worked around that with some weired constructions, but with the introduction of priorities this could be implemented quite easily and in a very clean way.
function Base.notify(@nospecialize(observable::AbstractObservable); priority::Union{Int, Function})
val = observable[]
for (p, f) in Observables.listeners(observable)::Vector{Pair{Int, Any}}
(priority isa Int ? p == priority : priority(p)) || continue
result = Base.invokelatest(f, val)
if result isa Consume && result.x
# stop calling callbacks if event got consumed
return true
end
end
return false
end
Example
using Observables
o = Observable(1)
on(o) do o
println("Called with standard priority: $o")
end
on(o, priority = -1) do r
println("Called with priority -1: $r")
end
notify(o, priority = -1);
# Called with priority -1: 1
notify(o, priority = 0);
# Called with standard priority: 1
notify(o, priority = <(1));
# Called with standard priority: 1
# Called with priority -1: 1
What do you think?
I'm very excited to see this project!
Previously, I tried to implement Rx in julia. Then, I change my mind to implement additional functionality to Reactive.jl.
I plan to leverage the |> operator
result = from([1,2,3,4,5])
|> map(x -> x + 1)
|> subscribe()
but I'm busy recently...
setexcludinghandlers!
is a bit long. Something like the following might be better:
setsilent!
setquiet!
setmuted!
And just a note here that if/when this is chosen, the examples in the GtkObservables.jl docs need to be updated. Currently uses .value
, which doesn't work because that's not the current field name.
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
After running:
obs = Observable(0)
t = @task for n=1:10
obs[] = n
sleep(1)
end
schedule(t)
sleep(1)
map(x->2x, obs)
# This alternative works:
# f(x) = 2x
# map(f, obs)
julia> obs
Observable{Int64} with 1 listeners. Value:
2
julia> t
Task (failed) @0x0000000017b7e290
MethodError: no method matching (::var"#127#128")(::Int64)
The applicable method may be too new: running in world age 27995, while current world is 27996.
Closest candidates are:
#127(::Any) at untitled-975cdec0428a7c38d7a195fd90bb4d35:16 (method too new to be called from this world context.)
[...]
(on 0.3.2, julia 1.5.2)
EDIT: looks like it's irrelevant the the function is anonymous. The issue also occurs with a named function, provided it's a new function (no applicable method existed when the task was compiled).
Observables.jl/src/Observables.jl
Line 477 in 0f94e69
For context, I have code that returns different things based on the state of observable:
app = Canvas()
hbox(app, map(app) do img_matrix
if img_matrix === nothing
return html"<b>Draw something!</b>"
end
heatmap_digit(img_matrix; size=(300, 300))
end
)
This causes issues™:
MethodError: Cannot `convert` an object of type Plots.Plot{Plots.GRBackend} to an object of type HTML{String}
Closest candidates are:
convert(::Type{T}, ::T) where T at essentials.jl:171
HTML{String}(::Any) where T at docs/utils.jl:23
I think it's the line referenced above that creates an Observable{T}
where T
is the type of the return value of the mapper function the first time it's executed.
In Reactive there's foldp
which is useful for accumulating state over updates (https://juliagizmos.github.io/Reactive.jl/#maintaining-state). I found it pretty useful. Right now I accomplish this with:
u = Observable(0)
oldu = Ref(u[])
p = Observable(0)
map!(p, u) do newu
s = oldu[] + newu
oldu[] = newu
p[] + s
end
which seems a bit cumbersome considering what it could be:
p = foldp(+, u)
In the discussion for #60, some outstanding issues in resource cleanup were identified. I was hoping to get to them before releasing 0.4, but I don't think that's going to happen. Filing this as a reminder to try to address these before 1.0.
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
I have difficulties in understanding what is compiled upon the first call of a callback function.
It seems that even if the callback function has been used before something is compiled upon the first notification.
MWE: with the definitions
using Observables
o = Observable("Hello")
function f(s::String)
println(s)
end
I end up with the following timings
julia> @time f("Hello")
Hello
0.000326 seconds (8 allocations: 160 bytes)
julia> on(f, o)
ObserverFunction `f` operating on Observable("Hello")
julia> @time notify(o);
Hello
0.002647 seconds (22 allocations: 976 bytes, 88.23% compilation time)
julia> @time notify(o);
Hello
0.000283 seconds (9 allocations: 208 bytes)
After redefinition of f
, I have the same scenario. Calling f
directly doesn't need any extra compilation (f
is probably compiled directly after definition). But calling f
on the observable needs extra compilation.
julia> function f(s::String)
s = s * s
println(s, " world!")
end
f (generic function with 1 method)
julia> @time f("Hello")
HelloHello world!
0.000377 seconds (10 allocations: 224 bytes)
julia> @time notify(o);
HelloHello world!
0.003482 seconds (29 allocations: 1.312 KiB, 91.26% compilation time)
julia> @time notify(o);
HelloHello world!
0.000332 seconds (12 allocations: 576 bytes)
Where does this compilation come from and could it be avoided? (Obviously calling notify()
would be one way of forcing precompilation.)
Add a String or Int id
field to Observable
which gives it a name, and also store each Observable in a global id
to WeakRef{Observable}
dict on construction.
This is required when working with objects across address spaces (like Julia <-> Browser :)).
Hi everyone,
I have a project where I need to remove listeners from many observables.
Sometimes it's about removing all, sometimes about removing all except the first one.
I don't want to keep track of all the observer functions so I wondered whether it was possible to remove the listeners by index.
Hence I came up with the follwing code.
export off!, nlistener
nlistener(@nospecialize(o::Observable)) = length(Observables.listeners(o))
function off!(@nospecialize(o::Observables.AbstractObservable), index::Union{AbstractRange{<:Integer}, Vector{<:Integer}})
allunique(index) || (@info("All indices must be distinct"); return BitVector(zeros(len)))
len = length(index)
callbacks = Observables.listeners(o)
success = BitVector(zeros(len))
for (i, n) in enumerate(reverse(sort(index)))
if 0 < n <= length(callbacks)
for g in Observables.removehandler_callbacks
g(observable, callbacks[n])
end
deleteat!(callbacks, n)
success[len - i + 1] = true
end
end
success
end
off!(@nospecialize(o::Observables.AbstractObservable), index::Integer) = off!(o, [index])[1]
off!(@nospecialize(o::Observables.AbstractObservable)) = off!(o, 1:length(Observables.listeners(o)))
Does that look like something you'd like to integrate in Observables?
If so, please let me know. I'd be happy to submit a PR.
I started to revisit the problems of deploying the documentation, when I realised, that it has been already done. Although it is still not working.
Latest build shows a Permission denied
error, and suggests that is may caused by a wrong DOCUMENTER_KEY
.
Another problem could be, that every job tries to deploy the documentation. If all of them succeeds, there will be four (six?) identical copies of the same version?
Though I'm not sure that it is a real problem, the following travis script avoids this:
jobs:
allow_failures:
- julia: nightly
include:
- stage: Documentation
julia: 1.0
os: linux
script:
- julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd()));
Pkg.instantiate()'
- julia --project=docs/ docs/make.jl
after_success: skip
I can open a PR for the second, but can't fix or test the first.
"""
map(f, observable::AbstractObservable, args...)
Creates a new observable ref which contains the result of f
applied to
values extracted from args. The second argument observable
must be an observable ref for dispatch reasons.
....
"""
The above italic text may introduce misunderstanding. Suggested correction will be:
applied to value contained in second argument observable
and values extracted from following args
The "helper macros" here use the &
syntax to denote observables that trigger an update, e.g. @map &x + &y
(see docs). At the time, this was somewhat inspired by the ampersand operator in C, even though tbh I'm not able to give a consistent argument in favor of it at the moment.
OTOH, in AbstractPlotting, which uses a very similar macro, the observables that trigger an update are denoted by $
, e.g. @lift $x + $y
(see docs).
From what I understand, Observables is going towards a 1.0 release, so I thought it was relevant to figure out whether we want a unified syntax and, if it is Observables that should change, make the change before tagging 1.0.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.