Skip to main content

cranpose_core/
composer.rs

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