Skip to main content

cranpose_core/
composer.rs

1use crate::collections::map::{HashMap, HashSet};
2use crate::retention::{RetainKey, RetentionManager};
3use crate::slot::{FinishGroupResult, PayloadKind};
4use crate::slot::{GroupStart, GroupStartKind, ValueSlotId};
5use crate::{
6    composer_context, empty_local_stack, explicit_group_key_seed, runtime, Applier, ApplierHost,
7    ChildList, Command, CommandQueue, CompositionLocal, DirtyBubble, Key, LocalKey,
8    LocalStackSnapshot, LocalStateEntry, MutableState, Node, NodeError, NodeId, Owned,
9    ProvidedValue, RecomposeOptions, RecomposeScope, RecycledNode, RetentionMode, RetentionPolicy,
10    RuntimeHandle, ScopeId, SlotId, SlotPassOutcome, SlotTable, SlotsHost, SnapshotStateList,
11    SnapshotStateMap, SnapshotStateObserver, StaticCompositionLocal, StaticLocalEntry,
12    SubcomposeState, COMMAND_FLUSH_THRESHOLD,
13};
14use smallvec::SmallVec;
15use std::any::Any;
16use std::cell::{Cell, RefCell, RefMut};
17use std::hash::Hash;
18use std::marker::PhantomData;
19use std::rc::Rc;
20
21pub struct ValueSlotHandle<'pass, T: 'static> {
22    slot: ValueSlotId,
23    _pass: PhantomData<&'pass Composer>,
24    _value: PhantomData<fn() -> T>,
25}
26
27impl<T: 'static> Copy for ValueSlotHandle<'_, T> {}
28
29impl<T: 'static> Clone for ValueSlotHandle<'_, T> {
30    fn clone(&self) -> Self {
31        *self
32    }
33}
34
35impl<T: 'static> ValueSlotHandle<'_, T> {
36    pub(crate) fn new(slot: ValueSlotId) -> Self {
37        Self {
38            slot,
39            _pass: PhantomData,
40            _value: PhantomData,
41        }
42    }
43
44    pub(crate) fn slot(self) -> ValueSlotId {
45        self.slot
46    }
47}
48
49fn slots_storage_key(host: &Rc<SlotsHost>) -> usize {
50    host.storage_key()
51}
52
53fn bind_slots_host_to_runtime_state(
54    state: &Rc<ComposerRuntimeState>,
55    host: &Rc<SlotsHost>,
56) -> Rc<SlotsHost> {
57    if let Some(bound_state) = host.runtime_state() {
58        if Rc::ptr_eq(&bound_state, state) {
59            state.bind_slots_host(host);
60            return Rc::clone(host);
61        }
62        drop(bound_state);
63        if host.rebind_orphaned_runtime_state(state) {
64            state.bind_slots_host(host);
65            return Rc::clone(host);
66        }
67        log::error!(
68            "slot host already belongs to a different composer runtime state; using a fresh slot host"
69        );
70        let replacement = Rc::new(SlotsHost::new(SlotTable::new()));
71        state.bind_slots_host(&replacement);
72        return replacement;
73    }
74    state.bind_slots_host(host);
75    Rc::clone(host)
76}
77
78struct SlotHostPassGuard {
79    core: Rc<ComposerCore>,
80    host: Rc<SlotsHost>,
81    active: bool,
82}
83
84impl SlotHostPassGuard {
85    fn close(&mut self) {
86        if !self.active {
87            return;
88        }
89        if self.host.has_active_pass() {
90            self.host.abandon_active_pass();
91        }
92        match self.core.slot_hosts.borrow_mut().pop() {
93            Some(host) if Rc::ptr_eq(&host, &self.host) => {}
94            Some(_) => {
95                log::error!("slot host stack mismatch while closing slot host pass");
96            }
97            None => {
98                log::error!("slot host stack underflow while closing slot host pass");
99            }
100        }
101        self.active = false;
102    }
103}
104
105impl Drop for SlotHostPassGuard {
106    fn drop(&mut self) {
107        self.close();
108    }
109}
110
111pub(crate) struct ComposerRuntimeState {
112    scope_registry: RefCell<HashMap<ScopeId, RecomposeScope>>,
113    retention_by_host: RefCell<HashMap<usize, RetentionManager>>,
114    retention_policy: Cell<RetentionPolicy>,
115    live_hosts: RefCell<HashMap<usize, std::rc::Weak<SlotsHost>>>,
116    applier_host: RefCell<Option<std::rc::Weak<dyn ApplierHost>>>,
117}
118
119impl Default for ComposerRuntimeState {
120    fn default() -> Self {
121        Self {
122            scope_registry: RefCell::new(HashMap::default()),
123            retention_by_host: RefCell::new(HashMap::default()),
124            retention_policy: Cell::new(RetentionPolicy::default()),
125            live_hosts: RefCell::new(HashMap::default()),
126            applier_host: RefCell::new(None),
127        }
128    }
129}
130
131impl ComposerRuntimeState {
132    pub(crate) fn clear_host_storage_key(&self, host_key: usize) {
133        self.retention_by_host.borrow_mut().remove(&host_key);
134        self.live_hosts.borrow_mut().remove(&host_key);
135        self.scope_registry
136            .borrow_mut()
137            .retain(|_, scope| scope.slots_storage_key() != Some(host_key));
138    }
139
140    pub(crate) fn bind_applier_host(&self, applier: &Rc<dyn ApplierHost>) {
141        *self.applier_host.borrow_mut() = Some(Rc::downgrade(applier));
142    }
143
144    pub(crate) fn has_live_applier_host(&self) -> bool {
145        self.applier_host
146            .borrow()
147            .as_ref()
148            .and_then(std::rc::Weak::upgrade)
149            .is_some()
150    }
151
152    pub(crate) fn bind_slots_host(self: &Rc<Self>, host: &Rc<SlotsHost>) {
153        host.bind_runtime_state(self);
154        self.live_hosts
155            .borrow_mut()
156            .insert(host.storage_key(), Rc::downgrade(host));
157    }
158
159    pub(crate) fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
160        self.scope_registry.borrow().get(&scope_id).cloned()
161    }
162
163    pub(crate) fn register_scope(&self, scope: &RecomposeScope) {
164        self.scope_registry
165            .borrow_mut()
166            .insert(scope.id(), scope.clone());
167    }
168
169    pub(crate) fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
170        self.scope_registry.borrow_mut().remove(&scope_id)
171    }
172
173    pub(crate) fn set_retention_policy(&self, policy: RetentionPolicy) {
174        self.retention_policy.set(policy);
175    }
176
177    pub(crate) fn retention_policy(&self) -> RetentionPolicy {
178        self.retention_policy.get()
179    }
180
181    pub(crate) fn scope_registry_len(&self) -> usize {
182        self.scope_registry.borrow().len()
183    }
184
185    pub(crate) fn take_retained(
186        &self,
187        host: &Rc<SlotsHost>,
188        key: RetainKey,
189        preflight: impl FnOnce(&crate::slot::DetachedSubtree) -> bool,
190    ) -> Option<crate::slot::DetachedSubtree> {
191        let host_key = slots_storage_key(host);
192        let mut retention = self.retention_by_host.borrow_mut();
193        let subtree = retention
194            .get_mut(&host_key)?
195            .take_after_restore_preflight(key, preflight);
196        if retention
197            .get(&host_key)
198            .is_some_and(|manager| manager.is_empty() && manager.evictions_total() == 0)
199        {
200            retention.remove(&host_key);
201        }
202        subtree
203    }
204
205    pub(crate) fn insert_retained(
206        &self,
207        host: &Rc<SlotsHost>,
208        key: RetainKey,
209        subtree: crate::slot::DetachedSubtree,
210    ) -> Vec<crate::slot::DetachedSubtree> {
211        let policy = self.retention_policy();
212        let mut retention_by_host = self.retention_by_host.borrow_mut();
213        let manager = retention_by_host
214            .entry(slots_storage_key(host))
215            .or_insert_with(|| RetentionManager::new(policy));
216        manager.set_policy(policy);
217        manager.insert(key, subtree)
218    }
219
220    pub(crate) fn advance_retention_pass(
221        &self,
222        host: &Rc<SlotsHost>,
223    ) -> Vec<crate::slot::DetachedSubtree> {
224        let host_key = slots_storage_key(host);
225        let policy = self.retention_policy();
226        let mut retention_by_host = self.retention_by_host.borrow_mut();
227        let Some(manager) = retention_by_host.get_mut(&host_key) else {
228            return Vec::new();
229        };
230        manager.set_policy(policy);
231        manager.advance_pass()
232    }
233
234    pub(crate) fn fill_slot_debug_snapshot(
235        &self,
236        host: &SlotsHost,
237        snapshot: &mut crate::SlotDebugSnapshot,
238    ) {
239        let retention = self.retention_debug_stats(host.storage_key());
240        snapshot.runtime_scope_registry_count = Some(self.scope_registry_len());
241        snapshot.retained_subtree_count = retention.subtree_count;
242        snapshot.retained_group_count = retention.group_count;
243        snapshot.retained_payload_count = retention.payload_count;
244        snapshot.retained_node_count = retention.node_count;
245        snapshot.retained_scope_count = retention.scope_count;
246    }
247
248    pub(crate) fn slot_retention_debug_stats(
249        &self,
250        host: &SlotsHost,
251    ) -> crate::slot::SlotRetentionDebugStats {
252        let retention = self.retention_debug_stats(host.storage_key());
253        crate::slot::SlotRetentionDebugStats {
254            retained_subtree_count: retention.subtree_count,
255            retained_group_count: retention.group_count,
256            retained_payload_count: retention.payload_count,
257            retained_node_count: retention.node_count,
258            retained_scope_count: retention.scope_count,
259            retained_anchor_count: retention.anchor_count,
260            retained_heap_bytes: retention.heap_bytes,
261            retained_evictions_total: retention.evictions_total,
262        }
263    }
264
265    pub(crate) fn compact_table_identity_storage_for_host(
266        &self,
267        host: &SlotsHost,
268        table: &mut SlotTable,
269        compact_anchors: bool,
270        compact_payloads: bool,
271    ) {
272        if !compact_anchors && !compact_payloads {
273            return;
274        }
275
276        let host_key = host.storage_key();
277        let mut retention = self.retention_by_host.borrow_mut();
278        if let Some(retained) = retention.get_mut(&host_key) {
279            if compact_anchors {
280                table.compact_anchor_registry_storage(Some(&mut *retained));
281            }
282            if compact_payloads {
283                table.compact_payload_anchor_registry_storage(Some(&mut *retained));
284            }
285        } else {
286            if compact_anchors {
287                table.compact_anchor_registry_storage(None);
288            }
289            if compact_payloads {
290                table.compact_payload_anchor_registry_storage(None);
291            }
292        }
293    }
294
295    pub(crate) fn clear_host(&self, host: &SlotsHost) {
296        let host_key = host.storage_key();
297        debug_assert!(
298            self.host_retention_is_empty(host),
299            "host retention must be drained before clearing host ownership"
300        );
301        self.clear_host_storage_key(host_key);
302    }
303
304    pub(crate) fn dispose_retained_subtrees_for_host(
305        &self,
306        host_key: usize,
307        table: &mut SlotTable,
308        lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
309    ) -> Result<(), NodeError> {
310        let applier_host = self
311            .applier_host
312            .borrow()
313            .as_ref()
314            .and_then(std::rc::Weak::upgrade);
315        if let Some(applier_host) = applier_host.as_ref() {
316            let retention_by_host = self.retention_by_host.borrow();
317            let Some(retention) = retention_by_host.get(&host_key) else {
318                return Ok(());
319            };
320            let mut applier = applier_host.borrow_dyn();
321            for subtree in retention.subtrees() {
322                crate::slot::dispose_detached_subtree_now(&mut *applier, subtree)?;
323            }
324        }
325        let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
326            return Ok(());
327        };
328        for subtree in retention.into_subtrees() {
329            for scope_id in subtree.scope_ids() {
330                if let Some(scope) = self.remove_scope(scope_id) {
331                    scope.deactivate();
332                }
333            }
334            table.invalidate_detached_subtree_anchors(&subtree);
335            lifecycle.queue_subtree_disposal(subtree);
336        }
337        Ok(())
338    }
339
340    pub(crate) fn abandon_retained_subtrees_for_host(
341        &self,
342        host_key: usize,
343        table: &mut SlotTable,
344        lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
345    ) {
346        let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
347            self.clear_host_storage_key(host_key);
348            return;
349        };
350        for subtree in retention.into_subtrees() {
351            for scope_id in subtree.scope_ids() {
352                if let Some(scope) = self.remove_scope(scope_id) {
353                    scope.deactivate();
354                }
355            }
356            table.invalidate_detached_subtree_anchors(&subtree);
357            lifecycle.queue_subtree_disposal(subtree);
358        }
359        self.clear_host_storage_key(host_key);
360    }
361
362    pub(crate) fn host_retention_is_empty(&self, host: &SlotsHost) -> bool {
363        self.retention_by_host
364            .borrow()
365            .get(&host.storage_key())
366            .is_none_or(RetentionManager::is_empty)
367    }
368
369    #[cfg(any(test, debug_assertions))]
370    pub(crate) fn debug_verify_host(&self, host: &SlotsHost, table: &SlotTable) {
371        if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
372            retention.debug_verify(table);
373        }
374    }
375
376    #[cfg(test)]
377    pub(crate) fn validate_host_retention(
378        &self,
379        host: &SlotsHost,
380        table: &SlotTable,
381    ) -> Result<(), crate::slot::SlotInvariantError> {
382        if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
383            retention.validate(table)?;
384        }
385        Ok(())
386    }
387
388    pub(crate) fn host_for_storage_key(&self, storage_key: usize) -> Option<Rc<SlotsHost>> {
389        self.live_hosts
390            .borrow()
391            .get(&storage_key)
392            .and_then(std::rc::Weak::upgrade)
393    }
394
395    fn retention_debug_stats(&self, host_key: usize) -> crate::retention::RetentionDebugStats {
396        self.retention_by_host
397            .borrow()
398            .get(&host_key)
399            .map(RetentionManager::debug_stats)
400            .unwrap_or_default()
401    }
402}
403
404pub(crate) struct ParentFrame {
405    pub(crate) id: NodeId,
406    pub(crate) previous: ChildList,
407    pub(crate) new_children: ChildList,
408    pub(crate) new_children_membership: Option<HashSet<NodeId>>,
409    pub(crate) attach_mode: ParentAttachMode,
410    pub(crate) synthetic_root: bool,
411}
412
413#[derive(Clone, Copy)]
414pub(crate) enum InitialParentFrame {
415    SyntheticRoot,
416    RealParent,
417}
418
419const LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD: usize = 16;
420
421#[derive(Clone, Copy, Debug, PartialEq, Eq)]
422pub(crate) enum ParentAttachMode {
423    ImmediateAppend,
424    DeferredSync,
425}
426
427#[derive(Default)]
428pub(crate) struct SubcomposeFrame {
429    pub(crate) nodes: Vec<NodeId>,
430    pub(crate) scopes: Vec<RecomposeScope>,
431}
432
433#[derive(Default, Clone)]
434pub(crate) struct LocalContext {
435    pub(crate) values: HashMap<LocalKey, Rc<dyn Any>>,
436}
437
438pub(crate) struct ComposerCore {
439    pub(crate) shared_state: Rc<ComposerRuntimeState>,
440    pub(crate) slots: Rc<SlotsHost>,
441    slot_hosts: RefCell<Vec<Rc<SlotsHost>>>,
442    pub(crate) applier: Rc<dyn ApplierHost>,
443    pub(crate) runtime: RuntimeHandle,
444    pub(crate) observer: SnapshotStateObserver,
445    pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
446    pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
447    pub(crate) root: Cell<Option<NodeId>>,
448    pub(crate) commands: RefCell<CommandQueue>,
449    pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
450    pub(crate) local_stack: RefCell<LocalStackSnapshot>,
451    pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
452    pub(crate) pending_scope_options: RefCell<Option<RecomposeOptions>>,
453    pub(crate) phase: Cell<crate::Phase>,
454    pub(crate) last_node_reused: Cell<Option<bool>>,
455    pub(crate) recranpose_parent_hint: Cell<Option<NodeId>>,
456    pub(crate) root_render_requested: Cell<bool>,
457    pub(crate) _not_send: PhantomData<*const ()>,
458}
459
460fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
461    match core.subcompose_stack.borrow_mut().pop() {
462        Some(frame) => frame,
463        None => {
464            log::error!("subcompose stack underflow while finishing {operation}");
465            SubcomposeFrame::default()
466        }
467    }
468}
469
470impl ComposerCore {
471    pub(crate) fn new(
472        shared_state: Rc<ComposerRuntimeState>,
473        slots: Rc<SlotsHost>,
474        applier: Rc<dyn ApplierHost>,
475        runtime: RuntimeHandle,
476        observer: SnapshotStateObserver,
477        root: Option<NodeId>,
478        initial_parent_frame: InitialParentFrame,
479    ) -> Self {
480        let parent_stack = if let Some(root_id) = root {
481            vec![ParentFrame {
482                id: root_id,
483                previous: ChildList::new(),
484                new_children: ChildList::new(),
485                new_children_membership: None,
486                attach_mode: ParentAttachMode::DeferredSync,
487                synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
488            }]
489        } else {
490            Vec::new()
491        };
492
493        Self {
494            shared_state,
495            slots,
496            slot_hosts: RefCell::new(Vec::new()),
497            applier,
498            runtime,
499            observer,
500            parent_stack: RefCell::new(parent_stack),
501            subcompose_stack: RefCell::new(Vec::new()),
502            root: Cell::new(root),
503            commands: RefCell::new(CommandQueue::default()),
504            scope_stack: RefCell::new(Vec::new()),
505            local_stack: RefCell::new(empty_local_stack()),
506            side_effects: RefCell::new(Vec::new()),
507            pending_scope_options: RefCell::new(None),
508            phase: Cell::new(crate::Phase::Compose),
509            last_node_reused: Cell::new(None),
510            recranpose_parent_hint: Cell::new(None),
511            root_render_requested: Cell::new(false),
512            _not_send: PhantomData,
513        }
514    }
515}
516
517#[derive(Clone)]
518pub struct Composer {
519    pub(crate) core: Rc<ComposerCore>,
520}
521
522pub(crate) enum EmittedNode {
523    Fresh(Box<dyn Node>),
524    Recycled(RecycledNode),
525}
526
527impl Composer {
528    pub(crate) fn new_with_shared_state(
529        shared_state: Rc<ComposerRuntimeState>,
530        slots: Rc<SlotsHost>,
531        applier: Rc<dyn ApplierHost>,
532        runtime: RuntimeHandle,
533        observer: SnapshotStateObserver,
534        root: Option<NodeId>,
535    ) -> Self {
536        Self::new_with_shared_state_with_parent_frame(
537            shared_state,
538            slots,
539            applier,
540            runtime,
541            observer,
542            root,
543            InitialParentFrame::SyntheticRoot,
544        )
545    }
546
547    fn new_with_shared_state_with_parent_frame(
548        shared_state: Rc<ComposerRuntimeState>,
549        slots: Rc<SlotsHost>,
550        applier: Rc<dyn ApplierHost>,
551        runtime: RuntimeHandle,
552        observer: SnapshotStateObserver,
553        root: Option<NodeId>,
554        initial_parent_frame: InitialParentFrame,
555    ) -> Self {
556        shared_state.bind_applier_host(&applier);
557        let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
558        let core = Rc::new(ComposerCore::new(
559            shared_state,
560            slots,
561            applier,
562            runtime,
563            observer,
564            root,
565            initial_parent_frame,
566        ));
567        Self { core }
568    }
569
570    pub fn new(
571        slots: Rc<SlotsHost>,
572        applier: Rc<dyn ApplierHost>,
573        runtime: RuntimeHandle,
574        observer: SnapshotStateObserver,
575        root: Option<NodeId>,
576    ) -> Self {
577        Self::new_with_shared_state_with_parent_frame(
578            slots
579                .runtime_state()
580                .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
581            slots,
582            applier,
583            runtime,
584            observer,
585            root,
586            InitialParentFrame::RealParent,
587        )
588    }
589
590    pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
591        Self { core }
592    }
593
594    pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
595        Rc::clone(&self.core)
596    }
597
598    fn observer(&self) -> SnapshotStateObserver {
599        self.core.observer.clone()
600    }
601
602    pub(crate) fn request_root_render(&self) {
603        self.core.root_render_requested.set(true);
604    }
605
606    pub(crate) fn take_root_render_request(&self) -> bool {
607        self.core.root_render_requested.replace(false)
608    }
609
610    pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
611        let observer = self.observer();
612        let scope_clone = scope.clone();
613        observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
614    }
615
616    pub(crate) fn active_slots_host(&self) -> Rc<SlotsHost> {
617        self.core
618            .slot_hosts
619            .borrow()
620            .last()
621            .cloned()
622            .unwrap_or_else(|| Rc::clone(&self.core.slots))
623    }
624
625    pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
626        let host = self.active_slots_host();
627        let slots = host.borrow();
628        f(&slots)
629    }
630
631    pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
632        let host = self.active_slots_host();
633        let mut slots = host.borrow_mut();
634        f(&mut slots)
635    }
636
637    pub(crate) fn with_slot_session_mut<R>(
638        &self,
639        f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
640    ) -> R {
641        self.active_slots_host().with_write_session(f)
642    }
643
644    pub(crate) fn try_with_slot_host_pass<R>(
645        &self,
646        slots: Rc<SlotsHost>,
647        mode: crate::slot::SlotPassMode,
648        f: impl FnOnce(&Composer) -> R,
649    ) -> Result<(R, SlotPassOutcome), NodeError> {
650        let mut guard = self.begin_slot_host_pass(&slots, mode);
651        let result = f(self);
652        let outcome = self.finish_slot_host_pass(&guard.host)?;
653        guard.close();
654        Ok((result, outcome))
655    }
656
657    pub(crate) fn with_slot_host_pass<R>(
658        &self,
659        slots: Rc<SlotsHost>,
660        mode: crate::slot::SlotPassMode,
661        f: impl FnOnce(&Composer) -> R,
662    ) -> (R, SlotPassOutcome) {
663        let mut guard = self.begin_slot_host_pass(&slots, mode);
664        let result = f(self);
665        let outcome = match self.finish_slot_host_pass(&guard.host) {
666            Ok(outcome) => outcome,
667            Err(err) => {
668                log::error!("slot host pass finalization failed: {err}");
669                SlotPassOutcome::default()
670            }
671        };
672        guard.close();
673        (result, outcome)
674    }
675
676    pub(crate) fn with_slot_override<R>(
677        &self,
678        slots: Rc<SlotsHost>,
679        f: impl FnOnce(&Composer) -> R,
680    ) -> (R, SlotPassOutcome) {
681        self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
682    }
683
684    fn begin_slot_host_pass(
685        &self,
686        slots: &Rc<SlotsHost>,
687        mode: crate::slot::SlotPassMode,
688    ) -> SlotHostPassGuard {
689        let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
690        slots.begin_pass(mode);
691        self.core.slot_hosts.borrow_mut().push(Rc::clone(&slots));
692        SlotHostPassGuard {
693            core: self.clone_core(),
694            host: slots,
695            active: true,
696        }
697    }
698
699    fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
700        let finished = {
701            let mut applier = self.core.applier.borrow_dyn();
702            slots.finish_pass(&mut *applier)
703        }?;
704        self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
705        self.evict_retained_subtrees_for_host(slots)?;
706        slots.complete_pass_cleanup(&finished.outcome);
707        Ok(finished.outcome)
708    }
709
710    pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
711        self.core.parent_stack.borrow_mut()
712    }
713
714    fn current_parent_hint(&self) -> Option<NodeId> {
715        let stack = self.core.parent_stack.borrow();
716        let stack_hint = stack
717            .last()
718            .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
719        stack_hint.or_else(|| self.core.recranpose_parent_hint.get())
720    }
721
722    pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
723        self.core.subcompose_stack.borrow_mut()
724    }
725
726    pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
727        self.core.commands.borrow_mut()
728    }
729
730    pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
731        self.commands_mut().push(Command::BubbleDirty {
732            node_id: id,
733            bubble: DirtyBubble::SEMANTICS,
734        });
735    }
736
737    pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
738        self.core.scope_stack.borrow_mut()
739    }
740
741    fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
742        self.core.shared_state.scope_for_id(scope_id)
743    }
744
745    fn register_scope(&self, scope: &RecomposeScope) {
746        self.core.shared_state.register_scope(scope);
747    }
748
749    fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
750        self.core.shared_state.remove_scope(scope_id)
751    }
752
753    pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
754        self.core.local_stack.borrow_mut()
755    }
756
757    pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
758        self.core.local_stack.borrow().clone()
759    }
760
761    pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
762        self.core.side_effects.borrow_mut()
763    }
764
765    fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
766        self.core.pending_scope_options.borrow_mut()
767    }
768
769    pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
770        self.core.applier.borrow_dyn()
771    }
772
773    /// Registers a virtual node in the Applier.
774    ///
775    /// This is used by SubcomposeLayoutNode to register virtual container nodes
776    /// so that subsequent insert_child commands can find them and attach children.
777    /// Without this, virtual nodes would only exist in SubcomposeLayoutNodeInner.virtual_nodes
778    /// and applier.get_mut(virtual_node_id) would fail, breaking child attachment.
779    pub fn register_virtual_node(
780        &self,
781        node_id: NodeId,
782        node: Box<dyn Node>,
783    ) -> Result<(), NodeError> {
784        let mut applier = self.borrow_applier();
785        applier.insert_with_id(node_id, node)
786    }
787
788    /// Checks if a node has no parent (is a root node).
789    /// Used by SubcomposeMeasureScope to filter subcompose results.
790    pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
791        let mut applier = self.borrow_applier();
792        match applier.get_mut(node_id) {
793            Ok(node) => node.parent().is_none(),
794            Err(_) => true,
795        }
796    }
797
798    /// Gets the children of a node from the Applier.
799    ///
800    /// This is used by SubcomposeLayoutNode to get children of virtual nodes
801    /// directly from the Applier, where insert_child commands have been applied.
802    pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
803        let mut applier = self.borrow_applier();
804        match applier.get_mut(node_id) {
805            Ok(node) => {
806                let mut children = SmallVec::<[NodeId; 8]>::new();
807                node.collect_children_into(&mut children);
808                children
809            }
810            Err(_) => SmallVec::<[NodeId; 8]>::new(),
811        }
812    }
813
814    pub fn nodes_need_measure(&self, node_ids: &[NodeId]) -> bool {
815        let mut applier = self.borrow_applier();
816        node_ids.iter().any(|node_id| {
817            applier
818                .get_mut(*node_id)
819                .is_ok_and(|node| node.needs_measure())
820        })
821    }
822
823    /// Records a child node in the current parent frame's expected children list.
824    ///
825    /// Used by SubcomposeLayout's `perform_subcompose` to register virtual nodes
826    /// with the outer composer's parent frame. This ensures that the `pop_parent`
827    /// call at the end of `subcompose_slot` generates a correct `SyncChildren`
828    /// command that preserves (rather than removes) the virtual nodes.
829    ///
830    /// Without this, `pop_parent` would generate `SyncChildren { expected: [] }`,
831    /// which removes all virtual nodes and their subtrees from the applier.
832    pub fn record_subcompose_child(&self, child_id: NodeId) {
833        let mut parent_stack = self.parent_stack();
834        if let Some(frame) = parent_stack.last_mut() {
835            if matches!(frame.attach_mode, ParentAttachMode::DeferredSync) {
836                if let Some(membership) = frame.new_children_membership.as_mut() {
837                    if membership.insert(child_id) {
838                        frame.new_children.push(child_id);
839                    }
840                } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
841                    let mut membership = HashSet::default();
842                    membership.reserve(frame.new_children.len() + 1);
843                    membership.extend(frame.new_children.iter().copied());
844                    if membership.insert(child_id) {
845                        frame.new_children.push(child_id);
846                    }
847                    frame.new_children_membership = Some(membership);
848                } else if !frame.new_children.contains(&child_id) {
849                    frame.new_children.push(child_id);
850                }
851            }
852        }
853    }
854
855    /// Clears all children of a node in the Applier.
856    ///
857    /// This is used by SubcomposeLayoutNode when reusing a virtual node for
858    /// different content. Without clearing, old children remain attached,
859    /// causing duplicate/interleaved items in lazy lists after scrolling.
860    pub fn clear_node_children(&self, node_id: NodeId) {
861        let mut applier = self.borrow_applier();
862        if let Ok(node) = applier.get_mut(node_id) {
863            node.update_children(&[]);
864        }
865    }
866
867    pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
868        let _composer_guard = composer_context::enter(self);
869        runtime::push_active_runtime(&self.core.runtime);
870        struct Guard;
871        impl Drop for Guard {
872            fn drop(&mut self) {
873                runtime::pop_active_runtime();
874            }
875        }
876        let guard = Guard;
877        let result = f(self);
878        drop(guard);
879        result
880    }
881
882    pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
883        let queued = self.core.commands.borrow().len();
884        if queued < COMMAND_FLUSH_THRESHOLD {
885            return Ok(());
886        }
887        self.apply_pending_commands()
888    }
889
890    /// Thin monomorphic shim: the group machinery lives in
891    /// [`Self::with_group_in_active_pass_dyn`] so it is compiled once instead
892    /// of once per composable call site (real apps have thousands).
893    fn with_group_in_active_pass<R>(
894        &self,
895        key: crate::slot::GroupKeySeed,
896        f: impl FnOnce(&Composer) -> R,
897    ) -> R {
898        let mut f = Some(f);
899        let mut result = None;
900        self.with_group_in_active_pass_dyn(key, &mut |composer| {
901            let f = f.take().expect("group body must run at most once");
902            result = Some(f(composer));
903        });
904        result.expect("group body must run exactly once")
905    }
906
907    /// `inline(never)`: with fat LTO this single-caller body would otherwise
908    /// be inlined back into every monomorphic shim, recreating the per-call-site
909    /// code explosion this split exists to prevent.
910    #[inline(never)]
911    fn with_group_in_active_pass_dyn(
912        &self,
913        key: crate::slot::GroupKeySeed,
914        f: &mut dyn FnMut(&Composer),
915    ) {
916        struct GroupGuard {
917            composer: Composer,
918            scope: RecomposeScope,
919        }
920
921        impl Drop for GroupGuard {
922            fn drop(&mut self) {
923                self.composer
924                    .close_current_group_body_for_scope(&self.scope);
925                self.scope.mark_recomposed();
926                self.composer
927                    .with_slot_session_mut(|slots| slots.end_group());
928                if let Err(err) = self.composer.flush_pending_commands_if_large() {
929                    log::error!("mid-composition command flush failed: {err}");
930                }
931            }
932        }
933
934        let parent_scope = self.current_recranpose_scope();
935        let options = self.pending_scope_options().take().unwrap_or_default();
936        let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
937        let reserved_key = self.with_slot_session_mut(|slots| slots.preview_group_key(key));
938        let host = self.active_slots_host();
939        let restored = self.core.shared_state.take_retained(
940            &host,
941            RetainKey {
942                parent_scope: parent_scope_id,
943                key: reserved_key,
944            },
945            |subtree| {
946                self.with_slot_session_mut(|slots| {
947                    slots.retained_restore_ready(reserved_key, subtree)
948                })
949            },
950        );
951        let (group, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
952            let GroupStart {
953                group,
954                scope_id,
955                kind,
956                ..
957            } = slots.begin_group(reserved_key, restored);
958            (group, scope_id, kind)
959        });
960        let scope_ref =
961            if let Some(scope) = start_scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
962                scope
963            } else {
964                let scope = RecomposeScope::new(self.runtime_handle());
965                self.register_scope(&scope);
966                self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
967                scope
968            };
969
970        scope_ref.reactivate();
971        scope_ref.set_parent_scope(parent_scope);
972        scope_ref.set_retention_mode(options.retention);
973
974        if options.force_recompose {
975            scope_ref.force_recompose();
976        } else if options.force_reuse {
977            scope_ref.force_reuse();
978        }
979        if matches!(start_kind, GroupStartKind::Restored) {
980            scope_ref.force_recompose();
981        }
982
983        scope_ref.set_slots_host(&host);
984
985        {
986            let mut stack = self.scope_stack();
987            stack.push(scope_ref.clone());
988        }
989
990        {
991            let mut stack = self.subcompose_stack();
992            if let Some(frame) = stack.last_mut() {
993                frame.scopes.push(scope_ref.clone());
994            }
995        }
996
997        scope_ref.snapshot_locals(self.current_local_stack());
998        {
999            let parent_hint = self.current_parent_hint();
1000            scope_ref.set_parent_hint(parent_hint);
1001        }
1002
1003        let guard = GroupGuard {
1004            composer: self.clone(),
1005            scope: scope_ref.clone(),
1006        };
1007        self.observe_scope(&scope_ref, || f(self));
1008        scope_ref.mark_composed_once();
1009        drop(guard);
1010    }
1011
1012    pub(crate) fn with_group_seed<R>(
1013        &self,
1014        key: crate::slot::GroupKeySeed,
1015        f: impl FnOnce(&Composer) -> R,
1016    ) -> R {
1017        let host = self.active_slots_host();
1018        if host.has_active_pass() {
1019            return self.with_group_in_active_pass(key, f);
1020        }
1021        let (result, _) =
1022            self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1023                composer.with_group_in_active_pass(key, f)
1024            });
1025        result
1026    }
1027
1028    pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1029        self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1030    }
1031
1032    pub fn cranpose_with_reuse<R>(
1033        &self,
1034        key: Key,
1035        mut options: RecomposeOptions,
1036        f: impl FnOnce(&Composer) -> R,
1037    ) -> R {
1038        options.retention = RetentionMode::RetainWhenInactive;
1039        self.pending_scope_options().replace(options);
1040        self.with_group(key, f)
1041    }
1042
1043    #[track_caller]
1044    pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1045        let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1046        self.with_group_seed(seed, f)
1047    }
1048
1049    fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1050        for node_id in nodes {
1051            self.commands_mut().push(Command::callback(move |applier| {
1052                crate::slot::dispose_detached_node_now(applier, node_id)
1053            }));
1054        }
1055    }
1056
1057    fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1058        for scope_id in scope_ids {
1059            if let Some(scope) = self.scope_for_id(scope_id) {
1060                scope.deactivate();
1061            }
1062        }
1063    }
1064
1065    fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1066        for scope_id in scope_ids {
1067            if let Some(scope) = self.remove_scope(scope_id) {
1068                scope.deactivate();
1069            }
1070        }
1071    }
1072
1073    fn detached_root_parent_commands(
1074        &self,
1075        subtree: &crate::slot::DetachedSubtree,
1076        context: &'static str,
1077    ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1078        let mut root_nodes = Vec::new();
1079        subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1080        let mut roots = Vec::with_capacity(root_nodes.len());
1081        for root in root_nodes {
1082            let parent_id = {
1083                let mut applier = self.borrow_applier();
1084                applier.get_mut(root)?.parent()
1085            };
1086            roots.push((root, parent_id));
1087        }
1088        Ok(roots)
1089    }
1090
1091    fn retain_detached_subtree_in_host(
1092        &self,
1093        slots_host: &Rc<SlotsHost>,
1094        parent_scope: Option<ScopeId>,
1095        subtree: crate::slot::DetachedSubtree,
1096    ) -> Result<(), NodeError> {
1097        // Retention and disposal must preserve the slot lifecycle contract in
1098        // docs/SLOT_TABLE_LIFECYCLE.md across the slot table, applier, and scope
1099        // registry.
1100        let Some(root_key) = subtree.root_key_checked() else {
1101            log::error!("retention rejected detached subtree without a root group");
1102            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1103            return Ok(());
1104        };
1105        let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1106        self.deactivate_scope_ids(subtree.scope_ids_iter());
1107        for (root, parent_id) in root_detaches {
1108            if let Some(parent_id) = parent_id {
1109                self.commands_mut().push(Command::DetachChild {
1110                    parent_id,
1111                    child_id: root,
1112                });
1113            }
1114        }
1115        let evicted = self.core.shared_state.insert_retained(
1116            slots_host,
1117            RetainKey {
1118                parent_scope,
1119                key: root_key,
1120            },
1121            subtree,
1122        );
1123        for subtree in evicted {
1124            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1125        }
1126        Ok(())
1127    }
1128
1129    fn evict_retained_subtrees_for_host(
1130        &self,
1131        slots_host: &Rc<SlotsHost>,
1132    ) -> Result<(), NodeError> {
1133        let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1134        for subtree in evicted {
1135            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1136        }
1137        Ok(())
1138    }
1139
1140    fn dispose_detached_subtree_in_host(
1141        &self,
1142        slots_host: &Rc<SlotsHost>,
1143        subtree: crate::slot::DetachedSubtree,
1144    ) -> Result<(), NodeError> {
1145        let root_nodes = self
1146            .detached_root_parent_commands(&subtree, "disposal")?
1147            .into_iter()
1148            .map(|(root, _)| root);
1149        self.dispose_scope_ids(subtree.scope_ids_iter());
1150        self.dispose_detached_nodes(root_nodes);
1151        slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1152            table.invalidate_detached_subtree_anchors(&subtree);
1153            lifecycle.queue_subtree_disposal(subtree);
1154        });
1155        Ok(())
1156    }
1157
1158    fn handle_detached_children_in_host(
1159        &self,
1160        slots_host: &Rc<SlotsHost>,
1161        parent_scope: Option<ScopeId>,
1162        detached: Vec<crate::slot::DetachedSubtree>,
1163    ) -> Result<(), NodeError> {
1164        for subtree in detached {
1165            let retention_mode = subtree
1166                .root_scope_id()
1167                .and_then(|scope_id| self.scope_for_id(scope_id))
1168                .map(|scope| scope.retention_mode())
1169                .unwrap_or_default();
1170            match retention_mode {
1171                RetentionMode::DisposeWhenInactive => {
1172                    self.dispose_detached_subtree_in_host(slots_host, subtree)?
1173                }
1174                RetentionMode::RetainWhenInactive => {
1175                    self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1176                }
1177            }
1178        }
1179        Ok(())
1180    }
1181
1182    fn handle_detached_children(
1183        &self,
1184        parent_scope: Option<ScopeId>,
1185        detached: Vec<crate::slot::DetachedSubtree>,
1186    ) {
1187        let host = self.active_slots_host();
1188        if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1189            log::error!("detached subtree handling failed while closing a group: {err}");
1190        }
1191    }
1192
1193    fn handle_finished_group_result(
1194        &self,
1195        parent_scope: Option<ScopeId>,
1196        result: FinishGroupResult,
1197    ) {
1198        let FinishGroupResult {
1199            detached_children,
1200            direct_nodes,
1201            root_nodes,
1202            was_skipped,
1203        } = result;
1204        if was_skipped {
1205            self.attach_root_nodes(root_nodes);
1206        }
1207        self.dispose_detached_nodes(direct_nodes);
1208        self.handle_detached_children(parent_scope, detached_children);
1209    }
1210
1211    pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1212        let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1213        self.handle_finished_group_result(Some(scope.id()), result);
1214        if let Some(popped) = self.scope_stack().pop() {
1215            debug_assert_eq!(
1216                popped.id(),
1217                scope.id(),
1218                "closed scope must match the active scope stack"
1219            );
1220        } else {
1221            log::error!("scope stack underflow while closing scope {}", scope.id());
1222        }
1223    }
1224
1225    pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1226        self.remember_with_kind(PayloadKind::Remember, init)
1227    }
1228
1229    pub(crate) fn remember_internal<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1230        self.remember_with_kind(PayloadKind::Internal, init)
1231    }
1232
1233    pub(crate) fn remember_effect<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1234        self.remember_with_kind(PayloadKind::Effect, init)
1235    }
1236
1237    fn remember_with_kind<T: 'static>(
1238        &self,
1239        kind: PayloadKind,
1240        init: impl FnOnce() -> T,
1241    ) -> Owned<T> {
1242        self.with_slot_session_mut(|slots| slots.remember_with_kind(kind, init))
1243    }
1244
1245    pub fn use_value_slot<'pass, T: 'static>(
1246        &'pass self,
1247        init: impl FnOnce() -> T,
1248    ) -> ValueSlotHandle<'pass, T> {
1249        let slot = self
1250            .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Internal, init));
1251        ValueSlotHandle::new(slot)
1252    }
1253
1254    #[doc(hidden)]
1255    pub fn __use_param_slot<'pass, T: 'static>(
1256        &'pass self,
1257        init: impl FnOnce() -> T,
1258    ) -> ValueSlotHandle<'pass, T> {
1259        let slot = self
1260            .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Param, init));
1261        ValueSlotHandle::new(slot)
1262    }
1263
1264    #[doc(hidden)]
1265    pub fn __use_return_slot<'pass, T: 'static>(
1266        &'pass self,
1267        init: impl FnOnce() -> T,
1268    ) -> ValueSlotHandle<'pass, T> {
1269        let slot = self
1270            .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Return, init));
1271        ValueSlotHandle::new(slot)
1272    }
1273
1274    #[doc(hidden)]
1275    pub fn __invalidate_return_consumer_scope(&self) {
1276        let Some(scope) = self.current_recranpose_scope() else {
1277            self.request_root_render();
1278            return;
1279        };
1280
1281        if let Some(target) = scope.callback_promotion_target() {
1282            target.invalidate();
1283        } else {
1284            self.request_root_render();
1285        }
1286    }
1287
1288    pub fn with_slot_value<'pass, T: 'static, R>(
1289        &'pass self,
1290        handle: ValueSlotHandle<'pass, T>,
1291        f: impl FnOnce(&T) -> R,
1292    ) -> R {
1293        self.with_slots(|slots| f(slots.read_value(handle.slot())))
1294    }
1295
1296    pub fn with_slot_value_mut<'pass, T: 'static, R>(
1297        &'pass self,
1298        handle: ValueSlotHandle<'pass, T>,
1299        f: impl FnOnce(&mut T) -> R,
1300    ) -> R {
1301        self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1302    }
1303
1304    pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1305        MutableState::with_runtime(initial, self.runtime_handle())
1306    }
1307
1308    pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1309    where
1310        T: Clone + 'static,
1311        I: IntoIterator<Item = T>,
1312    {
1313        SnapshotStateList::with_runtime(values, self.runtime_handle())
1314    }
1315
1316    pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1317    where
1318        K: Clone + Eq + Hash + 'static,
1319        V: Clone + 'static,
1320        I: IntoIterator<Item = (K, V)>,
1321    {
1322        SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1323    }
1324
1325    pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1326        let stack = self.core.local_stack.borrow();
1327        for context in stack.iter().rev() {
1328            if let Some(entry) = context.values.get(&local.key) {
1329                match entry.clone().downcast::<LocalStateEntry<T>>() {
1330                    Ok(typed) => return typed.value(),
1331                    Err(_) => {
1332                        log::error!(
1333                            "composition local entry type mismatch for key {}",
1334                            local.key
1335                        );
1336                        return local.default_value();
1337                    }
1338                }
1339            }
1340        }
1341        local.default_value()
1342    }
1343
1344    pub fn read_static_composition_local<T: Clone + 'static>(
1345        &self,
1346        local: &StaticCompositionLocal<T>,
1347    ) -> T {
1348        let stack = self.core.local_stack.borrow();
1349        for context in stack.iter().rev() {
1350            if let Some(entry) = context.values.get(&local.key) {
1351                match entry.clone().downcast::<StaticLocalEntry<T>>() {
1352                    Ok(typed) => return typed.value(),
1353                    Err(_) => {
1354                        log::error!(
1355                            "static composition local entry type mismatch for key {}",
1356                            local.key
1357                        );
1358                        return local.default_value();
1359                    }
1360                }
1361            }
1362        }
1363        local.default_value()
1364    }
1365
1366    pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
1367        self.core.scope_stack.borrow().last().cloned()
1368    }
1369
1370    pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1371        let stack = self.core.scope_stack.borrow();
1372        stack
1373            .iter()
1374            .rev()
1375            .find(|scope| scope.has_recompose_callback())
1376            .cloned()
1377            .or_else(|| stack.last().cloned())
1378    }
1379
1380    pub fn phase(&self) -> crate::Phase {
1381        self.core.phase.get()
1382    }
1383
1384    pub(crate) fn set_phase(&self, phase: crate::Phase) {
1385        self.core.phase.set(phase);
1386    }
1387
1388    pub fn enter_phase(&self, phase: crate::Phase) {
1389        self.set_phase(phase);
1390    }
1391
1392    pub(crate) fn subcompose<R>(
1393        &self,
1394        state: &mut SubcomposeState,
1395        slot_id: SlotId,
1396        content: impl FnOnce(&Composer) -> R,
1397    ) -> (R, Vec<NodeId>) {
1398        match self.phase() {
1399            crate::Phase::Measure | crate::Phase::Layout => {}
1400            current => panic!(
1401                "subcompose() may only be called during measure or layout; current phase: {:?}",
1402                current
1403            ),
1404        }
1405
1406        self.subcompose_stack().push(SubcomposeFrame::default());
1407        struct StackGuard {
1408            core: Rc<ComposerCore>,
1409            leaked: bool,
1410        }
1411        impl Drop for StackGuard {
1412            fn drop(&mut self) {
1413                if !self.leaked {
1414                    self.core.subcompose_stack.borrow_mut().pop();
1415                }
1416            }
1417        }
1418        let mut guard = StackGuard {
1419            core: self.clone_core(),
1420            leaked: false,
1421        };
1422
1423        let slot_host = state.get_or_create_slots(slot_id);
1424        let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1425            composer.with_group(slot_id.raw(), |composer| content(composer))
1426        });
1427
1428        let frame = {
1429            let frame = take_subcompose_frame(&guard.core, "subcompose");
1430            guard.leaked = true;
1431            frame
1432        };
1433        let nodes = frame.nodes;
1434        let scopes = frame.scopes;
1435        state.register_active(slot_id, &nodes, &scopes);
1436        (result, nodes)
1437    }
1438
1439    pub fn subcompose_measurement<R>(
1440        &self,
1441        state: &mut SubcomposeState,
1442        slot_id: SlotId,
1443        content: impl FnOnce(&Composer) -> R,
1444    ) -> (R, Vec<NodeId>) {
1445        let (result, nodes) = self.subcompose(state, slot_id, content);
1446        let roots = nodes
1447            .into_iter()
1448            .filter(|&id| self.node_has_no_parent(id))
1449            .collect();
1450
1451        (result, roots)
1452    }
1453
1454    pub fn subcompose_in<R>(
1455        &self,
1456        slots: &Rc<SlotsHost>,
1457        root: Option<NodeId>,
1458        f: impl FnOnce(&Composer) -> R,
1459    ) -> Result<R, NodeError> {
1460        let runtime_handle = self.runtime_handle();
1461        let phase = self.phase();
1462        let locals = self.current_local_stack();
1463        let shared_state = slots
1464            .runtime_state()
1465            .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1466        let core = Rc::new(ComposerCore::new(
1467            shared_state,
1468            Rc::clone(slots),
1469            Rc::clone(&self.core.applier),
1470            runtime_handle.clone(),
1471            self.observer(),
1472            root,
1473            InitialParentFrame::RealParent,
1474        ));
1475        core.phase.set(phase);
1476        *core.local_stack.borrow_mut() = locals;
1477        let composer = Composer::from_core(core);
1478        let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1479            let (output, outcome) = composer.try_with_slot_host_pass(
1480                Rc::clone(slots),
1481                crate::slot::SlotPassMode::Compose,
1482                |composer| f(composer),
1483            )?;
1484            let commands = composer.take_commands();
1485            let side_effects = composer.take_side_effects();
1486            Ok((output, commands, side_effects, outcome.compacted))
1487        })?;
1488        {
1489            let mut applier = self.borrow_applier();
1490            commands.apply(&mut *applier)?;
1491            for update in runtime_handle.take_updates() {
1492                update.apply(&mut *applier)?;
1493            }
1494        }
1495        if compact_applier {
1496            self.core.applier.compact();
1497            self.core.applier.borrow_dyn().clear_recycled_nodes();
1498        }
1499        runtime_handle.drain_ui();
1500        for effect in side_effects {
1501            effect();
1502        }
1503        runtime_handle.drain_ui();
1504        Ok(result)
1505    }
1506
1507    /// Subcomposes content using an isolated SlotsHost without resetting it.
1508    /// Unlike `subcompose_in`, this preserves existing slot state across calls,
1509    /// allowing efficient reuse during measurement passes. This is critical for
1510    /// lazy lists where items need stable slot positions.
1511    pub fn subcompose_slot<R>(
1512        &self,
1513        slots: &Rc<SlotsHost>,
1514        root: Option<NodeId>,
1515        f: impl FnOnce(&Composer) -> R,
1516    ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1517        let runtime_handle = self.runtime_handle();
1518        let phase = self.phase();
1519        let locals = self.current_local_stack();
1520        let shared_state = slots
1521            .runtime_state()
1522            .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1523        let core = Rc::new(ComposerCore::new(
1524            shared_state,
1525            Rc::clone(slots),
1526            Rc::clone(&self.core.applier),
1527            runtime_handle.clone(),
1528            self.observer(),
1529            root,
1530            InitialParentFrame::RealParent,
1531        ));
1532        core.phase.set(phase);
1533        *core.local_stack.borrow_mut() = locals;
1534        let composer = Composer::from_core(core);
1535        composer.subcompose_stack().push(SubcomposeFrame::default());
1536        struct StackGuard {
1537            core: Rc<ComposerCore>,
1538            leaked: bool,
1539        }
1540        impl Drop for StackGuard {
1541            fn drop(&mut self) {
1542                if !self.leaked {
1543                    self.core.subcompose_stack.borrow_mut().pop();
1544                }
1545            }
1546        }
1547        let mut guard = StackGuard {
1548            core: composer.clone_core(),
1549            leaked: false,
1550        };
1551        let root_group_key = crate::location_key(file!(), line!(), column!());
1552        let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1553            let (output, outcome) = composer.try_with_slot_host_pass(
1554                Rc::clone(slots),
1555                crate::slot::SlotPassMode::Compose,
1556                |composer| {
1557                    let output = composer.with_group(root_group_key, |composer| f(composer));
1558                    if root.is_some() {
1559                        composer.pop_parent();
1560                    }
1561                    output
1562                },
1563            )?;
1564            let commands = composer.take_commands();
1565            let side_effects = composer.take_side_effects();
1566            Ok((output, commands, side_effects, outcome.compacted))
1567        })?;
1568        let frame = {
1569            let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1570            guard.leaked = true;
1571            frame
1572        };
1573
1574        {
1575            let mut applier = self.borrow_applier();
1576            commands.apply(&mut *applier)?;
1577            for update in runtime_handle.take_updates() {
1578                update.apply(&mut *applier)?;
1579            }
1580        }
1581        if compact_applier {
1582            self.core.applier.compact();
1583            self.core.applier.borrow_dyn().clear_recycled_nodes();
1584        }
1585        runtime_handle.drain_ui();
1586        for effect in side_effects {
1587            effect();
1588        }
1589        runtime_handle.drain_ui();
1590        Ok((result, frame.scopes))
1591    }
1592
1593    fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
1594        for id in root_nodes {
1595            self.attach_to_parent_with_mode(id, true);
1596        }
1597    }
1598
1599    pub fn skip_current_group(&self) {
1600        self.with_slot_session_mut(|slots| slots.skip_group());
1601    }
1602
1603    pub fn runtime_handle(&self) -> RuntimeHandle {
1604        self.core.runtime.clone()
1605    }
1606
1607    pub fn set_recranpose_callback<F>(&self, callback: F)
1608    where
1609        F: FnMut(&Composer) + 'static,
1610    {
1611        self.set_recranpose_callback_boxed(Box::new(callback));
1612    }
1613
1614    /// Monomorphic core (`inline(never)` so fat LTO keeps one copy): the
1615    /// observer wiring here used to be re-instantiated for every composable
1616    /// call site through the generic entry point above.
1617    #[inline(never)]
1618    fn set_recranpose_callback_boxed(&self, mut callback: Box<dyn FnMut(&Composer)>) {
1619        if let Some(scope) = self.current_recranpose_scope() {
1620            let observer = self.observer();
1621            let scope_weak = scope.downgrade();
1622            scope.set_recompose(Box::new(move |composer: &Composer| {
1623                if let Some(inner) = scope_weak.upgrade() {
1624                    let scope_instance = RecomposeScope { inner };
1625                    observer.observe_reads(
1626                        scope_instance.clone(),
1627                        move |scope_ref| scope_ref.invalidate(),
1628                        || {
1629                            callback(composer);
1630                        },
1631                    );
1632                }
1633            }));
1634        }
1635    }
1636
1637    pub fn set_recranpose_fn(&self, callback: fn(&Composer)) {
1638        if let Some(scope) = self.current_recranpose_scope() {
1639            scope.set_recompose_fn(callback);
1640        }
1641    }
1642
1643    pub fn with_composition_locals<R>(
1644        &self,
1645        provided: Vec<ProvidedValue>,
1646        f: impl FnOnce(&Composer) -> R,
1647    ) -> R {
1648        if provided.is_empty() {
1649            return f(self);
1650        }
1651        let mut context = LocalContext::default();
1652        for value in provided {
1653            let (key, entry) = value.into_entry(self);
1654            context.values.insert(key, entry);
1655        }
1656        {
1657            let mut stack = self.local_stack();
1658            Rc::make_mut(&mut *stack).push(context);
1659        }
1660        let result = f(self);
1661        {
1662            let mut stack = self.local_stack();
1663            Rc::make_mut(&mut *stack).pop();
1664        }
1665        result
1666    }
1667}