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