Closed NeverGivinUp closed 4 years ago
Signal
and SignalVec
are two separate things: Signal
is a single value which changes over time, SignalVec
is a vector of values which change over time.
There are methods to manually convert between them (such as to_signal_vec
and to_signal_map
) but there is no automatic conversion.
If the graph structure doesn't change, why do you need a MutableVec
for it? MutableVec
is for situations where the children change. If the children don't change, you can just use children
to create a static DOM structure:
svg!("g", {
.children(translations.iter().map(|edge| {
let a_center = translations[edge.a_end()];
let b_center = translations[edge.b_end()];
svg!("line", {
.class("edge")
.attribute("node-1", &edge.a_end().to_string())
.attribute("x1", &a_center.x().to_string())
.attribute("y1", &a_center.y().to_string())
.attribute("node-2", &edge.b_end().to_string())
.attribute("x2", &b_center.x().to_string())
.attribute("y2", &b_center.y().to_string())
})
}))
})
What you did above is exactly what I did in the static case. (The only difference is, that I like the fluent style better than the macro-style, because CLion has very limited code assistance in macros.)
translations
is a Vec
of node positions of a graph. It's values change over time (though not it's length, so semantically it's a mutable slice). I want to display this change as an animation in real time, so my users can see the iterative layout computation while it is happening. During the layout computation the number of children -- the length of translations
-- does not change, but the values in translations
do change, and so must the x1,y1 and x2, y2 attributes.
I had been treating translations
as a single signal that creates the whole SVG group element, which holds the children. But that way Dominator must delete and recreate the elements on each change. I understand that is slower than just updating the attribute values. That is why I'd like to move to a MutableVec
(a MutableSlice
would suffice, really). Shouldn't I?
Trying to use MutableVec
for this will just add a lot of extra complexity (and performance issues). You would need something like this...
fn lookup(translations: &MutableVec<Edge>, index: usize) -> impl Signal<Item = Edge> {
translations.signal_vec().to_signal_map(move |translations| translations[index]).dedupe()
}
fn switch<S>(translations: MutableVec<Edge>, signal: S) -> impl Signal<Item = Edge>
where S: Signal<Item = usize> {
signal.switch(move |index| lookup(&translations, index))
}
fn a_end(translations: &MutableVec<Edge>, index: usize) -> impl Signal<Item = usize> {
lookup(translations, index).map(|edge| edge.a_end())
}
fn b_end(translations: &MutableVec<Edge>, index: usize) -> impl Signal<Item = usize> {
lookup(translations, index).map(|edge| edge.b_end())
}
svg!("g", {
.children((0..translations.lock_ref().len()).map(clone!(translations => move |index| {
svg!("line", {
.class("edge")
.attribute("node-1", a_end(translations, index).map(|a| a.to_string()))
.attribute("x1", switch(translations.clone(), a_end(translations, index)).map(|a_center| a_center.x().to_string()))
.attribute("y1", switch(translations.clone(), a_end(translations, index)).map(|a_center| a_center.y().to_string()))
.attribute("node-2", b_end(translations, index).map(|b| b.to_string()))
.attribute("x2", switch(translations.clone(), b_end(translations, index)).map(|b_center| b_center.x().to_string()))
.attribute("y2", switch(translations.clone(), b_end(translations, index)).map(|b_center| b_center.y().to_string()))
})
})))
})
But this means that on every change of translations
it will do ten lookups for every edge. So if you have 1,000 edges, that means it will do 10,000 lookups on every change. And it has to create new buffers for each of those, so you'll have 10,000 buffers. That's an awful lot of overhead.
So I really don't recommend doing that. Instead you should use an Rc<Vec<Mutable<Edge>>>
rather than a MutableVec
:
svg!("g", {
.children(translations.iter().map(|edge| {
fn lookup<A, S, F>(translations: Rc<Vec<Mutable<Edge>>>, signal: S, f: F) -> impl Signal<Item = A>
where S: Signal<Item = usize>,
F: FnMut(&Edge) -> A {
signal.switch(move |index| translations[index].signal_ref(f))
}
fn a_end(edge: &Mutable<Edge>) -> impl Signal<Item = usize> {
edge.signal_ref(|edge| edge.a_end())
}
fn b_end(edge: &Mutable<Edge>) -> impl Signal<Item = usize> {
edge.signal_ref(|edge| edge.b_end())
}
svg!("line", {
.class("edge")
.attribute("node-1", a_end(edge).map(|a| a.to_string()))
.attribute("x1", lookup(translations.clone(), a_end(edge), |a_center| a_center.x().to_string()))
.attribute("y1", lookup(translations.clone(), a_end(edge), |a_center| a_center.y().to_string()))
.attribute("node-2", b_end(edge).map(|b| b.to_string()))
.attribute("x2", lookup(translations.clone(), b_end(edge), |b_center| b_center.x().to_string()))
.attribute("y2", lookup(translations.clone(), b_end(edge), |b_center| b_center.y().to_string()))
})
}))
})
This will also be far faster. It will only update the minimum necessary, and it has no buffers. It's basically optimal performance.
The idea with signals is that they should match your update patterns. Since you're not updating the Vec
, using MutableVec
doesn't make sense. But you are updating each individual edge, so using Vec<Mutable<Edge>>
makes perfect sense. Or if you were updating individual fields, then putting a Mutable
on each field of Edge
would make perfect sense.
P.S. You mentioned you wanted animations, but animated_map
will only animate when an element is inserted or removed. It is designed for easily fading elements in/out when they are inserted/removed.
If you want to do something more complicated than that, then you need to use MutableAnimation
, which gives you full control over the animation.
Thank you @Pauan for the detailed explanation how to design this and what to urgently avoid. I'm obviously a raw beginner to designing with signals and your guidance on design patterns and anti-patterns (with examples) is very much appreciated. I'd recommend putting some in the documentation of the Dominator and Signals crate, too, when you approach version 1.0
I have a graph structure, described by edges and nodes, and a graph layout -- a
Vec
of node-positions. Say the graph structure won't change, but laying out the graph is an iterative process that updates the node positions 1000 to 10000 times depending on the size and complexity of the graph.Using Dominator, I thought I'd needed to replace the
Vec
with aMutableVec
to be able to create the SVG-Elements once, and update the theirtransform
attribute by using a signal. I must be doing something wrong though, because forI'm getting the error
despite
Map
implementingSignalVec
.I have no idea how to draw the edges. In a static context I'd iterate through the edges and read the positions of the start- and end-nodes to draw each edge, like
How can I get a the signal-equivalent of translations[edge.a_end()], when translations is a
MutableSignalVec
(whereedge.a_end()
is not a signal)?