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