Skip to main content

cranpose_core/
lib.rs

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