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