Skip to main content

cranpose_core/
lib.rs

1#![doc = r"Core runtime pieces for the Cranpose experiment."]
2
3pub extern crate self as cranpose_core;
4
5pub mod composer_context;
6pub mod frame_clock;
7mod launched_effect;
8pub mod owned;
9pub mod platform;
10pub mod runtime;
11pub mod snapshot_double_index_heap;
12pub mod snapshot_id_set;
13pub mod snapshot_pinning;
14pub mod snapshot_state_observer;
15pub mod snapshot_v2;
16mod snapshot_weak_set;
17mod state;
18pub mod subcompose;
19
20pub use frame_clock::{FrameCallbackRegistration, FrameClock};
21pub use launched_effect::{
22    CancelToken, LaunchedEffectScope, __launched_effect_async_impl, __launched_effect_impl,
23};
24pub use owned::Owned;
25pub use platform::{Clock, RuntimeScheduler};
26pub use runtime::{
27    current_runtime_handle, schedule_frame, schedule_node_update, DefaultScheduler, Runtime,
28    RuntimeHandle, StateId, TaskHandle,
29};
30pub use snapshot_state_observer::SnapshotStateObserver;
31
32/// Runs the provided closure inside a mutable snapshot and applies the result.
33///
34/// UI event handlers should wrap state mutations in this helper so that
35/// recomposition observes the updates atomically once the snapshot applies.
36///
37/// # Important
38/// ALL UI event handlers (keyboard, mouse, touch, animations, custom modifier nodes)
39/// that modify `MutableState` MUST use this function or [`dispatch_ui_event`].
40/// Without it, state changes may not be visible to other snapshot contexts.
41pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
42    let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
43
44    // Mark that we're in an applied snapshot context
45    IN_APPLIED_SNAPSHOT.with(|c| c.set(true));
46    let value = snapshot.enter(block);
47    IN_APPLIED_SNAPSHOT.with(|c| c.set(false));
48
49    match snapshot.apply() {
50        snapshot_v2::SnapshotApplyResult::Success => Ok(value),
51        snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
52    }
53}
54
55/// Dispatches a UI event in a proper snapshot context.
56///
57/// This is a convenience wrapper around [`run_in_mutable_snapshot`] that returns
58/// `Option<T>` instead of `Result<T, &str>`.
59///
60/// # Example
61/// ```ignore
62/// // In a keyboard event handler:
63/// dispatch_ui_event(|| {
64///     text_field_state.edit(|buffer| {
65///         buffer.insert("a");
66///     });
67/// });
68/// ```
69pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
70    run_in_mutable_snapshot(block).ok()
71}
72
73// ─── Event Handler Context Tracking ─────────────────────────────────────────
74//
75// These thread-locals track whether code is running in an event handler context
76// and whether it's properly wrapped in run_in_mutable_snapshot. This allows
77// debug-mode warnings when state is modified without proper snapshot handling.
78
79thread_local! {
80    /// Tracks if we're in a UI event handler context (keyboard, mouse, etc.)
81    pub(crate) static IN_EVENT_HANDLER: Cell<bool> = const { Cell::new(false) };
82    /// Tracks if we're in a properly-applied mutable snapshot
83    pub(crate) static IN_APPLIED_SNAPSHOT: Cell<bool> = const { Cell::new(false) };
84}
85
86/// Marks the start of an event handler context.
87/// Call this at the start of keyboard/mouse/touch event handling.
88/// Use `run_in_mutable_snapshot` or `dispatch_ui_event` inside to ensure proper snapshot handling.
89pub fn enter_event_handler() {
90    IN_EVENT_HANDLER.with(|c| c.set(true));
91}
92
93/// Marks the end of an event handler context.
94pub fn exit_event_handler() {
95    IN_EVENT_HANDLER.with(|c| c.set(false));
96}
97
98/// Returns true if currently in an event handler context.
99pub fn in_event_handler() -> bool {
100    IN_EVENT_HANDLER.with(|c| c.get())
101}
102
103/// Returns true if currently in an applied snapshot context.
104pub fn in_applied_snapshot() -> bool {
105    IN_APPLIED_SNAPSHOT.with(|c| c.get())
106}
107
108#[cfg(test)]
109pub use runtime::{TestRuntime, TestScheduler};
110
111use crate::collections::map::HashMap;
112use crate::collections::map::HashSet;
113use crate::runtime::{runtime_handle_for, RuntimeId};
114use crate::state::{NeverEqual, SnapshotMutableState, UpdateScope};
115use std::any::Any;
116use std::cell::{Cell, Ref, RefCell, RefMut};
117use std::fmt;
118use std::hash::{Hash, Hasher};
119use std::marker::PhantomData;
120use std::ops::{Deref, DerefMut};
121use std::rc::{Rc, Weak}; // FUTURE(no_std): replace Rc/Weak with arena-managed handles.
122use std::sync::atomic::{AtomicUsize, Ordering};
123use std::sync::Arc;
124
125pub type Key = u64;
126pub type NodeId = usize;
127
128/// Stable identifier for a slot in the slot table.
129///
130/// Anchors provide positional stability: they maintain their identity even when
131/// the slot table is reorganized (e.g., during conditional rendering or group moves).
132/// This prevents effect states from being prematurely removed during recomposition.
133#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
134pub struct AnchorId(usize);
135
136impl AnchorId {
137    /// Invalid anchor that represents no anchor.
138    pub(crate) const INVALID: AnchorId = AnchorId(0);
139
140    /// Create a new anchor ID from a raw value.
141    pub(crate) fn new(id: usize) -> Self {
142        Self(id)
143    }
144
145    /// Check if this anchor is valid (non-zero).
146    pub fn is_valid(&self) -> bool {
147        self.0 != 0
148    }
149}
150
151pub(crate) type ScopeId = usize;
152type LocalKey = usize;
153pub(crate) type FrameCallbackId = u64;
154
155static NEXT_SCOPE_ID: AtomicUsize = AtomicUsize::new(1);
156static NEXT_LOCAL_KEY: AtomicUsize = AtomicUsize::new(1);
157
158fn next_scope_id() -> ScopeId {
159    NEXT_SCOPE_ID.fetch_add(1, Ordering::Relaxed)
160}
161
162fn next_local_key() -> LocalKey {
163    NEXT_LOCAL_KEY.fetch_add(1, Ordering::Relaxed)
164}
165
166pub(crate) struct RecomposeScopeInner {
167    id: ScopeId,
168    runtime: RuntimeHandle,
169    invalid: Cell<bool>,
170    enqueued: Cell<bool>,
171    active: Cell<bool>,
172    pending_recompose: Cell<bool>,
173    force_reuse: Cell<bool>,
174    force_recompose: Cell<bool>,
175    parent_hint: Cell<Option<NodeId>>,
176    recompose: RefCell<Option<RecomposeCallback>>,
177    local_stack: RefCell<Vec<LocalContext>>,
178}
179
180impl RecomposeScopeInner {
181    fn new(runtime: RuntimeHandle) -> Self {
182        Self {
183            id: next_scope_id(),
184            runtime,
185            invalid: Cell::new(false),
186            enqueued: Cell::new(false),
187            active: Cell::new(true),
188            pending_recompose: Cell::new(false),
189            force_reuse: Cell::new(false),
190            force_recompose: Cell::new(false),
191            parent_hint: Cell::new(None),
192            recompose: RefCell::new(None),
193            local_stack: RefCell::new(Vec::new()),
194        }
195    }
196}
197
198type RecomposeCallback = Box<dyn FnMut(&Composer) + 'static>;
199
200#[derive(Clone)]
201pub struct RecomposeScope {
202    inner: Rc<RecomposeScopeInner>, // FUTURE(no_std): replace Rc with arena-managed scope handles.
203}
204
205impl PartialEq for RecomposeScope {
206    fn eq(&self, other: &Self) -> bool {
207        Rc::ptr_eq(&self.inner, &other.inner)
208    }
209}
210
211impl Eq for RecomposeScope {}
212
213impl RecomposeScope {
214    fn new(runtime: RuntimeHandle) -> Self {
215        Self {
216            inner: Rc::new(RecomposeScopeInner::new(runtime)),
217        }
218    }
219
220    pub fn id(&self) -> ScopeId {
221        self.inner.id
222    }
223
224    pub fn is_invalid(&self) -> bool {
225        self.inner.invalid.get()
226    }
227
228    pub fn is_active(&self) -> bool {
229        self.inner.active.get()
230    }
231
232    fn invalidate(&self) {
233        self.inner.invalid.set(true);
234        if !self.inner.active.get() {
235            return;
236        }
237        if !self.inner.enqueued.replace(true) {
238            self.inner
239                .runtime
240                .register_invalid_scope(self.inner.id, Rc::downgrade(&self.inner));
241        }
242    }
243
244    fn mark_recomposed(&self) {
245        self.inner.invalid.set(false);
246        self.inner.force_reuse.set(false);
247        self.inner.force_recompose.set(false);
248        if self.inner.enqueued.replace(false) {
249            self.inner.runtime.mark_scope_recomposed(self.inner.id);
250        }
251        let pending = self.inner.pending_recompose.replace(false);
252        if pending {
253            if self.inner.active.get() {
254                self.invalidate();
255            } else {
256                self.inner.invalid.set(true);
257            }
258        }
259    }
260
261    fn downgrade(&self) -> Weak<RecomposeScopeInner> {
262        Rc::downgrade(&self.inner)
263    }
264
265    fn set_recompose(&self, callback: RecomposeCallback) {
266        *self.inner.recompose.borrow_mut() = Some(callback);
267    }
268
269    fn run_recompose(&self, composer: &Composer) {
270        let mut callback_cell = self.inner.recompose.borrow_mut();
271        if let Some(mut callback) = callback_cell.take() {
272            drop(callback_cell);
273            callback(composer);
274        }
275    }
276
277    fn snapshot_locals(&self, stack: &[LocalContext]) {
278        *self.inner.local_stack.borrow_mut() = stack.to_vec();
279    }
280
281    fn local_stack(&self) -> Vec<LocalContext> {
282        self.inner.local_stack.borrow().clone()
283    }
284
285    fn set_parent_hint(&self, parent: Option<NodeId>) {
286        self.inner.parent_hint.set(parent);
287    }
288
289    fn parent_hint(&self) -> Option<NodeId> {
290        self.inner.parent_hint.get()
291    }
292
293    pub fn deactivate(&self) {
294        if !self.inner.active.replace(false) {
295            return;
296        }
297        if self.inner.enqueued.replace(false) {
298            self.inner.runtime.mark_scope_recomposed(self.inner.id);
299        }
300    }
301
302    pub fn reactivate(&self) {
303        if self.inner.active.replace(true) {
304            return;
305        }
306        if self.inner.invalid.get() && !self.inner.enqueued.replace(true) {
307            self.inner
308                .runtime
309                .register_invalid_scope(self.inner.id, Rc::downgrade(&self.inner));
310        }
311    }
312
313    pub fn force_reuse(&self) {
314        self.inner.force_reuse.set(true);
315        self.inner.force_recompose.set(false);
316        self.inner.pending_recompose.set(true);
317    }
318
319    pub fn force_recompose(&self) {
320        self.inner.force_recompose.set(true);
321        self.inner.force_reuse.set(false);
322        self.inner.pending_recompose.set(false);
323    }
324
325    pub fn should_recompose(&self) -> bool {
326        if self.inner.force_recompose.replace(false) {
327            self.inner.force_reuse.set(false);
328            return true;
329        }
330        if self.inner.force_reuse.replace(false) {
331            return false;
332        }
333        self.is_invalid()
334    }
335}
336
337#[cfg(test)]
338impl RecomposeScope {
339    pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
340        Self::new(runtime)
341    }
342}
343
344#[derive(Debug, Clone, Copy, Default)]
345pub struct RecomposeOptions {
346    pub force_reuse: bool,
347    pub force_recompose: bool,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub enum NodeError {
352    Missing { id: NodeId },
353    TypeMismatch { id: NodeId, expected: &'static str },
354    MissingContext { id: NodeId, reason: &'static str },
355    AlreadyExists { id: NodeId },
356}
357
358impl std::fmt::Display for NodeError {
359    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        match self {
361            NodeError::Missing { id } => write!(f, "node {id} missing"),
362            NodeError::TypeMismatch { id, expected } => {
363                write!(f, "node {id} type mismatch; expected {expected}")
364            }
365            NodeError::MissingContext { id, reason } => {
366                write!(f, "missing context for node {id}: {reason}")
367            }
368            NodeError::AlreadyExists { id } => {
369                write!(f, "node {id} already exists")
370            }
371        }
372    }
373}
374
375impl std::error::Error for NodeError {}
376
377pub use subcompose::{
378    ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
379};
380
381#[derive(Copy, Clone, Debug, PartialEq, Eq)]
382pub enum Phase {
383    Compose,
384    Measure,
385    Layout,
386}
387
388pub use composer_context::with_composer as with_current_composer;
389
390#[allow(non_snake_case)]
391pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
392    composer_context::with_composer(f)
393}
394
395fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
396    composer_context::try_with_composer(f)
397}
398
399pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
400    with_current_composer(|composer| composer.with_key(key, |_| content()));
401}
402
403#[allow(non_snake_case)]
404pub fn withKey<K: Hash>(key: &K, content: impl FnOnce()) {
405    with_key(key, content)
406}
407
408pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Owned<T> {
409    with_current_composer(|composer| composer.remember(init))
410}
411
412/// Returns a [`MutableState`] that always holds the latest value.
413///
414/// The state **reference** is stable across recompositions; only the **value** updates.
415/// This allows closures to capture a stable reference while reading fresh values.
416///
417/// # Use Case
418/// Use when a `remember`ed closure needs to read a value that changes each recomposition
419/// without recreating the closure itself.
420///
421/// # Example
422/// ```rust,ignore
423/// let config = build_config(); // Rebuilt each recomposition
424/// let config_state = rememberUpdatedState(config);
425///
426/// // This closure is created once, reads latest config via state
427/// let callback = remember(|| {
428///     let cfg = config_state.clone();
429///     Rc::new(move || do_something(&cfg.value()))
430/// }).with(|c| c.clone());
431/// ```
432///
433/// # JC Equivalent
434/// ```kotlin
435/// @Composable
436/// fun <T> rememberUpdatedState(newValue: T): State<T> =
437///     remember { mutableStateOf(newValue) }.apply { value = newValue }
438/// ```
439#[allow(non_snake_case)]
440pub fn rememberUpdatedState<T: Clone + 'static>(value: T) -> MutableState<T> {
441    let state = remember(|| mutableStateOf(value.clone()));
442    state.with(|s| {
443        s.set(value);
444        *s
445    })
446}
447
448#[allow(non_snake_case)]
449pub fn withFrameNanos(callback: impl FnOnce(u64) + 'static) -> FrameCallbackRegistration {
450    with_current_composer(|composer| {
451        composer
452            .runtime_handle()
453            .frame_clock()
454            .with_frame_nanos(callback)
455    })
456}
457
458#[allow(non_snake_case)]
459pub fn withFrameMillis(callback: impl FnOnce(u64) + 'static) -> FrameCallbackRegistration {
460    with_current_composer(|composer| {
461        composer
462            .runtime_handle()
463            .frame_clock()
464            .with_frame_millis(callback)
465    })
466}
467
468#[allow(non_snake_case)]
469pub fn mutableStateOf<T: Clone + 'static>(initial: T) -> MutableState<T> {
470    // Get runtime handle from current composer if available, otherwise from global registry.
471    // IMPORTANT: We always use with_runtime() (not composer.mutable_state_of) because
472    // slot-based storage doesn't work for state objects that are cloned and shared
473    // across different composition contexts (like TextFieldState).
474    let runtime = with_current_composer_opt(|composer| composer.runtime_handle())
475        .or_else(runtime::current_runtime_handle)
476        .expect("mutableStateOf requires an active runtime. Create state inside a composition or after a Runtime is created.");
477    MutableState::with_runtime(initial, runtime)
478}
479
480/// Like [`mutableStateOf`] but returns `None` if no runtime is available.
481///
482/// Use this when you want to lazily initialize reactive state and gracefully
483/// handle the case where the runtime isn't yet available.
484#[allow(non_snake_case)]
485pub fn try_mutableStateOf<T: Clone + 'static>(initial: T) -> Option<MutableState<T>> {
486    let runtime = with_current_composer_opt(|composer| composer.runtime_handle())
487        .or_else(runtime::current_runtime_handle)?;
488    Some(MutableState::with_runtime(initial, runtime))
489}
490
491#[allow(non_snake_case)]
492pub fn mutableStateListOf<T, I>(values: I) -> SnapshotStateList<T>
493where
494    T: Clone + 'static,
495    I: IntoIterator<Item = T>,
496{
497    with_current_composer(move |composer| composer.mutable_state_list_of(values))
498}
499
500#[allow(non_snake_case)]
501pub fn mutableStateList<T: Clone + 'static>() -> SnapshotStateList<T> {
502    mutableStateListOf(std::iter::empty::<T>())
503}
504
505#[allow(non_snake_case)]
506pub fn mutableStateMapOf<K, V, I>(pairs: I) -> SnapshotStateMap<K, V>
507where
508    K: Clone + Eq + Hash + 'static,
509    V: Clone + 'static,
510    I: IntoIterator<Item = (K, V)>,
511{
512    with_current_composer(move |composer| composer.mutable_state_map_of(pairs))
513}
514
515#[allow(non_snake_case)]
516pub fn mutableStateMap<K, V>() -> SnapshotStateMap<K, V>
517where
518    K: Clone + Eq + Hash + 'static,
519    V: Clone + 'static,
520{
521    mutableStateMapOf(std::iter::empty::<(K, V)>())
522}
523
524#[allow(non_snake_case)]
525pub fn useState<T: Clone + 'static>(init: impl FnOnce() -> T) -> MutableState<T> {
526    remember(|| mutableStateOf(init())).with(|state| *state)
527}
528
529#[allow(deprecated)]
530#[deprecated(
531    since = "0.1.0",
532    note = "use useState(|| value) instead of use_state(|| value)"
533)]
534pub fn use_state<T: Clone + 'static>(init: impl FnOnce() -> T) -> MutableState<T> {
535    useState(init)
536}
537
538#[allow(non_snake_case)]
539pub fn derivedStateOf<T: 'static + Clone>(compute: impl Fn() -> T + 'static) -> State<T> {
540    with_current_composer(|composer| {
541        let key = location_key(file!(), line!(), column!());
542        composer.with_group(key, |composer| {
543            let should_recompute = composer
544                .current_recranpose_scope()
545                .map(|scope| scope.should_recompose())
546                .unwrap_or(true);
547            let runtime = composer.runtime_handle();
548            let compute_rc: Rc<dyn Fn() -> T> = Rc::new(compute); // FUTURE(no_std): replace Rc with arena-managed callbacks.
549            let derived =
550                composer.remember(|| DerivedState::new(runtime.clone(), compute_rc.clone()));
551            derived.update(|derived| {
552                derived.set_compute(compute_rc.clone());
553                if should_recompute {
554                    derived.recompute();
555                }
556            });
557            derived.with(|derived| derived.state.as_state())
558        })
559    })
560}
561
562pub struct ProvidedValue {
563    key: LocalKey,
564    #[allow(clippy::type_complexity)] // Closure returns trait object for flexible local values
565    apply: Box<dyn Fn(&Composer) -> Rc<dyn Any>>, // FUTURE(no_std): return arena-backed local storage pointer.
566}
567
568impl ProvidedValue {
569    fn into_entry(self, composer: &Composer) -> (LocalKey, Rc<dyn Any>) {
570        // FUTURE(no_std): avoid Rc allocation per entry.
571        let ProvidedValue { key, apply } = self;
572        let entry = apply(composer);
573        (key, entry)
574    }
575}
576
577#[allow(non_snake_case)]
578pub fn CompositionLocalProvider(
579    values: impl IntoIterator<Item = ProvidedValue>,
580    content: impl FnOnce(),
581) {
582    with_current_composer(|composer| {
583        let provided: Vec<ProvidedValue> = values.into_iter().collect(); // FUTURE(no_std): replace Vec with stack-allocated small vec.
584        composer.with_composition_locals(provided, |_composer| content());
585    })
586}
587
588struct LocalStateEntry<T: Clone + 'static> {
589    state: MutableState<T>,
590}
591
592impl<T: Clone + 'static> LocalStateEntry<T> {
593    fn new(initial: T, runtime: RuntimeHandle) -> Self {
594        Self {
595            state: MutableState::with_runtime(initial, runtime),
596        }
597    }
598
599    fn set(&self, value: T) {
600        self.state.replace(value);
601    }
602
603    fn value(&self) -> T {
604        self.state.value()
605    }
606}
607
608struct StaticLocalEntry<T: Clone + 'static> {
609    value: RefCell<T>,
610}
611
612impl<T: Clone + 'static> StaticLocalEntry<T> {
613    fn new(value: T) -> Self {
614        Self {
615            value: RefCell::new(value),
616        }
617    }
618
619    fn set(&self, value: T) {
620        *self.value.borrow_mut() = value;
621    }
622
623    fn value(&self) -> T {
624        self.value.borrow().clone()
625    }
626}
627
628#[derive(Clone)]
629pub struct CompositionLocal<T: Clone + 'static> {
630    key: LocalKey,
631    default: Rc<dyn Fn() -> T>, // FUTURE(no_std): store default provider in arena-managed cell.
632}
633
634impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
635    fn eq(&self, other: &Self) -> bool {
636        self.key == other.key
637    }
638}
639
640impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
641
642impl<T: Clone + 'static> CompositionLocal<T> {
643    pub fn provides(&self, value: T) -> ProvidedValue {
644        let key = self.key;
645        ProvidedValue {
646            key,
647            apply: Box::new(move |composer: &Composer| {
648                let runtime = composer.runtime_handle();
649                let entry_ref = composer
650                    .remember(|| Rc::new(LocalStateEntry::new(value.clone(), runtime.clone())));
651                entry_ref.update(|entry| entry.set(value.clone()));
652                entry_ref.with(|entry| entry.clone() as Rc<dyn Any>) // FUTURE(no_std): expose erased handle without Rc boxing.
653            }),
654        }
655    }
656
657    pub fn current(&self) -> T {
658        with_current_composer(|composer| composer.read_composition_local(self))
659    }
660
661    pub fn default_value(&self) -> T {
662        (self.default)()
663    }
664}
665
666#[allow(non_snake_case)]
667pub fn compositionLocalOf<T: Clone + 'static>(
668    default: impl Fn() -> T + 'static,
669) -> CompositionLocal<T> {
670    CompositionLocal {
671        key: next_local_key(),
672        default: Rc::new(default), // FUTURE(no_std): allocate default provider in arena storage.
673    }
674}
675
676/// A `StaticCompositionLocal` is a CompositionLocal that is optimized for values that are
677/// unlikely to change. Unlike `CompositionLocal`, reads of a `StaticCompositionLocal` are not
678/// tracked by the recomposition system, which means:
679/// - Reading `.current()` does NOT establish a subscription
680/// - Changing the provided value does NOT automatically invalidate readers
681/// - This makes it more efficient for truly static values
682///
683/// This matches the API of Jetpack Compose's `staticCompositionLocalOf` but with simplified
684/// semantics. Use this for values that are guaranteed to never change during the lifetime of
685/// the CompositionLocalProvider scope (e.g., application-wide constants, configuration)
686#[derive(Clone)]
687pub struct StaticCompositionLocal<T: Clone + 'static> {
688    key: LocalKey,
689    default: Rc<dyn Fn() -> T>, // FUTURE(no_std): store default provider in arena-managed cell.
690}
691
692impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
693    fn eq(&self, other: &Self) -> bool {
694        self.key == other.key
695    }
696}
697
698impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
699
700impl<T: Clone + 'static> StaticCompositionLocal<T> {
701    pub fn provides(&self, value: T) -> ProvidedValue {
702        let key = self.key;
703        ProvidedValue {
704            key,
705            apply: Box::new(move |composer: &Composer| {
706                // For static locals, we don't use MutableState - just store the value directly
707                // This means reads won't be tracked, and changes will cause full subtree recomposition
708                let entry_ref = composer.remember(|| Rc::new(StaticLocalEntry::new(value.clone())));
709                entry_ref.update(|entry| entry.set(value.clone()));
710                entry_ref.with(|entry| entry.clone() as Rc<dyn Any>) // FUTURE(no_std): expose erased handle without Rc boxing.
711            }),
712        }
713    }
714
715    pub fn current(&self) -> T {
716        with_current_composer(|composer| composer.read_static_composition_local(self))
717    }
718
719    pub fn default_value(&self) -> T {
720        (self.default)()
721    }
722}
723
724#[allow(non_snake_case)]
725pub fn staticCompositionLocalOf<T: Clone + 'static>(
726    default: impl Fn() -> T + 'static,
727) -> StaticCompositionLocal<T> {
728    StaticCompositionLocal {
729        key: next_local_key(),
730        default: Rc::new(default), // FUTURE(no_std): allocate default provider in arena storage.
731    }
732}
733
734#[derive(Default)]
735struct DisposableEffectState {
736    key: Option<Key>,
737    cleanup: Option<Box<dyn FnOnce()>>,
738}
739
740impl DisposableEffectState {
741    fn should_run(&self, key: Key) -> bool {
742        match self.key {
743            Some(current) => current != key,
744            None => true,
745        }
746    }
747
748    fn set_key(&mut self, key: Key) {
749        self.key = Some(key);
750    }
751
752    fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
753        self.cleanup = cleanup;
754    }
755
756    fn run_cleanup(&mut self) {
757        if let Some(cleanup) = self.cleanup.take() {
758            cleanup();
759        }
760    }
761}
762
763impl Drop for DisposableEffectState {
764    fn drop(&mut self) {
765        self.run_cleanup();
766    }
767}
768
769#[derive(Clone, Copy, Debug, Default)]
770pub struct DisposableEffectScope;
771
772#[derive(Default)]
773pub struct DisposableEffectResult {
774    cleanup: Option<Box<dyn FnOnce()>>,
775}
776
777impl DisposableEffectScope {
778    pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
779        DisposableEffectResult::new(cleanup)
780    }
781}
782
783impl DisposableEffectResult {
784    pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
785        Self {
786            cleanup: Some(Box::new(cleanup)),
787        }
788    }
789
790    fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
791        self.cleanup
792    }
793}
794
795#[allow(non_snake_case)]
796pub fn SideEffect(effect: impl FnOnce() + 'static) {
797    with_current_composer(|composer| composer.register_side_effect(effect));
798}
799
800pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
801where
802    K: Hash,
803    F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
804{
805    // Create a group using the caller's location to ensure each DisposableEffect
806    // gets its own slot table entry, even in conditional branches
807    with_current_composer(|composer| {
808        composer.with_group(group_key, |composer| {
809            let key_hash = hash_key(&keys);
810            let state = composer.remember(DisposableEffectState::default);
811            if state.with(|state| state.should_run(key_hash)) {
812                state.update(|state| {
813                    state.run_cleanup();
814                    state.set_key(key_hash);
815                });
816                let state_for_effect = state.clone();
817                let mut effect_opt = Some(effect);
818                composer.register_side_effect(move || {
819                    if let Some(effect) = effect_opt.take() {
820                        let result = effect(DisposableEffectScope);
821                        state_for_effect.update(|state| state.set_cleanup(result.into_cleanup()));
822                    }
823                });
824            }
825        });
826    });
827}
828
829#[macro_export]
830macro_rules! DisposableEffect {
831    ($keys:expr, $effect:expr) => {
832        $crate::__disposable_effect_impl(
833            $crate::location_key(file!(), line!(), column!()),
834            $keys,
835            $effect,
836        )
837    };
838}
839
840pub fn with_node_mut<N: Node + 'static, R>(
841    id: NodeId,
842    f: impl FnOnce(&mut N) -> R,
843) -> Result<R, NodeError> {
844    with_current_composer(|composer| composer.with_node_mut(id, f))
845}
846
847pub fn push_parent(id: NodeId) {
848    with_current_composer(|composer| composer.push_parent(id));
849}
850
851pub fn pop_parent() {
852    with_current_composer(|composer| composer.pop_parent());
853}
854
855// ═══════════════════════════════════════════════════════════════════════════
856// Public SlotStorage trait and newtypes
857// ═══════════════════════════════════════════════════════════════════════════
858
859mod slot_storage;
860pub use slot_storage::{GroupId, SlotStorage, StartGroup, ValueSlotId};
861
862pub mod chunked_slot_storage;
863pub mod hierarchical_slot_storage;
864pub mod slot_backend;
865pub mod split_slot_storage;
866pub use slot_backend::{make_backend, SlotBackend, SlotBackendKind};
867
868// ═══════════════════════════════════════════════════════════════════════════
869// SlotTable: gap-buffer-based implementation
870// ═══════════════════════════════════════════════════════════════════════════
871
872pub mod slot_table;
873pub use slot_table::SlotTable;
874
875pub trait Node: Any {
876    fn mount(&mut self) {}
877    fn update(&mut self) {}
878    fn unmount(&mut self) {}
879    fn insert_child(&mut self, _child: NodeId) {}
880    fn remove_child(&mut self, _child: NodeId) {}
881    fn move_child(&mut self, _from: usize, _to: usize) {}
882    fn update_children(&mut self, _children: &[NodeId]) {}
883    fn children(&self) -> Vec<NodeId> {
884        Vec::new()
885    }
886    /// Called after the node is created to record its own ID.
887    /// Useful for nodes that need to store their ID for later operations.
888    fn set_node_id(&mut self, _id: NodeId) {}
889    /// Called when this node is attached to a parent.
890    /// Nodes with parent tracking should set their parent reference here.
891    fn on_attached_to_parent(&mut self, _parent: NodeId) {}
892    /// Called when this node is removed from its parent.
893    /// Nodes with parent tracking should clear their parent reference here.
894    fn on_removed_from_parent(&mut self) {}
895    /// Get this node's parent ID (for nodes that track parents).
896    /// Returns None if node has no parent or doesn't track parents.
897    fn parent(&self) -> Option<NodeId> {
898        None
899    }
900    /// Mark this node as needing layout (for nodes with dirty flags).
901    /// Called during bubbling to propagate dirtiness up the tree.
902    fn mark_needs_layout(&self) {}
903    /// Check if this node needs layout (for nodes with dirty flags).
904    fn needs_layout(&self) -> bool {
905        false
906    }
907    /// Mark this node as needing measure (size may have changed).
908    /// Called during bubbling when children are added/removed.
909    fn mark_needs_measure(&self) {}
910    /// Check if this node needs measure (for nodes with dirty flags).
911    fn needs_measure(&self) -> bool {
912        false
913    }
914    /// Mark this node as needing semantics recomputation.
915    fn mark_needs_semantics(&self) {}
916    /// Check if this node needs semantics recomputation.
917    fn needs_semantics(&self) -> bool {
918        false
919    }
920    /// Set parent reference for dirty flag bubbling ONLY.
921    /// This is a minimal version of on_attached_to_parent that doesn't trigger
922    /// registry updates or other side effects. Used during measurement when we
923    /// need to establish parent connections for bubble_measure_dirty without
924    /// causing the full attachment lifecycle.
925    ///
926    /// Default: delegates to on_attached_to_parent for backward compatibility.
927    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
928        self.on_attached_to_parent(parent);
929    }
930}
931
932/// Unified API for bubbling layout dirty flags from a node to the root (Applier context).
933///
934/// This is the canonical function for dirty bubbling during the apply phase (structural changes).
935/// Call this after mutations like insert/remove/move that happen during apply.
936///
937/// # Behavior
938/// 1. Marks the starting node as needing layout
939/// 2. Walks up the parent chain, marking each ancestor
940/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
941/// 4. Stops at the root (node with no parent)
942///
943/// # Performance
944/// This function is O(height) in the worst case, but typically O(1) due to early exit
945/// when encountering an already-dirty ancestor.
946///
947/// # Usage
948/// - Call from composer mutations (insert/remove/move) during apply phase
949/// - Call from applier-level operations that modify the tree structure
950pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
951    bubble_layout_dirty_applier(applier, node_id);
952}
953
954/// Unified API for bubbling measure dirty flags from a node to the root (Applier context).
955///
956/// Call this when a node's size may have changed (children added/removed, modifier changed).
957/// This ensures that measure_layout will increment the cache epoch and re-measure the subtree.
958///
959/// # Behavior
960/// 1. Marks the starting node as needing measure
961/// 2. Walks up the parent chain, marking each ancestor
962/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
963/// 4. Stops at the root (node with no parent)
964pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
965    bubble_measure_dirty_applier(applier, node_id);
966}
967
968/// Unified API for bubbling semantics dirty flags from a node to the root (Applier context).
969///
970/// This mirrors [`bubble_layout_dirty`] but toggles semantics-specific dirty
971/// flags instead of layout ones, allowing semantics updates to propagate during
972/// the apply phase without forcing layout work.
973pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
974    bubble_semantics_dirty_applier(applier, node_id);
975}
976
977/// Schedules semantics bubbling for a node using the active composer if present.
978///
979/// This defers the work to the apply phase where we can safely mutate the
980/// applier tree without re-entrantly borrowing the composer during composition.
981pub fn queue_semantics_invalidation(node_id: NodeId) {
982    let _ = composer_context::try_with_composer(|composer| {
983        composer.enqueue_semantics_invalidation(node_id);
984    });
985}
986
987/// Unified API for bubbling layout dirty flags from a node to the root (Composer context).
988///
989/// This is the canonical function for dirty bubbling during composition (property changes).
990/// Call this after property changes that happen during composition via with_node_mut.
991///
992/// # Behavior
993/// 1. Marks the starting node as needing layout
994/// 2. Walks up the parent chain, marking each ancestor
995/// 3. Stops when it reaches a node that's already dirty (O(1) optimization)
996/// 4. Stops at the root (node with no parent)
997///
998/// # Performance
999/// This function is O(height) in the worst case, but typically O(1) due to early exit
1000/// when encountering an already-dirty ancestor.
1001///
1002/// # Type Requirements
1003/// The node type N must implement Node (which includes mark_needs_layout, parent, etc.).
1004/// Typically this will be LayoutNode or similar layout-aware node types.
1005///
1006/// # Usage
1007/// - Call from property setters during composition (e.g., set_modifier, set_measure_policy)
1008/// - Call from widget composition when layout-affecting state changes
1009pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1010    bubble_layout_dirty_composer::<N>(node_id);
1011}
1012
1013/// Unified API for bubbling semantics dirty flags from a node to the root (Composer context).
1014///
1015/// This mirrors [`bubble_layout_dirty_in_composer`] but routes through the semantics
1016/// dirty flag instead of the layout one. Modifier nodes can request semantics
1017/// invalidations without triggering measure/layout work, and the runtime can
1018/// query the root to determine whether the semantics tree needs rebuilding.
1019pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1020    bubble_semantics_dirty_composer::<N>(node_id);
1021}
1022
1023/// Internal implementation for applier-based bubbling.
1024fn bubble_layout_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1025    // First, mark the starting node dirty (critical!)
1026    // This ensures root gets marked even if it has no parent
1027    if let Ok(node) = applier.get_mut(node_id) {
1028        node.mark_needs_layout();
1029    }
1030
1031    // Then bubble up to ancestors
1032    loop {
1033        // Get parent of current node
1034        let parent_id = match applier.get_mut(node_id) {
1035            Ok(node) => node.parent(),
1036            Err(_) => None,
1037        };
1038
1039        match parent_id {
1040            Some(pid) => {
1041                // Mark parent as needing layout
1042                if let Ok(parent) = applier.get_mut(pid) {
1043                    if !parent.needs_layout() {
1044                        parent.mark_needs_layout();
1045                        node_id = pid; // Continue bubbling
1046                    } else {
1047                        break; // Already dirty, stop
1048                    }
1049                } else {
1050                    break;
1051                }
1052            }
1053            None => break, // No parent, stop
1054        }
1055    }
1056}
1057
1058/// Internal implementation for applier-based bubbling of measure dirtiness.
1059fn bubble_measure_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1060    // First, mark the starting node as needing measure
1061    if let Ok(node) = applier.get_mut(node_id) {
1062        node.mark_needs_measure();
1063    }
1064
1065    // Then bubble up to ancestors
1066    loop {
1067        // Get parent of current node
1068        let parent_id = match applier.get_mut(node_id) {
1069            Ok(node) => node.parent(),
1070            Err(_) => None,
1071        };
1072
1073        match parent_id {
1074            Some(pid) => {
1075                // Mark parent as needing measure
1076                if let Ok(parent) = applier.get_mut(pid) {
1077                    if !parent.needs_measure() {
1078                        parent.mark_needs_measure();
1079                        node_id = pid; // Continue bubbling
1080                    } else {
1081                        break; // Already dirty, stop
1082                    }
1083                } else {
1084                    break;
1085                }
1086            }
1087            None => {
1088                break; // No parent, stop
1089            }
1090        }
1091    }
1092}
1093
1094/// Internal implementation for applier-based bubbling of semantics dirtiness.
1095fn bubble_semantics_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1096    if let Ok(node) = applier.get_mut(node_id) {
1097        node.mark_needs_semantics();
1098    }
1099
1100    loop {
1101        let parent_id = match applier.get_mut(node_id) {
1102            Ok(node) => node.parent(),
1103            Err(_) => None,
1104        };
1105
1106        match parent_id {
1107            Some(pid) => {
1108                if let Ok(parent) = applier.get_mut(pid) {
1109                    if !parent.needs_semantics() {
1110                        parent.mark_needs_semantics();
1111                        node_id = pid;
1112                    } else {
1113                        break;
1114                    }
1115                } else {
1116                    break;
1117                }
1118            }
1119            None => break,
1120        }
1121    }
1122}
1123
1124/// Internal implementation for composer-based bubbling.
1125/// This uses with_node_mut and works during composition with a concrete node type.
1126/// The node type N must implement Node (which includes mark_needs_layout, parent, etc.).
1127fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1128    // Mark the starting node dirty
1129    let _ = with_node_mut(node_id, |node: &mut N| {
1130        node.mark_needs_layout();
1131    });
1132
1133    // Then bubble up to ancestors
1134    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1135        let parent_id = pid;
1136
1137        // Mark parent as needing layout
1138        let should_continue = with_node_mut(parent_id, |node: &mut N| {
1139            if !node.needs_layout() {
1140                node.mark_needs_layout();
1141                true // Continue bubbling
1142            } else {
1143                false // Already dirty, stop (O(1) optimization)
1144            }
1145        })
1146        .unwrap_or(false);
1147
1148        if should_continue {
1149            node_id = parent_id;
1150        } else {
1151            break;
1152        }
1153    }
1154}
1155
1156/// Internal implementation for composer-based bubbling of semantics dirtiness.
1157fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1158    // Mark the starting node semantics-dirty.
1159    let _ = with_node_mut(node_id, |node: &mut N| {
1160        node.mark_needs_semantics();
1161    });
1162
1163    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1164        let parent_id = pid;
1165
1166        let should_continue = with_node_mut(parent_id, |node: &mut N| {
1167            if !node.needs_semantics() {
1168                node.mark_needs_semantics();
1169                true
1170            } else {
1171                false
1172            }
1173        })
1174        .unwrap_or(false);
1175
1176        if should_continue {
1177            node_id = parent_id;
1178        } else {
1179            break;
1180        }
1181    }
1182}
1183
1184impl dyn Node {
1185    pub fn as_any_mut(&mut self) -> &mut dyn Any {
1186        self
1187    }
1188}
1189
1190pub trait Applier: Any {
1191    fn create(&mut self, node: Box<dyn Node>) -> NodeId;
1192    fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
1193    fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
1194
1195    /// Inserts a node with a pre-assigned ID.
1196    ///
1197    /// This is used for virtual nodes whose IDs are allocated separately
1198    /// (e.g., via allocate_virtual_node_id()). Unlike `create()` which assigns
1199    /// a new ID, this method uses the provided ID.
1200    ///
1201    /// Returns Ok(()) if successful, or an error if the ID is already in use.
1202    fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
1203
1204    fn as_any(&self) -> &dyn Any
1205    where
1206        Self: Sized,
1207    {
1208        self
1209    }
1210
1211    fn as_any_mut(&mut self) -> &mut dyn Any
1212    where
1213        Self: Sized,
1214    {
1215        self
1216    }
1217}
1218
1219pub(crate) type Command = Box<dyn FnMut(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
1220
1221#[derive(Default)]
1222pub struct MemoryApplier {
1223    nodes: Vec<Option<Box<dyn Node>>>, // FUTURE(no_std): migrate to arena-backed node storage.
1224    /// Storage for high-ID nodes (like virtual nodes with IDs starting at 0xFFFFFFFF00000000)
1225    /// that can't be stored in the Vec without causing capacity overflow.
1226    high_id_nodes: std::collections::HashMap<NodeId, Box<dyn Node>>,
1227    layout_runtime: Option<RuntimeHandle>,
1228    slots: SlotBackend,
1229}
1230
1231impl MemoryApplier {
1232    pub fn new() -> Self {
1233        Self {
1234            nodes: Vec::new(),
1235            high_id_nodes: std::collections::HashMap::new(),
1236            layout_runtime: None,
1237            slots: SlotBackend::default(),
1238        }
1239    }
1240
1241    pub fn slots(&mut self) -> &mut SlotBackend {
1242        &mut self.slots
1243    }
1244
1245    pub fn with_node<N: Node + 'static, R>(
1246        &mut self,
1247        id: NodeId,
1248        f: impl FnOnce(&mut N) -> R,
1249    ) -> Result<R, NodeError> {
1250        let slot = self
1251            .nodes
1252            .get_mut(id)
1253            .ok_or(NodeError::Missing { id })?
1254            .as_deref_mut()
1255            .ok_or(NodeError::Missing { id })?;
1256        let typed = slot
1257            .as_any_mut()
1258            .downcast_mut::<N>()
1259            .ok_or(NodeError::TypeMismatch {
1260                id,
1261                expected: std::any::type_name::<N>(),
1262            })?;
1263        Ok(f(typed))
1264    }
1265
1266    pub fn len(&self) -> usize {
1267        self.nodes.iter().filter(|n| n.is_some()).count()
1268    }
1269
1270    pub fn is_empty(&self) -> bool {
1271        self.len() == 0
1272    }
1273
1274    pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
1275        self.layout_runtime = Some(handle);
1276    }
1277
1278    pub fn clear_runtime_handle(&mut self) {
1279        self.layout_runtime = None;
1280    }
1281
1282    pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
1283        self.layout_runtime.clone()
1284    }
1285
1286    pub fn dump_tree(&self, root: Option<NodeId>) -> String {
1287        let mut output = String::new();
1288        if let Some(root_id) = root {
1289            self.dump_node(&mut output, root_id, 0);
1290        } else {
1291            output.push_str("(no root)\n");
1292        }
1293        output
1294    }
1295
1296    fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
1297        let indent = "  ".repeat(depth);
1298        if let Some(Some(node)) = self.nodes.get(id) {
1299            let type_name = std::any::type_name_of_val(&**node);
1300            output.push_str(&format!("{}[{}] {}\n", indent, id, type_name));
1301
1302            let children = node.children();
1303            for child_id in children {
1304                self.dump_node(output, child_id, depth + 1);
1305            }
1306        } else {
1307            output.push_str(&format!("{}[{}] (missing)\n", indent, id));
1308        }
1309    }
1310}
1311
1312impl Applier for MemoryApplier {
1313    fn create(&mut self, node: Box<dyn Node>) -> NodeId {
1314        let id = self.nodes.len();
1315        self.nodes.push(Some(node));
1316        id
1317    }
1318
1319    fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
1320        // Check HashMap first for high-ID nodes (virtual nodes)
1321        if let Some(node) = self.high_id_nodes.get_mut(&id) {
1322            return Ok(node.as_mut());
1323        }
1324        // Fall back to Vec for normal IDs
1325        let slot = self
1326            .nodes
1327            .get_mut(id)
1328            .ok_or(NodeError::Missing { id })?
1329            .as_deref_mut()
1330            .ok_or(NodeError::Missing { id })?;
1331        Ok(slot)
1332    }
1333
1334    fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
1335        // Check if this is a high-ID node
1336        if self.high_id_nodes.contains_key(&id) {
1337            // Get children before removing
1338            let children = self
1339                .high_id_nodes
1340                .get(&id)
1341                .map(|n| n.children())
1342                .unwrap_or_default();
1343
1344            // Recursively remove children
1345            for child_id in children {
1346                let is_owned = self
1347                    .get_mut(child_id)
1348                    .map(|child| child.parent() == Some(id))
1349                    .unwrap_or(false);
1350                if is_owned {
1351                    let _ = self.remove(child_id);
1352                }
1353            }
1354
1355            self.high_id_nodes.remove(&id);
1356            return Ok(());
1357        }
1358
1359        // Normal Vec-based removal for low IDs
1360        let children = {
1361            let slot = self.nodes.get(id).ok_or(NodeError::Missing { id })?;
1362            if let Some(node) = slot {
1363                node.children()
1364            } else {
1365                return Err(NodeError::Missing { id });
1366            }
1367        };
1368
1369        // Recursively remove children, BUT ONLY if they are still owned by this node.
1370        for child_id in children {
1371            let is_owned = self
1372                .get_mut(child_id)
1373                .map(|child| child.parent() == Some(id))
1374                .unwrap_or(false);
1375
1376            if is_owned {
1377                let _ = self.remove(child_id);
1378            }
1379        }
1380
1381        let slot = self.nodes.get_mut(id).ok_or(NodeError::Missing { id })?;
1382        slot.take();
1383        Ok(())
1384    }
1385
1386    fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
1387        // Use HashMap for high IDs (virtual nodes) to avoid Vec capacity overflow
1388        // Virtual node IDs start at a very high value that can't fit in a Vec
1389        const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000; // 1 billion
1390
1391        if id >= HIGH_ID_THRESHOLD {
1392            if self.high_id_nodes.contains_key(&id) {
1393                return Err(NodeError::AlreadyExists { id });
1394            }
1395            self.high_id_nodes.insert(id, node);
1396            Ok(())
1397        } else {
1398            // Normal Vec-based insertion for low IDs
1399            if id >= self.nodes.len() {
1400                self.nodes.resize_with(id + 1, || None);
1401            }
1402
1403            if self.nodes[id].is_some() {
1404                return Err(NodeError::AlreadyExists { id });
1405            }
1406
1407            self.nodes[id] = Some(node);
1408            Ok(())
1409        }
1410    }
1411}
1412
1413pub trait ApplierHost {
1414    fn borrow_dyn(&self) -> RefMut<'_, dyn Applier>;
1415}
1416
1417pub struct ConcreteApplierHost<A: Applier + 'static> {
1418    inner: RefCell<A>,
1419}
1420
1421impl<A: Applier + 'static> ConcreteApplierHost<A> {
1422    pub fn new(applier: A) -> Self {
1423        Self {
1424            inner: RefCell::new(applier),
1425        }
1426    }
1427
1428    pub fn borrow_typed(&self) -> RefMut<'_, A> {
1429        self.inner.borrow_mut()
1430    }
1431
1432    pub fn try_borrow_typed(&self) -> Result<RefMut<'_, A>, std::cell::BorrowMutError> {
1433        self.inner.try_borrow_mut()
1434    }
1435
1436    pub fn into_inner(self) -> A {
1437        self.inner.into_inner()
1438    }
1439}
1440
1441impl<A: Applier + 'static> ApplierHost for ConcreteApplierHost<A> {
1442    fn borrow_dyn(&self) -> RefMut<'_, dyn Applier> {
1443        RefMut::map(self.inner.borrow_mut(), |applier| {
1444            applier as &mut dyn Applier
1445        })
1446    }
1447}
1448
1449pub struct ApplierGuard<'a, A: Applier + 'static> {
1450    inner: RefMut<'a, A>,
1451}
1452
1453impl<'a, A: Applier + 'static> ApplierGuard<'a, A> {
1454    fn new(inner: RefMut<'a, A>) -> Self {
1455        Self { inner }
1456    }
1457}
1458
1459impl<'a, A: Applier + 'static> Deref for ApplierGuard<'a, A> {
1460    type Target = A;
1461
1462    fn deref(&self) -> &Self::Target {
1463        &self.inner
1464    }
1465}
1466
1467impl<'a, A: Applier + 'static> DerefMut for ApplierGuard<'a, A> {
1468    fn deref_mut(&mut self) -> &mut Self::Target {
1469        &mut self.inner
1470    }
1471}
1472
1473pub struct SlotsHost {
1474    inner: RefCell<SlotBackend>,
1475}
1476
1477impl SlotsHost {
1478    pub fn new(storage: SlotBackend) -> Self {
1479        Self {
1480            inner: RefCell::new(storage),
1481        }
1482    }
1483
1484    pub fn borrow(&self) -> Ref<'_, SlotBackend> {
1485        self.inner.borrow()
1486    }
1487
1488    pub fn borrow_mut(&self) -> RefMut<'_, SlotBackend> {
1489        self.inner.borrow_mut()
1490    }
1491
1492    pub fn take(&self) -> SlotBackend {
1493        std::mem::take(&mut *self.inner.borrow_mut())
1494    }
1495}
1496
1497pub(crate) struct ComposerCore {
1498    slots: Rc<SlotsHost>,
1499    slots_override: RefCell<Vec<Rc<SlotsHost>>>,
1500    applier: Rc<dyn ApplierHost>,
1501    runtime: RuntimeHandle,
1502    observer: SnapshotStateObserver,
1503    parent_stack: RefCell<Vec<ParentFrame>>,
1504    subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
1505    root: Cell<Option<NodeId>>,
1506    commands: RefCell<Vec<Command>>,
1507    scope_stack: RefCell<Vec<RecomposeScope>>,
1508    local_stack: RefCell<Vec<LocalContext>>,
1509    side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
1510    pending_scope_options: RefCell<Option<RecomposeOptions>>,
1511    phase: Cell<Phase>,
1512    last_node_reused: Cell<Option<bool>>,
1513    recranpose_parent_hint: Cell<Option<NodeId>>,
1514    _not_send: PhantomData<*const ()>,
1515}
1516
1517impl ComposerCore {
1518    pub fn new(
1519        slots: Rc<SlotsHost>,
1520        applier: Rc<dyn ApplierHost>,
1521        runtime: RuntimeHandle,
1522        observer: SnapshotStateObserver,
1523        root: Option<NodeId>,
1524    ) -> Self {
1525        // Initialize parent_stack with root if provided.
1526        // This enables subcomposed nodes to be properly attached as children of the root
1527        // during composition via normal insert_child commands from pop_parent.
1528        // IMPORTANT: When using this, do NOT use set_active_children - let the composer
1529        // manage children naturally to avoid conflicts.
1530        let parent_stack = if let Some(root_id) = root {
1531            vec![ParentFrame {
1532                id: root_id,
1533                remembered: Owned::new(ParentChildren::default()),
1534                previous: Vec::new(),
1535                new_children: Vec::new(),
1536            }]
1537        } else {
1538            Vec::new()
1539        };
1540
1541        Self {
1542            slots,
1543            slots_override: RefCell::new(Vec::new()),
1544            applier,
1545            runtime,
1546            observer,
1547            parent_stack: RefCell::new(parent_stack),
1548            subcompose_stack: RefCell::new(Vec::new()),
1549            root: Cell::new(root),
1550            commands: RefCell::new(Vec::new()),
1551            scope_stack: RefCell::new(Vec::new()),
1552            local_stack: RefCell::new(Vec::new()),
1553            side_effects: RefCell::new(Vec::new()),
1554            pending_scope_options: RefCell::new(None),
1555            phase: Cell::new(Phase::Compose),
1556            last_node_reused: Cell::new(None),
1557            recranpose_parent_hint: Cell::new(None),
1558            _not_send: PhantomData,
1559        }
1560    }
1561}
1562
1563#[derive(Clone)]
1564pub struct Composer {
1565    core: Rc<ComposerCore>,
1566}
1567
1568impl Composer {
1569    pub fn new(
1570        slots: Rc<SlotsHost>,
1571        applier: Rc<dyn ApplierHost>,
1572        runtime: RuntimeHandle,
1573        observer: SnapshotStateObserver,
1574        root: Option<NodeId>,
1575    ) -> Self {
1576        let core = Rc::new(ComposerCore::new(slots, applier, runtime, observer, root));
1577        Self { core }
1578    }
1579
1580    pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
1581        Self { core }
1582    }
1583
1584    pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
1585        Rc::clone(&self.core)
1586    }
1587
1588    fn observer(&self) -> SnapshotStateObserver {
1589        self.core.observer.clone()
1590    }
1591
1592    fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
1593        let observer = self.observer();
1594        let scope_clone = scope.clone();
1595        observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
1596    }
1597
1598    fn active_slots_host(&self) -> Rc<SlotsHost> {
1599        self.core
1600            .slots_override
1601            .borrow()
1602            .last()
1603            .cloned()
1604            .unwrap_or_else(|| Rc::clone(&self.core.slots))
1605    }
1606
1607    fn with_slots<R>(&self, f: impl FnOnce(&SlotBackend) -> R) -> R {
1608        let host = self.active_slots_host();
1609        let slots = host.borrow();
1610        f(&slots)
1611    }
1612
1613    fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotBackend) -> R) -> R {
1614        let host = self.active_slots_host();
1615        let mut slots = host.borrow_mut();
1616        f(&mut slots)
1617    }
1618
1619    fn with_slot_override<R>(&self, slots: Rc<SlotsHost>, f: impl FnOnce(&Composer) -> R) -> R {
1620        self.core.slots_override.borrow_mut().push(slots);
1621        struct Guard {
1622            core: Rc<ComposerCore>,
1623        }
1624        impl Drop for Guard {
1625            fn drop(&mut self) {
1626                self.core.slots_override.borrow_mut().pop();
1627            }
1628        }
1629        let guard = Guard {
1630            core: self.clone_core(),
1631        };
1632        let result = f(self);
1633        drop(guard);
1634        result
1635    }
1636
1637    fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
1638        self.core.parent_stack.borrow_mut()
1639    }
1640
1641    fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
1642        self.core.subcompose_stack.borrow_mut()
1643    }
1644
1645    fn commands_mut(&self) -> RefMut<'_, Vec<Command>> {
1646        self.core.commands.borrow_mut()
1647    }
1648
1649    pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
1650        self.commands_mut()
1651            .push(Box::new(move |applier: &mut dyn Applier| {
1652                bubble_semantics_dirty(applier, id);
1653                Ok(())
1654            }));
1655    }
1656
1657    fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
1658        self.core.scope_stack.borrow_mut()
1659    }
1660
1661    fn local_stack(&self) -> RefMut<'_, Vec<LocalContext>> {
1662        self.core.local_stack.borrow_mut()
1663    }
1664
1665    fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
1666        self.core.side_effects.borrow_mut()
1667    }
1668
1669    fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
1670        self.core.pending_scope_options.borrow_mut()
1671    }
1672
1673    fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
1674        self.core.applier.borrow_dyn()
1675    }
1676
1677    /// Registers a virtual node in the Applier.
1678    ///
1679    /// This is used by SubcomposeLayoutNode to register virtual container nodes
1680    /// so that subsequent insert_child commands can find them and attach children.
1681    /// Without this, virtual nodes would only exist in SubcomposeLayoutNodeInner.virtual_nodes
1682    /// and applier.get_mut(virtual_node_id) would fail, breaking child attachment.
1683    pub fn register_virtual_node(
1684        &self,
1685        node_id: NodeId,
1686        node: Box<dyn Node>,
1687    ) -> Result<(), NodeError> {
1688        let mut applier = self.borrow_applier();
1689        applier.insert_with_id(node_id, node)
1690    }
1691
1692    /// Checks if a node has no parent (is a root node).
1693    /// Used by SubcomposeMeasureScope to filter subcompose results.
1694    pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
1695        let mut applier = self.borrow_applier();
1696        match applier.get_mut(node_id) {
1697            Ok(node) => node.parent().is_none(),
1698            Err(_) => true, // If we can't find the node, treat it as root (conservative)
1699        }
1700    }
1701
1702    /// Gets the children of a node from the Applier.
1703    ///
1704    /// This is used by SubcomposeLayoutNode to get children of virtual nodes
1705    /// directly from the Applier, where insert_child commands have been applied.
1706    pub fn get_node_children(&self, node_id: NodeId) -> Vec<NodeId> {
1707        let mut applier = self.borrow_applier();
1708        match applier.get_mut(node_id) {
1709            Ok(node) => node.children(),
1710            Err(_) => Vec::new(),
1711        }
1712    }
1713
1714    /// Clears all children of a node in the Applier.
1715    ///
1716    /// This is used by SubcomposeLayoutNode when reusing a virtual node for
1717    /// different content. Without clearing, old children remain attached,
1718    /// causing duplicate/interleaved items in lazy lists after scrolling.
1719    pub fn clear_node_children(&self, node_id: NodeId) {
1720        let mut applier = self.borrow_applier();
1721        if let Ok(node) = applier.get_mut(node_id) {
1722            // Use update_children with empty slice to clear all children
1723            node.update_children(&[]);
1724        }
1725    }
1726
1727    pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
1728        let _composer_guard = composer_context::enter(self);
1729        runtime::push_active_runtime(&self.core.runtime);
1730        struct Guard;
1731        impl Drop for Guard {
1732            fn drop(&mut self) {
1733                runtime::pop_active_runtime();
1734            }
1735        }
1736        let guard = Guard;
1737        let result = f(self);
1738        drop(guard);
1739        result
1740    }
1741
1742    pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1743        let (group, scope_ref, restored_from_gap) = self.with_slots_mut(|slots| {
1744            let StartGroup {
1745                group,
1746                restored_from_gap,
1747            } = slots.begin_group(key);
1748            let scope_ref = slots
1749                .remember(|| RecomposeScope::new(self.runtime_handle()))
1750                .with(|scope| scope.clone());
1751            (group, scope_ref, restored_from_gap)
1752        });
1753
1754        if restored_from_gap {
1755            scope_ref.force_recompose();
1756        }
1757
1758        if let Some(options) = self.pending_scope_options().take() {
1759            if options.force_recompose {
1760                scope_ref.force_recompose();
1761            } else if options.force_reuse {
1762                scope_ref.force_reuse();
1763            }
1764        }
1765
1766        self.with_slots_mut(|slots| {
1767            SlotStorage::set_group_scope(slots, group, scope_ref.id());
1768        });
1769
1770        {
1771            let mut stack = self.scope_stack();
1772            stack.push(scope_ref.clone());
1773        }
1774
1775        {
1776            let mut stack = self.subcompose_stack();
1777            if let Some(frame) = stack.last_mut() {
1778                frame.scopes.push(scope_ref.clone());
1779            }
1780        }
1781
1782        {
1783            let locals = self.core.local_stack.borrow();
1784            scope_ref.snapshot_locals(&locals);
1785        }
1786        {
1787            let parent_hint = self.parent_stack().last().map(|frame| frame.id);
1788            scope_ref.set_parent_hint(parent_hint);
1789        }
1790
1791        let result = self.observe_scope(&scope_ref, || f(self));
1792
1793        let trimmed = self.with_slots_mut(|slots| slots.finalize_current_group());
1794        if trimmed {
1795            scope_ref.force_recompose();
1796        }
1797
1798        {
1799            let mut stack = self.scope_stack();
1800            stack.pop();
1801        }
1802        scope_ref.mark_recomposed();
1803        self.with_slots_mut(|slots| slots.end_group());
1804        result
1805    }
1806
1807    pub fn cranpose_with_reuse<R>(
1808        &self,
1809        key: Key,
1810        options: RecomposeOptions,
1811        f: impl FnOnce(&Composer) -> R,
1812    ) -> R {
1813        self.pending_scope_options().replace(options);
1814        self.with_group(key, f)
1815    }
1816
1817    pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1818        let hashed = hash_key(key);
1819        self.with_group(hashed, f)
1820    }
1821
1822    pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1823        self.with_slots_mut(|slots| slots.remember(init))
1824    }
1825
1826    pub fn use_value_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> usize {
1827        self.with_slots_mut(|slots| slots.alloc_value_slot(init).index())
1828    }
1829
1830    pub fn with_slot_value<T: 'static, R>(&self, idx: usize, f: impl FnOnce(&T) -> R) -> R {
1831        self.with_slots(|slots| {
1832            let value = SlotStorage::read_value(slots, ValueSlotId::new(idx));
1833            f(value)
1834        })
1835    }
1836
1837    pub fn with_slot_value_mut<T: 'static, R>(&self, idx: usize, f: impl FnOnce(&mut T) -> R) -> R {
1838        self.with_slots_mut(|slots| {
1839            let value = SlotStorage::read_value_mut(slots, ValueSlotId::new(idx));
1840            f(value)
1841        })
1842    }
1843
1844    pub fn write_slot_value<T: 'static>(&self, idx: usize, value: T) {
1845        self.with_slots_mut(|slots| slots.write_value(ValueSlotId::new(idx), value));
1846    }
1847
1848    pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1849        MutableState::with_runtime(initial, self.runtime_handle())
1850    }
1851
1852    pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1853    where
1854        T: Clone + 'static,
1855        I: IntoIterator<Item = T>,
1856    {
1857        SnapshotStateList::with_runtime(values, self.runtime_handle())
1858    }
1859
1860    pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1861    where
1862        K: Clone + Eq + Hash + 'static,
1863        V: Clone + 'static,
1864        I: IntoIterator<Item = (K, V)>,
1865    {
1866        SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1867    }
1868
1869    pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1870        let stack = self.core.local_stack.borrow();
1871        for context in stack.iter().rev() {
1872            if let Some(entry) = context.values.get(&local.key) {
1873                let typed = entry
1874                    .clone()
1875                    .downcast::<LocalStateEntry<T>>()
1876                    .expect("composition local type mismatch");
1877                return typed.value();
1878            }
1879        }
1880        local.default_value()
1881    }
1882
1883    pub fn read_static_composition_local<T: Clone + 'static>(
1884        &self,
1885        local: &StaticCompositionLocal<T>,
1886    ) -> T {
1887        let stack = self.core.local_stack.borrow();
1888        for context in stack.iter().rev() {
1889            if let Some(entry) = context.values.get(&local.key) {
1890                let typed = entry
1891                    .clone()
1892                    .downcast::<StaticLocalEntry<T>>()
1893                    .expect("static composition local type mismatch");
1894                return typed.value();
1895            }
1896        }
1897        local.default_value()
1898    }
1899
1900    pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
1901        self.core.scope_stack.borrow().last().cloned()
1902    }
1903
1904    pub fn phase(&self) -> Phase {
1905        self.core.phase.get()
1906    }
1907
1908    pub(crate) fn set_phase(&self, phase: Phase) {
1909        self.core.phase.set(phase);
1910    }
1911
1912    pub fn enter_phase(&self, phase: Phase) {
1913        self.set_phase(phase);
1914    }
1915
1916    pub(crate) fn subcompose<R>(
1917        &self,
1918        state: &mut SubcomposeState,
1919        slot_id: SlotId,
1920        content: impl FnOnce(&Composer) -> R,
1921    ) -> (R, Vec<NodeId>) {
1922        match self.phase() {
1923            Phase::Measure | Phase::Layout => {}
1924            current => panic!(
1925                "subcompose() may only be called during measure or layout; current phase: {:?}",
1926                current
1927            ),
1928        }
1929
1930        self.subcompose_stack().push(SubcomposeFrame::default());
1931        struct StackGuard {
1932            core: Rc<ComposerCore>,
1933            leaked: bool,
1934        }
1935        impl Drop for StackGuard {
1936            fn drop(&mut self) {
1937                if !self.leaked {
1938                    self.core.subcompose_stack.borrow_mut().pop();
1939                }
1940            }
1941        }
1942        let mut guard = StackGuard {
1943            core: self.clone_core(),
1944            leaked: false,
1945        };
1946
1947        let slot_host = state.get_or_create_slots(slot_id);
1948        {
1949            let mut slots = slot_host.borrow_mut();
1950            slots.reset();
1951        }
1952        let result = self.with_slot_override(slot_host.clone(), |composer| {
1953            // Use with_group to create/reuse a group for this slot_id within the slot table.
1954            composer.with_group(slot_id.raw(), |composer| content(composer))
1955        });
1956        {
1957            let mut slots = slot_host.borrow_mut();
1958            slots.finalize_current_group();
1959            slots.flush();
1960        }
1961
1962        let frame = {
1963            let mut stack = guard.core.subcompose_stack.borrow_mut();
1964            let frame = stack.pop().expect("subcompose stack underflow");
1965            guard.leaked = true;
1966            frame
1967        };
1968        let nodes = frame.nodes;
1969        let scopes = frame.scopes;
1970        state.register_active(slot_id, &nodes, &scopes);
1971        (result, nodes)
1972    }
1973
1974    pub fn subcompose_measurement<R>(
1975        &self,
1976        state: &mut SubcomposeState,
1977        slot_id: SlotId,
1978        content: impl FnOnce(&Composer) -> R,
1979    ) -> (R, Vec<NodeId>) {
1980        let (result, nodes) = self.subcompose(state, slot_id, content);
1981
1982        // Filter to include only root nodes (those without a parent).
1983        // While record_node attempts to track only roots, checking the final
1984        // parent status ensures we only return true roots to the layout system.
1985        let roots = nodes
1986            .into_iter()
1987            .filter(|&id| self.node_has_no_parent(id))
1988            .collect();
1989
1990        (result, roots)
1991    }
1992
1993    pub fn subcompose_in<R>(
1994        &self,
1995        slots: &Rc<SlotsHost>,
1996        root: Option<NodeId>,
1997        f: impl FnOnce(&Composer) -> R,
1998    ) -> Result<R, NodeError> {
1999        let runtime_handle = self.runtime_handle();
2000        slots.borrow_mut().reset();
2001        let phase = self.phase();
2002        let locals = self.core.local_stack.borrow().clone();
2003        let core = Rc::new(ComposerCore::new(
2004            Rc::clone(slots),
2005            Rc::clone(&self.core.applier),
2006            runtime_handle.clone(),
2007            self.observer(),
2008            root,
2009        ));
2010        core.phase.set(phase);
2011        *core.local_stack.borrow_mut() = locals;
2012        let composer = Composer::from_core(core);
2013        let (result, mut commands, side_effects) = composer.install(|composer| {
2014            let output = f(composer);
2015            let commands = composer.take_commands();
2016            let side_effects = composer.take_side_effects();
2017            (output, commands, side_effects)
2018        });
2019
2020        {
2021            let mut applier = self.borrow_applier();
2022            for mut command in commands.drain(..) {
2023                command(&mut *applier)?;
2024            }
2025            for mut update in runtime_handle.take_updates() {
2026                update(&mut *applier)?;
2027            }
2028        }
2029        runtime_handle.drain_ui();
2030        for effect in side_effects {
2031            effect();
2032        }
2033        runtime_handle.drain_ui();
2034        {
2035            let mut slots_mut = slots.borrow_mut();
2036            slots_mut.finalize_current_group();
2037            slots_mut.flush();
2038        }
2039        Ok(result)
2040    }
2041
2042    /// Subcomposes content using an isolated SlotsHost without resetting it.
2043    /// Unlike `subcompose_in`, this preserves existing slot state across calls,
2044    /// allowing efficient reuse during measurement passes. This is critical for
2045    /// lazy lists where items need stable slot positions.
2046    pub fn subcompose_slot<R>(
2047        &self,
2048        slots: &Rc<SlotsHost>,
2049        root: Option<NodeId>,
2050        f: impl FnOnce(&Composer) -> R,
2051    ) -> Result<R, NodeError> {
2052        let runtime_handle = self.runtime_handle();
2053        // Reset cursor to 0 but preserve slot data for reuse (like JC's setContentWithReuse)
2054        // This allows remembered values to be found and reused
2055        slots.borrow_mut().reset();
2056        let phase = self.phase();
2057        let locals = self.core.local_stack.borrow().clone();
2058        let core = Rc::new(ComposerCore::new(
2059            Rc::clone(slots),
2060            Rc::clone(&self.core.applier),
2061            runtime_handle.clone(),
2062            self.observer(),
2063            root, // Root node for parent chain - enables dirty flag bubbling
2064        ));
2065        core.phase.set(phase);
2066        *core.local_stack.borrow_mut() = locals;
2067        let composer = Composer::from_core(core);
2068        let (result, mut commands, side_effects) = composer.install(|composer| {
2069            let output = f(composer);
2070            // CRITICAL FIX: Pop the root parent frame to generate insert_child commands.
2071            // Without this, the root frame's new_children list is populated but never
2072            // processed, so children are never attached to the virtual node.
2073            if root.is_some() {
2074                composer.pop_parent();
2075            }
2076            let commands = composer.take_commands();
2077            let side_effects = composer.take_side_effects();
2078            (output, commands, side_effects)
2079        });
2080
2081        {
2082            let mut applier = self.borrow_applier();
2083            for mut command in commands.drain(..) {
2084                command(&mut *applier)?;
2085            }
2086            for mut update in runtime_handle.take_updates() {
2087                update(&mut *applier)?;
2088            }
2089        }
2090        runtime_handle.drain_ui();
2091        for effect in side_effects {
2092            effect();
2093        }
2094        runtime_handle.drain_ui();
2095        // DON'T finalize or flush - for subcompose reuse, we need to keep all groups
2096        // in place so they can be found via O(1) HashMap lookup on the next measurement
2097        // pass. Calling finalize_current_group would convert valid lazy list item
2098        // groups to gaps if the cursor didn't reach them.
2099        Ok(result)
2100    }
2101
2102    pub fn skip_current_group(&self) {
2103        let nodes = self.with_slots(|slots| slots.nodes_in_current_group());
2104        self.with_slots_mut(|slots| slots.skip_current_group());
2105        // Get the current parent from the stack (if any)
2106        let current_parent = {
2107            let stack = self.parent_stack();
2108            stack.last().map(|frame| frame.id)
2109        };
2110
2111        // Only attach nodes whose parent matches the current parent in the stack.
2112        // This ensures we only attach direct children of the current parent,
2113        // not nested nodes that belong to other nodes within the skipped group.
2114        let mut applier = self.borrow_applier();
2115        for id in nodes {
2116            if let Ok(node) = applier.get_mut(id) {
2117                let node_parent = node.parent();
2118                if node_parent.is_none() || node_parent == current_parent {
2119                    drop(applier);
2120                    self.attach_to_parent(id);
2121                    applier = self.borrow_applier();
2122                }
2123            }
2124        }
2125    }
2126
2127    pub fn runtime_handle(&self) -> RuntimeHandle {
2128        self.core.runtime.clone()
2129    }
2130
2131    pub fn set_recranpose_callback<F>(&self, callback: F)
2132    where
2133        F: FnMut(&Composer) + 'static,
2134    {
2135        if let Some(scope) = self.current_recranpose_scope() {
2136            let observer = self.observer();
2137            let scope_weak = scope.downgrade();
2138            let mut callback = callback;
2139            scope.set_recompose(Box::new(move |composer: &Composer| {
2140                if let Some(inner) = scope_weak.upgrade() {
2141                    let scope_instance = RecomposeScope { inner };
2142                    observer.observe_reads(
2143                        scope_instance.clone(),
2144                        move |scope_ref| scope_ref.invalidate(),
2145                        || {
2146                            callback(composer);
2147                        },
2148                    );
2149                }
2150            }));
2151        }
2152    }
2153
2154    pub fn with_composition_locals<R>(
2155        &self,
2156        provided: Vec<ProvidedValue>,
2157        f: impl FnOnce(&Composer) -> R,
2158    ) -> R {
2159        if provided.is_empty() {
2160            return f(self);
2161        }
2162        let mut context = LocalContext::default();
2163        for value in provided {
2164            let (key, entry) = value.into_entry(self);
2165            context.values.insert(key, entry);
2166        }
2167        {
2168            let mut stack = self.local_stack();
2169            stack.push(context);
2170        }
2171        let result = f(self);
2172        {
2173            let mut stack = self.local_stack();
2174            stack.pop();
2175        }
2176        result
2177    }
2178
2179    fn recranpose_group(&self, scope: &RecomposeScope) {
2180        // CRITICAL FIX: Check if scope is still invalid before recomposing.
2181        // When parent and child scopes are both invalidated, the child may be
2182        // visited (and marked recomposed) during parent's recomposition.
2183        // Without this check, we'd recompose the child again with wrong parent_stack,
2184        // causing nodes to get attached to root instead of their actual parent.
2185        if !scope.is_invalid() {
2186            scope.mark_recomposed();
2187            return;
2188        }
2189        let started = self.with_slots_mut(|slots| slots.begin_recranpose_at_scope(scope.id()));
2190        if started.is_some() {
2191            let previous_hint = self
2192                .core
2193                .recranpose_parent_hint
2194                .replace(scope.parent_hint());
2195            struct HintGuard {
2196                core: Rc<ComposerCore>,
2197                previous: Option<NodeId>,
2198            }
2199            impl Drop for HintGuard {
2200                fn drop(&mut self) {
2201                    self.core.recranpose_parent_hint.set(self.previous);
2202                }
2203            }
2204            let _hint_guard = HintGuard {
2205                core: self.clone_core(),
2206                previous: previous_hint,
2207            };
2208            {
2209                let mut stack = self.scope_stack();
2210                stack.push(scope.clone());
2211            }
2212            let saved_locals = {
2213                let mut locals = self.local_stack();
2214                std::mem::take(&mut *locals)
2215            };
2216            {
2217                let mut locals = self.local_stack();
2218                *locals = scope.local_stack();
2219            }
2220            self.observe_scope(scope, || {
2221                scope.run_recompose(self);
2222            });
2223            {
2224                let mut locals = self.local_stack();
2225                *locals = saved_locals;
2226            }
2227            {
2228                let mut stack = self.scope_stack();
2229                stack.pop();
2230            }
2231            self.with_slots_mut(SlotStorage::end_recompose);
2232            scope.mark_recomposed();
2233        } else {
2234            scope.mark_recomposed();
2235        }
2236    }
2237
2238    pub fn use_state<T: Clone + 'static>(&self, init: impl FnOnce() -> T) -> MutableState<T> {
2239        let runtime = self.runtime_handle();
2240        let state = self.with_slots_mut(|slots| {
2241            slots.remember(|| MutableState::with_runtime(init(), runtime.clone()))
2242        });
2243        state.with(|state| *state)
2244    }
2245
2246    pub fn emit_node<N: Node + 'static>(&self, init: impl FnOnce() -> N) -> NodeId {
2247        // Peek at the slot without advancing cursor
2248        let (existing_id, type_matches) = {
2249            if let Some(id) = self.with_slots_mut(|slots| slots.peek_node()) {
2250                // Check if the node type matches
2251                let mut applier = self.borrow_applier();
2252                let matches = match applier.get_mut(id) {
2253                    Ok(node) => node.as_any_mut().downcast_ref::<N>().is_some(),
2254                    Err(_) => false,
2255                };
2256                (Some(id), matches)
2257            } else {
2258                (None, false)
2259            }
2260        };
2261
2262        // If we have a matching node, advance cursor and reuse it
2263        if let Some(id) = existing_id {
2264            if type_matches {
2265                // Type matches - reuse this node. The push_parent conditional ensures
2266                // that new parents start with empty previous children, so we don't
2267                // accidentally inherit children from a different parent.
2268                let reuse_allowed = true;
2269
2270                #[cfg(not(target_arch = "wasm32"))]
2271                if std::env::var("COMPOSE_DEBUG").is_ok() {
2272                    eprintln!("emit_node: candidate #{id} reuse_allowed={reuse_allowed}");
2273                }
2274
2275                if reuse_allowed {
2276                    self.core.last_node_reused.set(Some(true));
2277                    #[cfg(not(target_arch = "wasm32"))]
2278                    if std::env::var("COMPOSE_DEBUG").is_ok() {
2279                        eprintln!(
2280                            "emit_node: reusing node #{id} as {}",
2281                            std::any::type_name::<N>()
2282                        );
2283                    }
2284                    self.with_slots_mut(|slots| slots.advance_after_node_read());
2285
2286                    self.commands_mut()
2287                        .push(Box::new(move |applier: &mut dyn Applier| {
2288                            let node = match applier.get_mut(id) {
2289                                Ok(node) => node,
2290                                Err(NodeError::Missing { .. }) => return Ok(()),
2291                                Err(err) => return Err(err),
2292                            };
2293                            let typed = node.as_any_mut().downcast_mut::<N>().ok_or(
2294                                NodeError::TypeMismatch {
2295                                    id,
2296                                    expected: std::any::type_name::<N>(),
2297                                },
2298                            )?;
2299                            typed.update();
2300                            Ok(())
2301                        }));
2302                    self.attach_to_parent(id);
2303                    return id;
2304                }
2305            }
2306        }
2307
2308        // If there was a mismatched node in this slot, schedule its removal before creating a new one.
2309        if let Some(old_id) = existing_id {
2310            if !type_matches {
2311                #[cfg(not(target_arch = "wasm32"))]
2312                if std::env::var("COMPOSE_DEBUG").is_ok() {
2313                    eprintln!(
2314                        "emit_node: replacing node #{old_id} with new {}",
2315                        std::any::type_name::<N>()
2316                    );
2317                }
2318                self.commands_mut()
2319                    .push(Box::new(move |applier: &mut dyn Applier| {
2320                        if let Ok(node) = applier.get_mut(old_id) {
2321                            node.unmount();
2322                        }
2323                        match applier.remove(old_id) {
2324                            Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2325                            Err(err) => Err(err),
2326                        }
2327                    }));
2328            }
2329        }
2330
2331        // Type mismatch or no node: create new node
2332        // record_node() will handle replacing the mismatched slot
2333        let id = {
2334            let mut applier = self.borrow_applier();
2335            applier.create(Box::new(init()))
2336        };
2337        self.core.last_node_reused.set(Some(false));
2338        #[cfg(not(target_arch = "wasm32"))]
2339        if std::env::var("COMPOSE_DEBUG").is_ok() {
2340            eprintln!(
2341                "emit_node: creating node #{} as {}",
2342                id,
2343                std::any::type_name::<N>()
2344            );
2345        }
2346        {
2347            self.with_slots_mut(|slots| slots.record_node(id));
2348        }
2349        self.commands_mut()
2350            .push(Box::new(move |applier: &mut dyn Applier| {
2351                let node = match applier.get_mut(id) {
2352                    Ok(node) => node,
2353                    Err(NodeError::Missing { .. }) => return Ok(()),
2354                    Err(err) => return Err(err),
2355                };
2356                node.set_node_id(id);
2357                node.mount();
2358                Ok(())
2359            }));
2360        self.attach_to_parent(id);
2361        id
2362    }
2363
2364    fn attach_to_parent(&self, id: NodeId) {
2365        // IMPORTANT: Check parent_stack FIRST.
2366        // During subcomposition, if there's an active parent (e.g., Row),
2367        // child nodes (e.g., Text) should attach to that parent, NOT to the
2368        // subcompose frame. Only ROOT nodes (nodes with no active parent)
2369        // should be added to the subcompose frame.
2370        let mut parent_stack = self.parent_stack();
2371        if let Some(frame) = parent_stack.last_mut() {
2372            let parent_id = frame.id;
2373            if parent_id == id {
2374                return;
2375            }
2376            frame.new_children.push(id);
2377            drop(parent_stack);
2378
2379            // KEY FIX: Set parent link IMMEDIATELY, matching Jetpack Compose's
2380            // LayoutNode.insertAt pattern where _foldedParent is set synchronously.
2381            // This ensures that when bubble_measure_dirty runs (in commands),
2382            // the parent chain is already established.
2383            //
2384            // IMPORTANT: Only set parent if node doesn't have one or if the new parent
2385            // is not the root. This prevents double-recomposition scenarios where a
2386            // child scope (invalidated by CompositionLocalProvider during parent's
2387            // recomposition) gets processed again with parent_stack=[root], which would
2388            // incorrectly reparent nodes to root.
2389            {
2390                let mut applier = self.borrow_applier();
2391                if let Ok(child_node) = applier.get_mut(id) {
2392                    let existing_parent = child_node.parent();
2393                    // Only set parent if:
2394                    // 1. Node has no parent, OR
2395                    // 2. New parent is NOT the root (parent_id != 0 or != self.root)
2396                    // This prevents root from stealing children that belong to intermediate nodes.
2397                    let should_set = match existing_parent {
2398                        None => true,
2399                        Some(existing) => {
2400                            // Don't let root steal children from proper parents
2401                            let root_id = self.core.root.get();
2402                            parent_id != root_id.unwrap_or(0) || existing == root_id.unwrap_or(0)
2403                        }
2404                    };
2405                    if should_set {
2406                        child_node.set_parent_for_bubbling(parent_id);
2407                    }
2408                }
2409            }
2410            return;
2411        }
2412        drop(parent_stack);
2413
2414        // No active parent - check if we're in subcompose
2415        let in_subcompose = !self.subcompose_stack().is_empty();
2416        if in_subcompose {
2417            // During subcompose, only add ROOT nodes (nodes without a parent).
2418            // Child nodes already have their parent-child relationship from composition;
2419            // re-adding them to the subcompose frame would cause duplication.
2420            let has_parent = {
2421                let mut applier = self.borrow_applier();
2422                applier
2423                    .get_mut(id)
2424                    .map(|node| node.parent().is_some())
2425                    .unwrap_or(false)
2426            };
2427
2428            if !has_parent {
2429                let mut subcompose_stack = self.subcompose_stack();
2430                if let Some(frame) = subcompose_stack.last_mut() {
2431                    frame.nodes.push(id);
2432                }
2433            }
2434            return;
2435        }
2436
2437        // During recomposition, preserve the original parent when possible.
2438        if let Some(parent_hint) = self.core.recranpose_parent_hint.get() {
2439            let parent_status = {
2440                let mut applier = self.borrow_applier();
2441                applier
2442                    .get_mut(id)
2443                    .map(|node| node.parent())
2444                    .unwrap_or(None)
2445            };
2446            match parent_status {
2447                Some(existing) if existing == parent_hint => {}
2448                None => {
2449                    self.commands_mut()
2450                        .push(Box::new(move |applier: &mut dyn Applier| {
2451                            if let Ok(parent_node) = applier.get_mut(parent_hint) {
2452                                parent_node.insert_child(id);
2453                            }
2454                            if let Ok(child_node) = applier.get_mut(id) {
2455                                child_node.on_attached_to_parent(parent_hint);
2456                            }
2457                            bubble_layout_dirty(applier, parent_hint);
2458                            bubble_measure_dirty(applier, parent_hint);
2459                            Ok(())
2460                        }));
2461                }
2462                Some(_) => {}
2463            }
2464            return;
2465        }
2466
2467        // Neither parent nor subcompose - check if this node already has a parent.
2468        // During recomposition, reused nodes already have their correct parent from
2469        // initial composition. We should NOT set them as root, as that would corrupt
2470        // the tree structure and cause duplication.
2471        let has_parent = {
2472            let mut applier = self.borrow_applier();
2473            applier
2474                .get_mut(id)
2475                .map(|node| node.parent().is_some())
2476                .unwrap_or(false)
2477        };
2478        if has_parent {
2479            // Node already has a parent, nothing to do
2480            return;
2481        }
2482
2483        // Node has no parent and is not in subcompose - must be root
2484        self.set_root(Some(id));
2485    }
2486
2487    pub fn with_node_mut<N: Node + 'static, R>(
2488        &self,
2489        id: NodeId,
2490        f: impl FnOnce(&mut N) -> R,
2491    ) -> Result<R, NodeError> {
2492        let mut applier = self.borrow_applier();
2493        let node = applier.get_mut(id)?;
2494        let typed = node
2495            .as_any_mut()
2496            .downcast_mut::<N>()
2497            .ok_or(NodeError::TypeMismatch {
2498                id,
2499                expected: std::any::type_name::<N>(),
2500            })?;
2501        Ok(f(typed))
2502    }
2503
2504    pub fn push_parent(&self, id: NodeId) {
2505        let remembered = self.remember(ParentChildren::default);
2506        let reused = self.core.last_node_reused.take().unwrap_or(true);
2507        let in_subcompose = !self.core.subcompose_stack.borrow().is_empty();
2508
2509        // Only carry over previous children when the parent was reused (or in subcompose).
2510        // Otherwise, start fresh to prevent nodes from teleporting between parents.
2511        let previous = if reused || in_subcompose {
2512            remembered.with(|entry| entry.children.clone())
2513        } else {
2514            Vec::new()
2515        };
2516
2517        self.parent_stack().push(ParentFrame {
2518            id,
2519            remembered,
2520            previous,
2521            new_children: Vec::new(),
2522        });
2523    }
2524
2525    pub fn pop_parent(&self) {
2526        let frame_opt = {
2527            let mut stack = self.parent_stack();
2528            stack.pop()
2529        };
2530        if let Some(frame) = frame_opt {
2531            let ParentFrame {
2532                id,
2533                remembered,
2534                previous,
2535                new_children,
2536            } = frame;
2537
2538            #[cfg(not(target_arch = "wasm32"))]
2539            if std::env::var("COMPOSE_DEBUG").is_ok() {
2540                eprintln!("pop_parent: node #{}", id);
2541                eprintln!("  previous children: {:?}", previous);
2542                eprintln!("  new children: {:?}", new_children);
2543            }
2544            let children_changed = previous != new_children;
2545
2546            if children_changed {
2547                let mut current = previous.clone();
2548                let target = new_children.clone();
2549                let desired: HashSet<NodeId> = target.iter().copied().collect();
2550
2551                for index in (0..current.len()).rev() {
2552                    let child = current[index];
2553                    if !desired.contains(&child) {
2554                        current.remove(index);
2555                        self.commands_mut()
2556                            .push(Box::new(move |applier: &mut dyn Applier| {
2557                                // Remove child from parent and clear parent link atomically
2558                                if let Ok(parent_node) = applier.get_mut(id) {
2559                                    parent_node.remove_child(child);
2560                                }
2561                                // Bubble BEFORE clearing parent link so bubbling can verify consistency
2562                                bubble_layout_dirty(applier, id);
2563                                bubble_measure_dirty(applier, id);
2564                                // Now clear parent link and unmount. Remove if we still own the
2565                                // node OR if it is orphaned (parent=None). Only skip removal
2566                                // when the node has been reparented elsewhere.
2567                                let should_remove = if let Ok(node) = applier.get_mut(child) {
2568                                    match node.parent() {
2569                                        Some(parent_id) if parent_id == id => {
2570                                            node.on_removed_from_parent();
2571                                            node.unmount();
2572                                            true
2573                                        }
2574                                        None => {
2575                                            // Orphaned node: remove to prevent stale roots.
2576                                            node.unmount();
2577                                            true
2578                                        }
2579                                        Some(_) => false,
2580                                    }
2581                                } else {
2582                                    true
2583                                };
2584
2585                                if should_remove {
2586                                    let _ = applier.remove(child);
2587                                }
2588                                Ok(())
2589                            }));
2590                    }
2591                }
2592
2593                for (target_index, &child) in target.iter().enumerate() {
2594                    if let Some(current_index) = current.iter().position(|&c| c == child) {
2595                        if current_index != target_index {
2596                            let from_index = current_index;
2597                            current.remove(from_index);
2598                            let to_index = target_index.min(current.len());
2599                            current.insert(to_index, child);
2600                            self.commands_mut()
2601                                .push(Box::new(move |applier: &mut dyn Applier| {
2602                                    if let Ok(parent_node) = applier.get_mut(id) {
2603                                        parent_node.move_child(from_index, to_index);
2604                                    }
2605                                    Ok(())
2606                                }));
2607                            self.commands_mut()
2608                                .push(Box::new(move |applier: &mut dyn Applier| {
2609                                    // Bubble dirty flags to root after reordering
2610                                    // Even though parent doesn't change, layout needs recomputation
2611                                    bubble_layout_dirty(applier, id);
2612                                    bubble_measure_dirty(applier, id);
2613                                    Ok(())
2614                                }));
2615                        }
2616                    } else {
2617                        let insert_index = target_index.min(current.len());
2618                        let appended_index = current.len();
2619                        current.insert(insert_index, child);
2620                        self.commands_mut()
2621                            .push(Box::new(move |applier: &mut dyn Applier| {
2622                                // If the child is currently attached to a different parent,
2623                                // detach it from the old parent before reparenting.
2624                                let old_parent =
2625                                    applier.get_mut(child).ok().and_then(|node| node.parent());
2626                                if let Some(old_parent_id) = old_parent {
2627                                    if old_parent_id != id {
2628                                        if let Ok(old_parent_node) = applier.get_mut(old_parent_id)
2629                                        {
2630                                            old_parent_node.remove_child(child);
2631                                        }
2632                                        if let Ok(child_node) = applier.get_mut(child) {
2633                                            child_node.on_removed_from_parent();
2634                                        }
2635                                        bubble_layout_dirty(applier, old_parent_id);
2636                                        bubble_measure_dirty(applier, old_parent_id);
2637                                    }
2638                                }
2639                                // Insert child and set parent link atomically
2640                                if let Ok(parent_node) = applier.get_mut(id) {
2641                                    parent_node.insert_child(child);
2642                                }
2643                                // Set parent link immediately after insertion
2644                                if let Ok(child_node) = applier.get_mut(child) {
2645                                    child_node.on_attached_to_parent(id);
2646                                }
2647                                // Bubble dirty flags to root after insertion
2648                                bubble_layout_dirty(applier, id);
2649                                bubble_measure_dirty(applier, id);
2650                                Ok(())
2651                            }));
2652                        if insert_index != appended_index {
2653                            self.commands_mut()
2654                                .push(Box::new(move |applier: &mut dyn Applier| {
2655                                    if let Ok(parent_node) = applier.get_mut(id) {
2656                                        parent_node.move_child(appended_index, insert_index);
2657                                    }
2658                                    Ok(())
2659                                }));
2660                        }
2661                    }
2662                }
2663            }
2664
2665            let expected_children = new_children.clone();
2666            let needs_dirty_check = !children_changed;
2667            self.commands_mut()
2668                .push(Box::new(move |applier: &mut dyn Applier| {
2669                    let mut repaired = false;
2670                    for &child in &expected_children {
2671                        let needs_attach = if let Ok(node) = applier.get_mut(child) {
2672                            node.parent() != Some(id)
2673                        } else {
2674                            false
2675                        };
2676                        if needs_attach {
2677                            let old_parent =
2678                                applier.get_mut(child).ok().and_then(|node| node.parent());
2679                            if let Some(old_parent_id) = old_parent {
2680                                if old_parent_id != id {
2681                                    if let Ok(old_parent_node) = applier.get_mut(old_parent_id) {
2682                                        old_parent_node.remove_child(child);
2683                                    }
2684                                    if let Ok(child_node) = applier.get_mut(child) {
2685                                        child_node.on_removed_from_parent();
2686                                    }
2687                                    bubble_layout_dirty(applier, old_parent_id);
2688                                    bubble_measure_dirty(applier, old_parent_id);
2689                                }
2690                            }
2691                            if let Ok(parent_node) = applier.get_mut(id) {
2692                                parent_node.insert_child(child);
2693                            }
2694                            if let Ok(child_node) = applier.get_mut(child) {
2695                                child_node.on_attached_to_parent(id);
2696                            }
2697                            repaired = true;
2698                        }
2699                    }
2700                    let is_dirty = if needs_dirty_check {
2701                        if let Ok(node) = applier.get_mut(id) {
2702                            node.needs_layout()
2703                        } else {
2704                            false
2705                        }
2706                    } else {
2707                        false
2708                    };
2709                    if repaired {
2710                        bubble_layout_dirty(applier, id);
2711                        bubble_measure_dirty(applier, id);
2712                    } else if is_dirty {
2713                        bubble_layout_dirty(applier, id);
2714                    }
2715                    Ok(())
2716                }));
2717
2718            remembered.update(|entry| entry.children = new_children);
2719        }
2720    }
2721
2722    pub fn take_commands(&self) -> Vec<Command> {
2723        std::mem::take(&mut *self.commands_mut())
2724    }
2725
2726    /// Applies any pending applier commands and runtime updates.
2727    ///
2728    /// This is useful during measure-time subcomposition to ensure newly created
2729    /// nodes are available for measurement before the full composition is committed.
2730    pub fn apply_pending_commands(&self) -> Result<(), NodeError> {
2731        let mut commands = self.take_commands();
2732        let runtime_handle = self.runtime_handle();
2733        {
2734            let mut applier = self.borrow_applier();
2735            for mut command in commands.drain(..) {
2736                command(&mut *applier)?;
2737            }
2738            for mut update in runtime_handle.take_updates() {
2739                update(&mut *applier)?;
2740            }
2741        }
2742        runtime_handle.drain_ui();
2743        Ok(())
2744    }
2745
2746    pub fn register_side_effect(&self, effect: impl FnOnce() + 'static) {
2747        self.side_effects_mut().push(Box::new(effect));
2748    }
2749
2750    pub fn take_side_effects(&self) -> Vec<Box<dyn FnOnce()>> {
2751        std::mem::take(&mut *self.side_effects_mut())
2752    }
2753
2754    pub(crate) fn root(&self) -> Option<NodeId> {
2755        self.core.root.get()
2756    }
2757
2758    pub(crate) fn set_root(&self, node: Option<NodeId>) {
2759        self.core.root.set(node);
2760    }
2761}
2762
2763#[derive(Default, Clone)]
2764struct ParentChildren {
2765    children: Vec<NodeId>,
2766}
2767
2768struct ParentFrame {
2769    id: NodeId,
2770    remembered: Owned<ParentChildren>,
2771    previous: Vec<NodeId>,
2772    new_children: Vec<NodeId>,
2773}
2774
2775#[derive(Default)]
2776struct SubcomposeFrame {
2777    nodes: Vec<NodeId>,
2778    scopes: Vec<RecomposeScope>,
2779}
2780
2781#[derive(Default, Clone)]
2782struct LocalContext {
2783    values: HashMap<LocalKey, Rc<dyn Any>>,
2784}
2785
2786pub(crate) struct MutableStateInner<T: Clone + 'static> {
2787    state: Arc<SnapshotMutableState<T>>,
2788    watchers: RefCell<Vec<Weak<RecomposeScopeInner>>>, // FUTURE(no_std): move to stack-allocated subscription list.
2789    runtime: RuntimeHandle,
2790}
2791
2792impl<T: Clone + 'static> MutableStateInner<T> {
2793    fn new(value: T, runtime: RuntimeHandle) -> Self {
2794        Self {
2795            state: SnapshotMutableState::new_in_arc(value, Arc::new(NeverEqual)),
2796            watchers: RefCell::new(Vec::new()),
2797            runtime,
2798        }
2799    }
2800
2801    fn install_snapshot_observer(&self, state_id: StateId) {
2802        let runtime_handle = self.runtime.clone();
2803        self.state.add_apply_observer(Box::new(move || {
2804            let runtime = runtime_handle.clone();
2805            runtime_handle.enqueue_ui_task(Box::new(move || {
2806                runtime.with_state_arena(|arena| {
2807                    if let Some(inner) = arena.get_typed_opt::<T>(state_id) {
2808                        inner.invalidate_watchers();
2809                    }
2810                });
2811            }));
2812        }));
2813    }
2814
2815    fn with_value<R>(&self, f: impl FnOnce(&T) -> R) -> R {
2816        let value = self.state.get();
2817        f(&value)
2818    }
2819
2820    fn invalidate_watchers(&self) {
2821        let watchers: Vec<RecomposeScope> = {
2822            let mut watchers = self.watchers.borrow_mut();
2823            watchers.retain(|w| w.strong_count() > 0);
2824            watchers
2825                .iter()
2826                .filter_map(|w| w.upgrade())
2827                .map(|inner| RecomposeScope { inner })
2828                .collect()
2829        };
2830
2831        for watcher in watchers {
2832            watcher.invalidate();
2833        }
2834    }
2835}
2836
2837#[derive(Clone)]
2838pub struct State<T: Clone + 'static> {
2839    id: StateId,
2840    runtime_id: RuntimeId,
2841    _marker: PhantomData<fn() -> T>,
2842}
2843
2844impl<T: Clone + 'static> Copy for State<T> {}
2845
2846#[derive(Clone)]
2847pub struct MutableState<T: Clone + 'static> {
2848    id: StateId,
2849    runtime_id: RuntimeId,
2850    _marker: PhantomData<fn() -> T>,
2851}
2852
2853impl<T: Clone + 'static> Copy for MutableState<T> {}
2854
2855impl<T: Clone + 'static> PartialEq for State<T> {
2856    fn eq(&self, other: &Self) -> bool {
2857        self.id == other.id && self.runtime_id == other.runtime_id
2858    }
2859}
2860
2861impl<T: Clone + 'static> Eq for State<T> {}
2862
2863impl<T: Clone + 'static> PartialEq for MutableState<T> {
2864    fn eq(&self, other: &Self) -> bool {
2865        self.id == other.id && self.runtime_id == other.runtime_id
2866    }
2867}
2868
2869impl<T: Clone + 'static> Eq for MutableState<T> {}
2870
2871impl<T: Clone + 'static> State<T> {
2872    fn runtime_handle(&self) -> RuntimeHandle {
2873        runtime_handle_for(self.runtime_id).expect("runtime handle missing")
2874    }
2875
2876    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
2877        self.runtime_handle().with_state_arena(|arena| {
2878            let inner = arena.get_typed::<T>(self.id);
2879            f(&inner)
2880        })
2881    }
2882
2883    fn subscribe_current_scope(&self) {
2884        if let Some(Some(scope)) =
2885            with_current_composer_opt(|composer| composer.current_recranpose_scope())
2886        {
2887            self.with_inner(|inner| {
2888                let mut watchers = inner.watchers.borrow_mut();
2889                watchers.retain(|w| w.strong_count() > 0);
2890                let id = scope.id();
2891                let already_registered = watchers
2892                    .iter()
2893                    .any(|w| w.upgrade().map(|inner| inner.id == id).unwrap_or(false));
2894                if !already_registered {
2895                    watchers.push(scope.downgrade());
2896                }
2897            });
2898        }
2899    }
2900
2901    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
2902        self.subscribe_current_scope();
2903        self.with_inner(|inner| inner.with_value(f))
2904    }
2905
2906    pub fn value(&self) -> T {
2907        self.subscribe_current_scope();
2908        self.with(|value| value.clone())
2909    }
2910
2911    pub fn get(&self) -> T {
2912        self.value()
2913    }
2914}
2915
2916impl<T: Clone + 'static> MutableState<T> {
2917    pub fn with_runtime(value: T, runtime: RuntimeHandle) -> Self {
2918        let id = runtime.alloc_state(value);
2919        Self {
2920            id,
2921            runtime_id: runtime.id(),
2922            _marker: PhantomData,
2923        }
2924    }
2925
2926    fn runtime_handle(&self) -> RuntimeHandle {
2927        runtime_handle_for(self.runtime_id).expect("runtime handle missing")
2928    }
2929
2930    fn with_inner<R>(&self, f: impl FnOnce(&MutableStateInner<T>) -> R) -> R {
2931        self.runtime_handle().with_state_arena(|arena| {
2932            let inner = arena.get_typed::<T>(self.id);
2933            f(&inner)
2934        })
2935    }
2936
2937    pub fn as_state(&self) -> State<T> {
2938        State {
2939            id: self.id,
2940            runtime_id: self.runtime_id,
2941            _marker: PhantomData,
2942        }
2943    }
2944
2945    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
2946        self.as_state().with(f)
2947    }
2948
2949    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
2950        let runtime = self.runtime_handle();
2951        runtime.assert_ui_thread();
2952        runtime.with_state_arena(|arena| {
2953            let inner = arena.get_typed::<T>(self.id);
2954            let mut value = inner.state.get();
2955            let tracker = UpdateScope::new(inner.state.id());
2956            let result = f(&mut value);
2957            let wrote_elsewhere = tracker.finish();
2958            if !wrote_elsewhere {
2959                inner.state.set(value);
2960            }
2961            inner.invalidate_watchers();
2962            result
2963        })
2964    }
2965
2966    pub fn replace(&self, value: T) {
2967        let runtime = self.runtime_handle();
2968        runtime.assert_ui_thread();
2969        runtime.with_state_arena(|arena| {
2970            let inner = arena.get_typed::<T>(self.id);
2971            inner.state.set(value);
2972            inner.invalidate_watchers();
2973        });
2974    }
2975
2976    pub fn set_value(&self, value: T) {
2977        self.replace(value);
2978    }
2979
2980    pub fn set(&self, value: T) {
2981        self.replace(value);
2982    }
2983
2984    pub fn value(&self) -> T {
2985        self.as_state().value()
2986    }
2987
2988    pub fn get(&self) -> T {
2989        self.value()
2990    }
2991
2992    /// Gets the current value WITHOUT subscribing to recomposition.
2993    ///
2994    /// Use this in layout/measure/draw phases to read state without causing
2995    /// the current composition scope to recompose when the state changes.
2996    ///
2997    /// # When to use
2998    /// - In modifier nodes (like ScrollNode) during measure()
2999    /// - In any layout phase code that reads state but shouldn't trigger recomposition
3000    ///
3001    /// # When NOT to use
3002    /// - In composable functions that should update when state changes
3003    /// - When you want reactive UI updates
3004    pub fn get_non_reactive(&self) -> T {
3005        // Skip subscribe_current_scope() - just read the value directly
3006        self.with_inner(|inner| inner.state.get())
3007    }
3008
3009    #[cfg(test)]
3010    pub(crate) fn watcher_count(&self) -> usize {
3011        self.with_inner(|inner| inner.watchers.borrow().len())
3012    }
3013}
3014
3015impl<T: fmt::Debug + Clone + 'static> fmt::Debug for MutableState<T> {
3016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3017        self.with_inner(|inner| {
3018            inner.with_value(|value| {
3019                f.debug_struct("MutableState")
3020                    .field("value", value)
3021                    .finish()
3022            })
3023        })
3024    }
3025}
3026
3027#[derive(Clone)]
3028pub struct SnapshotStateList<T: Clone + 'static> {
3029    state: MutableState<Vec<T>>,
3030}
3031
3032impl<T: Clone + 'static> SnapshotStateList<T> {
3033    pub fn with_runtime<I>(values: I, runtime: RuntimeHandle) -> Self
3034    where
3035        I: IntoIterator<Item = T>,
3036    {
3037        let initial: Vec<T> = values.into_iter().collect();
3038        Self {
3039            state: MutableState::with_runtime(initial, runtime),
3040        }
3041    }
3042
3043    pub fn as_state(&self) -> State<Vec<T>> {
3044        self.state.as_state()
3045    }
3046
3047    pub fn as_mutable_state(&self) -> MutableState<Vec<T>> {
3048        self.state
3049    }
3050
3051    pub fn len(&self) -> usize {
3052        self.state.with(|values| values.len())
3053    }
3054
3055    pub fn is_empty(&self) -> bool {
3056        self.len() == 0
3057    }
3058
3059    pub fn to_vec(&self) -> Vec<T> {
3060        self.state.with(|values| values.clone())
3061    }
3062
3063    pub fn iter(&self) -> Vec<T> {
3064        self.to_vec()
3065    }
3066
3067    pub fn get(&self, index: usize) -> T {
3068        self.state.with(|values| values[index].clone())
3069    }
3070
3071    pub fn get_opt(&self, index: usize) -> Option<T> {
3072        self.state.with(|values| values.get(index).cloned())
3073    }
3074
3075    pub fn first(&self) -> Option<T> {
3076        self.get_opt(0)
3077    }
3078
3079    pub fn last(&self) -> Option<T> {
3080        self.state.with(|values| values.last().cloned())
3081    }
3082
3083    pub fn push(&self, value: T) {
3084        self.state.update(|values| values.push(value));
3085    }
3086
3087    pub fn extend<I>(&self, iter: I)
3088    where
3089        I: IntoIterator<Item = T>,
3090    {
3091        self.state.update(|values| values.extend(iter));
3092    }
3093
3094    pub fn insert(&self, index: usize, value: T) {
3095        self.state.update(|values| values.insert(index, value));
3096    }
3097
3098    pub fn set(&self, index: usize, value: T) -> T {
3099        self.state
3100            .update(|values| std::mem::replace(&mut values[index], value))
3101    }
3102
3103    pub fn remove(&self, index: usize) -> T {
3104        self.state.update(|values| values.remove(index))
3105    }
3106
3107    pub fn pop(&self) -> Option<T> {
3108        self.state.update(|values| values.pop())
3109    }
3110
3111    pub fn clear(&self) {
3112        self.state.replace(Vec::new());
3113    }
3114
3115    pub fn retain<F>(&self, mut predicate: F)
3116    where
3117        F: FnMut(&T) -> bool,
3118    {
3119        self.state
3120            .update(|values| values.retain(|value| predicate(value)));
3121    }
3122
3123    pub fn replace_with<I>(&self, iter: I)
3124    where
3125        I: IntoIterator<Item = T>,
3126    {
3127        self.state.replace(iter.into_iter().collect());
3128    }
3129}
3130
3131impl<T: fmt::Debug + Clone + 'static> fmt::Debug for SnapshotStateList<T> {
3132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3133        let contents = self.to_vec();
3134        f.debug_struct("SnapshotStateList")
3135            .field("values", &contents)
3136            .finish()
3137    }
3138}
3139
3140#[derive(Clone)]
3141pub struct SnapshotStateMap<K, V>
3142where
3143    K: Clone + Eq + Hash + 'static,
3144    V: Clone + 'static,
3145{
3146    state: MutableState<HashMap<K, V>>,
3147}
3148
3149impl<K, V> SnapshotStateMap<K, V>
3150where
3151    K: Clone + Eq + Hash + 'static,
3152    V: Clone + 'static,
3153{
3154    pub fn with_runtime<I>(pairs: I, runtime: RuntimeHandle) -> Self
3155    where
3156        I: IntoIterator<Item = (K, V)>,
3157    {
3158        let map: HashMap<K, V> = pairs.into_iter().collect();
3159        Self {
3160            state: MutableState::with_runtime(map, runtime),
3161        }
3162    }
3163
3164    pub fn as_state(&self) -> State<HashMap<K, V>> {
3165        self.state.as_state()
3166    }
3167
3168    pub fn as_mutable_state(&self) -> MutableState<HashMap<K, V>> {
3169        self.state
3170    }
3171
3172    pub fn len(&self) -> usize {
3173        self.state.with(|map| map.len())
3174    }
3175
3176    pub fn is_empty(&self) -> bool {
3177        self.state.with(|map| map.is_empty())
3178    }
3179
3180    pub fn contains_key(&self, key: &K) -> bool {
3181        self.state.with(|map| map.contains_key(key))
3182    }
3183
3184    pub fn get(&self, key: &K) -> Option<V> {
3185        self.state.with(|map| map.get(key).cloned())
3186    }
3187
3188    pub fn to_hash_map(&self) -> HashMap<K, V> {
3189        self.state.with(|map| map.clone())
3190    }
3191
3192    pub fn insert(&self, key: K, value: V) -> Option<V> {
3193        self.state.update(|map| map.insert(key, value))
3194    }
3195
3196    pub fn extend<I>(&self, iter: I)
3197    where
3198        I: IntoIterator<Item = (K, V)>,
3199    {
3200        self.state.update(|map| map.extend(iter));
3201        // extend returns (), but update requires returning something: we can just rely on ()
3202    }
3203
3204    pub fn remove(&self, key: &K) -> Option<V> {
3205        self.state.update(|map| map.remove(key))
3206    }
3207
3208    pub fn clear(&self) {
3209        self.state.replace(HashMap::default());
3210    }
3211
3212    pub fn retain<F>(&self, mut predicate: F)
3213    where
3214        F: FnMut(&K, &mut V) -> bool,
3215    {
3216        self.state.update(|map| map.retain(|k, v| predicate(k, v)));
3217    }
3218}
3219
3220impl<K, V> fmt::Debug for SnapshotStateMap<K, V>
3221where
3222    K: Clone + Eq + Hash + fmt::Debug + 'static,
3223    V: Clone + fmt::Debug + 'static,
3224{
3225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3226        let contents = self.to_hash_map();
3227        f.debug_struct("SnapshotStateMap")
3228            .field("entries", &contents)
3229            .finish()
3230    }
3231}
3232
3233struct DerivedState<T: Clone + 'static> {
3234    compute: Rc<dyn Fn() -> T>, // FUTURE(no_std): store compute closures in arena-managed cell.
3235    state: MutableState<T>,
3236}
3237
3238impl<T: Clone + 'static> DerivedState<T> {
3239    fn new(runtime: RuntimeHandle, compute: Rc<dyn Fn() -> T>) -> Self {
3240        // FUTURE(no_std): accept arena-managed compute handle.
3241        let initial = compute();
3242        Self {
3243            compute,
3244            state: MutableState::with_runtime(initial, runtime),
3245        }
3246    }
3247
3248    fn set_compute(&mut self, compute: Rc<dyn Fn() -> T>) {
3249        // FUTURE(no_std): accept arena-managed compute handle.
3250        self.compute = compute;
3251    }
3252
3253    fn recompute(&self) {
3254        let value = (self.compute)();
3255        self.state.set_value(value);
3256    }
3257}
3258
3259impl<T: fmt::Debug + Clone + 'static> fmt::Debug for State<T> {
3260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3261        self.with_inner(|inner| {
3262            inner.with_value(|value| f.debug_struct("State").field("value", value).finish())
3263        })
3264    }
3265}
3266
3267pub struct ParamState<T> {
3268    value: Option<T>,
3269}
3270
3271impl<T> ParamState<T> {
3272    pub fn update(&mut self, new_value: &T) -> bool
3273    where
3274        T: PartialEq + Clone,
3275    {
3276        match &self.value {
3277            Some(old) if old == new_value => false,
3278            _ => {
3279                self.value = Some(new_value.clone());
3280                true
3281            }
3282        }
3283    }
3284
3285    pub fn value(&self) -> Option<T>
3286    where
3287        T: Clone,
3288    {
3289        self.value.clone()
3290    }
3291}
3292
3293/// ParamSlot holds function/closure parameters by ownership (no PartialEq/Clone required).
3294/// Used by the #[composable] macro to store Fn-like parameters in the slot table.
3295pub struct ParamSlot<T> {
3296    val: RefCell<Option<T>>,
3297}
3298
3299impl<T> Default for ParamSlot<T> {
3300    fn default() -> Self {
3301        Self {
3302            val: RefCell::new(None),
3303        }
3304    }
3305}
3306
3307impl<T> ParamSlot<T> {
3308    pub fn set(&self, v: T) {
3309        *self.val.borrow_mut() = Some(v);
3310    }
3311
3312    /// Takes the value out temporarily (for recomposition callback)
3313    pub fn take(&self) -> T {
3314        self.val
3315            .borrow_mut()
3316            .take()
3317            .expect("ParamSlot take() called before set")
3318    }
3319}
3320
3321/// CallbackHolder keeps the latest callback closure alive across recompositions.
3322/// It stores the callback in an Rc<RefCell<...>> so that the composer can hand out
3323/// lightweight forwarder closures without cloning the underlying callback value.
3324#[derive(Clone)]
3325pub struct CallbackHolder {
3326    rc: Rc<RefCell<Box<dyn FnMut()>>>,
3327}
3328
3329impl CallbackHolder {
3330    /// Create a new holder with a no-op callback so that callers can immediately invoke it.
3331    pub fn new() -> Self {
3332        Self::default()
3333    }
3334
3335    /// Replace the stored callback with a new closure provided by the caller.
3336    pub fn update<F>(&self, f: F)
3337    where
3338        F: FnMut() + 'static,
3339    {
3340        *self.rc.borrow_mut() = Box::new(f);
3341    }
3342
3343    /// Produce a forwarder closure that keeps the holder alive and forwards calls to it.
3344    pub fn clone_rc(&self) -> impl FnMut() + 'static {
3345        let rc = self.rc.clone();
3346        move || {
3347            (rc.borrow_mut())();
3348        }
3349    }
3350}
3351
3352impl Default for CallbackHolder {
3353    fn default() -> Self {
3354        Self {
3355            rc: Rc::new(RefCell::new(Box::new(|| {}) as Box<dyn FnMut()>)),
3356        }
3357    }
3358}
3359
3360pub struct ReturnSlot<T> {
3361    value: Option<T>,
3362}
3363
3364impl<T: Clone> ReturnSlot<T> {
3365    pub fn store(&mut self, value: T) {
3366        self.value = Some(value);
3367    }
3368
3369    pub fn get(&self) -> Option<T> {
3370        self.value.clone()
3371    }
3372}
3373
3374impl<T> Default for ParamState<T> {
3375    fn default() -> Self {
3376        Self { value: None }
3377    }
3378}
3379
3380impl<T> Default for ReturnSlot<T> {
3381    fn default() -> Self {
3382        Self { value: None }
3383    }
3384}
3385
3386pub struct Composition<A: Applier + 'static> {
3387    slots: Rc<SlotsHost>,
3388    applier: Rc<ConcreteApplierHost<A>>,
3389    runtime: Runtime,
3390    observer: SnapshotStateObserver,
3391    root: Option<NodeId>,
3392}
3393
3394impl<A: Applier + 'static> Composition<A> {
3395    pub fn new(applier: A) -> Self {
3396        Self::with_runtime(applier, Runtime::new(Arc::new(DefaultScheduler)))
3397    }
3398
3399    pub fn with_runtime(applier: A, runtime: Runtime) -> Self {
3400        Self::with_backend(applier, runtime, SlotBackendKind::default())
3401    }
3402
3403    pub fn with_backend(applier: A, runtime: Runtime, backend_kind: SlotBackendKind) -> Self {
3404        let storage = make_backend(backend_kind);
3405        let slots = Rc::new(SlotsHost::new(storage));
3406        let applier = Rc::new(ConcreteApplierHost::new(applier));
3407        let observer_handle = runtime.handle();
3408        let observer = SnapshotStateObserver::new(move |callback| {
3409            observer_handle.enqueue_ui_task(callback);
3410        });
3411        observer.start();
3412        Self {
3413            slots,
3414            applier,
3415            runtime,
3416            observer,
3417            root: None,
3418        }
3419    }
3420
3421    fn slots_host(&self) -> Rc<SlotsHost> {
3422        Rc::clone(&self.slots)
3423    }
3424
3425    fn applier_host(&self) -> Rc<dyn ApplierHost> {
3426        self.applier.clone()
3427    }
3428
3429    pub fn render(&mut self, key: Key, mut content: impl FnMut()) -> Result<(), NodeError> {
3430        self.slots.borrow_mut().reset();
3431        let runtime_handle = self.runtime_handle();
3432        runtime_handle.drain_ui();
3433        let composer = Composer::new(
3434            Rc::clone(&self.slots),
3435            self.applier.clone(),
3436            runtime_handle.clone(),
3437            self.observer.clone(),
3438            self.root,
3439        );
3440        self.observer.begin_frame();
3441        let (root, mut commands, side_effects) = composer.install(|composer| {
3442            composer.with_group(key, |_| content());
3443            let root = composer.root();
3444            let commands = composer.take_commands();
3445            let side_effects = composer.take_side_effects();
3446            (root, commands, side_effects)
3447        });
3448
3449        {
3450            let mut applier = self.applier.borrow_dyn();
3451            for mut command in commands.drain(..) {
3452                command(&mut *applier)?;
3453            }
3454            for mut update in runtime_handle.take_updates() {
3455                update(&mut *applier)?;
3456            }
3457        }
3458
3459        runtime_handle.drain_ui();
3460        for effect in side_effects {
3461            effect();
3462        }
3463        runtime_handle.drain_ui();
3464        self.root = root;
3465        {
3466            let mut slots = self.slots.borrow_mut();
3467            let _ = slots.finalize_current_group();
3468            slots.flush();
3469        }
3470        let _ = self.process_invalid_scopes()?;
3471        if !self.runtime.has_updates()
3472            && !runtime_handle.has_invalid_scopes()
3473            && !runtime_handle.has_frame_callbacks()
3474            && !runtime_handle.has_pending_ui()
3475        {
3476            self.runtime.set_needs_frame(false);
3477        }
3478        Ok(())
3479    }
3480
3481    /// Returns true if composition needs to process invalid scopes (recompose).
3482    ///
3483    /// This checks both:
3484    /// - `has_updates()`: composition scopes that were invalidated by state changes
3485    /// - `needs_frame()`: animation callbacks that may have pending work
3486    ///
3487    /// Note: For scroll performance, ensure scroll state changes use Cell<T> instead
3488    /// of MutableState<T> to avoid triggering recomposition on every scroll frame.
3489    pub fn should_render(&self) -> bool {
3490        self.runtime.needs_frame() || self.runtime.has_updates()
3491    }
3492
3493    pub fn runtime_handle(&self) -> RuntimeHandle {
3494        self.runtime.handle()
3495    }
3496
3497    pub fn applier_mut(&mut self) -> ApplierGuard<'_, A> {
3498        ApplierGuard::new(self.applier.borrow_typed())
3499    }
3500
3501    pub fn root(&self) -> Option<NodeId> {
3502        self.root
3503    }
3504
3505    pub fn debug_dump_slot_table_groups(&self) -> Vec<(usize, Key, Option<ScopeId>, usize)> {
3506        self.slots.borrow().debug_dump_groups()
3507    }
3508
3509    pub fn debug_dump_all_slots(&self) -> Vec<(usize, String)> {
3510        self.slots.borrow().debug_dump_all_slots()
3511    }
3512
3513    pub fn process_invalid_scopes(&mut self) -> Result<bool, NodeError> {
3514        let runtime_handle = self.runtime_handle();
3515        let mut did_recompose = false;
3516        let mut loop_count = 0;
3517        loop {
3518            loop_count += 1;
3519            if loop_count > 100 {
3520                log::error!("process_invalid_scopes looped too many times! Breaking loop to prevent freeze.");
3521                break;
3522            }
3523            runtime_handle.drain_ui();
3524            let pending = runtime_handle.take_invalidated_scopes();
3525            if pending.is_empty() {
3526                break;
3527            }
3528            let mut scopes = Vec::new();
3529            for (id, weak) in pending {
3530                if let Some(inner) = weak.upgrade() {
3531                    scopes.push(RecomposeScope { inner });
3532                } else {
3533                    runtime_handle.mark_scope_recomposed(id);
3534                }
3535            }
3536            if scopes.is_empty() {
3537                continue;
3538            }
3539            did_recompose = true;
3540            let runtime_clone = runtime_handle.clone();
3541            let (mut commands, side_effects) = {
3542                let composer = Composer::new(
3543                    self.slots_host(),
3544                    self.applier_host(),
3545                    runtime_clone,
3546                    self.observer.clone(),
3547                    self.root,
3548                );
3549                self.observer.begin_frame();
3550                composer.install(|composer| {
3551                    for scope in scopes.iter() {
3552                        composer.recranpose_group(scope);
3553                    }
3554                    let commands = composer.take_commands();
3555                    let side_effects = composer.take_side_effects();
3556                    (commands, side_effects)
3557                })
3558            };
3559            {
3560                let mut applier = self.applier.borrow_dyn();
3561                for mut command in commands.drain(..) {
3562                    command(&mut *applier)?;
3563                }
3564                for mut update in runtime_handle.take_updates() {
3565                    update(&mut *applier)?;
3566                }
3567            }
3568            for effect in side_effects {
3569                effect();
3570            }
3571            runtime_handle.drain_ui();
3572        }
3573        if !self.runtime.has_updates()
3574            && !runtime_handle.has_invalid_scopes()
3575            && !runtime_handle.has_frame_callbacks()
3576            && !runtime_handle.has_pending_ui()
3577        {
3578            self.runtime.set_needs_frame(false);
3579        }
3580        Ok(did_recompose)
3581    }
3582
3583    pub fn flush_pending_node_updates(&mut self) -> Result<(), NodeError> {
3584        let updates = self.runtime_handle().take_updates();
3585        let mut applier = self.applier.borrow_dyn();
3586        for mut update in updates {
3587            update(&mut *applier)?;
3588        }
3589        Ok(())
3590    }
3591}
3592
3593impl<A: Applier + 'static> Drop for Composition<A> {
3594    fn drop(&mut self) {
3595        self.observer.stop();
3596    }
3597}
3598pub fn location_key(file: &str, line: u32, column: u32) -> Key {
3599    let base = file.as_ptr() as u64;
3600    base
3601        .wrapping_mul(0x9E37_79B9_7F4A_7C15) // cheap mix
3602        ^ ((line as u64) << 32)
3603        ^ (column as u64)
3604}
3605
3606fn hash_key<K: Hash>(key: &K) -> Key {
3607    let mut hasher = hash::default::new();
3608    key.hash(&mut hasher);
3609    hasher.finish()
3610}
3611
3612#[cfg(test)]
3613#[path = "tests/lib_tests.rs"]
3614mod tests;
3615
3616#[cfg(test)]
3617#[path = "tests/recursive_decrease_increase_test.rs"]
3618mod recursive_decrease_increase_test;
3619
3620#[cfg(test)]
3621#[path = "tests/slot_backend_tests.rs"]
3622mod slot_backend_tests;
3623
3624pub mod collections;
3625pub mod hash;