-
-
Notifications
You must be signed in to change notification settings - Fork 312
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
[WIP] Observable update refactor #4360
base: master
Are you sure you want to change the base?
Conversation
Some thoughts from prototyping:
It would be good to extract this into a separate type, e.g. UpdatableAttributes in my prototype, rather than adding this machinery to Plot directly. That way it can be reused for Scene, Axis, etc if it is compatible. For recipes/attribute pass-through/attribute connections between plots it may also be useful, since it enables creating and connecting attributes before creating the plot object. (Like now) Dense Some attributes are irrelevant to the backend for the plot they are attached to and thus shouldn't trigger updates. E.g. a legend label with no legend present, or inspectable which is queried when needed. If it doesn't have an update function, we don't need to resolve its updates. (This can be resolved at update-function-insertion time) For recipes/attribute connections to work the backend needs to know when anything has updated. We can either:
function setproperty!(obj, ...)
...
foreach(parent_update_notification, obj.children)
end
function resolve_updates!(obj)
foreach(resolve_update!, obj.outedated_parents)
empty!(obj.outdated_parents)
end Connecting Observables could be difficult because of the pull to push transition. If we make sure that every UpdatableAttributes object connects to the backend then I think it shouldn't be a problem. In that case we can just register update functions, which would then propagate an update request to the backend when they need to be called. If this connection does not exist we would probably need to add an immediate-update option. |
Notes from Discussions with SimonMy prototype goes in the direction we want to go, but we are aiming to do things more incrementally. The potential goals of this refactor are:
Initial PlanFor plots we are looking to implement an update function: function update(plot::PlotType; attr...) # also args, e.g. x = ...,
plot.updated = union(plot.updated, keys(attr))
foreach((k, v) -> plot[k].val = v, pairs(attr))
plot.dirty[] = true
end This function sets multiple attributes and/or args to new values based on the given kwargs. The changed names are tracked in function resolve_update!(plot::PlotType)
args = get_args(plot.changed)
attr = get_attr(plot.changed)
if !isempty(args)
plot.calculated[:converted] = convert_arguments(plot)
end
calculated_attributes!(plot) # multi input
for k in attr
plot.calculated[k] = convert_attribute(plot, k) # single input
end
return
end There are still a bunch of questions here, e.g.:
The backend would react to listeners(plot.dirty)
resolve_update!(plot)
<backend>_update!(plot)
...
cleanup!(plot) For GLMakie (and WGLMakie?) it may also be useful for For compatability with the current observables, Attributes may automatically trigger
Initial Version and Future ExtensionsWe are planning to implement this for one or a few primitive plot types at first. That way we can probe how these ideas work out, what falls into place and what doesn't. In the future we can:
|
This PR will take a while to materialize, but I already wanted to start thinking this through.
Refactor how internal events are handled
Motivation
Right now we heavily use Observables to propagate events internally, right up to the GPU memory.
A single scatter plot (with axis to be fair) creates 4403 observables:
Further problems with our observable infrastructure:
invoke_latest
.setproperty!
and make the attribute conversions pull based. This should also work quite easily for GLMakie, since it will pull the updates from it's own event thread/task, which doesn't care about where the update came from.Proposal
Get rid of all observables in
Plot
instead have two Dicts and a Set of changed fields:
Now, when accessing any of these in the backend, we will go through all
changed_fields
and run anyconvert_arguments
/convert_attributes
, that relies on that value and will write all values tocomputed_attributes
.isempty(changed_fields)
can also be used as an unexpensive check for GLMakie's on demand renderloop, and insetproperty!
we could also already tell the parent scene that there are changed plots.We can also still make
setproperty!
directly notify the backend by having an observable/condition triggered insetproperty!
.I would also remove the
args
vskw
thing and make them all attributes, if that doesn't make to many problems.Since we have the mapping from
arg_n
->attribute_name
, it should be possible to make this backward compatible.We will also add
Dict{Symbol, Observable}
to haveplot.attribute
still return an observable for backwards compatibility, but I think we should not rely on this in Makie and the backends itself, so that we don't end up with all attributes always materializing back as observables.Problems
calculated_attributes!
depends on connecting the calculations via observables. This is a mess, but I also am not 100% sure how to do this going forward. Same goes forconvert_arguments
, but I kind of hope without observables the code should actually become easier - but it's still a bit hard to judge.convert_attributes
,convert_arguments
andcalculated_attributes!
that need to run on each update.plot!(... ; other_plot.attributes...)
wouldn't propagate the updates anymore.plot!(recipe_plot, ...; color=recipe_plot.color)
neither. I feel like this needs a cleaner API anyways, but simply forwarding the observables here surely is simple from the users perspective. Instead of the aboveDict{Symbol, Observable}
for backwards compatibility, we could also return something likecolor=AttributeView(other_plot, :color)
, which the plot constructor connects internally to the other plot object (plot.color isa AttributeView
). AttributeView would have the large problem, thaton(f, plot.color)
won't easily work, which must be widely used across the Makie ecosystem.