1use std::{any::TypeId, cmp::Reverse, collections::BinaryHeap};
2
3use smallvec::SmallVec;
4
5use crate::{
6 Applier, Node, NodeError, NodeId, NodeMarks, RecycleOrigin, RecyclePool, RecycledNode,
7 RuntimeHandle, bubble_passes,
8 collections::map::{HashMap, HashSet},
9 layout_dirt_roots,
10 node_marks::MarkPlace,
11 stable_index,
12};
13
14mod applier_impl;
15
16#[derive(Default)]
19pub struct MemoryApplier {
20 pub(crate) state: Box<MemoryApplierState>,
23}
24
25#[derive(Default)]
26pub(crate) struct MemoryApplierState {
27 pub(crate) nodes: Vec<Option<Box<dyn Node>>>,
28 pub(crate) physical_stable_ids: Vec<u32>,
29 pub(crate) physical_warm_recycled_origins: Vec<RecycleOrigin>,
30 pub(crate) stable_index: stable_index::StableIndex,
31 pub(crate) free_ids: BinaryHeap<Reverse<usize>>,
32 pub(crate) high_ids: HashMap<NodeId, HighIdSlot>,
33 pub(crate) next_stable_id: NodeId,
34 pub(crate) layout_runtime: Option<RuntimeHandle>,
35 pub(crate) recycle_pools: HashMap<TypeId, RecyclePool>,
36 pub(crate) structural_change_parents: Vec<NodeId>,
37 pub(crate) virtual_node_ids: HashSet<NodeId>,
38 pub(crate) bubble_passes: bubble_passes::BubblePasses,
39 pub(crate) layout_dirt_roots: layout_dirt_roots::LayoutDirtRoots,
40}
41
42#[derive(Default)]
46pub(crate) struct HighIdSlot {
47 node: Option<Box<dyn Node>>,
48 origin: RecycleOrigin,
49 generation: u32,
50}
51
52#[derive(Default)]
57pub struct SceneNodeAttachmentScratch {
58 attached: NodeMarks,
59 path: Vec<MarkPlace>,
60}
61
62const ATTACHED: u8 = 1;
63const DETACHED: u8 = 2;
64
65struct RemovalFrame {
66 node_id: NodeId,
67 children: SmallVec<[NodeId; 8]>,
68 next_child: usize,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
74pub struct MemoryApplierDebugStats {
75 pub next_stable_id: NodeId,
77 pub nodes_len: usize,
79 pub nodes_cap: usize,
81 pub physical_stable_ids_len: usize,
83 pub physical_stable_ids_cap: usize,
85 pub stable_to_physical_len: usize,
87 pub stable_to_physical_cap: usize,
89 pub stable_generations_len: usize,
91 pub stable_generations_cap: usize,
93 pub free_ids_len: usize,
95 pub free_ids_cap: usize,
97 pub high_id_nodes_len: usize,
99 pub high_id_nodes_cap: usize,
101 pub high_id_generations_len: usize,
103 pub high_id_generations_cap: usize,
105 pub recycled_type_count: usize,
107 pub recycled_type_cap: usize,
109 pub recycled_node_count: usize,
111 pub recycled_node_capacity: usize,
113 pub warm_recycled_node_id_count: usize,
115 pub warm_recycled_node_id_capacity: usize,
117}
118
119impl MemoryApplier {
120 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
121 pub(crate) const HIGH_ID_THRESHOLD: NodeId = NodeId(1_000_000_000);
122 const INVALID_STABLE_ID: u32 = u32::MAX;
123 const INITIAL_DENSE_NODE_CAP: usize = 32;
124 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
125 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
126
127 fn pack_stable_id(stable_id: NodeId) -> u32 {
128 u32::try_from(stable_id.0).expect("stable id overflow")
129 }
130
131 fn unpack_stable_id(stable_id: u32) -> NodeId {
132 NodeId(stable_id as usize)
133 }
134
135 fn next_dense_node_target_len(old_len: usize) -> usize {
136 if old_len < Self::INITIAL_DENSE_NODE_CAP {
137 return Self::INITIAL_DENSE_NODE_CAP;
138 }
139 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
140 return old_len.saturating_mul(2);
141 }
142
143 let incremental_growth =
144 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
145 old_len.saturating_add(incremental_growth)
146 }
147
148 fn ensure_dense_node_storage_capacity(&mut self) {
149 let len = self
150 .state
151 .nodes
152 .len()
153 .max(self.state.physical_stable_ids.len())
154 .max(self.state.physical_warm_recycled_origins.len());
155 if len < self.state.nodes.capacity()
156 && len < self.state.physical_stable_ids.capacity()
157 && len < self.state.physical_warm_recycled_origins.capacity()
158 {
159 return;
160 }
161
162 let target = Self::next_dense_node_target_len(len);
163 if self.state.nodes.capacity() < target {
164 self.state
165 .nodes
166 .reserve_exact(target.saturating_sub(self.state.nodes.len()));
167 }
168 if self.state.physical_stable_ids.capacity() < target {
169 self.state
170 .physical_stable_ids
171 .reserve_exact(target.saturating_sub(self.state.physical_stable_ids.len()));
172 }
173 if self.state.physical_warm_recycled_origins.capacity() < target {
174 self.state.physical_warm_recycled_origins.reserve_exact(
175 target.saturating_sub(self.state.physical_warm_recycled_origins.len()),
176 );
177 }
178 }
179
180 pub fn new() -> Self {
182 Self::default()
183 }
184
185 pub fn scene_node_attached_to(&mut self, node_id: NodeId, root: NodeId) -> Option<NodeId> {
191 let resolved = self.first_non_virtual_ancestor(node_id)?;
192 self.is_attached_to(resolved, root).then_some(resolved)
193 }
194
195 pub fn scene_nodes_attached_to(
199 &mut self,
200 nodes: impl IntoIterator<Item = NodeId>,
201 root: NodeId,
202 ) -> Vec<Option<NodeId>> {
203 let mut result = Vec::new();
204 self.scene_nodes_attached_to_into(
205 nodes,
206 root,
207 &mut result,
208 &mut SceneNodeAttachmentScratch::default(),
209 );
210 result
211 }
212
213 pub fn scene_nodes_attached_to_into(
231 &mut self,
232 nodes: impl IntoIterator<Item = NodeId>,
233 root: NodeId,
234 output: &mut Vec<Option<NodeId>>,
235 scratch: &mut SceneNodeAttachmentScratch,
236 ) {
237 let nodes = nodes.into_iter();
238 output.clear();
239 let (lower_bound, upper_bound) = nodes.size_hint();
240 if lower_bound == 0 && upper_bound == Some(0) {
241 scratch.path.clear();
242 return;
243 }
244 scratch.attached.reset(self);
245 scratch.attached.set(self, root, ATTACHED);
246 output.reserve(lower_bound);
247 scratch.path.clear();
248 for node_id in nodes {
249 let Some(resolved) = self.first_non_virtual_ancestor(node_id) else {
250 output.push(None);
251 continue;
252 };
253 let mut current = MarkPlace::of(self, resolved);
254 scratch.path.clear();
255 let answer = loop {
256 match scratch.attached.mark_at(current) {
257 ATTACHED => break true,
258 DETACHED => break false,
259 _ => {}
260 }
261 scratch.path.push(current);
262 match self.parent_at(current) {
263 Some(parent) if scratch.path.len() < 100_000 => {
264 current = MarkPlace::of(self, parent);
265 }
266 _ => break false,
267 }
268 };
269 let mark = if answer { ATTACHED } else { DETACHED };
270 for visited in scratch.path.drain(..) {
271 *scratch.attached.entry_at(visited) = mark;
272 }
273 output.push(answer.then_some(resolved));
274 }
275 }
276
277 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
285 let mut candidates = Vec::new();
286 self.take_structural_change_parents_into(&mut candidates);
287 let mut seen = HashSet::<NodeId>::default();
288 candidates.retain_mut(|parent_id| {
289 let Some(resolved) = self.first_non_virtual_ancestor(*parent_id) else {
290 return false;
291 };
292 *parent_id = resolved;
293 self.is_attached_to(resolved, root) && seen.insert(resolved)
294 });
295 candidates
296 }
297
298 pub fn take_structural_change_parents_into(&mut self, output: &mut Vec<NodeId>) {
311 output.clear();
312 std::mem::swap(&mut self.state.structural_change_parents, output);
313 }
314
315 fn first_non_virtual_ancestor(&mut self, node_id: NodeId) -> Option<NodeId> {
316 if self.state.virtual_node_ids.is_empty() {
317 return Some(node_id);
318 }
319 let mut current = node_id;
320 for _ in 0..100_000 {
321 if !self.state.virtual_node_ids.contains(¤t) {
322 return Some(current);
323 }
324 match self.get_mut(current) {
325 Ok(node) => current = node.parent()?,
326 Err(_) => return None,
327 }
328 }
329 None
330 }
331
332 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
333 let mut current = node_id;
334 for _ in 0..100_000 {
335 if current == root {
336 return true;
337 }
338 match self.get_mut(current) {
339 Ok(node) => match node.parent() {
340 Some(parent) => current = parent,
341 None => return false,
342 },
343 Err(_) => return false,
344 }
345 }
346 false
347 }
348
349 pub fn with_node<N: Node + 'static, R>(
352 &mut self,
353 id: NodeId,
354 f: impl FnOnce(&mut N) -> R,
355 ) -> Result<R, NodeError> {
356 let typed = self
357 .get_mut(id)?
358 .as_any_mut()
359 .downcast_mut::<N>()
360 .ok_or_else(|| NodeError::TypeMismatch {
361 id,
362 expected: std::any::type_name::<N>(),
363 })?;
364 Ok(f(typed))
365 }
366
367 pub fn len(&self) -> usize {
369 self.state.nodes.iter().filter(|n| n.is_some()).count()
370 }
371
372 pub fn capacity(&self) -> usize {
374 self.state.nodes.len()
375 }
376
377 pub fn tombstone_count(&self) -> usize {
379 self.state.nodes.iter().filter(|n| n.is_none()).count()
380 }
381
382 pub fn freelist_len(&self) -> usize {
384 self.state.free_ids.len()
385 }
386
387 pub fn debug_recycled_node_count(&self) -> usize {
389 self.total_recycled_node_count()
390 }
391
392 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
394 let key = TypeId::of::<N>();
395 self.state
396 .recycle_pools
397 .get(&key)
398 .map_or(0, RecyclePool::len)
399 }
400
401 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
403 MemoryApplierDebugStats {
404 next_stable_id: self.state.next_stable_id,
405 nodes_len: self.len(),
406 nodes_cap: self.state.nodes.len(),
407 physical_stable_ids_len: self.state.physical_stable_ids.len(),
408 physical_stable_ids_cap: self.state.physical_stable_ids.capacity(),
409 stable_to_physical_len: self.state.stable_index.live(),
410 stable_to_physical_cap: self.state.stable_index.capacity(),
411 stable_generations_len: self.state.stable_index.occupied(),
412 stable_generations_cap: self.state.stable_index.capacity(),
413 free_ids_len: self.state.free_ids.len(),
414 free_ids_cap: self.state.free_ids.capacity(),
415 high_id_nodes_len: self.high_id_nodes().count(),
416 high_id_nodes_cap: self.state.high_ids.capacity(),
417 high_id_generations_len: self.state.high_ids.len(),
418 high_id_generations_cap: self.state.high_ids.capacity(),
419 recycled_type_count: self
420 .state
421 .recycle_pools
422 .values()
423 .filter(|pool| pool.len() > 0)
424 .count(),
425 recycled_type_cap: self.state.recycle_pools.capacity(),
426 recycled_node_count: self.total_recycled_node_count(),
427 recycled_node_capacity: self.total_recycled_node_capacity(),
428 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
429 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
430 }
431 }
432
433 pub fn is_empty(&self) -> bool {
435 self.len() == 0
436 }
437
438 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut dyn Node)) {
440 for node in self.state.nodes.iter_mut().flatten() {
441 visit(node.as_mut());
442 }
443 for node in self
444 .state
445 .high_ids
446 .values_mut()
447 .filter_map(|slot| slot.node.as_mut())
448 {
449 visit(node.as_mut());
450 }
451 }
452
453 pub fn debug_live_node_heap_bytes(&self) -> usize {
455 let dense_nodes = self
456 .state
457 .nodes
458 .iter()
459 .flatten()
460 .map(|node| size_of_val(&**node) + node.debug_heap_bytes())
461 .sum::<usize>();
462 let high_id_nodes = self
463 .high_id_nodes()
464 .map(|node| size_of_val(node) + node.debug_heap_bytes())
465 .sum::<usize>();
466 dense_nodes + high_id_nodes
467 }
468
469 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
471 self.state
472 .recycle_pools
473 .values()
474 .flat_map(RecyclePool::nodes)
475 .map(|node| size_of_val(&*node.node) + node.node.debug_heap_bytes())
476 .sum::<usize>()
477 }
478
479 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
482 self.state.layout_runtime = Some(handle);
483 }
484
485 pub fn clear_runtime_handle(&mut self) {
487 self.state.layout_runtime = None;
488 }
489
490 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
492 self.state.layout_runtime.clone()
493 }
494
495 fn total_recycled_node_count(&self) -> usize {
496 self.state
497 .recycle_pools
498 .values()
499 .map(RecyclePool::len)
500 .sum()
501 }
502
503 fn total_recycled_node_capacity(&self) -> usize {
504 self.state
505 .recycle_pools
506 .values()
507 .map(RecyclePool::capacity)
508 .sum()
509 }
510
511 fn total_warm_recycled_node_id_count(&self) -> usize {
512 self.live_warm_recycled_origin_count()
513 + self
514 .state
515 .recycle_pools
516 .values()
517 .map(|pool| pool.warm.len() + pool.returning.len())
518 .sum::<usize>()
519 }
520
521 fn total_warm_recycled_node_id_capacity(&self) -> usize {
522 self.live_warm_recycled_origin_capacity()
523 + self
524 .state
525 .recycle_pools
526 .values()
527 .map(|pool| pool.warm.capacity() + pool.returning.capacity())
528 .sum::<usize>()
529 }
530
531 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
532 if recycle_pool_limit.is_some() || self.state.recycle_pools.contains_key(&key) {
533 self.state.recycle_pools.entry(key).or_default().limit = recycle_pool_limit;
534 }
535 }
536
537 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
538 self.state
539 .recycle_pools
540 .get(&key)
541 .map_or(0, |pool| pool.warm.len())
542 }
543
544 fn live_warm_recycled_origin_count(&self) -> usize {
545 self.state
546 .physical_warm_recycled_origins
547 .iter()
548 .zip(self.state.nodes.iter())
549 .filter(|(origin, node)| **origin == RecycleOrigin::Warm && node.is_some())
550 .count()
551 + self
552 .state
553 .high_ids
554 .values()
555 .filter(|slot| slot.origin == RecycleOrigin::Warm && slot.node.is_some())
556 .count()
557 }
558
559 fn high_id_nodes(&self) -> impl Iterator<Item = &dyn Node> {
560 self.state
561 .high_ids
562 .values()
563 .filter_map(|slot| slot.node.as_deref())
564 }
565
566 fn live_warm_recycled_origin_capacity(&self) -> usize {
567 self.state.physical_warm_recycled_origins.capacity() + self.state.high_ids.capacity()
568 }
569
570 fn push_recycled_node(
571 &mut self,
572 key: TypeId,
573 recycle_pool_limit: Option<usize>,
574 recycled: RecycledNode,
575 ) {
576 self.remember_recycle_pool_limit(key, recycle_pool_limit);
577 let pool = self.state.recycle_pools.entry(key).or_default();
578 pool.remember_prototype(recycled.node.as_ref());
579 pool.push(recycled);
580 }
581
582 fn seed_recycled_node_shell_impl(
583 &mut self,
584 key: TypeId,
585 recycle_pool_limit: Option<usize>,
586 shell: Box<dyn Node>,
587 ) {
588 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
589 if self.warm_recycled_pool_len(key) >= limit {
590 return;
591 }
592
593 self.remember_recycle_pool_limit(key, recycle_pool_limit);
594 let stable_id = self.state.next_stable_id;
595 self.state.next_stable_id = self.state.next_stable_id.next();
596 let pool = self.state.recycle_pools.entry(key).or_default();
597 pool.remember_prototype(shell.as_ref());
598 pool.push_warm(RecycledNode::from_shell(
599 stable_id,
600 shell,
601 RecycleOrigin::Warm,
602 ));
603 }
604
605 fn prune_stable_generations(&mut self) {
606 let retained_len = self.state.stable_index.live() + self.total_recycled_node_count();
607 if retained_len == self.state.stable_index.occupied() {
608 return;
609 }
610
611 let recycled: HashSet<NodeId> = self
612 .state
613 .recycle_pools
614 .values()
615 .flat_map(RecyclePool::nodes)
616 .map(RecycledNode::stable_id)
617 .collect();
618 self.state
619 .stable_index
620 .retain_retired(|stable_id| recycled.contains(&stable_id));
621 }
622
623 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
626 let mut output = String::new();
627 if let Some(root_id) = root {
628 self.dump_node(&mut output, root_id, 0);
629 } else {
630 output.push_str("(no root)\n");
631 }
632 output
633 }
634
635 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
636 let indent = " ".repeat(depth);
637 let Ok(node) = self.get_ref(id) else {
638 if let Some(physical_id) = self.resolve_node_index(id) {
639 output.push_str(&format!(
640 "{indent}[{id}] (missing physical node {physical_id})\n"
641 ));
642 } else {
643 output.push_str(&format!("{indent}[{id}] (missing)\n"));
644 }
645 return;
646 };
647 let type_name = std::any::type_name_of_val(node);
648 output.push_str(&format!("{indent}[{id}] {type_name}\n"));
649
650 let mut children = SmallVec::<[NodeId; 8]>::new();
651 node.collect_children_into(&mut children);
652 for child_id in children {
653 self.dump_node(output, child_id, depth + 1);
654 }
655 }
656
657 pub fn take_layout_dirt_roots(&mut self, out: &mut Vec<NodeId>) -> bool {
662 self.state.layout_dirt_roots.take_into(out)
663 }
664
665 pub fn has_layout_dirt_roots(&self) -> bool {
668 self.state.layout_dirt_roots.any()
669 }
670
671 #[inline]
672 pub(crate) fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
673 self.state.stable_index.slot(id)
674 }
675
676 fn contains_node_id(&self, id: NodeId) -> bool {
677 self.resolve_node_index(id).is_some() || self.high_id_node(id).is_ok()
678 }
679
680 fn insert_high_id_node(
681 &mut self,
682 stable_id: NodeId,
683 node: Box<dyn Node>,
684 origin: RecycleOrigin,
685 ) {
686 let slot = self.state.high_ids.entry(stable_id).or_default();
687 slot.node = Some(node);
688 slot.origin = origin;
689 }
690
691 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
692 if stable_id >= Self::HIGH_ID_THRESHOLD {
693 self.insert_high_id_node(stable_id, node, RecycleOrigin::Cold);
694 return;
695 }
696
697 let physical_id = if let Some(Reverse(free_physical_id)) = self.state.free_ids.pop() {
698 self.state.nodes[free_physical_id] = Some(node);
699 self.state.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
700 self.state.physical_warm_recycled_origins[free_physical_id] = RecycleOrigin::Cold;
701 free_physical_id
702 } else {
703 self.ensure_dense_node_storage_capacity();
704 let physical_id = self.state.nodes.len();
705 self.state.nodes.push(Some(node));
706 self.state
707 .physical_stable_ids
708 .push(Self::pack_stable_id(stable_id));
709 self.state
710 .physical_warm_recycled_origins
711 .push(RecycleOrigin::Cold);
712 physical_id
713 };
714
715 self.state.next_stable_id = self.state.next_stable_id.max(stable_id.next());
716 self.state.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
717 self.state.stable_index.set_slot(stable_id, physical_id);
718 }
719
720 #[inline]
723 pub fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
724 match self.resolve_node_index(id) {
725 Some(physical_id) => self
726 .state
727 .nodes
728 .get(physical_id)
729 .and_then(Option::as_deref)
730 .ok_or(NodeError::Missing { id }),
731 None => self.high_id_node(id),
732 }
733 }
734
735 #[inline]
738 pub(crate) fn parent_at(&self, place: MarkPlace) -> Option<NodeId> {
739 match place {
740 MarkPlace::Dense(slot) => self.state.nodes.get(slot)?.as_deref()?.parent(),
741 MarkPlace::Elsewhere(id) => self.high_id_node(id).ok()?.parent(),
742 }
743 }
744
745 pub(crate) fn high_id_node(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
746 self.state
747 .high_ids
748 .get(&id)
749 .and_then(|slot| slot.node.as_deref())
750 .ok_or(NodeError::Missing { id })
751 }
752
753 fn high_id_node_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
754 self.state
755 .high_ids
756 .get_mut(&id)
757 .and_then(|slot| slot.node.as_deref_mut())
758 .ok_or(NodeError::Missing { id })
759 }
760
761 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
762 Ok(self.get_ref(id)?.parent())
763 }
764
765 fn collect_owned_children(
766 &self,
767 node_id: NodeId,
768 out: &mut SmallVec<[NodeId; 8]>,
769 ) -> Result<(), NodeError> {
770 self.get_ref(node_id)?.collect_owned_children_into(out);
771 out.retain(|child_id| {
772 self.node_parent(*child_id)
773 .is_ok_and(|parent| parent == Some(node_id))
774 });
775 Ok(())
776 }
777
778 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
779 self.state.virtual_node_ids.remove(&node_id);
780 if let Some(slot) = self.state.high_ids.get_mut(&node_id)
781 && let Some(node) = slot.node.take()
782 {
783 let origin = std::mem::take(&mut slot.origin);
784 slot.generation = slot.generation.wrapping_add(1);
785 if let Some(key) = node.recycle_key() {
786 let recycle_pool_limit = node.recycle_pool_limit();
787 self.push_recycled_node(
788 key,
789 recycle_pool_limit,
790 RecycledNode::new(node_id, node, origin),
791 );
792 }
793 return Ok(());
794 }
795
796 let physical_id = self
797 .resolve_node_index(node_id)
798 .ok_or(NodeError::Missing { id: node_id })?;
799 if let Some(node) = self.state.nodes[physical_id].take()
800 && let Some(key) = node.recycle_key()
801 {
802 let recycle_pool_limit = node.recycle_pool_limit();
803 let origin = self
804 .state
805 .physical_warm_recycled_origins
806 .get_mut(physical_id)
807 .map(std::mem::take)
808 .unwrap_or_default();
809 self.push_recycled_node(
810 key,
811 recycle_pool_limit,
812 RecycledNode::new(node_id, node, origin),
813 );
814 }
815 self.state.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
816 self.state.stable_index.release(node_id);
817 self.state.free_ids.push(Reverse(physical_id));
818 Ok(())
819 }
820
821 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
822 self.get_ref(id)?;
823
824 let mut root_children = SmallVec::<[NodeId; 8]>::new();
825 self.collect_owned_children(id, &mut root_children)?;
826
827 let mut stack = Vec::new();
828 stack.push(RemovalFrame {
829 node_id: id,
830 children: root_children,
831 next_child: 0,
832 });
833 let mut max_depth = stack.len();
834
835 while let Some(frame) = stack.last_mut() {
836 if frame.next_child < frame.children.len() {
837 let child_id = frame.children[frame.next_child];
838 frame.next_child += 1;
839
840 if let Ok(child) = self.get_mut(child_id) {
841 child.on_removed_from_parent();
842 child.unmount();
843 }
844
845 let mut child_children = SmallVec::<[NodeId; 8]>::new();
846 self.collect_owned_children(child_id, &mut child_children)?;
847 stack.push(RemovalFrame {
848 node_id: child_id,
849 children: child_children,
850 next_child: 0,
851 });
852 max_depth = max_depth.max(stack.len());
853 continue;
854 }
855
856 let node_id = frame.node_id;
857 stack.pop();
858 self.remove_node_storage(node_id)?;
859 }
860
861 Ok(max_depth)
862 }
863
864 #[cfg(test)]
865 pub(crate) fn debug_remove_max_traversal_depth(
866 &mut self,
867 id: NodeId,
868 ) -> Result<usize, NodeError> {
869 self.remove_subtree_postorder(id)
870 }
871}