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