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