From 09d8872f15cb14a87563f6a1f6e972dc49e8cbf9 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Mon, 2 Nov 2026 20:33:29 -0500 Subject: [PATCH] Depth limit: merge containers at the cap in one pass over the siblings limit() replaced each too-deep container by its children with Vec::splice in the middle of the sibling list, moving every later sibling each time: a wide list of such containers took quadratic time to load. The list is now rebuilt in order from a worklist, so each node moves a bounded number of times. --- crates/doc/src/depth.rs | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/doc/src/depth.rs b/crates/doc/src/depth.rs index 6f10da9b7..5abba9c63 100644 --- a/crates/doc/src/depth.rs +++ b/crates/doc/src/depth.rs @@ -107,15 +107,21 @@ fn nested_deeper(now: &[Arc], depth: usize) -> bool { /// an opacity mask too deep are left out); what it held stays hidden or locked as it was. Returns /// whether anything changed. Recurses no deeper than the cap. fn limit(nodes: &mut Vec>, depth: usize) -> bool { - let mut changed = false; - let mut i = 0; - while let Some(n) = nodes.get_mut(i) { - if !nested_deeper(std::slice::from_ref(n), depth) { - i += 1; + if !nested_deeper(nodes, depth) { + return false; + } + // The list is rebuilt in order rather than spliced in place: replacing one container of many + // by what it holds would move all the siblings after it each time. + let mut out = Vec::with_capacity(nodes.len()); + // What is left to look at, the next one last. + let mut left: Vec> = std::mem::take(nodes); + left.reverse(); + while let Some(mut a) = left.pop() { + if !nested_deeper(std::slice::from_ref(&a), depth) { + out.push(a); continue; } - changed = true; - let n = Arc::make_mut(n); + let n = Arc::make_mut(&mut a); if n.extras().mask.as_ref().is_some_and(|m| nested_deeper(std::slice::from_ref(&m.art), depth + 1)) { n.extras_mut().mask = None; } @@ -123,7 +129,7 @@ fn limit(nodes: &mut Vec>, depth: usize) -> bool { if let Some(ch) = n.children_mut() { limit(ch, depth + 1); } - i += 1; + out.push(a); continue; } let clip = matches!(n.kind, NodeKind::Group { clip: true, .. } | NodeKind::Layer { clip: true, .. }); @@ -140,9 +146,10 @@ fn limit(nodes: &mut Vec>, depth: usize) -> bool { } } // What it held takes its place, and is looked at in its turn. - nodes.splice(i..=i, held); + left.extend(held.into_iter().rev()); } - changed + *nodes = out; + true } /// A symbol (`false`) or a pattern (`true`) of the document, by name: what art draws by name, an @@ -439,6 +446,24 @@ mod tests { } } + #[test] + fn many_containers_at_the_cap_are_merged_in_order() { + // A group just above the cap holding many groups of two paths each: every group is + // replaced by its two paths, in order (in time linear in their number). + const PAIRS: u64 = 50_000; + let path = |id| Arc::new(Node::path(NodeId(id), Default::default(), Default::default())); + let pairs = (0..PAIRS).map(|i| Arc::new(Node::group(NodeId(1_000_000 + i), vec![path(2 * i + 1), path(2 * i + 2)]))).collect(); + let mut art = Arc::new(Node::group(NodeId(500_000), pairs)); + for i in 0..MAX_DEPTH - 3 { + art = Arc::new(Node::group(NodeId(600_000 + i as u64), vec![art])); + } + let mut d = doc(art); + assert!(d.limit_depth()); + assert!(!d.too_deep(None)); + let holder = d.node(NodeId(500_000)).and_then(Node::children).map(|c| c.iter().map(|c| c.id.0).collect::>()); + assert_eq!(holder, Some((1..=2 * PAIRS).collect())); + } + #[test] fn a_clipping_path_and_a_mask_too_deep_are_left_out_and_hidden_art_stays_hidden() { let mut inner = Node::new(NodeId(9000), NodeKind::Group { children: vec![nested(0), nested(0)], clip: true });