mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-30 05:16:56 -06:00
* M2: components can't contain their own instances (#1084) Edits that would make a component hold an instance of itself, directly, through other components or through another variant of its set, are refused with the loop named. Files holding such a loop open with one instance per loop detached and a note; a rebuild never expands an instance inside a copy of itself, so a loop merged in a live session stops growing. * M2: a variant may hold a sibling variant's instance; only true loops are refused (#1084) Each variant counts as its own component: refusals and open-time repairs apply only to a component leading back to itself through instances (an instance-swap value included), so files with nested sibling variants open unchanged. sync_instance_depth is split into match_layers and copy_layers.
549 lines
23 KiB
Rust
549 lines
23 KiB
Rust
use super::diff::{binding_group, bindings_of, diff_groups, follow_boolean_defaults, geometry_derived, variable_groups};
|
|
use super::guard::{own_slot_content, relink_copy};
|
|
use super::instances::{copy_group, field_group, sync_instance};
|
|
use super::sync::insert_linked;
|
|
use super::{
|
|
BTreeMap, BTreeSet, Document, MAX_DEPTH, Node, NodeId, NodeKind, default_instance_name, holds_content, inside_instance, owning_component,
|
|
owning_instance, root_instance, scope, slot_edited, slot_scope,
|
|
};
|
|
|
|
type OverrideGroups = (Vec<&'static str>, Vec<&'static str>, Vec<&'static str>);
|
|
|
|
/// After an edit: record overrides inside instances, then rebuild instances whose main changed,
|
|
/// whose properties or main were switched, or whose overrides were reset.
|
|
pub fn sync_after_edit(doc: &mut Document, before: &Document, changed: &[NodeId], touched: &BTreeSet<NodeId>) {
|
|
let mut changed = changed.to_vec();
|
|
for id in follow_boolean_defaults(doc, before, &changed) {
|
|
if !changed.contains(&id) {
|
|
changed.push(id);
|
|
}
|
|
}
|
|
let changed = changed.as_slice();
|
|
adopt_slot_content(doc, before, changed);
|
|
let mut resync: BTreeSet<NodeId> = BTreeSet::new();
|
|
record_overrides(doc, before, changed, &mut resync);
|
|
// 2. Components whose layers changed, were added or removed. Value-only edits (paint, text,
|
|
// visibility, …) are carried to the copies directly; anything else rebuilds the instances.
|
|
let mut comps: BTreeSet<NodeId> = BTreeSet::new();
|
|
let mut patches: Vec<(NodeId, Vec<&'static str>)> = Vec::new();
|
|
// Main layers that only gained new layers or lost some: their copies take the same change.
|
|
let mut restructured: Vec<NodeId> = Vec::new();
|
|
let mut covered: BTreeSet<NodeId> = BTreeSet::new();
|
|
for &id in changed {
|
|
if let (Some(b), Some(n)) = (before.node(id), doc.node(id))
|
|
&& b.children != n.children
|
|
&& owning_instance(doc, id).is_none()
|
|
&& owning_component(doc, id).is_some()
|
|
&& let Some(layers) = restructurable(doc, before, b, n)
|
|
{
|
|
restructured.push(id);
|
|
covered.extend(layers);
|
|
}
|
|
}
|
|
for &id in changed {
|
|
if covered.contains(&id) || restructured.contains(&id) {
|
|
continue;
|
|
}
|
|
let Some(c) = owning_component(doc, id) else { continue };
|
|
let (Some(b), Some(n)) = (before.node(id), doc.node(id)) else {
|
|
comps.insert(c);
|
|
continue;
|
|
};
|
|
if b == n {
|
|
continue;
|
|
}
|
|
match patchable(doc, b, n) {
|
|
Some(groups) => patches.push((id, groups)),
|
|
None => {
|
|
comps.insert(c);
|
|
}
|
|
}
|
|
}
|
|
for &id in touched {
|
|
if !doc.contains(id)
|
|
&& !covered.contains(&id)
|
|
&& let Some(c) = owning_component(before, id)
|
|
&& doc.contains(c)
|
|
{
|
|
comps.insert(c);
|
|
}
|
|
}
|
|
for r in resync {
|
|
if doc.contains(r) {
|
|
sync_instance(doc, r);
|
|
if let Some(c) = owning_component(doc, r) {
|
|
comps.insert(c);
|
|
}
|
|
}
|
|
}
|
|
// A copy the patch can't follow exactly rebuilds instead (as does any other change).
|
|
let patched = comps.is_empty()
|
|
&& !full_rebuild()
|
|
&& (restructured.is_empty() || patch_structure(doc, before, &restructured))
|
|
&& (patches.is_empty() || patch_copies(doc, &patches));
|
|
#[cfg(test)]
|
|
if patched && (!restructured.is_empty() || !patches.is_empty()) {
|
|
PATCHED.with(|c| c.set(c.get() + 1));
|
|
}
|
|
if !patched {
|
|
comps.extend(patches.iter().map(|(id, _)| *id).chain(restructured.iter().copied()).filter_map(|id| owning_component(doc, id)));
|
|
}
|
|
// 3. Instances of changed components follow; an instance inside another component makes that
|
|
// one change too.
|
|
rebuild_instances(doc, &comps);
|
|
}
|
|
|
|
/// Slot content changes: layers added, removed or reordered in a slot become the instance's own.
|
|
fn adopt_slot_content(doc: &mut Document, before: &Document, changed: &[NodeId]) {
|
|
// 0. A slot whose layers were added, removed or reordered holds the instance's own content
|
|
// from now on; copies of instance layers placed elsewhere stop mirroring their main.
|
|
for &id in changed {
|
|
let (Some(n), Some(b)) = (doc.node(id), before.node(id)) else { continue };
|
|
if n.children == b.children || owning_instance(doc, id).is_none() {
|
|
continue;
|
|
}
|
|
if let Some(slot) = slot_scope(doc, id)
|
|
&& !slot_edited(doc, slot)
|
|
{
|
|
own_slot_content(doc, slot);
|
|
}
|
|
}
|
|
for &id in changed {
|
|
if !before.contains(id) {
|
|
relink_copy(doc, id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Overrides for layers edited inside instances; the instances to rebuild go into `resync`.
|
|
fn record_overrides(doc: &mut Document, before: &Document, changed: &[NodeId], resync: &mut BTreeSet<NodeId>) {
|
|
for &id in changed {
|
|
let (Some(n), Some(b)) = (doc.node(id), before.node(id)) else { continue };
|
|
let Some(inst) = owning_instance(doc, id) else { continue };
|
|
let top_root = inst == id && !inside_instance(doc, id);
|
|
if top_root
|
|
&& let (NodeKind::Instance { component: c1, props: p1 }, NodeKind::Instance { component: c2, props: p2 }) = (&n.kind, &b.kind)
|
|
&& (c1 != c2 || p1 != p2)
|
|
{
|
|
// Switching the main or a property value of an instance isn't an override.
|
|
resync.insert(id);
|
|
}
|
|
let (raw, groups, cleared) = override_groups(doc, before, id, n, b, inst, top_root);
|
|
let lost = b.overrides.iter().any(|g| !n.overrides.contains(g));
|
|
let nested_switch = !top_root && raw.iter().any(|g| matches!(*g, "component" | "props"));
|
|
if (!groups.is_empty() || !cleared.is_empty())
|
|
&& let Some(m) = doc.node_mut(id)
|
|
{
|
|
for g in groups {
|
|
m.overrides.insert(g.to_string());
|
|
}
|
|
for g in cleared {
|
|
m.overrides.remove(g);
|
|
}
|
|
}
|
|
if (lost || nested_switch)
|
|
&& let Some(r) = root_instance(doc, id)
|
|
{
|
|
resync.insert(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `(raw, groups, cleared)` of layer `id` edited from `b` to `n`: the raw changed groups, the ones
|
|
/// that are now overrides and the overrides that were set back to the main's value.
|
|
fn override_groups(doc: &Document, before: &Document, id: NodeId, n: &Node, b: &Node, inst: NodeId, top_root: bool) -> OverrideGroups {
|
|
let raw = diff_groups(b, n);
|
|
let mut groups = raw.clone();
|
|
// An instance's own place, resizing, export settings, interactions and variable modes are
|
|
// its own, not overrides.
|
|
if top_root {
|
|
groups.retain(|g| !matches!(*g, "component" | "props" | "position" | "resizing" | "exports" | "interactions" | "modes"));
|
|
}
|
|
if groups.iter().any(|g| matches!(*g, "size" | "position")) {
|
|
groups.retain(|g| !matches!(*g, "size" | "position") || !geometry_derived(doc, before, id, g));
|
|
}
|
|
// A value equal to the main's isn't an override (setting it back clears the override).
|
|
let mut cleared = Vec::new();
|
|
if let Some(src) = n.source.and_then(|s| doc.node(s)) {
|
|
let mut differs = diff_groups(src, n);
|
|
if inst == id {
|
|
differs.retain(|g| *g != "name");
|
|
if n.name != default_instance_name(doc, src.id) {
|
|
differs.push("name");
|
|
}
|
|
}
|
|
// A value from the same variable as the main's (resolved in this layer's modes) isn't an
|
|
// override either.
|
|
let from_variables = variable_groups(n, src);
|
|
groups.retain(|g| differs.contains(g) && !from_variables.contains(g));
|
|
// Binding another variable (or none) where the main binds one is an override.
|
|
for (k, _) in b.bound.iter().chain(n.bound.iter()) {
|
|
let Some(g) = binding_group(k) else { continue };
|
|
let rebound = b.bound.get(k) != n.bound.get(k) && !bindings_of(n, g).eq(bindings_of(src, g));
|
|
if rebound && !groups.contains(&g) {
|
|
groups.push(g);
|
|
}
|
|
}
|
|
cleared = raw.iter().filter(|g| !matches!(**g, "size" | "position") && !differs.contains(g) && n.overrides.contains(**g)).copied().collect();
|
|
if top_root {
|
|
cleared.retain(|g| !matches!(*g, "component" | "props"));
|
|
}
|
|
}
|
|
(raw, groups, cleared)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
thread_local! {
|
|
/// Tests: rebuild instances even for edits that could be patched (to compare both ways).
|
|
pub(crate) static FULL_REBUILD: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
|
/// Tests: edits carried to the copies without rebuilding instances.
|
|
pub(crate) static PATCHED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn full_rebuild() -> bool {
|
|
FULL_REBUILD.with(std::cell::Cell::get)
|
|
}
|
|
|
|
#[cfg(not(test))]
|
|
fn full_rebuild() -> bool {
|
|
false
|
|
}
|
|
|
|
/// The property groups of a main-component layer edited from `b` to `n` when the edit changed
|
|
/// values only (paint, text, visibility, size, position, …) that copies take as they are: carried to them without
|
|
/// rebuilding the instances ([`patch_copies`]). `None` when anything else changed.
|
|
fn patchable(doc: &Document, b: &Node, n: &Node) -> Option<Vec<&'static str>> {
|
|
const VALUES: [&str; 17] = [
|
|
"fills",
|
|
"strokes",
|
|
"effects",
|
|
"opacity",
|
|
"visible",
|
|
"name",
|
|
"corners",
|
|
"characters",
|
|
"textStyle",
|
|
"padding",
|
|
"gap",
|
|
"alignment",
|
|
"grids",
|
|
"exports",
|
|
"interactions",
|
|
"size",
|
|
"position",
|
|
];
|
|
if b.children != n.children || b.parent != n.parent || b.kind.type_name() != n.kind.type_name() {
|
|
return None;
|
|
}
|
|
let groups = diff_groups(b, n);
|
|
if groups.is_empty() || groups.iter().any(|g| !VALUES.contains(g)) {
|
|
return None;
|
|
}
|
|
// Exactly those groups (and the layer's own overrides) changed.
|
|
let mut probe = n.clone();
|
|
for g in &groups {
|
|
copy_group(b, &mut probe, g);
|
|
}
|
|
probe.overrides = b.overrides.clone();
|
|
if probe != *b {
|
|
return None;
|
|
}
|
|
// Values a component property sets (on this layer or a main it mirrors) come from instances.
|
|
let mut cur = Some(n);
|
|
for _ in 0..=MAX_DEPTH {
|
|
let Some(x) = cur else { break };
|
|
if x.prop_refs.keys().any(|f| groups.contains(&field_group(f))) {
|
|
return None;
|
|
}
|
|
cur = x.source.and_then(|s| doc.node(s));
|
|
}
|
|
Some(groups)
|
|
}
|
|
|
|
/// Copies by the layer they mirror; nested instances swapped to a main also follow it.
|
|
fn copies_index(doc: &Document) -> BTreeMap<NodeId, Vec<NodeId>> {
|
|
let mut copies: BTreeMap<NodeId, Vec<NodeId>> = BTreeMap::new();
|
|
for n in doc.nodes() {
|
|
let Some(s) = n.source else { continue };
|
|
copies.entry(s).or_default().push(n.id);
|
|
if let (NodeKind::Instance { component, .. }, Some(NodeKind::Instance { component: other, .. })) = (&n.kind, doc.node(s).map(|x| &x.kind))
|
|
&& component != other
|
|
{
|
|
copies.entry(*component).or_default().push(n.id);
|
|
}
|
|
}
|
|
copies
|
|
}
|
|
|
|
/// When main layer `b` → `n` only gained new plain layers (created in this edit: no instances,
|
|
/// components or property bindings in them) or lost layers that were deleted: those layers, which
|
|
/// [`patch_structure`] carries to the copies. `None` for anything else.
|
|
fn restructurable(doc: &Document, before: &Document, b: &Node, n: &Node) -> Option<Vec<NodeId>> {
|
|
let mut probe = n.clone();
|
|
probe.children = b.children.clone();
|
|
probe.overrides = b.overrides.clone();
|
|
if probe != *b {
|
|
return None;
|
|
}
|
|
let mut layers = Vec::new();
|
|
for c in n.children.iter().filter(|c| !b.children.contains(c)) {
|
|
for d in doc.subtree(*c) {
|
|
let x = doc.node(d)?;
|
|
let plain = !matches!(x.kind, NodeKind::Instance { .. } | NodeKind::Component { .. } | NodeKind::ComponentSet { .. });
|
|
if before.contains(d) || !plain || !x.prop_refs.is_empty() {
|
|
return None;
|
|
}
|
|
layers.push(d);
|
|
}
|
|
}
|
|
for c in b.children.iter().filter(|c| !n.children.contains(c)) {
|
|
if doc.contains(*c) {
|
|
return None;
|
|
}
|
|
layers.extend(before.subtree(*c));
|
|
}
|
|
Some(layers)
|
|
}
|
|
|
|
/// Carry the new and deleted layers of main layers `seeds` ([`restructurable`]) to every copy,
|
|
/// through copies of copies (slots holding an instance's own content keep it). Returns false
|
|
/// (leaving a partial patch for the caller to rebuild over) when a copy can't follow exactly.
|
|
fn patch_structure(doc: &mut Document, before: &Document, seeds: &[NodeId]) -> bool {
|
|
let copies = copies_index(doc);
|
|
let mut work: Vec<NodeId> = seeds.to_vec();
|
|
let mut steps = 0usize;
|
|
while let Some(p) = work.pop() {
|
|
steps += 1;
|
|
if steps > 10_000_000 {
|
|
return false;
|
|
}
|
|
let Some(pn) = doc.node(p).cloned() else { continue };
|
|
for c in copies.get(&p).cloned().unwrap_or_default() {
|
|
let Some(cn) = doc.node(c).cloned() else { continue };
|
|
if holds_content(&cn) {
|
|
continue;
|
|
}
|
|
if let (NodeKind::Instance { component: a, .. }, NodeKind::Instance { component: b, .. }) = (&pn.kind, &cn.kind)
|
|
&& a != b
|
|
{
|
|
continue;
|
|
}
|
|
// The copy's layers by the layer of `p` they mirror.
|
|
let mut mirrors: BTreeMap<NodeId, NodeId> = BTreeMap::new();
|
|
for k in &cn.children {
|
|
let Some(src) = doc.node(*k).and_then(|x| x.source) else { return false };
|
|
if mirrors.insert(src, *k).is_some() {
|
|
return false;
|
|
}
|
|
}
|
|
let mut order = Vec::with_capacity(pn.children.len());
|
|
for pc in &pn.children {
|
|
if let Some(k) = mirrors.remove(pc) {
|
|
order.push(k);
|
|
continue;
|
|
}
|
|
// A layer the copy lacks must be new in this edit, placed as in the main.
|
|
let absolute = doc.node(*pc).is_some_and(|x| x.child_layout.absolute);
|
|
if before.contains(*pc) || !child_follows_main(doc, c, absolute) {
|
|
return false;
|
|
}
|
|
match copy_layers(doc, *pc, c) {
|
|
Some(k) => order.push(k),
|
|
None => return false,
|
|
}
|
|
}
|
|
// Copies of layers the main lost.
|
|
for (src, k) in mirrors {
|
|
if doc.contains(src) || doc.remove(k).is_err() {
|
|
return false;
|
|
}
|
|
}
|
|
if doc.node(c).is_some_and(|x| x.children != order)
|
|
&& let Some(m) = doc.node_mut(c)
|
|
{
|
|
m.children = order;
|
|
}
|
|
work.push(c);
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Whether a layer placed in copy `parent` (absolutely positioned or not) takes its main layer's
|
|
/// place and size: nothing on the way up applies constraints ([`follows_main_geometry`]).
|
|
fn child_follows_main(doc: &Document, parent: NodeId, absolute: bool) -> bool {
|
|
let Some(p) = doc.node(parent) else { return false };
|
|
let constrains = p.layout.is_none() || absolute;
|
|
let resized = sized_own(p)
|
|
|| match &p.kind {
|
|
NodeKind::Instance { component, .. } => {
|
|
doc.node(*component).is_none_or(|m| (m.width - p.width).abs() > 1e-9 || (m.height - p.height).abs() > 1e-9)
|
|
}
|
|
_ => false,
|
|
};
|
|
!(constrains && resized) && follows_main_geometry(doc, parent)
|
|
}
|
|
|
|
/// Copy main layer `src` (with its layers) into copy `parent` as layers mirroring them; returns the
|
|
/// new copy (the caller places it among `parent`'s layers).
|
|
fn copy_layers(doc: &mut Document, src: NodeId, parent: NodeId) -> Option<NodeId> {
|
|
let sub = doc.subtree(src);
|
|
let map: BTreeMap<NodeId, NodeId> = sub.iter().map(|d| (*d, doc.alloc_id())).collect();
|
|
for d in &sub {
|
|
let mut n = doc.node(*d)?.clone();
|
|
n.id = *map.get(d)?;
|
|
n.parent = if *d == src { Some(parent) } else { n.parent.and_then(|x| map.get(&x).copied()) };
|
|
n.children = n.children.iter().filter_map(|k| map.get(k).copied()).collect();
|
|
n.source = Some(*d);
|
|
n.overrides.clear();
|
|
n.prop_refs.clear();
|
|
n.annotations.clear();
|
|
n.measurements.clear();
|
|
insert_linked(doc, n).ok()?;
|
|
}
|
|
map.get(&src).copied()
|
|
}
|
|
|
|
/// Carry value-only edits of main-component layers (`seeds`: layer, groups) to every copy, through
|
|
/// copies of copies, except where a copy overrides the group. Returns false (leaving a partial
|
|
/// patch for the caller to rebuild over) when a copy can't follow exactly.
|
|
fn patch_copies(doc: &mut Document, seeds: &[(NodeId, Vec<&'static str>)]) -> bool {
|
|
let copies = copies_index(doc);
|
|
let text = |g: &str| matches!(g, "characters" | "textStyle");
|
|
let mut work: Vec<(NodeId, Vec<&'static str>)> = seeds.to_vec();
|
|
let mut steps = 0usize;
|
|
while let Some((p, groups)) = work.pop() {
|
|
steps += 1;
|
|
if steps > 10_000_000 {
|
|
return false;
|
|
}
|
|
let Some(pn) = doc.node(p).cloned() else { continue };
|
|
for c in copies.get(&p).cloned().unwrap_or_default() {
|
|
let Some(cn) = doc.node(c) else { continue };
|
|
// A swapped nested instance shows its own main, not the layer it stands in for.
|
|
if let (NodeKind::Instance { component: a, .. }, NodeKind::Instance { component: b, .. }) = (&pn.kind, &cn.kind)
|
|
&& a != b
|
|
{
|
|
continue;
|
|
}
|
|
// An instance keeps its own visibility, place, export settings and interactions; its
|
|
// name follows the main's unless renamed; a resized one lays its layers out against
|
|
// the main's size.
|
|
let root = matches!(pn.kind, NodeKind::Component { .. });
|
|
if root && ((groups.contains(&"name") && !cn.overrides.contains("name")) || (groups.contains(&"size") && cn.overrides.contains("size"))) {
|
|
return false;
|
|
}
|
|
let gs: Vec<&'static str> = groups
|
|
.iter()
|
|
.copied()
|
|
.filter(|g| !(cn.overrides.contains(*g) || root && matches!(*g, "visible" | "name" | "position" | "exports" | "interactions")))
|
|
.collect();
|
|
if gs.is_empty() {
|
|
continue;
|
|
}
|
|
let geometry = gs.iter().any(|g| matches!(*g, "size" | "position"));
|
|
if geometry && !follows_main_geometry(doc, c) {
|
|
return false;
|
|
}
|
|
// Text content and style share their runs: a copy overriding one keeps its runs when the
|
|
// other follows the main (as a rebuild carries them).
|
|
let runs = match &cn.kind {
|
|
NodeKind::Text(t) if gs.iter().any(|g| text(g)) && cn.overrides.iter().any(|g| text(g)) => Some(t.runs.clone()),
|
|
_ => None,
|
|
};
|
|
if let Some(m) = doc.node_mut(c) {
|
|
for g in &gs {
|
|
copy_group(&pn, m, g);
|
|
}
|
|
if let (Some(r), NodeKind::Text(t)) = (runs, &mut m.kind) {
|
|
t.runs = r;
|
|
}
|
|
}
|
|
if (geometry || gs.iter().any(|g| text(g)))
|
|
&& let Some((w, h)) = doc.node(c).and_then(figcraft_text::auto_size)
|
|
&& doc.node(c).is_some_and(|n| (n.width - w).abs() > 1e-6 || (n.height - h).abs() > 1e-6)
|
|
{
|
|
figcraft_layout::set_size(doc, c, w, h);
|
|
}
|
|
work.push((c, gs));
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
/// Whether a copy's size and place are its main layer's: no layer around it inside its instances
|
|
/// applies constraints on the way down to it (a resized instance or frame without auto layout, or
|
|
/// a resized auto layout instance holding it absolutely positioned). Auto layout re-flows the rest.
|
|
fn follows_main_geometry(doc: &Document, id: NodeId) -> bool {
|
|
let chain = scope(doc, id);
|
|
chain.windows(2).all(|w| {
|
|
let (Some(below), Some(a)) = (w.first().and_then(|i| doc.node(*i)), w.get(1).and_then(|i| doc.node(*i))) else { return false };
|
|
let constrains = a.layout.is_none() || below.child_layout.absolute;
|
|
if sized_own(a) && constrains {
|
|
return false;
|
|
}
|
|
match &a.kind {
|
|
NodeKind::Instance { component, .. } => {
|
|
!constrains || doc.node(*component).is_some_and(|m| (m.width - a.width).abs() <= 1e-9 && (m.height - a.height).abs() <= 1e-9)
|
|
}
|
|
_ => true,
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Whether a layer inside an instance overrides its size or resizing: it may not have its main
|
|
/// layer's size, so the layers in it can't take their main layers' places as they are.
|
|
fn sized_own(n: &Node) -> bool {
|
|
n.overrides.contains("size") || n.overrides.contains("resizing")
|
|
}
|
|
|
|
/// Rebuild every instance of `comps` (and of the components that hold those instances, and so on
|
|
/// outwards), each outermost instance once, inner components first.
|
|
pub(super) fn rebuild_instances(doc: &mut Document, comps: &BTreeSet<NodeId>) {
|
|
if comps.is_empty() {
|
|
return;
|
|
}
|
|
// Outermost instances by the components their layers instantiate.
|
|
let mut roots_of: BTreeMap<NodeId, BTreeSet<NodeId>> = BTreeMap::new();
|
|
for n in doc.nodes() {
|
|
if let NodeKind::Instance { component, .. } = &n.kind {
|
|
roots_of.entry(*component).or_default().insert(root_instance(doc, n.id).unwrap_or(n.id));
|
|
}
|
|
}
|
|
// Affected components, with how deep their dependencies go (an instance inside component X of
|
|
// component Y: X follows Y).
|
|
let mut level: BTreeMap<NodeId, usize> = comps.iter().map(|c| (*c, 0)).collect();
|
|
let mut outer: BTreeMap<NodeId, Option<NodeId>> = BTreeMap::new();
|
|
let mut frontier: Vec<NodeId> = comps.iter().copied().collect();
|
|
let mut rounds = 0;
|
|
while !frontier.is_empty() && rounds < 64 {
|
|
rounds += 1;
|
|
let mut next = Vec::new();
|
|
for c in frontier {
|
|
let lc = level.get(&c).copied().unwrap_or(0);
|
|
for r in roots_of.get(&c).into_iter().flatten() {
|
|
let o = *outer.entry(*r).or_insert_with(|| owning_component(doc, *r));
|
|
if let Some(x) = o
|
|
&& x != c
|
|
{
|
|
let lx = level.entry(x).or_insert(0);
|
|
if *lx < lc + 1 {
|
|
*lx = lc + 1;
|
|
next.push(x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
frontier = next;
|
|
}
|
|
// Instances inside components by level, then the rest (each once).
|
|
let mut order: Vec<(usize, NodeId)> = outer.iter().map(|(r, o)| (o.and_then(|x| level.get(&x).copied()).unwrap_or(usize::MAX), *r)).collect();
|
|
order.sort();
|
|
for (_, r) in order {
|
|
if doc.contains(r) {
|
|
sync_instance(doc, r);
|
|
}
|
|
}
|
|
}
|