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