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    fn with_group_in_active_pass<R>(
891        &self,
892        key: crate::slot::GroupKeySeed,
893        f: impl FnOnce(&Composer) -> R,
894    ) -> R {
895        struct GroupGuard {
896            composer: Composer,
897            scope: RecomposeScope,
898        }
899
900        impl Drop for GroupGuard {
901            fn drop(&mut self) {
902                self.composer
903                    .close_current_group_body_for_scope(&self.scope);
904                self.scope.mark_recomposed();
905                self.composer
906                    .with_slot_session_mut(|slots| slots.end_group());
907                if let Err(err) = self.composer.flush_pending_commands_if_large() {
908                    log::error!("mid-composition command flush failed: {err}");
909                }
910            }
911        }
912
913        let parent_scope = self.current_recranpose_scope();
914        let options = self.pending_scope_options().take().unwrap_or_default();
915        let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
916        let reserved_key = self.with_slot_session_mut(|slots| slots.preview_group_key(key));
917        let host = self.active_slots_host();
918        let restored = self.core.shared_state.take_retained(
919            &host,
920            RetainKey {
921                parent_scope: parent_scope_id,
922                key: reserved_key,
923            },
924            |subtree| {
925                self.with_slot_session_mut(|slots| {
926                    slots.retained_restore_ready(reserved_key, subtree)
927                })
928            },
929        );
930        let (group, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
931            let GroupStart {
932                group,
933                scope_id,
934                kind,
935                ..
936            } = slots.begin_group(reserved_key, restored);
937            (group, scope_id, kind)
938        });
939        let scope_ref =
940            if let Some(scope) = start_scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
941                scope
942            } else {
943                let scope = RecomposeScope::new(self.runtime_handle());
944                self.register_scope(&scope);
945                self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
946                scope
947            };
948
949        scope_ref.reactivate();
950        scope_ref.set_parent_scope(parent_scope);
951        scope_ref.set_retention_mode(options.retention);
952
953        if options.force_recompose {
954            scope_ref.force_recompose();
955        } else if options.force_reuse {
956            scope_ref.force_reuse();
957        }
958        if matches!(start_kind, GroupStartKind::Restored) {
959            scope_ref.force_recompose();
960        }
961
962        scope_ref.set_slots_host(&host);
963
964        {
965            let mut stack = self.scope_stack();
966            stack.push(scope_ref.clone());
967        }
968
969        {
970            let mut stack = self.subcompose_stack();
971            if let Some(frame) = stack.last_mut() {
972                frame.scopes.push(scope_ref.clone());
973            }
974        }
975
976        scope_ref.snapshot_locals(self.current_local_stack());
977        {
978            let parent_hint = self.current_parent_hint();
979            scope_ref.set_parent_hint(parent_hint);
980        }
981
982        let guard = GroupGuard {
983            composer: self.clone(),
984            scope: scope_ref.clone(),
985        };
986        let result = self.observe_scope(&scope_ref, || f(self));
987        scope_ref.mark_composed_once();
988        drop(guard);
989        result
990    }
991
992    pub(crate) fn with_group_seed<R>(
993        &self,
994        key: crate::slot::GroupKeySeed,
995        f: impl FnOnce(&Composer) -> R,
996    ) -> R {
997        let host = self.active_slots_host();
998        if host.has_active_pass() {
999            return self.with_group_in_active_pass(key, f);
1000        }
1001        let (result, _) =
1002            self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1003                composer.with_group_in_active_pass(key, f)
1004            });
1005        result
1006    }
1007
1008    pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1009        self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1010    }
1011
1012    pub fn cranpose_with_reuse<R>(
1013        &self,
1014        key: Key,
1015        mut options: RecomposeOptions,
1016        f: impl FnOnce(&Composer) -> R,
1017    ) -> R {
1018        options.retention = RetentionMode::RetainWhenInactive;
1019        self.pending_scope_options().replace(options);
1020        self.with_group(key, f)
1021    }
1022
1023    #[track_caller]
1024    pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1025        let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1026        self.with_group_seed(seed, f)
1027    }
1028
1029    fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1030        for node_id in nodes {
1031            self.commands_mut().push(Command::callback(move |applier| {
1032                crate::slot::dispose_detached_node_now(applier, node_id)
1033            }));
1034        }
1035    }
1036
1037    fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1038        for scope_id in scope_ids {
1039            if let Some(scope) = self.scope_for_id(scope_id) {
1040                scope.deactivate();
1041            }
1042        }
1043    }
1044
1045    fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1046        for scope_id in scope_ids {
1047            if let Some(scope) = self.remove_scope(scope_id) {
1048                scope.deactivate();
1049            }
1050        }
1051    }
1052
1053    fn detached_root_parent_commands(
1054        &self,
1055        subtree: &crate::slot::DetachedSubtree,
1056        context: &'static str,
1057    ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1058        let mut root_nodes = Vec::new();
1059        subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1060        let mut roots = Vec::with_capacity(root_nodes.len());
1061        for root in root_nodes {
1062            let parent_id = {
1063                let mut applier = self.borrow_applier();
1064                applier.get_mut(root)?.parent()
1065            };
1066            roots.push((root, parent_id));
1067        }
1068        Ok(roots)
1069    }
1070
1071    fn retain_detached_subtree_in_host(
1072        &self,
1073        slots_host: &Rc<SlotsHost>,
1074        parent_scope: Option<ScopeId>,
1075        subtree: crate::slot::DetachedSubtree,
1076    ) -> Result<(), NodeError> {
1077        // Retention and disposal must preserve the slot lifecycle contract in
1078        // docs/SLOT_TABLE_LIFECYCLE.md across the slot table, applier, and scope
1079        // registry.
1080        let Some(root_key) = subtree.root_key_checked() else {
1081            log::error!("retention rejected detached subtree without a root group");
1082            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1083            return Ok(());
1084        };
1085        let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1086        self.deactivate_scope_ids(subtree.scope_ids_iter());
1087        for (root, parent_id) in root_detaches {
1088            if let Some(parent_id) = parent_id {
1089                self.commands_mut().push(Command::DetachChild {
1090                    parent_id,
1091                    child_id: root,
1092                });
1093            }
1094        }
1095        let evicted = self.core.shared_state.insert_retained(
1096            slots_host,
1097            RetainKey {
1098                parent_scope,
1099                key: root_key,
1100            },
1101            subtree,
1102        );
1103        for subtree in evicted {
1104            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1105        }
1106        Ok(())
1107    }
1108
1109    fn evict_retained_subtrees_for_host(
1110        &self,
1111        slots_host: &Rc<SlotsHost>,
1112    ) -> Result<(), NodeError> {
1113        let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1114        for subtree in evicted {
1115            self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1116        }
1117        Ok(())
1118    }
1119
1120    fn dispose_detached_subtree_in_host(
1121        &self,
1122        slots_host: &Rc<SlotsHost>,
1123        subtree: crate::slot::DetachedSubtree,
1124    ) -> Result<(), NodeError> {
1125        let root_nodes = self
1126            .detached_root_parent_commands(&subtree, "disposal")?
1127            .into_iter()
1128            .map(|(root, _)| root);
1129        self.dispose_scope_ids(subtree.scope_ids_iter());
1130        self.dispose_detached_nodes(root_nodes);
1131        slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1132            table.invalidate_detached_subtree_anchors(&subtree);
1133            lifecycle.queue_subtree_disposal(subtree);
1134        });
1135        Ok(())
1136    }
1137
1138    fn handle_detached_children_in_host(
1139        &self,
1140        slots_host: &Rc<SlotsHost>,
1141        parent_scope: Option<ScopeId>,
1142        detached: Vec<crate::slot::DetachedSubtree>,
1143    ) -> Result<(), NodeError> {
1144        for subtree in detached {
1145            let retention_mode = subtree
1146                .root_scope_id()
1147                .and_then(|scope_id| self.scope_for_id(scope_id))
1148                .map(|scope| scope.retention_mode())
1149                .unwrap_or_default();
1150            match retention_mode {
1151                RetentionMode::DisposeWhenInactive => {
1152                    self.dispose_detached_subtree_in_host(slots_host, subtree)?
1153                }
1154                RetentionMode::RetainWhenInactive => {
1155                    self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1156                }
1157            }
1158        }
1159        Ok(())
1160    }
1161
1162    fn handle_detached_children(
1163        &self,
1164        parent_scope: Option<ScopeId>,
1165        detached: Vec<crate::slot::DetachedSubtree>,
1166    ) {
1167        let host = self.active_slots_host();
1168        if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1169            log::error!("detached subtree handling failed while closing a group: {err}");
1170        }
1171    }
1172
1173    fn handle_finished_group_result(
1174        &self,
1175        parent_scope: Option<ScopeId>,
1176        result: FinishGroupResult,
1177    ) {
1178        let FinishGroupResult {
1179            detached_children,
1180            direct_nodes,
1181            root_nodes,
1182            was_skipped,
1183        } = result;
1184        if was_skipped {
1185            self.attach_root_nodes(root_nodes);
1186        }
1187        self.dispose_detached_nodes(direct_nodes);
1188        self.handle_detached_children(parent_scope, detached_children);
1189    }
1190
1191    pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1192        let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1193        self.handle_finished_group_result(Some(scope.id()), result);
1194        if let Some(popped) = self.scope_stack().pop() {
1195            debug_assert_eq!(
1196                popped.id(),
1197                scope.id(),
1198                "closed scope must match the active scope stack"
1199            );
1200        } else {
1201            log::error!("scope stack underflow while closing scope {}", scope.id());
1202        }
1203    }
1204
1205    pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1206        self.remember_with_kind(PayloadKind::Remember, init)
1207    }
1208
1209    pub(crate) fn remember_internal<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1210        self.remember_with_kind(PayloadKind::Internal, init)
1211    }
1212
1213    pub(crate) fn remember_effect<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1214        self.remember_with_kind(PayloadKind::Effect, init)
1215    }
1216
1217    fn remember_with_kind<T: 'static>(
1218        &self,
1219        kind: PayloadKind,
1220        init: impl FnOnce() -> T,
1221    ) -> Owned<T> {
1222        self.with_slot_session_mut(|slots| slots.remember_with_kind(kind, init))
1223    }
1224
1225    pub fn use_value_slot<'pass, T: 'static>(
1226        &'pass self,
1227        init: impl FnOnce() -> T,
1228    ) -> ValueSlotHandle<'pass, T> {
1229        let slot = self
1230            .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Internal, init));
1231        ValueSlotHandle::new(slot)
1232    }
1233
1234    #[doc(hidden)]
1235    pub fn __use_param_slot<'pass, T: 'static>(
1236        &'pass self,
1237        init: impl FnOnce() -> T,
1238    ) -> ValueSlotHandle<'pass, T> {
1239        let slot = self
1240            .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Param, init));
1241        ValueSlotHandle::new(slot)
1242    }
1243
1244    #[doc(hidden)]
1245    pub fn __use_return_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::Return, init));
1251        ValueSlotHandle::new(slot)
1252    }
1253
1254    #[doc(hidden)]
1255    pub fn __invalidate_return_consumer_scope(&self) {
1256        let Some(scope) = self.current_recranpose_scope() else {
1257            self.request_root_render();
1258            return;
1259        };
1260
1261        if let Some(target) = scope.callback_promotion_target() {
1262            target.invalidate();
1263        } else {
1264            self.request_root_render();
1265        }
1266    }
1267
1268    pub fn with_slot_value<'pass, T: 'static, R>(
1269        &'pass self,
1270        handle: ValueSlotHandle<'pass, T>,
1271        f: impl FnOnce(&T) -> R,
1272    ) -> R {
1273        self.with_slots(|slots| f(slots.read_value(handle.slot())))
1274    }
1275
1276    pub fn with_slot_value_mut<'pass, T: 'static, R>(
1277        &'pass self,
1278        handle: ValueSlotHandle<'pass, T>,
1279        f: impl FnOnce(&mut T) -> R,
1280    ) -> R {
1281        self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1282    }
1283
1284    pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1285        MutableState::with_runtime(initial, self.runtime_handle())
1286    }
1287
1288    pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1289    where
1290        T: Clone + 'static,
1291        I: IntoIterator<Item = T>,
1292    {
1293        SnapshotStateList::with_runtime(values, self.runtime_handle())
1294    }
1295
1296    pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1297    where
1298        K: Clone + Eq + Hash + 'static,
1299        V: Clone + 'static,
1300        I: IntoIterator<Item = (K, V)>,
1301    {
1302        SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1303    }
1304
1305    pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1306        let stack = self.core.local_stack.borrow();
1307        for context in stack.iter().rev() {
1308            if let Some(entry) = context.values.get(&local.key) {
1309                match entry.clone().downcast::<LocalStateEntry<T>>() {
1310                    Ok(typed) => return typed.value(),
1311                    Err(_) => {
1312                        log::error!(
1313                            "composition local entry type mismatch for key {}",
1314                            local.key
1315                        );
1316                        return local.default_value();
1317                    }
1318                }
1319            }
1320        }
1321        local.default_value()
1322    }
1323
1324    pub fn read_static_composition_local<T: Clone + 'static>(
1325        &self,
1326        local: &StaticCompositionLocal<T>,
1327    ) -> T {
1328        let stack = self.core.local_stack.borrow();
1329        for context in stack.iter().rev() {
1330            if let Some(entry) = context.values.get(&local.key) {
1331                match entry.clone().downcast::<StaticLocalEntry<T>>() {
1332                    Ok(typed) => return typed.value(),
1333                    Err(_) => {
1334                        log::error!(
1335                            "static composition local entry type mismatch for key {}",
1336                            local.key
1337                        );
1338                        return local.default_value();
1339                    }
1340                }
1341            }
1342        }
1343        local.default_value()
1344    }
1345
1346    pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
1347        self.core.scope_stack.borrow().last().cloned()
1348    }
1349
1350    pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1351        let stack = self.core.scope_stack.borrow();
1352        stack
1353            .iter()
1354            .rev()
1355            .find(|scope| scope.has_recompose_callback())
1356            .cloned()
1357            .or_else(|| stack.last().cloned())
1358    }
1359
1360    pub fn phase(&self) -> crate::Phase {
1361        self.core.phase.get()
1362    }
1363
1364    pub(crate) fn set_phase(&self, phase: crate::Phase) {
1365        self.core.phase.set(phase);
1366    }
1367
1368    pub fn enter_phase(&self, phase: crate::Phase) {
1369        self.set_phase(phase);
1370    }
1371
1372    pub(crate) fn subcompose<R>(
1373        &self,
1374        state: &mut SubcomposeState,
1375        slot_id: SlotId,
1376        content: impl FnOnce(&Composer) -> R,
1377    ) -> (R, Vec<NodeId>) {
1378        match self.phase() {
1379            crate::Phase::Measure | crate::Phase::Layout => {}
1380            current => panic!(
1381                "subcompose() may only be called during measure or layout; current phase: {:?}",
1382                current
1383            ),
1384        }
1385
1386        self.subcompose_stack().push(SubcomposeFrame::default());
1387        struct StackGuard {
1388            core: Rc<ComposerCore>,
1389            leaked: bool,
1390        }
1391        impl Drop for StackGuard {
1392            fn drop(&mut self) {
1393                if !self.leaked {
1394                    self.core.subcompose_stack.borrow_mut().pop();
1395                }
1396            }
1397        }
1398        let mut guard = StackGuard {
1399            core: self.clone_core(),
1400            leaked: false,
1401        };
1402
1403        let slot_host = state.get_or_create_slots(slot_id);
1404        let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1405            composer.with_group(slot_id.raw(), |composer| content(composer))
1406        });
1407
1408        let frame = {
1409            let frame = take_subcompose_frame(&guard.core, "subcompose");
1410            guard.leaked = true;
1411            frame
1412        };
1413        let nodes = frame.nodes;
1414        let scopes = frame.scopes;
1415        state.register_active(slot_id, &nodes, &scopes);
1416        (result, nodes)
1417    }
1418
1419    pub fn subcompose_measurement<R>(
1420        &self,
1421        state: &mut SubcomposeState,
1422        slot_id: SlotId,
1423        content: impl FnOnce(&Composer) -> R,
1424    ) -> (R, Vec<NodeId>) {
1425        let (result, nodes) = self.subcompose(state, slot_id, content);
1426        let roots = nodes
1427            .into_iter()
1428            .filter(|&id| self.node_has_no_parent(id))
1429            .collect();
1430
1431        (result, roots)
1432    }
1433
1434    pub fn subcompose_in<R>(
1435        &self,
1436        slots: &Rc<SlotsHost>,
1437        root: Option<NodeId>,
1438        f: impl FnOnce(&Composer) -> R,
1439    ) -> Result<R, NodeError> {
1440        let runtime_handle = self.runtime_handle();
1441        let phase = self.phase();
1442        let locals = self.current_local_stack();
1443        let shared_state = slots
1444            .runtime_state()
1445            .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1446        let core = Rc::new(ComposerCore::new(
1447            shared_state,
1448            Rc::clone(slots),
1449            Rc::clone(&self.core.applier),
1450            runtime_handle.clone(),
1451            self.observer(),
1452            root,
1453            InitialParentFrame::RealParent,
1454        ));
1455        core.phase.set(phase);
1456        *core.local_stack.borrow_mut() = locals;
1457        let composer = Composer::from_core(core);
1458        let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1459            let (output, outcome) = composer.try_with_slot_host_pass(
1460                Rc::clone(slots),
1461                crate::slot::SlotPassMode::Compose,
1462                |composer| f(composer),
1463            )?;
1464            let commands = composer.take_commands();
1465            let side_effects = composer.take_side_effects();
1466            Ok((output, commands, side_effects, outcome.compacted))
1467        })?;
1468        {
1469            let mut applier = self.borrow_applier();
1470            commands.apply(&mut *applier)?;
1471            for update in runtime_handle.take_updates() {
1472                update.apply(&mut *applier)?;
1473            }
1474        }
1475        if compact_applier {
1476            self.core.applier.compact();
1477            self.core.applier.borrow_dyn().clear_recycled_nodes();
1478        }
1479        runtime_handle.drain_ui();
1480        for effect in side_effects {
1481            effect();
1482        }
1483        runtime_handle.drain_ui();
1484        Ok(result)
1485    }
1486
1487    /// Subcomposes content using an isolated SlotsHost without resetting it.
1488    /// Unlike `subcompose_in`, this preserves existing slot state across calls,
1489    /// allowing efficient reuse during measurement passes. This is critical for
1490    /// lazy lists where items need stable slot positions.
1491    pub fn subcompose_slot<R>(
1492        &self,
1493        slots: &Rc<SlotsHost>,
1494        root: Option<NodeId>,
1495        f: impl FnOnce(&Composer) -> R,
1496    ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1497        let runtime_handle = self.runtime_handle();
1498        let phase = self.phase();
1499        let locals = self.current_local_stack();
1500        let shared_state = slots
1501            .runtime_state()
1502            .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1503        let core = Rc::new(ComposerCore::new(
1504            shared_state,
1505            Rc::clone(slots),
1506            Rc::clone(&self.core.applier),
1507            runtime_handle.clone(),
1508            self.observer(),
1509            root,
1510            InitialParentFrame::RealParent,
1511        ));
1512        core.phase.set(phase);
1513        *core.local_stack.borrow_mut() = locals;
1514        let composer = Composer::from_core(core);
1515        composer.subcompose_stack().push(SubcomposeFrame::default());
1516        struct StackGuard {
1517            core: Rc<ComposerCore>,
1518            leaked: bool,
1519        }
1520        impl Drop for StackGuard {
1521            fn drop(&mut self) {
1522                if !self.leaked {
1523                    self.core.subcompose_stack.borrow_mut().pop();
1524                }
1525            }
1526        }
1527        let mut guard = StackGuard {
1528            core: composer.clone_core(),
1529            leaked: false,
1530        };
1531        let root_group_key = crate::location_key(file!(), line!(), column!());
1532        let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1533            let (output, outcome) = composer.try_with_slot_host_pass(
1534                Rc::clone(slots),
1535                crate::slot::SlotPassMode::Compose,
1536                |composer| {
1537                    let output = composer.with_group(root_group_key, |composer| f(composer));
1538                    if root.is_some() {
1539                        composer.pop_parent();
1540                    }
1541                    output
1542                },
1543            )?;
1544            let commands = composer.take_commands();
1545            let side_effects = composer.take_side_effects();
1546            Ok((output, commands, side_effects, outcome.compacted))
1547        })?;
1548        let frame = {
1549            let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1550            guard.leaked = true;
1551            frame
1552        };
1553
1554        {
1555            let mut applier = self.borrow_applier();
1556            commands.apply(&mut *applier)?;
1557            for update in runtime_handle.take_updates() {
1558                update.apply(&mut *applier)?;
1559            }
1560        }
1561        if compact_applier {
1562            self.core.applier.compact();
1563            self.core.applier.borrow_dyn().clear_recycled_nodes();
1564        }
1565        runtime_handle.drain_ui();
1566        for effect in side_effects {
1567            effect();
1568        }
1569        runtime_handle.drain_ui();
1570        Ok((result, frame.scopes))
1571    }
1572
1573    fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
1574        for id in root_nodes {
1575            self.attach_to_parent_with_mode(id, true);
1576        }
1577    }
1578
1579    pub fn skip_current_group(&self) {
1580        self.with_slot_session_mut(|slots| slots.skip_group());
1581    }
1582
1583    pub fn runtime_handle(&self) -> RuntimeHandle {
1584        self.core.runtime.clone()
1585    }
1586
1587    pub fn set_recranpose_callback<F>(&self, callback: F)
1588    where
1589        F: FnMut(&Composer) + 'static,
1590    {
1591        if let Some(scope) = self.current_recranpose_scope() {
1592            let observer = self.observer();
1593            let scope_weak = scope.downgrade();
1594            let mut callback = callback;
1595            scope.set_recompose(Box::new(move |composer: &Composer| {
1596                if let Some(inner) = scope_weak.upgrade() {
1597                    let scope_instance = RecomposeScope { inner };
1598                    observer.observe_reads(
1599                        scope_instance.clone(),
1600                        move |scope_ref| scope_ref.invalidate(),
1601                        || {
1602                            callback(composer);
1603                        },
1604                    );
1605                }
1606            }));
1607        }
1608    }
1609
1610    pub fn set_recranpose_fn(&self, callback: fn(&Composer)) {
1611        if let Some(scope) = self.current_recranpose_scope() {
1612            scope.set_recompose_fn(callback);
1613        }
1614    }
1615
1616    pub fn with_composition_locals<R>(
1617        &self,
1618        provided: Vec<ProvidedValue>,
1619        f: impl FnOnce(&Composer) -> R,
1620    ) -> R {
1621        if provided.is_empty() {
1622            return f(self);
1623        }
1624        let mut context = LocalContext::default();
1625        for value in provided {
1626            let (key, entry) = value.into_entry(self);
1627            context.values.insert(key, entry);
1628        }
1629        {
1630            let mut stack = self.local_stack();
1631            Rc::make_mut(&mut *stack).push(context);
1632        }
1633        let result = f(self);
1634        {
1635            let mut stack = self.local_stack();
1636            Rc::make_mut(&mut *stack).pop();
1637        }
1638        result
1639    }
1640}