Skip to main content

cranpose_core/
lib.rs

1#![doc = r"Core runtime pieces for the Cranpose experiment."]
2#![deny(unsafe_code)]
3
4pub extern crate self as cranpose_core;
5
6mod callbacks;
7mod composer;
8pub mod composer_context;
9mod composition;
10mod composition_locals;
11mod debug_trace;
12mod emit;
13#[cfg(any(feature = "internal", test))]
14mod frame_clock;
15mod hooks;
16mod launched_effect;
17pub mod owned;
18pub mod platform;
19mod recompose;
20mod retention;
21pub mod runtime;
22mod slot;
23pub mod snapshot_double_index_heap;
24pub mod snapshot_id_set;
25pub mod snapshot_pinning;
26pub mod snapshot_state_observer;
27pub mod snapshot_v2;
28mod snapshot_weak_set;
29mod state;
30pub mod subcompose;
31
32#[cfg(feature = "internal")]
33#[doc(hidden)]
34pub mod internal {
35    pub use crate::frame_clock::{FrameCallbackRegistration, FrameClock};
36}
37pub use callbacks::{CallbackHolder, CallbackHolder1, ParamSlot, ParamState, ReturnSlot};
38pub use composer::{CapturedCompositionLocals, Composer, ValueSlotHandle};
39pub(crate) use composer::{ComposerCore, EmittedNode, ParentAttachMode, ParentFrame};
40pub use composition::{Composition, ROOT_RENDER_REPLAY_LIMIT};
41pub use composition_locals::{
42    compositionLocalOf, compositionLocalOfWithPolicy, staticCompositionLocalOf, CompositionLocal,
43    CompositionLocalProvider, ProvidedValue, StaticCompositionLocal,
44};
45pub(crate) use composition_locals::{LocalStateEntry, StaticLocalEntry};
46#[doc(hidden)]
47pub use debug_trace::{
48    debug_label_current_scope, debug_live_recompose_scope_count,
49    debug_recompose_scope_registry_stats, debug_scope_invalidation_sources, debug_scope_label,
50};
51pub use hooks::{
52    derivedStateOf, mutableStateList, mutableStateListOf, mutableStateMap, mutableStateMapOf,
53    mutableStateOf, ownedMutableStateOf, remember, rememberUpdatedState, try_mutableStateOf,
54    useState,
55};
56#[cfg(feature = "internal")]
57#[doc(hidden)]
58pub use hooks::{withFrameMillis, withFrameNanos};
59pub use launched_effect::{
60    __launched_effect_async_impl, __launched_effect_impl, CancelToken, LaunchedEffectScope,
61};
62pub use owned::Owned;
63pub use platform::{Clock, RuntimeScheduler};
64pub use retention::{RetentionBudget, RetentionEvictionPolicy, RetentionMode, RetentionPolicy};
65#[doc(hidden)]
66pub use runtime::{
67    current_runtime_handle, schedule_frame, schedule_node_update, DefaultScheduler, Runtime,
68    RuntimeHandle, StateId, TaskHandle,
69};
70pub use slot::{
71    SlotDebugAnchor, SlotDebugEntry, SlotDebugEntryKind, SlotDebugGroup, SlotDebugScope,
72    SlotDebugSnapshot, SlotRetentionDebugStats, SlotTable, SlotTableDebugStats,
73    SlotTableLocalDebugStats, SlotTableMutationDebugStats,
74};
75#[doc(hidden)]
76pub use snapshot_state_observer::SnapshotStateObserver;
77
78/// Runs the provided closure inside a mutable snapshot and applies the result.
79///
80/// Use this function when you need to update `MutableState` from outside the
81/// composition or layout phase, typically in event handlers or async tasks.
82///
83/// # Why is this needed?
84/// Cranpose uses a snapshot system (MVCC) to isolate state changes. Modifications
85/// made to `MutableState` are only visible to the current thread's active snapshot.
86/// To make changes visible to the rest of the system (and trigger recomposition),
87/// they must be "applied" by committing the snapshot. This helper handles that
88/// lifecycle for you.
89///
90/// # Example
91///
92/// ```ignore
93/// // Inside a button click handler
94/// run_in_mutable_snapshot(|| {
95///     count.set(count.value() + 1);
96/// });
97/// ```
98///
99/// # Important
100/// ALL UI event handlers (keyboard, mouse, touch, animations) that modify state
101/// MUST use this function or [`dispatch_ui_event`].
102pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
103    let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
104
105    let _applied_guard = AppliedSnapshotFlagGuard::enter();
106    let value = snapshot.enter(block);
107
108    match snapshot.apply() {
109        snapshot_v2::SnapshotApplyResult::Success => Ok(value),
110        snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
111    }
112}
113
114struct AppliedSnapshotFlagGuard {
115    previous: bool,
116}
117
118impl AppliedSnapshotFlagGuard {
119    fn enter() -> Self {
120        let previous = IN_APPLIED_SNAPSHOT.with(|flag| {
121            let previous = flag.get();
122            flag.set(true);
123            previous
124        });
125        Self { previous }
126    }
127}
128
129impl Drop for AppliedSnapshotFlagGuard {
130    fn drop(&mut self) {
131        IN_APPLIED_SNAPSHOT.with(|flag| flag.set(self.previous));
132    }
133}
134
135/// Dispatches a UI event in a proper snapshot context.
136///
137/// This is a convenience wrapper around [`run_in_mutable_snapshot`] that returns
138/// `Option<T>` instead of `Result<T, &str>`.
139///
140/// # Example
141/// ```ignore
142/// // In a keyboard event handler:
143/// dispatch_ui_event(|| {
144///     text_field_state.edit(|buffer| {
145///         buffer.insert("a");
146///     });
147/// });
148/// ```
149pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
150    run_in_mutable_snapshot(block).ok()
151}
152
153// ─── Event Handler Context Tracking ─────────────────────────────────────────
154//
155// These thread-locals track whether code is running in an event handler context
156// and whether it's properly wrapped in run_in_mutable_snapshot. This allows
157// debug-mode warnings when state is modified without proper snapshot handling.
158
159thread_local! {
160    /// Tracks if we're in a UI event handler context (keyboard, mouse, etc.)
161    pub(crate) static IN_EVENT_HANDLER: Cell<bool> = const { Cell::new(false) };
162    /// Tracks if we're in a properly-applied mutable snapshot
163    pub(crate) static IN_APPLIED_SNAPSHOT: Cell<bool> = const { Cell::new(false) };
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
167pub struct CompositionPassDebugStats {
168    pub commands_len: usize,
169    pub commands_cap: usize,
170    pub command_payload_len_bytes: usize,
171    pub command_payload_cap_bytes: usize,
172    pub sync_children_len: usize,
173    pub sync_children_cap: usize,
174    pub sync_child_ids_len: usize,
175    pub sync_child_ids_cap: usize,
176    pub side_effects_len: usize,
177    pub side_effects_cap: usize,
178}
179
180#[must_use]
181pub struct EventHandlerScopeGuard {
182    previous: bool,
183}
184
185impl Drop for EventHandlerScopeGuard {
186    fn drop(&mut self) {
187        IN_EVENT_HANDLER.with(|flag| flag.set(self.previous));
188    }
189}
190
191pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
192    let previous = IN_EVENT_HANDLER.with(|flag| {
193        let previous = flag.get();
194        flag.set(true);
195        previous
196    });
197    EventHandlerScopeGuard { previous }
198}
199
200/// Returns true if currently in an event handler context.
201pub fn in_event_handler() -> bool {
202    IN_EVENT_HANDLER.with(|c| c.get())
203}
204
205/// Returns true if currently in an applied snapshot context.
206pub fn in_applied_snapshot() -> bool {
207    IN_APPLIED_SNAPSHOT.with(|c| c.get())
208}
209
210#[cfg(test)]
211pub use runtime::{TestRuntime, TestScheduler};
212
213use crate::collections::map::{HashMap, HashSet};
214use smallvec::SmallVec;
215use std::any::{Any, TypeId};
216use std::cell::{Cell, Ref, RefCell, RefMut};
217use std::cmp::Reverse;
218use std::collections::BinaryHeap;
219use std::hash::{Hash, Hasher};
220use std::ops::{Deref, DerefMut};
221use std::rc::{Rc, Weak};
222
223pub type Key = u64;
224pub type NodeId = usize;
225
226#[cfg(any(test, debug_assertions))]
227#[derive(Clone, Debug, PartialEq, Eq)]
228struct LocationKeyDebugInfo {
229    file: String,
230    line: u32,
231    column: u32,
232}
233
234#[cfg(any(test, debug_assertions))]
235thread_local! {
236    static LOCATION_KEY_REGISTRY: RefCell<HashMap<Key, LocationKeyDebugInfo>> =
237        RefCell::new(HashMap::default());
238    static LOCATION_KEY_COLLISION_COUNT: Cell<usize> = const { Cell::new(0) };
239}
240
241#[cfg(any(test, debug_assertions))]
242fn register_location_key_debug_info(key: Key, file: &str, line: u32, column: u32) {
243    let info = LocationKeyDebugInfo {
244        file: file.to_owned(),
245        line,
246        column,
247    };
248    let collision = LOCATION_KEY_REGISTRY.with(|registry| {
249        let mut registry = registry.borrow_mut();
250        match registry.entry(key) {
251            std::collections::hash_map::Entry::Vacant(entry) => {
252                entry.insert(info);
253                None
254            }
255            std::collections::hash_map::Entry::Occupied(entry) => {
256                let existing = entry.get();
257                (existing != &info).then(|| (existing.clone(), info))
258            }
259        }
260    });
261    if let Some((existing, incoming)) = collision {
262        LOCATION_KEY_COLLISION_COUNT.with(|count| {
263            count.set(count.get().saturating_add(1));
264        });
265        log::error!("location key collision: key={key} first={existing:?} second={incoming:?}");
266    }
267}
268
269#[cfg(all(debug_assertions, not(test)))]
270fn location_key_diagnostics_enabled() -> bool {
271    std::env::var_os("CRANPOSE_LOCATION_KEY_DIAGNOSTICS").is_some()
272}
273
274#[cfg(test)]
275pub(crate) fn register_location_key_debug_info_for_test(
276    key: Key,
277    file: &str,
278    line: u32,
279    column: u32,
280) {
281    register_location_key_debug_info(key, file, line, column);
282}
283
284#[cfg(test)]
285pub(crate) fn location_key_debug_collision_count_for_test() -> usize {
286    LOCATION_KEY_COLLISION_COUNT.with(Cell::get)
287}
288
289#[cfg(test)]
290pub(crate) fn location_key_debug_info_for_test(key: Key) -> Option<LocationKeyDebugInfo> {
291    LOCATION_KEY_REGISTRY.with(|registry| registry.borrow().get(&key).cloned())
292}
293
294#[cfg(test)]
295pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
296    true
297}
298
299#[cfg(all(debug_assertions, not(test)))]
300pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
301    std::env::var_os("CRANPOSE_VALIDATE_SLOTS").is_some()
302}
303
304fn source_location_key(file: &str, line: u32, column: u32) -> Key {
305    let mut hash = 0xcbf2_9ce4_8422_2325u64;
306    hash = fnv1a_location_key_bytes(hash, file.as_bytes());
307    hash = fnv1a_location_key_bytes(hash, &[0xff]);
308    hash = fnv1a_location_key_bytes(hash, &line.to_le_bytes());
309    hash = fnv1a_location_key_bytes(hash, &[0xfe]);
310    hash = fnv1a_location_key_bytes(hash, &column.to_le_bytes());
311    avalanche_location_key(hash)
312}
313
314fn fnv1a_location_key_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
315    for byte in bytes {
316        hash ^= u64::from(*byte);
317        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
318    }
319    hash
320}
321
322fn avalanche_location_key(mut value: u64) -> u64 {
323    value ^= value >> 33;
324    value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
325    value ^= value >> 33;
326    value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
327    value ^ (value >> 33)
328}
329
330pub fn location_key(file: &str, line: u32, column: u32) -> Key {
331    let key = source_location_key(file, line, column);
332    #[cfg(test)]
333    register_location_key_debug_info(key, file, line, column);
334    #[cfg(all(debug_assertions, not(test)))]
335    if location_key_diagnostics_enabled() {
336        register_location_key_debug_info(key, file, line, column);
337    }
338    key
339}
340
341/// Stable identifier for a slot in the slot table.
342///
343/// Anchors provide positional stability: they maintain their identity even when
344/// the slot table is reorganized (e.g., during conditional rendering or group moves).
345/// This prevents effect states from being prematurely removed during recomposition.
346#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
347pub struct AnchorId {
348    id: u32,
349    generation: u32,
350}
351
352impl AnchorId {
353    /// Invalid anchor that represents no anchor.
354    pub(crate) const INVALID: AnchorId = AnchorId {
355        id: 0,
356        generation: 0,
357    };
358
359    pub(crate) fn new(id: usize) -> Self {
360        Self {
361            id: crate::slot::checked_usize_to_u32(id, "anchor id"),
362            generation: 1,
363        }
364    }
365
366    /// Check if this anchor is valid (non-zero).
367    pub fn is_valid(&self) -> bool {
368        self.id != 0
369    }
370}
371
372pub(crate) type ScopeId = usize;
373pub(crate) type FrameCallbackId = u64;
374type LocalStackSnapshot = Rc<Vec<composer::LocalContext>>;
375
376#[derive(Clone)]
377pub(crate) struct LocalKey(Rc<()>);
378
379impl LocalKey {
380    fn new() -> Self {
381        Self(Rc::new(()))
382    }
383}
384
385impl std::fmt::Debug for LocalKey {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        f.debug_tuple("LocalKey")
388            .field(&(Rc::as_ptr(&self.0) as usize))
389            .finish()
390    }
391}
392
393impl std::fmt::Display for LocalKey {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        write!(f, "{:p}", Rc::as_ptr(&self.0))
396    }
397}
398
399impl PartialEq for LocalKey {
400    fn eq(&self, other: &Self) -> bool {
401        Rc::ptr_eq(&self.0, &other.0)
402    }
403}
404
405impl Eq for LocalKey {}
406
407impl Hash for LocalKey {
408    fn hash<H: Hasher>(&self, state: &mut H) {
409        Rc::as_ptr(&self.0).hash(state);
410    }
411}
412
413thread_local! {
414    static EMPTY_LOCAL_STACK: LocalStackSnapshot = Rc::new(Vec::new());
415    #[cfg(debug_assertions)]
416    static DEBUG_SCOPE_LABELS: RefCell<HashMap<usize, &'static str>> = RefCell::new(HashMap::default());
417    #[cfg(debug_assertions)]
418    static DEBUG_SCOPE_INVALIDATION_SOURCES: RefCell<HashMap<usize, HashSet<String>>> =
419        RefCell::new(HashMap::default());
420    #[cfg(all(test, debug_assertions))]
421    static DEBUG_SCOPE_TRACKING_OVERRIDE: Cell<Option<bool>> = const { Cell::new(None) };
422}
423
424fn empty_local_stack() -> LocalStackSnapshot {
425    EMPTY_LOCAL_STACK.with(Rc::clone)
426}
427
428enum RecomposeCallback {
429    Static(fn(&Composer)),
430    Dynamic(Box<dyn FnMut(&Composer) + 'static>),
431}
432
433pub(crate) struct RecomposeScopeInner {
434    runtime: RuntimeHandle,
435    invalid: Cell<bool>,
436    enqueued: Cell<bool>,
437    active: Cell<bool>,
438    composed_once: Cell<bool>,
439    pending_recompose: Cell<bool>,
440    force_reuse: Cell<bool>,
441    force_recompose: Cell<bool>,
442    retention_mode: Cell<RetentionMode>,
443    parent_hint: Cell<Option<NodeId>>,
444    recompose: RefCell<Option<RecomposeCallback>>,
445    parent_scope: RefCell<Option<Weak<RecomposeScopeInner>>>,
446    local_stack: RefCell<LocalStackSnapshot>,
447    slots_storage_key: Cell<usize>,
448    slots_runtime_state: RefCell<Option<std::rc::Weak<crate::composer::ComposerRuntimeState>>>,
449    state_subscriptions: RefCell<HashSet<StateId>>,
450    invalidation_sources: RefCell<Option<HashSet<StateId>>>,
451}
452
453impl RecomposeScopeInner {
454    fn new(runtime: RuntimeHandle) -> Self {
455        runtime.increment_live_recompose_scope_count();
456        Self {
457            runtime,
458            invalid: Cell::new(false),
459            enqueued: Cell::new(false),
460            active: Cell::new(true),
461            composed_once: Cell::new(false),
462            pending_recompose: Cell::new(false),
463            force_reuse: Cell::new(false),
464            force_recompose: Cell::new(false),
465            retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
466            parent_hint: Cell::new(None),
467            recompose: RefCell::new(None),
468            parent_scope: RefCell::new(None),
469            local_stack: RefCell::new(empty_local_stack()),
470            slots_storage_key: Cell::new(0),
471            slots_runtime_state: RefCell::new(None),
472            state_subscriptions: RefCell::new(HashSet::default()),
473            invalidation_sources: RefCell::new(Some(HashSet::default())),
474        }
475    }
476
477    fn id(&self) -> ScopeId {
478        std::ptr::from_ref(self).addr()
479    }
480}
481
482impl Drop for RecomposeScopeInner {
483    fn drop(&mut self) {
484        let id = self.id();
485        self.runtime.decrement_live_recompose_scope_count();
486        let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
487        for state_id in subscriptions {
488            self.runtime.unregister_state_scope(state_id, id);
489        }
490        #[cfg(debug_assertions)]
491        {
492            let _ = DEBUG_SCOPE_LABELS.try_with(|labels| {
493                labels.borrow_mut().remove(&id);
494            });
495            let _ = DEBUG_SCOPE_INVALIDATION_SOURCES.try_with(|sources| {
496                sources.borrow_mut().remove(&id);
497            });
498        }
499        if self.enqueued.replace(false) {
500            self.runtime.mark_scope_recomposed(id);
501        }
502    }
503}
504
505#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
506pub struct RecomposeScopeRegistryDebugStats {
507    pub len: usize,
508    pub capacity: usize,
509}
510
511#[derive(Clone)]
512pub struct RecomposeScope {
513    inner: Rc<RecomposeScopeInner>,
514}
515
516impl PartialEq for RecomposeScope {
517    fn eq(&self, other: &Self) -> bool {
518        Rc::ptr_eq(&self.inner, &other.inner)
519    }
520}
521
522impl Eq for RecomposeScope {}
523
524impl Hash for RecomposeScope {
525    fn hash<H: Hasher>(&self, state: &mut H) {
526        self.id().hash(state);
527    }
528}
529
530impl RecomposeScope {
531    fn new(runtime: RuntimeHandle) -> Self {
532        Self {
533            inner: Rc::new(RecomposeScopeInner::new(runtime)),
534        }
535    }
536
537    pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
538        Rc::downgrade(&self.inner)
539    }
540
541    pub fn id(&self) -> ScopeId {
542        self.inner.id()
543    }
544
545    pub fn is_invalid(&self) -> bool {
546        self.inner.invalid.get()
547    }
548
549    pub fn is_active(&self) -> bool {
550        self.inner.active.get()
551    }
552
553    fn record_state_subscription(&self, state_id: StateId) {
554        self.inner.state_subscriptions.borrow_mut().insert(state_id);
555    }
556
557    fn record_unknown_invalidation_source(&self) {
558        *self.inner.invalidation_sources.borrow_mut() = None;
559    }
560
561    fn record_state_invalidation_source(&self, state_id: StateId) {
562        let mut sources = self.inner.invalidation_sources.borrow_mut();
563        if let Some(source_set) = sources.as_mut() {
564            source_set.insert(state_id);
565        }
566    }
567
568    fn enqueue_invalidation(&self) {
569        self.inner.invalid.set(true);
570        if !self.inner.active.get() {
571            return;
572        }
573        if !self.inner.enqueued.replace(true) {
574            self.inner
575                .runtime
576                .register_invalid_scope(self.id(), self.downgrade());
577        }
578    }
579
580    fn invalidate(&self) {
581        self.record_unknown_invalidation_source();
582        self.enqueue_invalidation();
583    }
584
585    pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
586        self.record_state_invalidation_source(state_id);
587        self.enqueue_invalidation();
588    }
589
590    fn mark_recomposed(&self) {
591        self.inner.invalid.set(false);
592        self.inner.force_reuse.set(false);
593        self.inner.force_recompose.set(false);
594        self.inner
595            .invalidation_sources
596            .borrow_mut()
597            .replace(HashSet::default());
598        if self.inner.enqueued.replace(false) {
599            self.inner.runtime.mark_scope_recomposed(self.id());
600        }
601        let pending = self.inner.pending_recompose.replace(false);
602        if pending {
603            if self.inner.active.get() {
604                self.invalidate();
605            } else {
606                self.inner.invalid.set(true);
607            }
608        }
609    }
610
611    fn set_recompose(&self, callback: Box<dyn FnMut(&Composer) + 'static>) {
612        *self.inner.recompose.borrow_mut() = Some(RecomposeCallback::Dynamic(callback));
613    }
614
615    fn set_recompose_fn(&self, callback: fn(&Composer)) {
616        *self.inner.recompose.borrow_mut() = Some(RecomposeCallback::Static(callback));
617    }
618
619    /// Returns true if a callback was found and executed, false if no callback was registered.
620    fn run_recompose(&self, composer: &Composer) -> bool {
621        let callback = self.inner.recompose.borrow_mut().take();
622        if let Some(callback) = callback {
623            match callback {
624                RecomposeCallback::Static(callback) => callback(composer),
625                RecomposeCallback::Dynamic(mut callback) => callback(composer),
626            }
627            true
628        } else {
629            false
630        }
631    }
632
633    fn has_recompose_callback(&self) -> bool {
634        self.inner.recompose.borrow().is_some()
635    }
636
637    fn snapshot_locals(&self, stack: LocalStackSnapshot) {
638        *self.inner.local_stack.borrow_mut() = stack;
639    }
640
641    fn local_stack(&self) -> LocalStackSnapshot {
642        self.inner.local_stack.borrow().clone()
643    }
644
645    fn set_parent_hint(&self, parent: Option<NodeId>) {
646        self.inner.parent_hint.set(parent);
647    }
648
649    fn set_parent_scope(&self, parent: Option<RecomposeScope>) {
650        *self.inner.parent_scope.borrow_mut() = parent.map(|scope| scope.downgrade());
651    }
652
653    fn parent_scope(&self) -> Option<RecomposeScope> {
654        self.inner
655            .parent_scope
656            .borrow()
657            .as_ref()
658            .and_then(Weak::upgrade)
659            .map(|inner| RecomposeScope { inner })
660    }
661
662    fn callback_promotion_target(&self) -> Option<RecomposeScope> {
663        let mut current = self.parent_scope();
664        while let Some(scope) = current {
665            if scope.has_recompose_callback() {
666                return Some(scope);
667            }
668            current = scope.parent_scope();
669        }
670        None
671    }
672
673    fn parent_hint(&self) -> Option<NodeId> {
674        self.inner.parent_hint.get()
675    }
676
677    fn set_slots_host(&self, host: &Rc<SlotsHost>) {
678        self.inner.slots_storage_key.set(host.storage_key());
679        *self.inner.slots_runtime_state.borrow_mut() =
680            host.runtime_state().map(|state| Rc::downgrade(&state));
681    }
682
683    pub(crate) fn slots_storage_key(&self) -> Option<usize> {
684        let key = self.inner.slots_storage_key.get();
685        (key != 0).then_some(key)
686    }
687
688    pub(crate) fn slots_runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
689        self.inner
690            .slots_runtime_state
691            .borrow()
692            .as_ref()
693            .and_then(std::rc::Weak::upgrade)
694    }
695
696    pub fn deactivate(&self) {
697        if !self.inner.active.replace(false) {
698            return;
699        }
700        if self.inner.enqueued.replace(false) {
701            self.inner.runtime.mark_scope_recomposed(self.id());
702        }
703    }
704
705    pub fn reactivate(&self) {
706        if self.inner.active.replace(true) {
707            return;
708        }
709        if self.inner.invalid.get() && !self.inner.enqueued.replace(true) {
710            self.inner
711                .runtime
712                .register_invalid_scope(self.id(), self.downgrade());
713        }
714    }
715
716    pub fn force_reuse(&self) {
717        self.inner.force_reuse.set(true);
718        self.inner.force_recompose.set(false);
719        self.inner.pending_recompose.set(true);
720    }
721
722    pub fn force_recompose(&self) {
723        self.inner.force_recompose.set(true);
724        self.inner.force_reuse.set(false);
725        self.inner.pending_recompose.set(false);
726    }
727
728    pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
729        self.inner.retention_mode.set(mode);
730    }
731
732    pub(crate) fn retention_mode(&self) -> RetentionMode {
733        self.inner.retention_mode.get()
734    }
735
736    pub fn should_recompose(&self) -> bool {
737        if self.inner.force_recompose.replace(false) {
738            self.inner.force_reuse.set(false);
739            return true;
740        }
741        if self.inner.force_reuse.replace(false) {
742            return false;
743        }
744        self.is_invalid()
745    }
746
747    pub fn has_composed_once(&self) -> bool {
748        self.inner.composed_once.get()
749    }
750
751    fn mark_composed_once(&self) {
752        self.inner.composed_once.set(true);
753    }
754
755    fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
756        let sources = self.inner.invalidation_sources.borrow();
757        let sources = sources.as_ref()?;
758        if sources.is_empty() {
759            return None;
760        }
761        Some(
762            sources
763                .iter()
764                .all(|source| allowed_sources.contains(source)),
765        )
766    }
767
768    fn has_unknown_invalidation_source(&self) -> bool {
769        self.inner.invalidation_sources.borrow().is_none()
770    }
771}
772
773#[cfg(test)]
774impl RecomposeScope {
775    pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
776        Self::new(runtime)
777    }
778}
779
780#[derive(Debug, Clone, Copy, Default)]
781pub struct RecomposeOptions {
782    pub force_reuse: bool,
783    pub force_recompose: bool,
784    pub retention: RetentionMode,
785}
786
787#[derive(Debug, Clone, PartialEq, Eq)]
788pub enum NodeError {
789    Missing {
790        id: NodeId,
791    },
792    TypeMismatch {
793        id: NodeId,
794        expected: &'static str,
795    },
796    MissingContext {
797        id: NodeId,
798        reason: &'static str,
799    },
800    AlreadyExists {
801        id: NodeId,
802    },
803    MalformedCommandPayload {
804        tag: &'static str,
805    },
806    SlotHostUnavailable {
807        operation: &'static str,
808        reason: &'static str,
809    },
810    RecompositionLimitExceeded {
811        operation: &'static str,
812        limit: usize,
813    },
814}
815
816impl std::fmt::Display for NodeError {
817    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
818        match self {
819            NodeError::Missing { id } => write!(f, "node {id} missing"),
820            NodeError::TypeMismatch { id, expected } => {
821                write!(f, "node {id} type mismatch; expected {expected}")
822            }
823            NodeError::MissingContext { id, reason } => {
824                write!(f, "missing context for node {id}: {reason}")
825            }
826            NodeError::AlreadyExists { id } => {
827                write!(f, "node {id} already exists")
828            }
829            NodeError::MalformedCommandPayload { tag } => {
830                write!(f, "command queue missing or invalid {tag} payload")
831            }
832            NodeError::SlotHostUnavailable { operation, reason } => {
833                write!(f, "{operation} cannot access slot host: {reason}")
834            }
835            NodeError::RecompositionLimitExceeded { operation, limit } => {
836                write!(
837                    f,
838                    "{operation} exceeded {limit} iterations while reconciling composition"
839                )
840            }
841        }
842    }
843}
844
845impl std::error::Error for NodeError {}
846
847pub use subcompose::{
848    ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
849};
850
851#[derive(Copy, Clone, Debug, PartialEq, Eq)]
852pub enum Phase {
853    Compose,
854    Measure,
855    Layout,
856}
857
858pub use composer_context::with_composer as with_current_composer;
859
860#[allow(non_snake_case)]
861pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
862    composer_context::with_composer(f)
863}
864
865fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
866    composer_context::try_with_composer(f)
867}
868
869#[doc(hidden)]
870pub fn current_recompose_scope_invalidated_only_by(
871    allowed_sources: impl IntoIterator<Item = StateId>,
872) -> Option<bool> {
873    with_current_composer_opt(|composer| {
874        let allowed_sources = allowed_sources.into_iter().collect();
875        let mut scope = composer.current_recranpose_scope();
876        let mut saw_unknown_source = false;
877        while let Some(current) = scope {
878            if current.has_unknown_invalidation_source() {
879                saw_unknown_source = true;
880                scope = current.parent_scope();
881                continue;
882            }
883            if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
884                return Some(matches);
885            }
886            scope = current.parent_scope();
887        }
888        saw_unknown_source.then_some(false)
889    })
890    .flatten()
891}
892
893#[track_caller]
894pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
895    let seed = explicit_group_key_seed(key, std::panic::Location::caller());
896    with_current_composer(|composer| composer.with_group_seed(seed, |_| content()));
897}
898
899#[allow(non_snake_case)]
900pub fn withKey<K: Hash>(key: &K, content: impl FnOnce()) {
901    with_key(key, content)
902}
903
904#[derive(Default)]
905struct DisposableEffectState {
906    key: Option<Key>,
907    cleanup: Option<Box<dyn FnOnce()>>,
908}
909
910impl DisposableEffectState {
911    fn should_run(&self, key: Key) -> bool {
912        match self.key {
913            Some(current) => current != key,
914            None => true,
915        }
916    }
917
918    fn set_key(&mut self, key: Key) {
919        self.key = Some(key);
920    }
921
922    fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
923        self.cleanup = cleanup;
924    }
925
926    fn run_cleanup(&mut self) {
927        if let Some(cleanup) = self.cleanup.take() {
928            cleanup();
929        }
930    }
931}
932
933impl Drop for DisposableEffectState {
934    fn drop(&mut self) {
935        self.run_cleanup();
936    }
937}
938
939#[derive(Clone, Copy, Debug, Default)]
940pub struct DisposableEffectScope;
941
942#[derive(Default)]
943pub struct DisposableEffectResult {
944    cleanup: Option<Box<dyn FnOnce()>>,
945}
946
947impl DisposableEffectScope {
948    pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
949        DisposableEffectResult::new(cleanup)
950    }
951}
952
953impl DisposableEffectResult {
954    pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
955        Self {
956            cleanup: Some(Box::new(cleanup)),
957        }
958    }
959
960    fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
961        self.cleanup
962    }
963}
964
965#[allow(non_snake_case)]
966pub fn SideEffect(effect: impl FnOnce() + 'static) {
967    with_current_composer(|composer| composer.register_side_effect(effect));
968}
969
970pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
971where
972    K: Hash,
973    F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
974{
975    // Create a group using the caller's location to ensure each DisposableEffect
976    // gets its own slot table entry, even in conditional branches
977    with_current_composer(|composer| {
978        composer.with_group(group_key, |composer| {
979            let key_hash = hash_key(&keys);
980            let state = composer.remember_effect(DisposableEffectState::default);
981            if state.with(|state| state.should_run(key_hash)) {
982                state.update(|state| {
983                    state.run_cleanup();
984                    state.set_key(key_hash);
985                });
986                let state_for_effect = state.clone();
987                let mut effect_opt = Some(effect);
988                composer.register_side_effect(move || {
989                    if let Some(effect) = effect_opt.take() {
990                        let result = effect(DisposableEffectScope);
991                        state_for_effect.update(|state| state.set_cleanup(result.into_cleanup()));
992                    }
993                });
994            }
995        });
996    });
997}
998
999#[macro_export]
1000macro_rules! DisposableEffect {
1001    ($keys:expr, $effect:expr) => {
1002        $crate::__disposable_effect_impl(
1003            $crate::location_key(file!(), line!(), column!()),
1004            $keys,
1005            $effect,
1006        )
1007    };
1008}
1009
1010#[macro_export]
1011macro_rules! clone_captures {
1012    ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1013        $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1014        $body
1015    }};
1016    (@clone $alias:ident = $value:expr) => {
1017        ($value).clone()
1018    };
1019    (@clone $alias:ident) => {
1020        $alias.clone()
1021    };
1022}
1023
1024pub fn with_node_mut<N: Node + 'static, R>(
1025    id: NodeId,
1026    f: impl FnOnce(&mut N) -> R,
1027) -> Result<R, NodeError> {
1028    with_current_composer(|composer| composer.with_node_mut(id, f))
1029}
1030
1031pub fn push_parent(id: NodeId) {
1032    with_current_composer(|composer| composer.push_parent(id));
1033}
1034
1035pub fn pop_parent() {
1036    with_current_composer(|composer| composer.pop_parent());
1037}
1038
1039// ═══════════════════════════════════════════════════════════════════════════
1040// Public slot storage helper types
1041// ═══════════════════════════════════════════════════════════════════════════
1042
1043pub trait Node: Any {
1044    fn mount(&mut self) {}
1045    fn update(&mut self) {}
1046    fn unmount(&mut self) {}
1047    fn insert_child(&mut self, _child: NodeId) {}
1048    fn remove_child(&mut self, _child: NodeId) {}
1049    fn move_child(&mut self, _from: usize, _to: usize) {}
1050    fn update_children(&mut self, _children: &[NodeId]) {}
1051    fn children(&self) -> Vec<NodeId> {
1052        Vec::new()
1053    }
1054    /// Copies child IDs into the provided scratch buffer without allocating a
1055    /// fresh container for every traversal.
1056    fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1057        out.clear();
1058        out.extend(self.children());
1059    }
1060    /// Called after the node is created to record its own ID.
1061    /// Useful for nodes that need to store their ID for later operations.
1062    fn set_node_id(&mut self, _id: NodeId) {}
1063    /// Called when this node is attached to a parent.
1064    /// Nodes with parent tracking should set their parent reference here.
1065    fn on_attached_to_parent(&mut self, _parent: NodeId) {}
1066    /// Called when this node is removed from its parent.
1067    /// Nodes with parent tracking should clear their parent reference here.
1068    fn on_removed_from_parent(&mut self) {}
1069    /// Get this node's parent ID (for nodes that track parents).
1070    /// Returns None if node has no parent or doesn't track parents.
1071    fn parent(&self) -> Option<NodeId> {
1072        None
1073    }
1074    /// Mark this node as needing layout (for nodes with dirty flags).
1075    /// Called during bubbling to propagate dirtiness up the tree.
1076    fn mark_needs_layout(&self) {}
1077    /// Check if this node needs layout (for nodes with dirty flags).
1078    fn needs_layout(&self) -> bool {
1079        false
1080    }
1081    /// Mark this node as needing measure (size may have changed).
1082    /// Called during bubbling when children are added/removed.
1083    fn mark_needs_measure(&self) {}
1084    /// Check if this node needs measure (for nodes with dirty flags).
1085    fn needs_measure(&self) -> bool {
1086        false
1087    }
1088    /// Mark this node as needing semantics recomputation.
1089    fn mark_needs_semantics(&self) {}
1090    /// Check if this node needs semantics recomputation.
1091    fn needs_semantics(&self) -> bool {
1092        false
1093    }
1094    /// Set parent reference for dirty flag bubbling ONLY.
1095    /// This is a minimal version of on_attached_to_parent that doesn't trigger
1096    /// registry updates or other side effects. Used during measurement when we
1097    /// need to establish parent connections for bubble_measure_dirty without
1098    /// causing the full attachment lifecycle.
1099    ///
1100    /// Default implementation uses the normal parent-attachment hook.
1101    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1102        self.on_attached_to_parent(parent);
1103    }
1104
1105    /// Returns a recycle pool key when this node supports shell reuse.
1106    fn recycle_key(&self) -> Option<TypeId> {
1107        None
1108    }
1109
1110    /// Bounds how many recyclable shells of this node type should be retained.
1111    fn recycle_pool_limit(&self) -> Option<usize> {
1112        None
1113    }
1114
1115    /// Clears live attachments before the node shell enters a recycle pool.
1116    fn prepare_for_recycle(&mut self) {}
1117
1118    /// Optionally provides a compact replacement box for this recycled shell.
1119    ///
1120    /// Returning `Some` lets nodes move pooled survivors onto fresh compact
1121    /// storage so the recycle pool does not pin large spike-era allocations.
1122    fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
1123        None
1124    }
1125
1126    /// Optionally moves a live node onto a fresh box during applier compaction.
1127    ///
1128    /// This is used after large-majority teardowns where a small surviving live
1129    /// tree can otherwise pin allocator pages from a much larger spike-era node
1130    /// population. Implementations must preserve the node's live state.
1131    fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
1132        None
1133    }
1134
1135    /// Returns the node-owned heap retained beyond the node's own box allocation.
1136    fn debug_heap_bytes(&self) -> usize {
1137        0
1138    }
1139}
1140
1141/// Unified API for bubbling layout dirty flags from a node to the root (Applier context).
1142///
1143/// This is the canonical function for dirty bubbling during the apply phase (structural changes).
1144/// Call this after mutations like insert/remove/move that happen during apply.
1145///
1146/// # Behavior
1147/// 1. Marks the starting node as needing layout
1148/// 2. Walks up the parent chain, marking each ancestor
1149/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
1150/// 4. Stops at the root (node with no parent)
1151///
1152/// # Performance
1153/// This function is O(height) in the worst case, but typically O(1) due to early exit
1154/// when encountering an already-dirty ancestor.
1155///
1156/// # Usage
1157/// - Call from composer mutations (insert/remove/move) during apply phase
1158/// - Call from applier-level operations that modify the tree structure
1159pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1160    bubble_layout_dirty_applier(applier, node_id);
1161}
1162
1163/// Unified API for bubbling measure dirty flags from a node to the root (Applier context).
1164///
1165/// Call this when a node's size may have changed (children added/removed, modifier changed).
1166/// This ensures that measure_layout will increment the cache epoch and re-measure the subtree.
1167///
1168/// # Behavior
1169/// 1. Marks the starting node as needing measure
1170/// 2. Walks up the parent chain, marking each ancestor
1171/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
1172/// 4. Stops at the root (node with no parent)
1173pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1174    bubble_measure_dirty_applier(applier, node_id);
1175}
1176
1177/// Unified API for bubbling semantics dirty flags from a node to the root (Applier context).
1178///
1179/// This mirrors [`bubble_layout_dirty`] but toggles semantics-specific dirty
1180/// flags instead of layout ones, allowing semantics updates to propagate during
1181/// the apply phase without forcing layout work.
1182pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1183    bubble_semantics_dirty_applier(applier, node_id);
1184}
1185
1186/// Schedules semantics bubbling for a node using the active composer if present.
1187///
1188/// This defers the work to the apply phase where we can safely mutate the
1189/// applier tree without re-entrantly borrowing the composer during composition.
1190pub fn queue_semantics_invalidation(node_id: NodeId) {
1191    let _ = composer_context::try_with_composer(|composer| {
1192        composer.enqueue_semantics_invalidation(node_id);
1193    });
1194}
1195
1196/// Unified API for bubbling layout dirty flags from a node to the root (Composer context).
1197///
1198/// This is the canonical function for dirty bubbling during composition (property changes).
1199/// Call this after property changes that happen during composition via with_node_mut.
1200///
1201/// # Behavior
1202/// 1. Marks the starting node as needing layout
1203/// 2. Walks up the parent chain, marking each ancestor
1204/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
1205/// 4. Stops at the root (node with no parent)
1206///
1207/// # Performance
1208/// This function is O(height) in the worst case, but typically O(1) due to early exit
1209/// when encountering an already-dirty ancestor.
1210///
1211/// # Type Requirements
1212/// The node type N must implement Node (which includes mark_needs_layout, parent, etc.).
1213/// Typically this will be LayoutNode or similar layout-aware node types.
1214///
1215/// # Usage
1216/// - Call from property setters during composition (e.g., set_modifier, set_measure_policy)
1217/// - Call from widget composition when layout-affecting state changes
1218pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1219    bubble_layout_dirty_composer::<N>(node_id);
1220}
1221
1222/// Unified API for bubbling measure dirty flags from a node to the root during composition.
1223///
1224/// This queues a dirty-bubble command on the active composer so measure invalidation
1225/// runs during the apply phase, avoiding re-entrant applier borrows while widgets are
1226/// mutating nodes via `with_node_mut`.
1227pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
1228    with_current_composer(|composer| {
1229        composer.commands_mut().push(Command::BubbleDirty {
1230            node_id,
1231            bubble: DirtyBubble {
1232                layout: false,
1233                measure: true,
1234                semantics: false,
1235            },
1236        });
1237    });
1238}
1239
1240/// Unified API for bubbling semantics dirty flags from a node to the root (Composer context).
1241///
1242/// This mirrors [`bubble_layout_dirty_in_composer`] but routes through the semantics
1243/// dirty flag instead of the layout one. Modifier nodes can request semantics
1244/// invalidations without triggering measure/layout work, and the runtime can
1245/// query the root to determine whether the semantics tree needs rebuilding.
1246pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1247    bubble_semantics_dirty_composer::<N>(node_id);
1248}
1249
1250/// Internal implementation for applier-based bubbling.
1251fn bubble_layout_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1252    // First, mark the starting node dirty (critical!)
1253    // This ensures root gets marked even if it has no parent
1254    if let Ok(node) = applier.get_mut(node_id) {
1255        node.mark_needs_layout();
1256    }
1257
1258    // Then bubble up to ancestors
1259    loop {
1260        // Get parent of current node
1261        let parent_id = match applier.get_mut(node_id) {
1262            Ok(node) => node.parent(),
1263            Err(_) => None,
1264        };
1265
1266        match parent_id {
1267            Some(pid) => {
1268                // Mark parent as needing layout
1269                if let Ok(parent) = applier.get_mut(pid) {
1270                    let parent_already_dirty = parent.needs_layout();
1271                    if !parent_already_dirty {
1272                        parent.mark_needs_layout();
1273                    }
1274                    node_id = pid;
1275                } else {
1276                    break;
1277                }
1278            }
1279            None => break, // No parent, stop
1280        }
1281    }
1282}
1283
1284/// Internal implementation for applier-based bubbling of measure dirtiness.
1285fn bubble_measure_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1286    // First, mark the starting node as needing measure
1287    if let Ok(node) = applier.get_mut(node_id) {
1288        node.mark_needs_measure();
1289    }
1290
1291    // Then bubble up to ancestors
1292    loop {
1293        // Get parent of current node
1294        let parent_id = match applier.get_mut(node_id) {
1295            Ok(node) => node.parent(),
1296            Err(_) => None,
1297        };
1298
1299        match parent_id {
1300            Some(pid) => {
1301                // Mark parent as needing measure
1302                if let Ok(parent) = applier.get_mut(pid) {
1303                    if !parent.needs_measure() {
1304                        parent.mark_needs_measure();
1305                    }
1306                    node_id = pid;
1307                } else {
1308                    break;
1309                }
1310            }
1311            None => {
1312                break; // No parent, stop
1313            }
1314        }
1315    }
1316}
1317
1318/// Internal implementation for applier-based bubbling of semantics dirtiness.
1319fn bubble_semantics_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1320    if let Ok(node) = applier.get_mut(node_id) {
1321        node.mark_needs_semantics();
1322    }
1323
1324    loop {
1325        let parent_id = match applier.get_mut(node_id) {
1326            Ok(node) => node.parent(),
1327            Err(_) => None,
1328        };
1329
1330        match parent_id {
1331            Some(pid) => {
1332                if let Ok(parent) = applier.get_mut(pid) {
1333                    if !parent.needs_semantics() {
1334                        parent.mark_needs_semantics();
1335                    }
1336                    node_id = pid;
1337                } else {
1338                    break;
1339                }
1340            }
1341            None => break,
1342        }
1343    }
1344}
1345
1346/// Internal implementation for composer-based bubbling.
1347/// This uses with_node_mut and works during composition with a concrete node type.
1348/// The node type N must implement Node (which includes mark_needs_layout, parent, etc.).
1349fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1350    // Mark the starting node dirty
1351    let _ = with_node_mut(node_id, |node: &mut N| {
1352        node.mark_needs_layout();
1353    });
1354
1355    // Then bubble up to ancestors
1356    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1357        let parent_id = pid;
1358
1359        // Mark parent as needing layout
1360        let advanced = with_node_mut(parent_id, |node: &mut N| {
1361            if !node.needs_layout() {
1362                node.mark_needs_layout();
1363            }
1364            true
1365        })
1366        .unwrap_or(false);
1367
1368        if advanced {
1369            node_id = parent_id;
1370        } else {
1371            break;
1372        }
1373    }
1374}
1375
1376/// Internal implementation for composer-based bubbling of semantics dirtiness.
1377fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1378    // Mark the starting node semantics-dirty.
1379    let _ = with_node_mut(node_id, |node: &mut N| {
1380        node.mark_needs_semantics();
1381    });
1382
1383    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1384        let parent_id = pid;
1385
1386        let advanced = with_node_mut(parent_id, |node: &mut N| {
1387            if !node.needs_semantics() {
1388                node.mark_needs_semantics();
1389            }
1390            true
1391        })
1392        .unwrap_or(false);
1393
1394        if advanced {
1395            node_id = parent_id;
1396        } else {
1397            break;
1398        }
1399    }
1400}
1401
1402impl dyn Node {
1403    pub fn as_any_mut(&mut self) -> &mut dyn Any {
1404        self
1405    }
1406}
1407
1408pub struct RecycledNode {
1409    stable_id: NodeId,
1410    node: Box<dyn Node>,
1411    warm_origin: bool,
1412}
1413
1414impl RecycledNode {
1415    fn new(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
1416        let node = node.rehouse_for_recycle().unwrap_or(node);
1417        Self {
1418            stable_id,
1419            node,
1420            warm_origin,
1421        }
1422    }
1423
1424    fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
1425        Self {
1426            stable_id,
1427            node,
1428            warm_origin,
1429        }
1430    }
1431
1432    pub fn stable_id(&self) -> NodeId {
1433        self.stable_id
1434    }
1435
1436    fn warm_origin(&self) -> bool {
1437        self.warm_origin
1438    }
1439
1440    fn set_warm_origin(&mut self, warm_origin: bool) {
1441        self.warm_origin = warm_origin;
1442    }
1443
1444    pub fn node_mut(&mut self) -> &mut dyn Node {
1445        self.node.as_mut()
1446    }
1447
1448    pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
1449        (self.stable_id, self.node, self.warm_origin)
1450    }
1451}
1452
1453#[derive(Debug, Clone, PartialEq, Eq)]
1454pub struct RecycledNodeInsertion {
1455    pub id: NodeId,
1456    pub stable_id_reused: bool,
1457    pub fallback_error: Option<NodeError>,
1458}
1459
1460impl RecycledNodeInsertion {
1461    fn reused(stable_id: NodeId) -> Self {
1462        Self {
1463            id: stable_id,
1464            stable_id_reused: true,
1465            fallback_error: None,
1466        }
1467    }
1468
1469    fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
1470        Self {
1471            id,
1472            stable_id_reused: false,
1473            fallback_error,
1474        }
1475    }
1476}
1477
1478pub trait Applier: Any {
1479    fn create(&mut self, node: Box<dyn Node>) -> NodeId;
1480    fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
1481    fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
1482
1483    /// Records that `parent_id`'s child list changed structurally this frame
1484    /// (insert, remove, move, or reparent). Incremental scene consumers drain
1485    /// the recorded parents and re-patch those subtrees so removed nodes are
1486    /// evicted from a persistent render graph even when the frame's scene
1487    /// update is otherwise scoped to unrelated dirty nodes.
1488    fn record_structural_change(&mut self, _parent_id: NodeId) {}
1489
1490    /// Returns the current generation for a node index.
1491    /// Generation is incremented when an index is reused from the freelist,
1492    /// preventing stale slot entries from matching recycled nodes.
1493    fn node_generation(&self, id: NodeId) -> u32;
1494
1495    /// Inserts a node with a pre-assigned ID.
1496    ///
1497    /// This is used for virtual nodes whose IDs are allocated separately
1498    /// (e.g., via allocate_virtual_node_id()). Unlike `create()` which assigns
1499    /// a new ID, this method uses the provided ID.
1500    ///
1501    /// Returns Ok(()) if successful, or an error if the ID is already in use.
1502    fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
1503
1504    /// Reinserts a recycled node at its retained stable ID, or creates a fresh ID if that
1505    /// retained ID is no longer available.
1506    fn insert_recycled_node_or_create(
1507        &mut self,
1508        stable_id: NodeId,
1509        node: Box<dyn Node>,
1510    ) -> RecycledNodeInsertion {
1511        let id = self.create(node);
1512        RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
1513    }
1514
1515    fn as_any(&self) -> &dyn Any
1516    where
1517        Self: Sized,
1518    {
1519        self
1520    }
1521
1522    fn as_any_mut(&mut self) -> &mut dyn Any
1523    where
1524        Self: Sized,
1525    {
1526        self
1527    }
1528
1529    /// Trim trailing tombstones/unused capacity after structural changes.
1530    fn compact(&mut self) {}
1531
1532    /// Returns a previously recycled node shell and its stable ID for the requested concrete type.
1533    fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
1534        None
1535    }
1536
1537    /// Marks whether a reinserted recycled node originated from the warm recycle path.
1538    fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
1539
1540    /// Seeds a warm recyclable shell for future reuse without requiring a prior removal.
1541    fn seed_recycled_node_shell(
1542        &mut self,
1543        _key: TypeId,
1544        _recycle_pool_limit: Option<usize>,
1545        _shell: Box<dyn Node>,
1546    ) {
1547    }
1548
1549    /// Records that the current apply pass had to allocate a fresh recyclable shell for this type.
1550    fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
1551
1552    /// Drops any recyclable shells that should not survive beyond the current apply pass.
1553    fn clear_recycled_nodes(&mut self) {}
1554}
1555
1556type TypedNodeUpdate = fn(&mut dyn Node, NodeId) -> Result<(), NodeError>;
1557type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
1558
1559#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1560pub(crate) struct DirtyBubble {
1561    layout: bool,
1562    measure: bool,
1563    semantics: bool,
1564}
1565
1566impl DirtyBubble {
1567    pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
1568        layout: true,
1569        measure: true,
1570        semantics: false,
1571    };
1572
1573    pub(crate) const SEMANTICS: Self = Self {
1574        layout: false,
1575        measure: false,
1576        semantics: true,
1577    };
1578
1579    fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
1580        if self.layout {
1581            bubble_layout_dirty(applier, node_id);
1582        }
1583        if self.measure {
1584            bubble_measure_dirty(applier, node_id);
1585        }
1586        if self.semantics {
1587            bubble_semantics_dirty(applier, node_id);
1588        }
1589    }
1590}
1591
1592pub(crate) enum Command {
1593    BubbleDirty {
1594        node_id: NodeId,
1595        bubble: DirtyBubble,
1596    },
1597    UpdateTypedNode {
1598        id: NodeId,
1599        updater: TypedNodeUpdate,
1600    },
1601    RemoveNode {
1602        id: NodeId,
1603    },
1604    MountNode {
1605        id: NodeId,
1606    },
1607    AttachChild {
1608        parent_id: NodeId,
1609        child_id: NodeId,
1610        bubble: DirtyBubble,
1611    },
1612    InsertChild {
1613        parent_id: NodeId,
1614        child_id: NodeId,
1615        appended_index: usize,
1616        insert_index: usize,
1617        bubble: DirtyBubble,
1618    },
1619    MoveChild {
1620        parent_id: NodeId,
1621        from_index: usize,
1622        to_index: usize,
1623        bubble: DirtyBubble,
1624    },
1625    RemoveChild {
1626        parent_id: NodeId,
1627        child_id: NodeId,
1628    },
1629    DetachChild {
1630        parent_id: NodeId,
1631        child_id: NodeId,
1632    },
1633    SyncChildren {
1634        parent_id: NodeId,
1635        expected_children: ChildList,
1636    },
1637    Callback(CommandCallback),
1638}
1639
1640#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1641struct DeferredChildCleanup {
1642    child_id: NodeId,
1643    generation: u32,
1644    removed_from_parent: bool,
1645}
1646
1647#[derive(Default)]
1648struct DeferredChildCleanupQueue {
1649    pending: Vec<DeferredChildCleanup>,
1650    preserved: Vec<(NodeId, u32)>,
1651}
1652
1653impl DeferredChildCleanupQueue {
1654    fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
1655        if self
1656            .preserved
1657            .iter()
1658            .any(|&(preserved_id, preserved_generation)| {
1659                preserved_id == child_id && preserved_generation == generation
1660            })
1661        {
1662            return;
1663        }
1664        self.pending.push(DeferredChildCleanup {
1665            child_id,
1666            generation,
1667            removed_from_parent,
1668        });
1669    }
1670
1671    fn preserve(&mut self, child_id: NodeId, generation: u32) {
1672        if !self
1673            .preserved
1674            .iter()
1675            .any(|&(preserved_id, preserved_generation)| {
1676                preserved_id == child_id && preserved_generation == generation
1677            })
1678        {
1679            self.preserved.push((child_id, generation));
1680        }
1681        self.pending
1682            .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
1683    }
1684
1685    fn flush(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
1686        for cleanup in self.pending {
1687            cleanup_detached_child(applier, cleanup)?;
1688        }
1689        Ok(())
1690    }
1691}
1692
1693impl Command {
1694    pub(crate) fn update_node<N: Node + 'static>(id: NodeId) -> Self {
1695        Self::UpdateTypedNode {
1696            id,
1697            updater: update_typed_node::<N>,
1698        }
1699    }
1700
1701    pub(crate) fn callback(
1702        callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1703    ) -> Self {
1704        Self::Callback(Box::new(callback))
1705    }
1706
1707    pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
1708        let mut deferred_cleanup = DeferredChildCleanupQueue::default();
1709        self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
1710        deferred_cleanup.flush(applier)
1711    }
1712
1713    fn apply_with_cleanup(
1714        self,
1715        applier: &mut dyn Applier,
1716        deferred_cleanup: &mut DeferredChildCleanupQueue,
1717    ) -> Result<(), NodeError> {
1718        match self {
1719            Self::BubbleDirty { node_id, bubble } => {
1720                bubble.apply(applier, node_id);
1721                Ok(())
1722            }
1723            Self::UpdateTypedNode { id, updater } => {
1724                let node = match applier.get_mut(id) {
1725                    Ok(node) => node,
1726                    Err(NodeError::Missing { .. }) => return Ok(()),
1727                    Err(err) => return Err(err),
1728                };
1729                updater(node, id)
1730            }
1731            Self::RemoveNode { id } => {
1732                if let Ok(node) = applier.get_mut(id) {
1733                    node.unmount();
1734                }
1735                match applier.remove(id) {
1736                    Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
1737                    Err(err) => Err(err),
1738                }
1739            }
1740            Self::MountNode { id } => {
1741                let node = match applier.get_mut(id) {
1742                    Ok(node) => node,
1743                    Err(NodeError::Missing { .. }) => return Ok(()),
1744                    Err(err) => return Err(err),
1745                };
1746                node.set_node_id(id);
1747                node.mount();
1748                Ok(())
1749            }
1750            Self::AttachChild {
1751                parent_id,
1752                child_id,
1753                bubble,
1754            } => {
1755                insert_child_with_reparenting(applier, parent_id, child_id);
1756                bubble.apply(applier, parent_id);
1757                Ok(())
1758            }
1759            Self::InsertChild {
1760                parent_id,
1761                child_id,
1762                appended_index,
1763                insert_index,
1764                bubble,
1765            } => {
1766                insert_child_with_reparenting(applier, parent_id, child_id);
1767                bubble.apply(applier, parent_id);
1768                if insert_index != appended_index {
1769                    if let Ok(parent_node) = applier.get_mut(parent_id) {
1770                        parent_node.move_child(appended_index, insert_index);
1771                    }
1772                }
1773                Ok(())
1774            }
1775            Self::MoveChild {
1776                parent_id,
1777                from_index,
1778                to_index,
1779                bubble,
1780            } => {
1781                if let Ok(parent_node) = applier.get_mut(parent_id) {
1782                    parent_node.move_child(from_index, to_index);
1783                }
1784                bubble.apply(applier, parent_id);
1785                applier.record_structural_change(parent_id);
1786                Ok(())
1787            }
1788            Self::RemoveChild {
1789                parent_id,
1790                child_id,
1791            } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
1792            Self::DetachChild {
1793                parent_id,
1794                child_id,
1795            } => {
1796                let generation = applier.node_generation(child_id);
1797                detach_child_from_parent(applier, parent_id, child_id)?;
1798                deferred_cleanup.preserve(child_id, generation);
1799                Ok(())
1800            }
1801            Self::SyncChildren {
1802                parent_id,
1803                expected_children,
1804            } => sync_children(applier, parent_id, &expected_children, deferred_cleanup),
1805            Self::Callback(callback) => callback(applier),
1806        }
1807    }
1808}
1809
1810const COMMAND_CHUNK_CAPACITY: usize = 1024;
1811const COMMAND_FLUSH_THRESHOLD: usize = COMMAND_CHUNK_CAPACITY * 4;
1812type ChildList = SmallVec<[NodeId; 4]>;
1813const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
1814
1815#[derive(Copy, Clone)]
1816enum CommandTag {
1817    BubbleDirty,
1818    UpdateTypedNode,
1819    RemoveNode,
1820    MountNode,
1821    AttachChild,
1822    InsertChild,
1823    MoveChild,
1824    RemoveChild,
1825    DetachChild,
1826    SyncChildren,
1827    Callback,
1828}
1829
1830impl CommandTag {
1831    fn label(self) -> &'static str {
1832        match self {
1833            Self::BubbleDirty => "BubbleDirty",
1834            Self::UpdateTypedNode => "UpdateTypedNode",
1835            Self::RemoveNode => "RemoveNode",
1836            Self::MountNode => "MountNode",
1837            Self::AttachChild => "AttachChild",
1838            Self::InsertChild => "InsertChild",
1839            Self::MoveChild => "MoveChild",
1840            Self::RemoveChild => "RemoveChild",
1841            Self::DetachChild => "DetachChild",
1842            Self::SyncChildren => "SyncChildren",
1843            Self::Callback => "Callback",
1844        }
1845    }
1846}
1847
1848#[derive(Copy, Clone)]
1849struct BubbleDirtyCommand {
1850    node_id: NodeId,
1851    bubble: DirtyBubble,
1852}
1853
1854#[derive(Copy, Clone)]
1855struct UpdateTypedNodeCommand {
1856    id: NodeId,
1857    updater: TypedNodeUpdate,
1858}
1859
1860#[derive(Copy, Clone)]
1861struct AttachChildCommand {
1862    parent_id: NodeId,
1863    child_id: NodeId,
1864    bubble: DirtyBubble,
1865}
1866
1867#[derive(Copy, Clone)]
1868struct InsertChildCommand {
1869    parent_id: NodeId,
1870    child_id: NodeId,
1871    appended_index: usize,
1872    insert_index: usize,
1873    bubble: DirtyBubble,
1874}
1875
1876#[derive(Copy, Clone)]
1877struct MoveChildCommand {
1878    parent_id: NodeId,
1879    from_index: usize,
1880    to_index: usize,
1881    bubble: DirtyBubble,
1882}
1883
1884#[derive(Copy, Clone)]
1885struct RemoveChildCommand {
1886    parent_id: NodeId,
1887    child_id: NodeId,
1888}
1889
1890#[derive(Copy, Clone)]
1891struct DetachChildCommand {
1892    parent_id: NodeId,
1893    child_id: NodeId,
1894}
1895
1896struct SyncChildrenCommand {
1897    parent_id: NodeId,
1898    child_start: usize,
1899    child_len: usize,
1900}
1901
1902#[derive(Default)]
1903struct CommandQueue {
1904    chunks: Vec<Vec<CommandTag>>,
1905    len: usize,
1906    bubble_dirty: Vec<BubbleDirtyCommand>,
1907    update_typed_nodes: Vec<UpdateTypedNodeCommand>,
1908    remove_nodes: Vec<NodeId>,
1909    mount_nodes: Vec<NodeId>,
1910    attach_children: Vec<AttachChildCommand>,
1911    insert_children: Vec<InsertChildCommand>,
1912    move_children: Vec<MoveChildCommand>,
1913    remove_children: Vec<RemoveChildCommand>,
1914    detach_children: Vec<DetachChildCommand>,
1915    sync_children: Vec<SyncChildrenCommand>,
1916    sync_child_ids: Vec<NodeId>,
1917    callbacks: Vec<CommandCallback>,
1918}
1919
1920impl CommandQueue {
1921    fn push_tag(&mut self, tag: CommandTag) {
1922        let needs_chunk = self
1923            .chunks
1924            .last()
1925            .map(|chunk| chunk.len() == chunk.capacity())
1926            .unwrap_or(true);
1927        if needs_chunk {
1928            self.chunks.push(Vec::with_capacity(COMMAND_CHUNK_CAPACITY));
1929        }
1930        if let Some(chunk) = self.chunks.last_mut() {
1931            chunk.push(tag);
1932            self.len += 1;
1933        }
1934    }
1935
1936    fn push(&mut self, command: Command) {
1937        match command {
1938            Command::BubbleDirty { node_id, bubble } => {
1939                self.bubble_dirty
1940                    .push(BubbleDirtyCommand { node_id, bubble });
1941                self.push_tag(CommandTag::BubbleDirty);
1942            }
1943            Command::UpdateTypedNode { id, updater } => {
1944                self.update_typed_nodes
1945                    .push(UpdateTypedNodeCommand { id, updater });
1946                self.push_tag(CommandTag::UpdateTypedNode);
1947            }
1948            Command::RemoveNode { id } => {
1949                self.remove_nodes.push(id);
1950                self.push_tag(CommandTag::RemoveNode);
1951            }
1952            Command::MountNode { id } => {
1953                self.mount_nodes.push(id);
1954                self.push_tag(CommandTag::MountNode);
1955            }
1956            Command::AttachChild {
1957                parent_id,
1958                child_id,
1959                bubble,
1960            } => {
1961                self.attach_children.push(AttachChildCommand {
1962                    parent_id,
1963                    child_id,
1964                    bubble,
1965                });
1966                self.push_tag(CommandTag::AttachChild);
1967            }
1968            Command::InsertChild {
1969                parent_id,
1970                child_id,
1971                appended_index,
1972                insert_index,
1973                bubble,
1974            } => {
1975                self.insert_children.push(InsertChildCommand {
1976                    parent_id,
1977                    child_id,
1978                    appended_index,
1979                    insert_index,
1980                    bubble,
1981                });
1982                self.push_tag(CommandTag::InsertChild);
1983            }
1984            Command::MoveChild {
1985                parent_id,
1986                from_index,
1987                to_index,
1988                bubble,
1989            } => {
1990                self.move_children.push(MoveChildCommand {
1991                    parent_id,
1992                    from_index,
1993                    to_index,
1994                    bubble,
1995                });
1996                self.push_tag(CommandTag::MoveChild);
1997            }
1998            Command::RemoveChild {
1999                parent_id,
2000                child_id,
2001            } => {
2002                self.remove_children.push(RemoveChildCommand {
2003                    parent_id,
2004                    child_id,
2005                });
2006                self.push_tag(CommandTag::RemoveChild);
2007            }
2008            Command::DetachChild {
2009                parent_id,
2010                child_id,
2011            } => {
2012                self.detach_children.push(DetachChildCommand {
2013                    parent_id,
2014                    child_id,
2015                });
2016                self.push_tag(CommandTag::DetachChild);
2017            }
2018            Command::SyncChildren {
2019                parent_id,
2020                expected_children,
2021            } => {
2022                let child_start = self.sync_child_ids.len();
2023                let child_len = expected_children.len();
2024                self.sync_child_ids.extend(expected_children);
2025                self.sync_children.push(SyncChildrenCommand {
2026                    parent_id,
2027                    child_start,
2028                    child_len,
2029                });
2030                self.push_tag(CommandTag::SyncChildren);
2031            }
2032            Command::Callback(callback) => {
2033                self.callbacks.push(callback);
2034                self.push_tag(CommandTag::Callback);
2035            }
2036        }
2037    }
2038
2039    fn len(&self) -> usize {
2040        self.len
2041    }
2042
2043    fn capacity(&self) -> usize {
2044        self.chunks.iter().map(Vec::capacity).sum()
2045    }
2046
2047    fn payload_len_bytes(&self) -> usize {
2048        self.bubble_dirty
2049            .len()
2050            .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2051            .saturating_add(
2052                self.update_typed_nodes
2053                    .len()
2054                    .saturating_mul(std::mem::size_of::<UpdateTypedNodeCommand>()),
2055            )
2056            .saturating_add(
2057                self.remove_nodes
2058                    .len()
2059                    .saturating_mul(std::mem::size_of::<NodeId>()),
2060            )
2061            .saturating_add(
2062                self.mount_nodes
2063                    .len()
2064                    .saturating_mul(std::mem::size_of::<NodeId>()),
2065            )
2066            .saturating_add(
2067                self.attach_children
2068                    .len()
2069                    .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2070            )
2071            .saturating_add(
2072                self.insert_children
2073                    .len()
2074                    .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2075            )
2076            .saturating_add(
2077                self.move_children
2078                    .len()
2079                    .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2080            )
2081            .saturating_add(
2082                self.remove_children
2083                    .len()
2084                    .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2085            )
2086            .saturating_add(
2087                self.detach_children
2088                    .len()
2089                    .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2090            )
2091            .saturating_add(
2092                self.sync_children
2093                    .len()
2094                    .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2095            )
2096            .saturating_add(
2097                self.sync_child_ids
2098                    .len()
2099                    .saturating_mul(std::mem::size_of::<NodeId>()),
2100            )
2101            .saturating_add(
2102                self.callbacks
2103                    .len()
2104                    .saturating_mul(std::mem::size_of::<CommandCallback>()),
2105            )
2106    }
2107
2108    fn payload_capacity_bytes(&self) -> usize {
2109        self.bubble_dirty
2110            .capacity()
2111            .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2112            .saturating_add(
2113                self.update_typed_nodes
2114                    .capacity()
2115                    .saturating_mul(std::mem::size_of::<UpdateTypedNodeCommand>()),
2116            )
2117            .saturating_add(
2118                self.remove_nodes
2119                    .capacity()
2120                    .saturating_mul(std::mem::size_of::<NodeId>()),
2121            )
2122            .saturating_add(
2123                self.mount_nodes
2124                    .capacity()
2125                    .saturating_mul(std::mem::size_of::<NodeId>()),
2126            )
2127            .saturating_add(
2128                self.attach_children
2129                    .capacity()
2130                    .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2131            )
2132            .saturating_add(
2133                self.insert_children
2134                    .capacity()
2135                    .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2136            )
2137            .saturating_add(
2138                self.move_children
2139                    .capacity()
2140                    .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2141            )
2142            .saturating_add(
2143                self.remove_children
2144                    .capacity()
2145                    .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2146            )
2147            .saturating_add(
2148                self.detach_children
2149                    .capacity()
2150                    .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2151            )
2152            .saturating_add(
2153                self.sync_children
2154                    .capacity()
2155                    .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2156            )
2157            .saturating_add(
2158                self.sync_child_ids
2159                    .capacity()
2160                    .saturating_mul(std::mem::size_of::<NodeId>()),
2161            )
2162            .saturating_add(
2163                self.callbacks
2164                    .capacity()
2165                    .saturating_mul(std::mem::size_of::<CommandCallback>()),
2166            )
2167    }
2168
2169    fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2170        let mut bubble_dirty = self.bubble_dirty.into_iter();
2171        let mut update_typed_nodes = self.update_typed_nodes.into_iter();
2172        let mut remove_nodes = self.remove_nodes.into_iter();
2173        let mut mount_nodes = self.mount_nodes.into_iter();
2174        let mut attach_children = self.attach_children.into_iter();
2175        let mut insert_children = self.insert_children.into_iter();
2176        let mut move_children = self.move_children.into_iter();
2177        let mut remove_children = self.remove_children.into_iter();
2178        let mut detach_children = self.detach_children.into_iter();
2179        let mut sync_children_commands = self.sync_children.into_iter();
2180        let sync_child_ids = self.sync_child_ids;
2181        let mut callbacks = self.callbacks.into_iter();
2182        let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2183
2184        for chunk in self.chunks {
2185            for tag in chunk {
2186                match tag {
2187                    CommandTag::BubbleDirty => {
2188                        let BubbleDirtyCommand { node_id, bubble } =
2189                            next_command_payload(&mut bubble_dirty, tag)?;
2190                        Command::BubbleDirty { node_id, bubble }
2191                            .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2192                    }
2193                    CommandTag::UpdateTypedNode => {
2194                        let UpdateTypedNodeCommand { id, updater } =
2195                            next_command_payload(&mut update_typed_nodes, tag)?;
2196                        Command::UpdateTypedNode { id, updater }
2197                            .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2198                    }
2199                    CommandTag::RemoveNode => {
2200                        let id = next_command_payload(&mut remove_nodes, tag)?;
2201                        Command::RemoveNode { id }
2202                            .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2203                    }
2204                    CommandTag::MountNode => {
2205                        let id = next_command_payload(&mut mount_nodes, tag)?;
2206                        Command::MountNode { id }
2207                            .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2208                    }
2209                    CommandTag::AttachChild => {
2210                        let AttachChildCommand {
2211                            parent_id,
2212                            child_id,
2213                            bubble,
2214                        } = next_command_payload(&mut attach_children, tag)?;
2215                        Command::AttachChild {
2216                            parent_id,
2217                            child_id,
2218                            bubble,
2219                        }
2220                        .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2221                    }
2222                    CommandTag::InsertChild => {
2223                        let InsertChildCommand {
2224                            parent_id,
2225                            child_id,
2226                            appended_index,
2227                            insert_index,
2228                            bubble,
2229                        } = next_command_payload(&mut insert_children, tag)?;
2230                        Command::InsertChild {
2231                            parent_id,
2232                            child_id,
2233                            appended_index,
2234                            insert_index,
2235                            bubble,
2236                        }
2237                        .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2238                    }
2239                    CommandTag::MoveChild => {
2240                        let MoveChildCommand {
2241                            parent_id,
2242                            from_index,
2243                            to_index,
2244                            bubble,
2245                        } = next_command_payload(&mut move_children, tag)?;
2246                        Command::MoveChild {
2247                            parent_id,
2248                            from_index,
2249                            to_index,
2250                            bubble,
2251                        }
2252                        .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2253                    }
2254                    CommandTag::RemoveChild => {
2255                        let RemoveChildCommand {
2256                            parent_id,
2257                            child_id,
2258                        } = next_command_payload(&mut remove_children, tag)?;
2259                        Command::RemoveChild {
2260                            parent_id,
2261                            child_id,
2262                        }
2263                        .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2264                    }
2265                    CommandTag::DetachChild => {
2266                        let DetachChildCommand {
2267                            parent_id,
2268                            child_id,
2269                        } = next_command_payload(&mut detach_children, tag)?;
2270                        Command::DetachChild {
2271                            parent_id,
2272                            child_id,
2273                        }
2274                        .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2275                    }
2276                    CommandTag::SyncChildren => {
2277                        let SyncChildrenCommand {
2278                            parent_id,
2279                            child_start,
2280                            child_len,
2281                        } = next_command_payload(&mut sync_children_commands, tag)?;
2282                        let child_end = child_start
2283                            .checked_add(child_len)
2284                            .ok_or_else(|| command_payload_error(tag))?;
2285                        let expected_children = sync_child_ids
2286                            .get(child_start..child_end)
2287                            .ok_or_else(|| command_payload_error(tag))?;
2288                        sync_children(
2289                            applier,
2290                            parent_id,
2291                            expected_children,
2292                            &mut deferred_cleanup,
2293                        )?;
2294                    }
2295                    CommandTag::Callback => {
2296                        let callback = next_command_payload(&mut callbacks, tag)?;
2297                        Command::Callback(callback)
2298                            .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2299                    }
2300                }
2301            }
2302        }
2303
2304        debug_assert!(bubble_dirty.next().is_none());
2305        debug_assert!(update_typed_nodes.next().is_none());
2306        debug_assert!(remove_nodes.next().is_none());
2307        debug_assert!(mount_nodes.next().is_none());
2308        debug_assert!(attach_children.next().is_none());
2309        debug_assert!(insert_children.next().is_none());
2310        debug_assert!(move_children.next().is_none());
2311        debug_assert!(remove_children.next().is_none());
2312        debug_assert!(detach_children.next().is_none());
2313        debug_assert!(sync_children_commands.next().is_none());
2314        debug_assert!(callbacks.next().is_none());
2315        deferred_cleanup.flush(applier)
2316    }
2317}
2318
2319fn command_payload_error(tag: CommandTag) -> NodeError {
2320    NodeError::MalformedCommandPayload { tag: tag.label() }
2321}
2322
2323fn next_command_payload<T>(
2324    payloads: &mut impl Iterator<Item = T>,
2325    tag: CommandTag,
2326) -> Result<T, NodeError> {
2327    payloads.next().ok_or_else(|| command_payload_error(tag))
2328}
2329
2330fn update_typed_node<N: Node + 'static>(node: &mut dyn Node, id: NodeId) -> Result<(), NodeError> {
2331    let typed = node
2332        .as_any_mut()
2333        .downcast_mut::<N>()
2334        .ok_or(NodeError::TypeMismatch {
2335            id,
2336            expected: std::any::type_name::<N>(),
2337        })?;
2338    typed.update();
2339    Ok(())
2340}
2341
2342fn insert_child_with_reparenting(applier: &mut dyn Applier, parent_id: NodeId, child_id: NodeId) {
2343    if parent_id == child_id {
2344        debug_assert_ne!(
2345            parent_id, child_id,
2346            "a node cannot be attached as its own child"
2347        );
2348        return;
2349    }
2350
2351    let old_parent = applier
2352        .get_mut(child_id)
2353        .ok()
2354        .and_then(|node| node.parent());
2355    if let Some(old_parent_id) = old_parent {
2356        if old_parent_id != parent_id {
2357            if let Ok(old_parent_node) = applier.get_mut(old_parent_id) {
2358                old_parent_node.remove_child(child_id);
2359            }
2360            if let Ok(child_node) = applier.get_mut(child_id) {
2361                child_node.on_removed_from_parent();
2362            }
2363            bubble_layout_dirty(applier, old_parent_id);
2364            bubble_measure_dirty(applier, old_parent_id);
2365            applier.record_structural_change(old_parent_id);
2366        }
2367    }
2368
2369    if let Ok(parent_node) = applier.get_mut(parent_id) {
2370        parent_node.insert_child(child_id);
2371    }
2372    applier.record_structural_change(parent_id);
2373    if let Ok(child_node) = applier.get_mut(child_id) {
2374        child_node.on_attached_to_parent(parent_id);
2375    }
2376}
2377
2378fn apply_remove_child(
2379    applier: &mut dyn Applier,
2380    parent_id: NodeId,
2381    child_id: NodeId,
2382    deferred_cleanup: &mut DeferredChildCleanupQueue,
2383) -> Result<(), NodeError> {
2384    detach_child_from_parent(applier, parent_id, child_id)?;
2385
2386    let generation = applier.node_generation(child_id);
2387    let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
2388        node.parent().is_none()
2389    } else {
2390        return Ok(());
2391    };
2392    deferred_cleanup.push(child_id, generation, removed_from_parent);
2393    Ok(())
2394}
2395
2396fn detach_child_from_parent(
2397    applier: &mut dyn Applier,
2398    parent_id: NodeId,
2399    child_id: NodeId,
2400) -> Result<(), NodeError> {
2401    if let Ok(parent_node) = applier.get_mut(parent_id) {
2402        parent_node.remove_child(child_id);
2403    }
2404    bubble_layout_dirty(applier, parent_id);
2405    bubble_measure_dirty(applier, parent_id);
2406    applier.record_structural_change(parent_id);
2407
2408    if let Ok(node) = applier.get_mut(child_id) {
2409        match node.parent() {
2410            Some(existing_parent_id) if existing_parent_id == parent_id => {
2411                node.on_removed_from_parent();
2412            }
2413            None => {}
2414            Some(_) => return Ok(()),
2415        }
2416    } else {
2417        return Ok(());
2418    }
2419
2420    Ok(())
2421}
2422
2423fn cleanup_detached_child(
2424    applier: &mut dyn Applier,
2425    cleanup: DeferredChildCleanup,
2426) -> Result<(), NodeError> {
2427    if applier.node_generation(cleanup.child_id) != cleanup.generation {
2428        return Ok(());
2429    }
2430
2431    let parent_id = match applier.get_mut(cleanup.child_id) {
2432        Ok(node) => node.parent(),
2433        Err(NodeError::Missing { .. }) => return Ok(()),
2434        Err(err) => return Err(err),
2435    };
2436    if parent_id.is_some() {
2437        return Ok(());
2438    }
2439
2440    if let Ok(node) = applier.get_mut(cleanup.child_id) {
2441        if !cleanup.removed_from_parent {
2442            node.on_removed_from_parent();
2443        }
2444        node.unmount();
2445    }
2446    match applier.remove(cleanup.child_id) {
2447        Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2448        Err(err) => Err(err),
2449    }
2450}
2451
2452fn remove_child_and_cleanup_now(
2453    applier: &mut dyn Applier,
2454    parent_id: NodeId,
2455    child_id: NodeId,
2456) -> Result<(), NodeError> {
2457    let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2458    apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
2459    deferred_cleanup.flush(applier)
2460}
2461
2462fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
2463    let mut scratch = SmallVec::<[NodeId; 8]>::new();
2464    if let Ok(node) = applier.get_mut(parent_id) {
2465        node.collect_children_into(&mut scratch);
2466    }
2467    let mut current = ChildList::new();
2468    current.extend(scratch);
2469    current
2470}
2471
2472fn sync_children(
2473    applier: &mut dyn Applier,
2474    parent_id: NodeId,
2475    expected_children: &[NodeId],
2476    deferred_cleanup: &mut DeferredChildCleanupQueue,
2477) -> Result<(), NodeError> {
2478    let mut current = collect_current_children(applier, parent_id);
2479    let children_changed = current.as_slice() != expected_children;
2480
2481    if children_changed {
2482        if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
2483            sync_children_small(
2484                applier,
2485                parent_id,
2486                &mut current,
2487                expected_children,
2488                deferred_cleanup,
2489            )?;
2490        } else {
2491            let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
2492            target_positions.reserve(expected_children.len());
2493            for (index, &child) in expected_children.iter().enumerate() {
2494                target_positions.insert(child, index);
2495            }
2496
2497            for index in (0..current.len()).rev() {
2498                let child = current[index];
2499                if !target_positions.contains_key(&child) {
2500                    current.remove(index);
2501                    apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
2502                }
2503            }
2504
2505            let mut current_positions = build_child_positions(&current);
2506            for (target_index, &child) in expected_children.iter().enumerate() {
2507                if let Some(current_index) = current_positions.get(&child).copied() {
2508                    if current_index != target_index {
2509                        let from_index = current_index;
2510                        let to_index = move_child_in_diff_state(
2511                            &mut current,
2512                            &mut current_positions,
2513                            from_index,
2514                            target_index,
2515                        );
2516                        Command::MoveChild {
2517                            parent_id,
2518                            from_index,
2519                            to_index,
2520                            bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2521                        }
2522                        .apply(applier)?;
2523                    }
2524                } else {
2525                    let insert_index = target_index.min(current.len());
2526                    let appended_index = current.len();
2527                    insert_child_into_diff_state(
2528                        &mut current,
2529                        &mut current_positions,
2530                        insert_index,
2531                        child,
2532                    );
2533                    Command::InsertChild {
2534                        parent_id,
2535                        child_id: child,
2536                        appended_index,
2537                        insert_index,
2538                        bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2539                    }
2540                    .apply(applier)?;
2541                }
2542            }
2543        }
2544    }
2545
2546    reconcile_children(applier, parent_id, expected_children, !children_changed)
2547}
2548
2549fn sync_children_small(
2550    applier: &mut dyn Applier,
2551    parent_id: NodeId,
2552    current: &mut ChildList,
2553    expected_children: &[NodeId],
2554    deferred_cleanup: &mut DeferredChildCleanupQueue,
2555) -> Result<(), NodeError> {
2556    for index in (0..current.len()).rev() {
2557        let child = current[index];
2558        if !expected_children.contains(&child) {
2559            current.remove(index);
2560            apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
2561        }
2562    }
2563
2564    for (target_index, &child) in expected_children.iter().enumerate() {
2565        if let Some(current_index) = current
2566            .iter()
2567            .position(|&current_child| current_child == child)
2568        {
2569            if current_index != target_index {
2570                let child = current.remove(current_index);
2571                let to_index = target_index.min(current.len());
2572                current.insert(to_index, child);
2573                Command::MoveChild {
2574                    parent_id,
2575                    from_index: current_index,
2576                    to_index,
2577                    bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2578                }
2579                .apply(applier)?;
2580            }
2581        } else {
2582            let insert_index = target_index.min(current.len());
2583            let appended_index = current.len();
2584            current.insert(insert_index, child);
2585            Command::InsertChild {
2586                parent_id,
2587                child_id: child,
2588                appended_index,
2589                insert_index,
2590                bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2591            }
2592            .apply(applier)?;
2593        }
2594    }
2595
2596    Ok(())
2597}
2598
2599fn reconcile_children(
2600    applier: &mut dyn Applier,
2601    parent_id: NodeId,
2602    expected_children: &[NodeId],
2603    needs_dirty_check: bool,
2604) -> Result<(), NodeError> {
2605    let mut repaired = false;
2606    for &child_id in expected_children {
2607        let needs_attach = if let Ok(node) = applier.get_mut(child_id) {
2608            node.parent() != Some(parent_id)
2609        } else {
2610            false
2611        };
2612
2613        if needs_attach {
2614            insert_child_with_reparenting(applier, parent_id, child_id);
2615            repaired = true;
2616        }
2617    }
2618
2619    let is_dirty = if needs_dirty_check {
2620        if let Ok(node) = applier.get_mut(parent_id) {
2621            node.needs_layout()
2622        } else {
2623            false
2624        }
2625    } else {
2626        false
2627    };
2628
2629    if repaired {
2630        bubble_layout_dirty(applier, parent_id);
2631        bubble_measure_dirty(applier, parent_id);
2632    } else if is_dirty {
2633        bubble_layout_dirty(applier, parent_id);
2634    }
2635
2636    Ok(())
2637}
2638
2639#[derive(Default)]
2640pub struct MemoryApplier {
2641    nodes: Vec<Option<Box<dyn Node>>>,
2642    /// Stable node ID occupying each physical slot.
2643    physical_stable_ids: Vec<u32>,
2644    physical_warm_recycled_origins: Vec<bool>,
2645    /// Physical storage location for each live stable node ID.
2646    stable_to_physical: HashMap<NodeId, usize>,
2647    /// Generation counter per stable node ID.
2648    stable_generations: HashMap<NodeId, u32>,
2649    /// Lowest free physical slots are reused first so the dense storage stays compact.
2650    free_ids: BinaryHeap<Reverse<usize>>,
2651    /// Storage for high-ID nodes (like virtual nodes with IDs starting at 0xFFFFFFFF00000000)
2652    /// that can't be stored in the Vec without causing capacity overflow.
2653    high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
2654    high_id_warm_recycled_origins: HashMap<NodeId, bool>,
2655    high_id_generations: HashMap<NodeId, u32>,
2656    next_stable_id: NodeId,
2657    layout_runtime: Option<RuntimeHandle>,
2658    slots: SlotTable,
2659    recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2660    returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2661    cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2662    recycled_node_limits: HashMap<TypeId, usize>,
2663    warm_recycled_node_targets: HashMap<TypeId, usize>,
2664    fresh_recyclable_creations: HashMap<TypeId, usize>,
2665    recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
2666    /// Parents whose child lists changed structurally since the last drain
2667    /// (see [`Applier::record_structural_change`]).
2668    structural_change_parents: Vec<NodeId>,
2669}
2670
2671struct RemovalFrame {
2672    node_id: NodeId,
2673    children: SmallVec<[NodeId; 8]>,
2674    next_child: usize,
2675}
2676
2677#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2678pub struct MemoryApplierDebugStats {
2679    pub next_stable_id: NodeId,
2680    pub nodes_len: usize,
2681    pub nodes_cap: usize,
2682    pub physical_stable_ids_len: usize,
2683    pub physical_stable_ids_cap: usize,
2684    pub stable_to_physical_len: usize,
2685    pub stable_to_physical_cap: usize,
2686    pub stable_generations_len: usize,
2687    pub stable_generations_cap: usize,
2688    pub free_ids_len: usize,
2689    pub free_ids_cap: usize,
2690    pub high_id_nodes_len: usize,
2691    pub high_id_nodes_cap: usize,
2692    pub high_id_generations_len: usize,
2693    pub high_id_generations_cap: usize,
2694    pub recycled_type_count: usize,
2695    pub recycled_type_cap: usize,
2696    pub recycled_node_count: usize,
2697    pub recycled_node_capacity: usize,
2698    pub warm_recycled_node_id_count: usize,
2699    pub warm_recycled_node_id_capacity: usize,
2700}
2701
2702impl MemoryApplier {
2703    const EAGER_COMPACT_NODE_LEN: usize = 1_024;
2704    const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
2705    const INVALID_STABLE_ID: u32 = u32::MAX;
2706    const INITIAL_DENSE_NODE_CAP: usize = 32;
2707    const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
2708    const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
2709
2710    fn pack_stable_id(stable_id: NodeId) -> u32 {
2711        u32::try_from(stable_id).expect("stable id overflow")
2712    }
2713
2714    fn unpack_stable_id(stable_id: u32) -> NodeId {
2715        stable_id as NodeId
2716    }
2717
2718    fn next_dense_node_target_len(old_len: usize) -> usize {
2719        if old_len < Self::INITIAL_DENSE_NODE_CAP {
2720            return Self::INITIAL_DENSE_NODE_CAP;
2721        }
2722        if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
2723            return old_len.saturating_mul(2);
2724        }
2725
2726        let incremental_growth =
2727            (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
2728        old_len.saturating_add(incremental_growth)
2729    }
2730
2731    fn ensure_dense_node_storage_capacity(&mut self) {
2732        let len = self
2733            .nodes
2734            .len()
2735            .max(self.physical_stable_ids.len())
2736            .max(self.physical_warm_recycled_origins.len());
2737        if len < self.nodes.capacity()
2738            && len < self.physical_stable_ids.capacity()
2739            && len < self.physical_warm_recycled_origins.capacity()
2740        {
2741            return;
2742        }
2743
2744        let target = Self::next_dense_node_target_len(len);
2745        if self.nodes.capacity() < target {
2746            self.nodes
2747                .reserve_exact(target.saturating_sub(self.nodes.len()));
2748        }
2749        if self.physical_stable_ids.capacity() < target {
2750            self.physical_stable_ids
2751                .reserve_exact(target.saturating_sub(self.physical_stable_ids.len()));
2752        }
2753        if self.physical_warm_recycled_origins.capacity() < target {
2754            self.physical_warm_recycled_origins
2755                .reserve_exact(target.saturating_sub(self.physical_warm_recycled_origins.len()));
2756        }
2757    }
2758
2759    fn ensure_stable_index_capacity(&mut self) {
2760        let len = self
2761            .stable_to_physical
2762            .len()
2763            .max(self.stable_generations.len());
2764        if len < self.stable_to_physical.capacity() && len < self.stable_generations.capacity() {
2765            return;
2766        }
2767
2768        let target = Self::next_dense_node_target_len(len);
2769        let additional = target.saturating_sub(len);
2770        if self.stable_to_physical.capacity() < target {
2771            self.stable_to_physical.reserve(additional);
2772        }
2773        if self.stable_generations.capacity() < target {
2774            self.stable_generations.reserve(additional);
2775        }
2776    }
2777
2778    pub fn new() -> Self {
2779        Self {
2780            nodes: Vec::new(),
2781            physical_stable_ids: Vec::new(),
2782            physical_warm_recycled_origins: Vec::new(),
2783            stable_to_physical: HashMap::default(),
2784            stable_generations: HashMap::default(),
2785            free_ids: BinaryHeap::new(),
2786            high_id_nodes: HashMap::default(),
2787            high_id_warm_recycled_origins: HashMap::default(),
2788            high_id_generations: HashMap::default(),
2789            next_stable_id: 0,
2790            layout_runtime: None,
2791            slots: SlotTable::default(),
2792            recycled_nodes: HashMap::default(),
2793            returning_recycled_nodes: HashMap::default(),
2794            cold_recycled_nodes: HashMap::default(),
2795            recycled_node_limits: HashMap::default(),
2796            warm_recycled_node_targets: HashMap::default(),
2797            fresh_recyclable_creations: HashMap::default(),
2798            recycled_node_prototypes: HashMap::default(),
2799            structural_change_parents: Vec::new(),
2800        }
2801    }
2802
2803    pub fn slots(&mut self) -> &mut SlotTable {
2804        &mut self.slots
2805    }
2806
2807    /// Drains the parents recorded via [`Applier::record_structural_change`],
2808    /// keeping only nodes still attached to `root` (a parent that was itself
2809    /// removed is covered by its own surviving ancestor's record).
2810    pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
2811        let recorded = std::mem::take(&mut self.structural_change_parents);
2812        let mut attached = Vec::with_capacity(recorded.len());
2813        for parent_id in recorded {
2814            if self.is_attached_to(parent_id, root) && !attached.contains(&parent_id) {
2815                attached.push(parent_id);
2816            }
2817        }
2818        attached
2819    }
2820
2821    fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
2822        let mut current = node_id;
2823        // Parent chains are tree-shaped; the guard only bounds a corrupted
2824        // cyclic chain so the walk cannot spin forever.
2825        for _ in 0..100_000 {
2826            if current == root {
2827                return true;
2828            }
2829            match self.get_mut(current) {
2830                Ok(node) => match node.parent() {
2831                    Some(parent) => current = parent,
2832                    None => return false,
2833                },
2834                Err(_) => return false,
2835            }
2836        }
2837        false
2838    }
2839
2840    pub fn with_node<N: Node + 'static, R>(
2841        &mut self,
2842        id: NodeId,
2843        f: impl FnOnce(&mut N) -> R,
2844    ) -> Result<R, NodeError> {
2845        let physical_id = self
2846            .resolve_node_index(id)
2847            .ok_or(NodeError::Missing { id })?;
2848        let slot = self
2849            .nodes
2850            .get_mut(physical_id)
2851            .ok_or(NodeError::Missing { id })?
2852            .as_deref_mut()
2853            .ok_or(NodeError::Missing { id })?;
2854        let typed = slot
2855            .as_any_mut()
2856            .downcast_mut::<N>()
2857            .ok_or(NodeError::TypeMismatch {
2858                id,
2859                expected: std::any::type_name::<N>(),
2860            })?;
2861        Ok(f(typed))
2862    }
2863
2864    pub fn len(&self) -> usize {
2865        self.nodes.iter().filter(|n| n.is_some()).count()
2866    }
2867
2868    pub fn capacity(&self) -> usize {
2869        self.nodes.len()
2870    }
2871
2872    pub fn tombstone_count(&self) -> usize {
2873        self.nodes.iter().filter(|n| n.is_none()).count()
2874    }
2875
2876    pub fn freelist_len(&self) -> usize {
2877        self.free_ids.len()
2878    }
2879
2880    pub fn debug_recycled_node_count(&self) -> usize {
2881        self.total_recycled_node_count()
2882    }
2883
2884    pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
2885        let key = TypeId::of::<N>();
2886        self.recycled_nodes.get(&key).map(Vec::len).unwrap_or(0)
2887            + self
2888                .returning_recycled_nodes
2889                .get(&key)
2890                .map(Vec::len)
2891                .unwrap_or(0)
2892            + self
2893                .cold_recycled_nodes
2894                .get(&key)
2895                .map(Vec::len)
2896                .unwrap_or(0)
2897    }
2898
2899    pub fn debug_stats(&self) -> MemoryApplierDebugStats {
2900        let mut recycled_keys: HashSet<TypeId> = HashSet::default();
2901        recycled_keys.extend(self.recycled_nodes.keys().copied());
2902        recycled_keys.extend(self.returning_recycled_nodes.keys().copied());
2903        recycled_keys.extend(self.cold_recycled_nodes.keys().copied());
2904
2905        MemoryApplierDebugStats {
2906            next_stable_id: self.next_stable_id,
2907            nodes_len: self.len(),
2908            nodes_cap: self.nodes.len(),
2909            physical_stable_ids_len: self.physical_stable_ids.len(),
2910            physical_stable_ids_cap: self.physical_stable_ids.capacity(),
2911            stable_to_physical_len: self.stable_to_physical.len(),
2912            stable_to_physical_cap: self.stable_to_physical.capacity(),
2913            stable_generations_len: self.stable_generations.len(),
2914            stable_generations_cap: self.stable_generations.capacity(),
2915            free_ids_len: self.free_ids.len(),
2916            free_ids_cap: self.free_ids.capacity(),
2917            high_id_nodes_len: self.high_id_nodes.len(),
2918            high_id_nodes_cap: self.high_id_nodes.capacity(),
2919            high_id_generations_len: self.high_id_generations.len(),
2920            high_id_generations_cap: self.high_id_generations.capacity(),
2921            recycled_type_count: recycled_keys.len(),
2922            recycled_type_cap: self.recycled_nodes.capacity()
2923                + self.returning_recycled_nodes.capacity()
2924                + self.cold_recycled_nodes.capacity(),
2925            recycled_node_count: self.total_recycled_node_count(),
2926            recycled_node_capacity: self.total_recycled_node_capacity(),
2927            warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
2928            warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
2929        }
2930    }
2931
2932    pub fn is_empty(&self) -> bool {
2933        self.len() == 0
2934    }
2935
2936    pub fn debug_live_node_heap_bytes(&self) -> usize {
2937        let dense_nodes = self
2938            .nodes
2939            .iter()
2940            .flatten()
2941            .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
2942            .sum::<usize>();
2943        let high_id_nodes = self
2944            .high_id_nodes
2945            .values()
2946            .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
2947            .sum::<usize>();
2948        dense_nodes + high_id_nodes
2949    }
2950
2951    pub fn debug_recycled_node_heap_bytes(&self) -> usize {
2952        let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
2953            pools
2954                .values()
2955                .flat_map(|nodes| nodes.iter())
2956                .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
2957                .sum::<usize>()
2958        };
2959
2960        pool_bytes(&self.recycled_nodes)
2961            + pool_bytes(&self.returning_recycled_nodes)
2962            + pool_bytes(&self.cold_recycled_nodes)
2963    }
2964
2965    pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
2966        self.layout_runtime = Some(handle);
2967    }
2968
2969    pub fn clear_runtime_handle(&mut self) {
2970        self.layout_runtime = None;
2971    }
2972
2973    pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
2974        self.layout_runtime.clone()
2975    }
2976
2977    fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
2978        pools.values().map(Vec::len).sum()
2979    }
2980
2981    fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
2982        pools.values().map(Vec::capacity).sum()
2983    }
2984
2985    fn total_recycled_node_count(&self) -> usize {
2986        Self::pool_node_count(&self.recycled_nodes)
2987            + Self::pool_node_count(&self.returning_recycled_nodes)
2988            + Self::pool_node_count(&self.cold_recycled_nodes)
2989    }
2990
2991    fn total_recycled_node_capacity(&self) -> usize {
2992        Self::pool_node_capacity(&self.recycled_nodes)
2993            + Self::pool_node_capacity(&self.returning_recycled_nodes)
2994            + Self::pool_node_capacity(&self.cold_recycled_nodes)
2995    }
2996
2997    fn total_warm_recycled_node_id_count(&self) -> usize {
2998        self.live_warm_recycled_origin_count()
2999            + Self::pool_node_count(&self.recycled_nodes)
3000            + Self::pool_node_count(&self.returning_recycled_nodes)
3001    }
3002
3003    fn total_warm_recycled_node_id_capacity(&self) -> usize {
3004        self.live_warm_recycled_origin_capacity()
3005            + Self::pool_node_capacity(&self.recycled_nodes)
3006            + Self::pool_node_capacity(&self.returning_recycled_nodes)
3007    }
3008
3009    fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
3010        if let Some(limit) = recycle_pool_limit {
3011            self.recycled_node_limits.insert(key, limit);
3012        } else {
3013            self.recycled_node_limits.remove(&key);
3014        }
3015    }
3016
3017    fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
3018        self.recycled_node_limits.get(&key).copied()
3019    }
3020
3021    fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
3022        self.recycled_nodes.get(&key).map(Vec::len).unwrap_or(0)
3023    }
3024
3025    fn warm_recycled_node_target(&self, key: TypeId) -> usize {
3026        self.warm_recycled_node_targets
3027            .get(&key)
3028            .copied()
3029            .unwrap_or(0)
3030    }
3031
3032    fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
3033        let Some(limit) = self.recycle_pool_limit_for(key) else {
3034            return usize::MAX;
3035        };
3036        if limit <= 8 {
3037            limit
3038        } else {
3039            limit / 4
3040        }
3041    }
3042
3043    fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
3044        let target_limit = self.warm_recycled_node_target_limit(key);
3045        let existing = self.warm_recycled_node_target(key).min(target_limit);
3046        if observed_demand == 0 {
3047            return existing;
3048        }
3049
3050        let target = match self.recycle_pool_limit_for(key) {
3051            Some(limit) if limit > 8 => target_limit,
3052            Some(_) => observed_demand.min(target_limit),
3053            None => observed_demand,
3054        };
3055        self.warm_recycled_node_targets.insert(key, target);
3056        target
3057    }
3058
3059    fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
3060        if self.recycled_node_prototypes.contains_key(&key) {
3061            return;
3062        }
3063        if let Some(prototype) = shell.rehouse_for_recycle() {
3064            self.recycled_node_prototypes.insert(key, prototype);
3065        }
3066    }
3067
3068    fn live_warm_recycled_origin_count(&self) -> usize {
3069        self.physical_warm_recycled_origins
3070            .iter()
3071            .zip(self.nodes.iter())
3072            .filter(|(warm_origin, node)| **warm_origin && node.is_some())
3073            .count()
3074            + self
3075                .high_id_warm_recycled_origins
3076                .values()
3077                .filter(|warm_origin| **warm_origin)
3078                .count()
3079    }
3080
3081    fn live_warm_recycled_origin_capacity(&self) -> usize {
3082        self.physical_warm_recycled_origins.capacity()
3083            + self.high_id_warm_recycled_origins.capacity()
3084    }
3085
3086    fn push_recycled_node(
3087        &mut self,
3088        key: TypeId,
3089        recycle_pool_limit: Option<usize>,
3090        recycled: RecycledNode,
3091    ) {
3092        self.remember_recycle_pool_limit(key, recycle_pool_limit);
3093        self.remember_recycled_node_prototype(key, recycled.node.as_ref());
3094
3095        let warm_origin = recycled.warm_origin();
3096        let pool = if warm_origin {
3097            self.returning_recycled_nodes.entry(key).or_default()
3098        } else {
3099            self.cold_recycled_nodes.entry(key).or_default()
3100        };
3101        pool.push(recycled);
3102        if let Some(limit) = recycle_pool_limit {
3103            if pool.len() > limit {
3104                let excess = pool.len() - limit;
3105                let dropped: Vec<_> = pool.drain(0..excess).collect();
3106                drop(dropped);
3107            }
3108        }
3109    }
3110
3111    fn push_warm_recycled_node(
3112        &mut self,
3113        key: TypeId,
3114        recycle_pool_limit: Option<usize>,
3115        mut recycled: RecycledNode,
3116    ) {
3117        self.remember_recycle_pool_limit(key, recycle_pool_limit);
3118
3119        recycled.set_warm_origin(true);
3120        let mut dropped = Vec::new();
3121        let mut remove_pool_entry = false;
3122        {
3123            let pool = self.recycled_nodes.entry(key).or_default();
3124            pool.push(recycled);
3125            if let Some(limit) = recycle_pool_limit {
3126                if pool.len() > limit {
3127                    let excess = pool.len() - limit;
3128                    dropped = pool.drain(0..excess).collect();
3129                    remove_pool_entry = pool.is_empty();
3130                }
3131            }
3132        }
3133        if remove_pool_entry {
3134            self.recycled_nodes.remove(&key);
3135        }
3136        drop(dropped);
3137    }
3138
3139    fn seed_recycled_node_shell_impl(
3140        &mut self,
3141        key: TypeId,
3142        recycle_pool_limit: Option<usize>,
3143        shell: Box<dyn Node>,
3144    ) {
3145        let limit = recycle_pool_limit.unwrap_or(usize::MAX);
3146        if self.warm_recycled_pool_len(key) >= limit {
3147            return;
3148        }
3149
3150        self.remember_recycled_node_prototype(key, shell.as_ref());
3151        let stable_id = self.next_stable_id;
3152        self.next_stable_id = self.next_stable_id.saturating_add(1);
3153        self.push_warm_recycled_node(
3154            key,
3155            recycle_pool_limit,
3156            RecycledNode::from_shell(stable_id, shell, true),
3157        );
3158    }
3159
3160    fn take_recycled_node_from_pool(
3161        pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
3162        key: TypeId,
3163    ) -> Option<RecycledNode> {
3164        let pool = pools.get_mut(&key)?;
3165        let node = pool.pop();
3166        if pool.is_empty() {
3167            pools.remove(&key);
3168        }
3169        node
3170    }
3171
3172    fn compact_idle_warm_pool(&mut self, key: TypeId) {
3173        let Some(pool) = self.recycled_nodes.get_mut(&key) else {
3174            return;
3175        };
3176        if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
3177            return;
3178        }
3179
3180        let retained = pool.len();
3181        let mut compacted = Vec::with_capacity(retained);
3182        compacted.append(pool);
3183        let remove_pool_entry = compacted.is_empty();
3184        *pool = compacted;
3185        let _ = pool;
3186
3187        if remove_pool_entry {
3188            self.recycled_nodes.remove(&key);
3189        }
3190    }
3191
3192    fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
3193        let pool_len = self.warm_recycled_pool_len(key);
3194        if pool_len <= target {
3195            return;
3196        }
3197
3198        let Some(pool) = self.recycled_nodes.get_mut(&key) else {
3199            return;
3200        };
3201        let removable = (pool_len - target).min(pool.len());
3202        let dropped: Vec<_> = pool.drain(0..removable).collect();
3203        let remove_pool_entry = pool.is_empty();
3204        let _ = pool;
3205
3206        if remove_pool_entry {
3207            self.recycled_nodes.remove(&key);
3208        }
3209        drop(dropped);
3210    }
3211
3212    fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
3213        let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
3214        if missing == 0 {
3215            return;
3216        }
3217
3218        let recycle_pool_limit = self.recycle_pool_limit_for(key);
3219        let mut shells = Vec::with_capacity(missing);
3220        if let Some(prototype) = self.recycled_node_prototypes.get(&key) {
3221            for _ in 0..missing {
3222                let Some(shell) = prototype.rehouse_for_recycle() else {
3223                    break;
3224                };
3225                shells.push(shell);
3226            }
3227        }
3228
3229        for shell in shells {
3230            self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
3231        }
3232    }
3233
3234    fn prune_stable_generations(&mut self) {
3235        let retained_len = self.stable_to_physical.len() + self.total_recycled_node_count();
3236        if retained_len == self.stable_generations.len() {
3237            return;
3238        }
3239
3240        let mut retained = HashMap::default();
3241        retained.reserve(retained_len);
3242        for stable_id in self.stable_to_physical.keys().copied() {
3243            if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3244                retained.insert(stable_id, generation);
3245            }
3246        }
3247        for stable_id in self
3248            .recycled_nodes
3249            .values()
3250            .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3251        {
3252            if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3253                retained.insert(stable_id, generation);
3254            }
3255        }
3256        for stable_id in self
3257            .returning_recycled_nodes
3258            .values()
3259            .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3260        {
3261            if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3262                retained.insert(stable_id, generation);
3263            }
3264        }
3265        for stable_id in self
3266            .cold_recycled_nodes
3267            .values()
3268            .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3269        {
3270            if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3271                retained.insert(stable_id, generation);
3272            }
3273        }
3274        self.stable_generations = retained;
3275    }
3276
3277    pub fn dump_tree(&self, root: Option<NodeId>) -> String {
3278        let mut output = String::new();
3279        if let Some(root_id) = root {
3280            self.dump_node(&mut output, root_id, 0);
3281        } else {
3282            output.push_str("(no root)\n");
3283        }
3284        output
3285    }
3286
3287    fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
3288        let indent = "  ".repeat(depth);
3289        if let Some(physical_id) = self.resolve_node_index(id) {
3290            if let Some(node) = self.nodes.get(physical_id).and_then(Option::as_ref) {
3291                let type_name = std::any::type_name_of_val(&**node);
3292                output.push_str(&format!("{}[{}] {}\n", indent, id, type_name));
3293
3294                let children = node.children();
3295                for child_id in children {
3296                    self.dump_node(output, child_id, depth + 1);
3297                }
3298            } else {
3299                output.push_str(&format!(
3300                    "{}[{}] (missing physical node {})\n",
3301                    indent, id, physical_id
3302                ));
3303            }
3304        } else {
3305            output.push_str(&format!("{}[{}] (missing)\n", indent, id));
3306        }
3307    }
3308
3309    fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
3310        self.stable_to_physical.get(&id).copied()
3311    }
3312
3313    fn contains_node_id(&self, id: NodeId) -> bool {
3314        self.resolve_node_index(id).is_some() || self.high_id_nodes.contains_key(&id)
3315    }
3316
3317    fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
3318        self.high_id_nodes.insert(stable_id, node);
3319        self.high_id_warm_recycled_origins
3320            .insert(stable_id, warm_origin);
3321        self.high_id_generations.entry(stable_id).or_insert(0);
3322    }
3323
3324    fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
3325        if stable_id >= Self::HIGH_ID_THRESHOLD {
3326            self.insert_high_id_node(stable_id, node, false);
3327            return;
3328        }
3329
3330        let physical_id = if let Some(Reverse(free_physical_id)) = self.free_ids.pop() {
3331            self.nodes[free_physical_id] = Some(node);
3332            self.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
3333            self.physical_warm_recycled_origins[free_physical_id] = false;
3334            free_physical_id
3335        } else {
3336            self.ensure_dense_node_storage_capacity();
3337            let physical_id = self.nodes.len();
3338            self.nodes.push(Some(node));
3339            self.physical_stable_ids
3340                .push(Self::pack_stable_id(stable_id));
3341            self.physical_warm_recycled_origins.push(false);
3342            physical_id
3343        };
3344
3345        self.next_stable_id = self.next_stable_id.max(stable_id.saturating_add(1));
3346        self.ensure_stable_index_capacity();
3347        self.stable_generations.entry(stable_id).or_insert(0);
3348        self.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
3349        self.stable_to_physical.insert(stable_id, physical_id);
3350    }
3351
3352    fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
3353        if let Some(physical_id) = self.resolve_node_index(id) {
3354            let slot = self
3355                .nodes
3356                .get(physical_id)
3357                .ok_or(NodeError::Missing { id })?
3358                .as_deref()
3359                .ok_or(NodeError::Missing { id })?;
3360            return Ok(slot);
3361        }
3362
3363        self.high_id_nodes
3364            .get(&id)
3365            .map(|node| node.as_ref())
3366            .ok_or(NodeError::Missing { id })
3367    }
3368
3369    fn collect_node_children_into(
3370        &self,
3371        id: NodeId,
3372        out: &mut SmallVec<[NodeId; 8]>,
3373    ) -> Result<(), NodeError> {
3374        self.get_ref(id)?.collect_children_into(out);
3375        Ok(())
3376    }
3377
3378    fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
3379        Ok(self.get_ref(id)?.parent())
3380    }
3381
3382    fn collect_owned_children(
3383        &self,
3384        node_id: NodeId,
3385        out: &mut SmallVec<[NodeId; 8]>,
3386    ) -> Result<(), NodeError> {
3387        self.collect_node_children_into(node_id, out)?;
3388        out.retain(|child_id| {
3389            self.node_parent(*child_id)
3390                .map(|parent| parent == Some(node_id))
3391                .unwrap_or(false)
3392        });
3393        Ok(())
3394    }
3395
3396    fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
3397        if self.high_id_nodes.contains_key(&node_id) {
3398            if let Some(mut node) = self.high_id_nodes.remove(&node_id) {
3399                if let Some(key) = node.recycle_key() {
3400                    let recycle_pool_limit = node.recycle_pool_limit();
3401                    let warm_origin = self
3402                        .high_id_warm_recycled_origins
3403                        .remove(&node_id)
3404                        .unwrap_or(false);
3405                    node.prepare_for_recycle();
3406                    self.push_recycled_node(
3407                        key,
3408                        recycle_pool_limit,
3409                        RecycledNode::new(node_id, node, warm_origin),
3410                    );
3411                }
3412            }
3413            let generation = self.high_id_generations.entry(node_id).or_insert(0);
3414            *generation = generation.wrapping_add(1);
3415            return Ok(());
3416        }
3417
3418        let physical_id = self
3419            .resolve_node_index(node_id)
3420            .ok_or(NodeError::Missing { id: node_id })?;
3421        if let Some(mut node) = self.nodes[physical_id].take() {
3422            if let Some(key) = node.recycle_key() {
3423                let recycle_pool_limit = node.recycle_pool_limit();
3424                let warm_origin = self
3425                    .physical_warm_recycled_origins
3426                    .get_mut(physical_id)
3427                    .map(std::mem::take)
3428                    .unwrap_or(false);
3429                node.prepare_for_recycle();
3430                self.push_recycled_node(
3431                    key,
3432                    recycle_pool_limit,
3433                    RecycledNode::new(node_id, node, warm_origin),
3434                );
3435            }
3436        }
3437        self.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
3438        self.stable_to_physical.remove(&node_id);
3439        if let Some(generation) = self.stable_generations.get_mut(&node_id) {
3440            *generation = generation.wrapping_add(1);
3441        } else {
3442            self.stable_generations.insert(node_id, 1);
3443        }
3444        self.free_ids.push(Reverse(physical_id));
3445        Ok(())
3446    }
3447
3448    fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
3449        self.get_ref(id)?;
3450
3451        let mut root_children = SmallVec::<[NodeId; 8]>::new();
3452        self.collect_owned_children(id, &mut root_children)?;
3453
3454        let mut stack = Vec::new();
3455        stack.push(RemovalFrame {
3456            node_id: id,
3457            children: root_children,
3458            next_child: 0,
3459        });
3460        let mut max_depth = stack.len();
3461
3462        while let Some(frame) = stack.last_mut() {
3463            if frame.next_child < frame.children.len() {
3464                let child_id = frame.children[frame.next_child];
3465                frame.next_child += 1;
3466
3467                if let Ok(child) = self.get_mut(child_id) {
3468                    child.on_removed_from_parent();
3469                    child.unmount();
3470                }
3471
3472                let mut child_children = SmallVec::<[NodeId; 8]>::new();
3473                self.collect_owned_children(child_id, &mut child_children)?;
3474                stack.push(RemovalFrame {
3475                    node_id: child_id,
3476                    children: child_children,
3477                    next_child: 0,
3478                });
3479                max_depth = max_depth.max(stack.len());
3480                continue;
3481            }
3482
3483            let node_id = frame.node_id;
3484            stack.pop();
3485            self.remove_node_storage(node_id)?;
3486        }
3487
3488        Ok(max_depth)
3489    }
3490
3491    #[cfg(test)]
3492    fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
3493        self.remove_subtree_postorder(id)
3494    }
3495}
3496
3497impl Applier for MemoryApplier {
3498    fn record_structural_change(&mut self, parent_id: NodeId) {
3499        if self.structural_change_parents.last() != Some(&parent_id) {
3500            self.structural_change_parents.push(parent_id);
3501        }
3502    }
3503
3504    fn create(&mut self, node: Box<dyn Node>) -> NodeId {
3505        let stable_id = self.next_stable_id;
3506        self.next_stable_id = self.next_stable_id.saturating_add(1);
3507        if stable_id >= Self::HIGH_ID_THRESHOLD {
3508            self.insert_high_id_node(stable_id, node, false);
3509            return stable_id;
3510        }
3511
3512        self.ensure_stable_index_capacity();
3513        self.stable_generations.insert(stable_id, 0);
3514
3515        let physical_id = if let Some(Reverse(id)) = self.free_ids.pop() {
3516            debug_assert!(self.nodes[id].is_none(), "freelist entry {id} is not None");
3517            self.nodes[id] = Some(node);
3518            self.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
3519            self.physical_warm_recycled_origins[id] = false;
3520            id
3521        } else {
3522            self.ensure_dense_node_storage_capacity();
3523            let id = self.nodes.len();
3524            self.nodes.push(Some(node));
3525            self.physical_stable_ids
3526                .push(Self::pack_stable_id(stable_id));
3527            self.physical_warm_recycled_origins.push(false);
3528            id
3529        };
3530        self.stable_to_physical.insert(stable_id, physical_id);
3531        stable_id
3532    }
3533
3534    fn node_generation(&self, id: NodeId) -> u32 {
3535        self.high_id_generations
3536            .get(&id)
3537            .copied()
3538            .or_else(|| self.stable_generations.get(&id).copied())
3539            .unwrap_or(0)
3540    }
3541
3542    fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
3543        // Try Vec first (common case for normal nodes), then HashMap for virtual nodes.
3544        if let Some(physical_id) = self.resolve_node_index(id) {
3545            let slot = self.nodes[physical_id]
3546                .as_deref_mut()
3547                .ok_or(NodeError::Missing { id })?;
3548            return Ok(slot);
3549        }
3550        self.high_id_nodes
3551            .get_mut(&id)
3552            .map(|n| n.as_mut())
3553            .ok_or(NodeError::Missing { id })
3554    }
3555
3556    fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
3557        self.remove_subtree_postorder(id).map(|_| ())
3558    }
3559
3560    fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
3561        if self.contains_node_id(id) {
3562            return Err(NodeError::AlreadyExists { id });
3563        }
3564        self.insert_available_with_id(id, node);
3565        Ok(())
3566    }
3567
3568    fn insert_recycled_node_or_create(
3569        &mut self,
3570        stable_id: NodeId,
3571        node: Box<dyn Node>,
3572    ) -> RecycledNodeInsertion {
3573        if self.contains_node_id(stable_id) {
3574            let id = self.create(node);
3575            return RecycledNodeInsertion::fresh(
3576                id,
3577                Some(NodeError::AlreadyExists { id: stable_id }),
3578            );
3579        }
3580
3581        self.insert_available_with_id(stable_id, node);
3582        RecycledNodeInsertion::reused(stable_id)
3583    }
3584
3585    fn compact(&mut self) {
3586        let live_count = self.nodes.iter().filter(|slot| slot.is_some()).count();
3587        let tombstone_count = self.nodes.len().saturating_sub(live_count);
3588        if tombstone_count == 0 {
3589            return;
3590        }
3591        if self.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
3592            return;
3593        }
3594        let rehouse_live_nodes = tombstone_count >= live_count;
3595        let mut packed_nodes = Vec::with_capacity(live_count);
3596        let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
3597        let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
3598        let mut stable_to_physical = HashMap::default();
3599        stable_to_physical.reserve(live_count);
3600
3601        for physical_id in 0..self.nodes.len() {
3602            let Some(mut node) = self.nodes[physical_id].take() else {
3603                continue;
3604            };
3605            if rehouse_live_nodes {
3606                if let Some(rehoused) = node.rehouse_for_live_compaction() {
3607                    node = rehoused;
3608                }
3609            }
3610            let stable_id = std::mem::replace(
3611                &mut self.physical_stable_ids[physical_id],
3612                Self::INVALID_STABLE_ID,
3613            );
3614            debug_assert_ne!(
3615                stable_id,
3616                Self::INVALID_STABLE_ID,
3617                "live physical slot must have a stable id",
3618            );
3619            let stable_id = Self::unpack_stable_id(stable_id);
3620            packed_nodes.push(Some(node));
3621            packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
3622            packed_warm_recycled_origins.push(self.physical_warm_recycled_origins[physical_id]);
3623            stable_to_physical.insert(stable_id, packed_nodes.len() - 1);
3624        }
3625
3626        self.nodes = packed_nodes;
3627        self.physical_stable_ids = packed_physical_stable_ids;
3628        self.physical_warm_recycled_origins = packed_warm_recycled_origins;
3629        self.free_ids = BinaryHeap::new();
3630        self.stable_to_physical = stable_to_physical;
3631        self.prune_stable_generations();
3632    }
3633
3634    fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
3635        Self::take_recycled_node_from_pool(&mut self.returning_recycled_nodes, key)
3636            .or_else(|| Self::take_recycled_node_from_pool(&mut self.recycled_nodes, key))
3637    }
3638
3639    fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
3640        if let Some(physical_id) = self.resolve_node_index(id) {
3641            self.physical_warm_recycled_origins[physical_id] = warm_origin;
3642        } else if self.high_id_nodes.contains_key(&id) {
3643            self.high_id_warm_recycled_origins.insert(id, warm_origin);
3644        }
3645    }
3646
3647    fn seed_recycled_node_shell(
3648        &mut self,
3649        key: TypeId,
3650        recycle_pool_limit: Option<usize>,
3651        shell: Box<dyn Node>,
3652    ) {
3653        self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
3654    }
3655
3656    fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
3657        *self.fresh_recyclable_creations.entry(key).or_insert(0) += 1;
3658    }
3659
3660    fn clear_recycled_nodes(&mut self) {
3661        let returning = std::mem::take(&mut self.returning_recycled_nodes);
3662        for (key, mut nodes) in returning {
3663            let pool = self.recycled_nodes.entry(key).or_default();
3664            pool.append(&mut nodes);
3665        }
3666
3667        let fresh_recyclable_creations = std::mem::take(&mut self.fresh_recyclable_creations);
3668        let cold = std::mem::take(&mut self.cold_recycled_nodes);
3669        for (key, mut nodes) in cold {
3670            let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
3671            if needed > 0 {
3672                let remaining_limit = self
3673                    .recycle_pool_limit_for(key)
3674                    .unwrap_or(usize::MAX)
3675                    .saturating_sub(self.warm_recycled_pool_len(key));
3676                let promote = nodes.len().min(needed).min(remaining_limit);
3677                let split_at = nodes.len().saturating_sub(promote);
3678                let promoted = nodes.split_off(split_at);
3679                for mut recycled in promoted {
3680                    recycled.set_warm_origin(true);
3681                    self.recycled_nodes.entry(key).or_default().push(recycled);
3682                }
3683            }
3684        }
3685
3686        let mut keys: HashSet<TypeId> = HashSet::default();
3687        keys.extend(self.recycled_nodes.keys().copied());
3688        keys.extend(self.recycled_node_limits.keys().copied());
3689        keys.extend(self.warm_recycled_node_targets.keys().copied());
3690        keys.extend(self.recycled_node_prototypes.keys().copied());
3691        for key in keys {
3692            let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
3693            let target = self.update_warm_recycled_node_target(key, observed_demand);
3694            self.replenish_warm_pool_to_target(key, target);
3695            self.trim_idle_warm_pool_to_target(key, target);
3696            self.compact_idle_warm_pool(key);
3697        }
3698        self.prune_stable_generations();
3699        // Compact the dense physical storage if it has grown very large relative
3700        // to the live node count (e.g. after a spike of recyclable nodes that
3701        // were all removed). This keeps warm_recycled_node_id_capacity bounded.
3702        self.compact();
3703    }
3704}
3705
3706pub trait ApplierHost {
3707    fn borrow_dyn(&self) -> RefMut<'_, dyn Applier>;
3708    /// Compact internal storage after commands have been applied.
3709    fn compact(&self) {}
3710}
3711
3712pub struct ConcreteApplierHost<A: Applier + 'static> {
3713    inner: RefCell<A>,
3714}
3715
3716impl<A: Applier + 'static> ConcreteApplierHost<A> {
3717    pub fn new(applier: A) -> Self {
3718        Self {
3719            inner: RefCell::new(applier),
3720        }
3721    }
3722
3723    pub fn borrow_typed(&self) -> RefMut<'_, A> {
3724        self.inner.borrow_mut()
3725    }
3726
3727    pub fn try_borrow_typed(&self) -> Result<RefMut<'_, A>, std::cell::BorrowMutError> {
3728        self.inner.try_borrow_mut()
3729    }
3730
3731    pub fn into_inner(self) -> A {
3732        self.inner.into_inner()
3733    }
3734}
3735
3736impl<A: Applier + 'static> ApplierHost for ConcreteApplierHost<A> {
3737    fn borrow_dyn(&self) -> RefMut<'_, dyn Applier> {
3738        RefMut::map(self.inner.borrow_mut(), |applier| {
3739            applier as &mut dyn Applier
3740        })
3741    }
3742
3743    fn compact(&self) {
3744        self.inner.borrow_mut().compact();
3745    }
3746}
3747
3748pub struct ApplierGuard<'a, A: Applier + 'static> {
3749    inner: RefMut<'a, A>,
3750}
3751
3752impl<'a, A: Applier + 'static> ApplierGuard<'a, A> {
3753    fn new(inner: RefMut<'a, A>) -> Self {
3754        Self { inner }
3755    }
3756}
3757
3758impl<'a, A: Applier + 'static> Deref for ApplierGuard<'a, A> {
3759    type Target = A;
3760
3761    fn deref(&self) -> &Self::Target {
3762        &self.inner
3763    }
3764}
3765
3766impl<'a, A: Applier + 'static> DerefMut for ApplierGuard<'a, A> {
3767    fn deref_mut(&mut self) -> &mut Self::Target {
3768        &mut self.inner
3769    }
3770}
3771
3772pub struct SlotsHost {
3773    storage_key: Cell<usize>,
3774    inner: RefCell<SlotsHostInner>,
3775}
3776
3777#[derive(Debug, Default)]
3778pub(crate) struct SlotPassOutcome {
3779    pub(crate) compacted: bool,
3780    pub(crate) compact_anchor_registry_storage: bool,
3781    pub(crate) compact_payload_storage: bool,
3782}
3783
3784#[derive(Default)]
3785pub(crate) struct FinishedSlotPass {
3786    pub(crate) outcome: SlotPassOutcome,
3787    pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
3788}
3789
3790struct ActivePassState {
3791    state: slot::SlotWriteSessionState,
3792}
3793
3794struct SlotsHostInner {
3795    table: SlotTable,
3796    lifecycle: slot::SlotLifecycleCoordinator,
3797    runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
3798    active_pass: Option<ActivePassState>,
3799}
3800
3801impl Drop for SlotsHost {
3802    fn drop(&mut self) {
3803        let storage_key = self.storage_key.get();
3804        let inner = self.inner.get_mut();
3805        if let Some(state) = inner.runtime_state.clone() {
3806            if let Err(err) = state.dispose_retained_subtrees_for_host(
3807                storage_key,
3808                &mut inner.table,
3809                &mut inner.lifecycle,
3810            ) {
3811                log::error!(
3812                    "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
3813                );
3814                state.abandon_retained_subtrees_for_host(
3815                    storage_key,
3816                    &mut inner.table,
3817                    &mut inner.lifecycle,
3818                );
3819            } else {
3820                state.clear_host_storage_key(storage_key);
3821            }
3822        }
3823        inner.lifecycle.dispose_slot_table(&mut inner.table);
3824    }
3825}
3826
3827impl SlotsHost {
3828    pub(crate) fn storage_key(&self) -> usize {
3829        self.storage_key.get()
3830    }
3831
3832    pub fn new(storage: SlotTable) -> Self {
3833        let storage_key = storage.storage_id();
3834        Self {
3835            storage_key: Cell::new(storage_key),
3836            inner: RefCell::new(SlotsHostInner {
3837                table: storage,
3838                lifecycle: slot::SlotLifecycleCoordinator::default(),
3839                runtime_state: None,
3840                active_pass: None,
3841            }),
3842        }
3843    }
3844
3845    pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
3846        let mut inner = self.inner.borrow_mut();
3847        inner.runtime_state = Some(Rc::clone(state));
3848    }
3849
3850    pub(crate) fn rebind_orphaned_runtime_state(
3851        &self,
3852        state: &Rc<crate::composer::ComposerRuntimeState>,
3853    ) -> bool {
3854        let inner = self.inner.borrow();
3855        if inner.active_pass.is_some() {
3856            log::error!("cannot rebind SlotsHost during an active pass");
3857            return false;
3858        }
3859        let Some(bound_state) = inner.runtime_state.as_ref() else {
3860            drop(inner);
3861            self.bind_runtime_state(state);
3862            return true;
3863        };
3864        if Rc::ptr_eq(bound_state, state) {
3865            return true;
3866        }
3867        if bound_state.has_live_applier_host() {
3868            return false;
3869        }
3870        drop(inner);
3871
3872        let mut inner = self.inner.borrow_mut();
3873        let Some(bound_state) = inner.runtime_state.as_ref() else {
3874            inner.runtime_state = Some(Rc::clone(state));
3875            return true;
3876        };
3877        if Rc::ptr_eq(bound_state, state) {
3878            return true;
3879        }
3880        if bound_state.has_live_applier_host() {
3881            return false;
3882        }
3883
3884        let previous_state = Rc::clone(bound_state);
3885        let mut lifecycle = std::mem::take(&mut inner.lifecycle);
3886        lifecycle.flush_pending_drops();
3887        let host_key = self.storage_key();
3888        if previous_state
3889            .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
3890            .is_err()
3891        {
3892            inner.lifecycle = lifecycle;
3893            return false;
3894        }
3895        previous_state.clear_host(self);
3896        lifecycle.flush_pending_drops();
3897        inner.runtime_state = Some(Rc::clone(state));
3898        inner.lifecycle = lifecycle;
3899        true
3900    }
3901
3902    pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
3903        self.inner.borrow().runtime_state.clone()
3904    }
3905
3906    pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
3907        Ref::map(self.inner.borrow(), |inner| &inner.table)
3908    }
3909
3910    pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
3911        RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
3912    }
3913
3914    pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
3915        if Rc::strong_count(&self) != 1 {
3916            return Err(NodeError::SlotHostUnavailable {
3917                operation: "SlotsHost::into_table",
3918                reason: "other host references are alive",
3919            });
3920        }
3921        self.take_table_for_transfer()
3922    }
3923
3924    fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
3925        let inner = self.inner.borrow();
3926        if inner.active_pass.is_some() {
3927            return Err(NodeError::SlotHostUnavailable {
3928                operation: "SlotsHost::into_table",
3929                reason: "slot pass is active",
3930            });
3931        }
3932        drop(inner);
3933        let mut inner = self.inner.borrow_mut();
3934        let mut lifecycle = std::mem::take(&mut inner.lifecycle);
3935        lifecycle.flush_pending_drops();
3936        if let Some(state) = inner.runtime_state.clone() {
3937            let host_key = self.storage_key();
3938            state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)?;
3939            state.clear_host(self);
3940            lifecycle.flush_pending_drops();
3941        }
3942        let taken = std::mem::take(&mut inner.table);
3943        self.storage_key.set(inner.table.storage_id());
3944        inner.runtime_state = None;
3945        inner.lifecycle = lifecycle;
3946        Ok(taken)
3947    }
3948
3949    pub fn reset(&self) -> Result<(), NodeError> {
3950        let inner = self.inner.borrow();
3951        if inner.active_pass.is_some() {
3952            return Err(NodeError::SlotHostUnavailable {
3953                operation: "SlotsHost::reset",
3954                reason: "slot pass is active",
3955            });
3956        }
3957        let runtime_state = inner.runtime_state.clone();
3958        drop(inner);
3959        let mut inner = self.inner.borrow_mut();
3960        let mut lifecycle = std::mem::take(&mut inner.lifecycle);
3961        if let Some(state) = runtime_state {
3962            let host_key = self.storage_key();
3963            state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)?;
3964            state.clear_host(self);
3965        }
3966        lifecycle.dispose_slot_table(&mut inner.table);
3967        inner.table = SlotTable::default();
3968        self.storage_key.set(inner.table.storage_id());
3969        inner.runtime_state = None;
3970        inner.lifecycle = slot::SlotLifecycleCoordinator::default();
3971        Ok(())
3972    }
3973
3974    pub(crate) fn abandon_after_apply_failure(&self) {
3975        let inner = self.inner.borrow();
3976        if inner.active_pass.is_some() {
3977            log::error!("cannot abandon SlotsHost during an active pass");
3978            return;
3979        }
3980        let runtime_state = inner.runtime_state.clone();
3981        drop(inner);
3982        let mut inner = self.inner.borrow_mut();
3983        let mut lifecycle = std::mem::take(&mut inner.lifecycle);
3984        if let Some(state) = runtime_state {
3985            let host_key = self.storage_key();
3986            state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
3987        }
3988        lifecycle.dispose_slot_table(&mut inner.table);
3989        inner.table = SlotTable::default();
3990        self.storage_key.set(inner.table.storage_id());
3991        inner.runtime_state = None;
3992        inner.lifecycle = slot::SlotLifecycleCoordinator::default();
3993    }
3994
3995    pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
3996        let inner = self.inner.borrow();
3997        let local = inner.table.debug_stats();
3998        let lifecycle = inner.lifecycle.debug_stats();
3999        let retention = inner
4000            .runtime_state
4001            .clone()
4002            .map(|state| state.slot_retention_debug_stats(self))
4003            .unwrap_or_default();
4004        SlotTableDebugStats::from_parts(local, lifecycle, retention)
4005    }
4006
4007    pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
4008        let inner = self.inner.borrow();
4009        let mut snapshot = inner.table.debug_snapshot();
4010        if let Some(state) = inner.runtime_state.clone() {
4011            state.fill_slot_debug_snapshot(self, &mut snapshot);
4012        }
4013        snapshot
4014    }
4015
4016    pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode) {
4017        let mut inner = self.inner.borrow_mut();
4018        if inner.active_pass.is_some() {
4019            log::error!("slot pass already active for host");
4020            return;
4021        }
4022        let mut state = slot::SlotWriteSessionState::default();
4023        state.reset_for_pass(mode);
4024        inner.active_pass = Some(ActivePassState { state });
4025    }
4026
4027    pub(crate) fn has_active_pass(&self) -> bool {
4028        self.inner.borrow().active_pass.is_some()
4029    }
4030
4031    pub(crate) fn abandon_active_pass(&self) {
4032        self.inner.borrow_mut().active_pass = None;
4033    }
4034
4035    pub(crate) fn with_write_session<R>(
4036        &self,
4037        f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
4038    ) -> R {
4039        let mut inner = self.inner.borrow_mut();
4040        let SlotsHostInner {
4041            table,
4042            lifecycle,
4043            active_pass,
4044            ..
4045        } = &mut *inner;
4046        let active_pass = active_pass
4047            .as_mut()
4048            .expect("slot write session requires an active pass");
4049        let mut session = table.write_session(lifecycle, &mut active_pass.state);
4050        f(&mut session)
4051    }
4052
4053    pub(crate) fn with_table_and_lifecycle_mut<R>(
4054        &self,
4055        f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
4056    ) -> R {
4057        let mut inner = self.inner.borrow_mut();
4058        let SlotsHostInner {
4059            table, lifecycle, ..
4060        } = &mut *inner;
4061        f(table, lifecycle)
4062    }
4063
4064    pub(crate) fn finish_pass(
4065        &self,
4066        applier: &mut dyn Applier,
4067    ) -> Result<FinishedSlotPass, NodeError> {
4068        let mut inner = self.inner.borrow_mut();
4069        let SlotsHostInner {
4070            table,
4071            lifecycle,
4072            active_pass: active_pass_slot,
4073            ..
4074        } = &mut *inner;
4075        let Some(mut active_pass) = active_pass_slot.take() else {
4076            return Ok(FinishedSlotPass::default());
4077        };
4078
4079        active_pass.state.flush_payload_location_refreshes(table);
4080
4081        #[cfg(debug_assertions)]
4082        if let Err(err) = active_pass.state.validate(table) {
4083            log::error!("slot writer invariant violation before finalize_pass: {err:?}");
4084            return Err(NodeError::SlotHostUnavailable {
4085                operation: "SlotsHost::finish_pass",
4086                reason: "slot writer invariant violation",
4087            });
4088        }
4089
4090        let detached_root_children = {
4091            let mut session = table.write_session(lifecycle, &mut active_pass.state);
4092            session.finalize_pass(applier)?
4093        };
4094
4095        Ok(FinishedSlotPass {
4096            outcome: SlotPassOutcome {
4097                compacted: active_pass.state.request_compaction,
4098                compact_anchor_registry_storage: active_pass
4099                    .state
4100                    .request_anchor_storage_compaction,
4101                compact_payload_storage: active_pass.state.request_payload_storage_compaction,
4102            },
4103            detached_root_children,
4104        })
4105    }
4106
4107    pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
4108        let mut inner = self.inner.borrow_mut();
4109        let SlotsHostInner {
4110            table,
4111            lifecycle,
4112            runtime_state,
4113            ..
4114        } = &mut *inner;
4115        lifecycle.flush_pending_drops();
4116        if outcome.compacted {
4117            table.compact_storage();
4118            lifecycle.compact_storage();
4119        }
4120        if let Some(state) = runtime_state.clone() {
4121            state.compact_table_identity_storage_for_host(
4122                self,
4123                table,
4124                outcome.compact_anchor_registry_storage,
4125                outcome.compact_payload_storage,
4126            );
4127        } else {
4128            if outcome.compact_anchor_registry_storage {
4129                table.compact_anchor_registry_storage(None);
4130            }
4131            if outcome.compact_payload_storage {
4132                table.compact_payload_anchor_registry_storage(None);
4133            }
4134        }
4135        table.assert_fast_integrity("slot pass cleanup");
4136        #[cfg(any(test, debug_assertions))]
4137        {
4138            table.debug_verify();
4139            if let Some(state) = runtime_state.clone() {
4140                state.debug_verify_host(self, table);
4141            }
4142        }
4143    }
4144}
4145
4146fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
4147    let mut positions = HashMap::default();
4148    positions.reserve(children.len());
4149    for (index, &child) in children.iter().enumerate() {
4150        positions.insert(child, index);
4151    }
4152    positions
4153}
4154
4155fn refresh_child_positions(
4156    current: &[NodeId],
4157    positions: &mut HashMap<NodeId, usize>,
4158    start: usize,
4159    end: usize,
4160) {
4161    if current.is_empty() || start >= current.len() {
4162        return;
4163    }
4164    let end = end.min(current.len() - 1);
4165    for (offset, &child) in current[start..=end].iter().enumerate() {
4166        positions.insert(child, start + offset);
4167    }
4168}
4169
4170fn insert_child_into_diff_state(
4171    current: &mut ChildList,
4172    positions: &mut HashMap<NodeId, usize>,
4173    index: usize,
4174    child: NodeId,
4175) {
4176    let index = index.min(current.len());
4177    current.insert(index, child);
4178    refresh_child_positions(current, positions, index, current.len() - 1);
4179}
4180
4181fn move_child_in_diff_state(
4182    current: &mut ChildList,
4183    positions: &mut HashMap<NodeId, usize>,
4184    from_index: usize,
4185    target_index: usize,
4186) -> usize {
4187    let child = current.remove(from_index);
4188    let to_index = target_index.min(current.len());
4189    current.insert(to_index, child);
4190    refresh_child_positions(
4191        current,
4192        positions,
4193        from_index.min(to_index),
4194        from_index.max(to_index),
4195    );
4196    to_index
4197}
4198
4199pub(crate) use state::MutableStateInner;
4200pub use state::{MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State};
4201
4202fn hash_key<K: Hash>(key: &K) -> Key {
4203    let mut hasher = hash::default::new();
4204    key.hash(&mut hasher);
4205    hasher.finish()
4206}
4207
4208pub(crate) fn explicit_group_key_seed<K: Hash>(
4209    key: &K,
4210    caller: &'static std::panic::Location<'static>,
4211) -> slot::GroupKeySeed {
4212    let source_key = location_key(caller.file(), caller.line(), caller.column());
4213    let explicit_key = hash_key(key);
4214    slot::GroupKeySeed::keyed(source_key, explicit_key)
4215}
4216
4217#[cfg(test)]
4218#[path = "tests/mod.rs"]
4219mod tests;
4220
4221#[cfg(test)]
4222#[path = "tests/recursive_decrease_increase_test.rs"]
4223mod recursive_decrease_increase_test;
4224
4225pub mod collections;
4226pub mod hash;