Skip to main content

cranpose_core/
composer.rs

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