Skip to main content

cranpose_core/
lib.rs

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