Skip to main content

cranpose_core/
composer.rs

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