Skip to main content

cranpose_core/
composer.rs

1use crate::collections::map::HashMap;
2use crate::{
3    composer_context, empty_local_stack, hash_key, remove_child_and_cleanup_now, runtime, Applier,
4    ApplierHost, ChildList, Command, CommandQueue, CompositionLocal, DirtyBubble, Key, LocalKey,
5    LocalStackSnapshot, LocalStateEntry, MutableState, Node, NodeError, NodeId, Owned,
6    ProvidedValue, RecomposeOptions, RecomposeScope, RecycledNode, RuntimeHandle, SlotId,
7    SlotTable, SlotsHost, SnapshotStateList, SnapshotStateMap, SnapshotStateObserver, StartGroup,
8    StaticCompositionLocal, StaticLocalEntry, SubcomposeState, COMMAND_FLUSH_THRESHOLD,
9};
10use smallvec::SmallVec;
11use std::any::Any;
12use std::cell::{Cell, RefCell, RefMut};
13use std::hash::Hash;
14use std::marker::PhantomData;
15use std::rc::Rc;
16
17pub(crate) struct ParentFrame {
18    pub(crate) id: NodeId,
19    pub(crate) previous: ChildList,
20    pub(crate) new_children: ChildList,
21    pub(crate) attach_mode: ParentAttachMode,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub(crate) enum ParentAttachMode {
26    ImmediateAppend,
27    DeferredSync,
28}
29
30#[derive(Default)]
31pub(crate) struct SubcomposeFrame {
32    pub(crate) nodes: Vec<NodeId>,
33    pub(crate) scopes: Vec<RecomposeScope>,
34}
35
36#[derive(Default, Clone)]
37pub(crate) struct LocalContext {
38    pub(crate) values: HashMap<LocalKey, Rc<dyn Any>>,
39}
40
41pub(crate) struct ComposerCore {
42    pub(crate) slots: Rc<SlotsHost>,
43    pub(crate) slots_override: RefCell<Vec<Rc<SlotsHost>>>,
44    pub(crate) applier: Rc<dyn ApplierHost>,
45    pub(crate) runtime: RuntimeHandle,
46    pub(crate) observer: SnapshotStateObserver,
47    pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
48    pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
49    pub(crate) root: Cell<Option<NodeId>>,
50    pub(crate) commands: RefCell<CommandQueue>,
51    pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
52    pub(crate) local_stack: RefCell<LocalStackSnapshot>,
53    pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
54    pub(crate) pending_scope_options: RefCell<Option<RecomposeOptions>>,
55    pub(crate) phase: Cell<crate::Phase>,
56    pub(crate) last_node_reused: Cell<Option<bool>>,
57    pub(crate) recranpose_parent_hint: Cell<Option<NodeId>>,
58    pub(crate) root_render_requested: Cell<bool>,
59    pub(crate) _not_send: PhantomData<*const ()>,
60}
61
62impl ComposerCore {
63    pub(crate) fn new(
64        slots: Rc<SlotsHost>,
65        applier: Rc<dyn ApplierHost>,
66        runtime: RuntimeHandle,
67        observer: SnapshotStateObserver,
68        root: Option<NodeId>,
69    ) -> Self {
70        let parent_stack = if let Some(root_id) = root {
71            vec![ParentFrame {
72                id: root_id,
73                previous: ChildList::new(),
74                new_children: ChildList::new(),
75                attach_mode: ParentAttachMode::DeferredSync,
76            }]
77        } else {
78            Vec::new()
79        };
80
81        Self {
82            slots,
83            slots_override: RefCell::new(Vec::new()),
84            applier,
85            runtime,
86            observer,
87            parent_stack: RefCell::new(parent_stack),
88            subcompose_stack: RefCell::new(Vec::new()),
89            root: Cell::new(root),
90            commands: RefCell::new(CommandQueue::default()),
91            scope_stack: RefCell::new(Vec::new()),
92            local_stack: RefCell::new(empty_local_stack()),
93            side_effects: RefCell::new(Vec::new()),
94            pending_scope_options: RefCell::new(None),
95            phase: Cell::new(crate::Phase::Compose),
96            last_node_reused: Cell::new(None),
97            recranpose_parent_hint: Cell::new(None),
98            root_render_requested: Cell::new(false),
99            _not_send: PhantomData,
100        }
101    }
102}
103
104#[derive(Clone)]
105pub struct Composer {
106    pub(crate) core: Rc<ComposerCore>,
107}
108
109pub(crate) enum EmittedNode {
110    Fresh(Box<dyn Node>),
111    Recycled(RecycledNode),
112}
113
114impl Composer {
115    pub fn new(
116        slots: Rc<SlotsHost>,
117        applier: Rc<dyn ApplierHost>,
118        runtime: RuntimeHandle,
119        observer: SnapshotStateObserver,
120        root: Option<NodeId>,
121    ) -> Self {
122        let core = Rc::new(ComposerCore::new(slots, applier, runtime, observer, root));
123        Self { core }
124    }
125
126    pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
127        Self { core }
128    }
129
130    pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
131        Rc::clone(&self.core)
132    }
133
134    fn observer(&self) -> SnapshotStateObserver {
135        self.core.observer.clone()
136    }
137
138    pub(crate) fn request_root_render(&self) {
139        self.core.root_render_requested.set(true);
140    }
141
142    pub(crate) fn take_root_render_request(&self) -> bool {
143        self.core.root_render_requested.replace(false)
144    }
145
146    pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
147        let observer = self.observer();
148        let scope_clone = scope.clone();
149        observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
150    }
151
152    fn active_slots_host(&self) -> Rc<SlotsHost> {
153        self.core
154            .slots_override
155            .borrow()
156            .last()
157            .cloned()
158            .unwrap_or_else(|| Rc::clone(&self.core.slots))
159    }
160
161    pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
162        let override_host = {
163            let overrides = self.core.slots_override.borrow();
164            overrides.last().cloned()
165        };
166        if let Some(host) = override_host {
167            let slots = host.borrow();
168            f(&slots)
169        } else {
170            let slots = self.core.slots.borrow();
171            f(&slots)
172        }
173    }
174
175    pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
176        let override_host = {
177            let overrides = self.core.slots_override.borrow();
178            overrides.last().cloned()
179        };
180        if let Some(host) = override_host {
181            let mut slots = host.borrow_mut();
182            f(&mut slots)
183        } else {
184            let mut slots = self.core.slots.borrow_mut();
185            f(&mut slots)
186        }
187    }
188
189    pub(crate) fn with_slot_override<R>(
190        &self,
191        slots: Rc<SlotsHost>,
192        f: impl FnOnce(&Composer) -> R,
193    ) -> R {
194        self.core.slots_override.borrow_mut().push(slots);
195        struct Guard {
196            core: Rc<ComposerCore>,
197        }
198        impl Drop for Guard {
199            fn drop(&mut self) {
200                self.core.slots_override.borrow_mut().pop();
201            }
202        }
203        let guard = Guard {
204            core: self.clone_core(),
205        };
206        let result = f(self);
207        drop(guard);
208        result
209    }
210
211    pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
212        self.core.parent_stack.borrow_mut()
213    }
214
215    pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
216        self.core.subcompose_stack.borrow_mut()
217    }
218
219    pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
220        self.core.commands.borrow_mut()
221    }
222
223    pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
224        self.commands_mut().push(Command::BubbleDirty {
225            node_id: id,
226            bubble: DirtyBubble::SEMANTICS,
227        });
228    }
229
230    pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
231        self.core.scope_stack.borrow_mut()
232    }
233
234    pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
235        self.core.local_stack.borrow_mut()
236    }
237
238    pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
239        self.core.local_stack.borrow().clone()
240    }
241
242    pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
243        self.core.side_effects.borrow_mut()
244    }
245
246    fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
247        self.core.pending_scope_options.borrow_mut()
248    }
249
250    pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
251        self.core.applier.borrow_dyn()
252    }
253
254    /// Registers a virtual node in the Applier.
255    ///
256    /// This is used by SubcomposeLayoutNode to register virtual container nodes
257    /// so that subsequent insert_child commands can find them and attach children.
258    /// Without this, virtual nodes would only exist in SubcomposeLayoutNodeInner.virtual_nodes
259    /// and applier.get_mut(virtual_node_id) would fail, breaking child attachment.
260    pub fn register_virtual_node(
261        &self,
262        node_id: NodeId,
263        node: Box<dyn Node>,
264    ) -> Result<(), NodeError> {
265        let mut applier = self.borrow_applier();
266        applier.insert_with_id(node_id, node)
267    }
268
269    /// Checks if a node has no parent (is a root node).
270    /// Used by SubcomposeMeasureScope to filter subcompose results.
271    pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
272        let mut applier = self.borrow_applier();
273        match applier.get_mut(node_id) {
274            Ok(node) => node.parent().is_none(),
275            Err(_) => true,
276        }
277    }
278
279    /// Gets the children of a node from the Applier.
280    ///
281    /// This is used by SubcomposeLayoutNode to get children of virtual nodes
282    /// directly from the Applier, where insert_child commands have been applied.
283    pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
284        let mut applier = self.borrow_applier();
285        match applier.get_mut(node_id) {
286            Ok(node) => {
287                let mut children = SmallVec::<[NodeId; 8]>::new();
288                node.collect_children_into(&mut children);
289                children
290            }
291            Err(_) => SmallVec::<[NodeId; 8]>::new(),
292        }
293    }
294
295    /// Records a child node in the current parent frame's expected children list.
296    ///
297    /// Used by SubcomposeLayout's `perform_subcompose` to register virtual nodes
298    /// with the outer composer's parent frame. This ensures that the `pop_parent`
299    /// call at the end of `subcompose_slot` generates a correct `SyncChildren`
300    /// command that preserves (rather than removes) the virtual nodes.
301    ///
302    /// Without this, `pop_parent` would generate `SyncChildren { expected: [] }`,
303    /// which removes all virtual nodes and their subtrees from the applier.
304    pub fn record_subcompose_child(&self, child_id: NodeId) {
305        let mut parent_stack = self.parent_stack();
306        if let Some(frame) = parent_stack.last_mut() {
307            if matches!(frame.attach_mode, ParentAttachMode::DeferredSync)
308                && !frame.new_children.contains(&child_id)
309            {
310                frame.new_children.push(child_id);
311            }
312        }
313    }
314
315    /// Clears all children of a node in the Applier.
316    ///
317    /// This is used by SubcomposeLayoutNode when reusing a virtual node for
318    /// different content. Without clearing, old children remain attached,
319    /// causing duplicate/interleaved items in lazy lists after scrolling.
320    pub fn clear_node_children(&self, node_id: NodeId) {
321        let mut applier = self.borrow_applier();
322        if let Ok(node) = applier.get_mut(node_id) {
323            node.update_children(&[]);
324        }
325    }
326
327    pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
328        let _composer_guard = composer_context::enter(self);
329        runtime::push_active_runtime(&self.core.runtime);
330        struct Guard;
331        impl Drop for Guard {
332            fn drop(&mut self) {
333                runtime::pop_active_runtime();
334            }
335        }
336        let guard = Guard;
337        let result = f(self);
338        drop(guard);
339        result
340    }
341
342    pub(crate) fn flush_pending_commands_if_large(&self) {
343        let queued = self.core.commands.borrow().len();
344        if queued < COMMAND_FLUSH_THRESHOLD {
345            return;
346        }
347        self.apply_pending_commands()
348            .expect("mid-composition command flush failed");
349    }
350
351    pub(crate) fn drain_orphaned_nodes_from_slots(&self) {
352        let orphaned = self.with_slots_mut(|slots| slots.drain_orphaned_node_ids());
353        if orphaned.is_empty() {
354            return;
355        }
356        let mut deferred = Vec::new();
357        let mut applier = self.borrow_applier();
358        for orphaned in orphaned {
359            match self.with_slots(|slots| slots.orphaned_node_state(orphaned)) {
360                crate::slot_table::NodeSlotState::Active => continue,
361                crate::slot_table::NodeSlotState::PreservedGap => {
362                    deferred.push(orphaned);
363                    continue;
364                }
365                crate::slot_table::NodeSlotState::Missing => {}
366            }
367            if applier.node_generation(orphaned.id) != orphaned.generation {
368                continue;
369            }
370            let parent_id = applier
371                .get_mut(orphaned.id)
372                .ok()
373                .and_then(|node| node.parent());
374            if let Some(parent_id) = parent_id {
375                let _ = remove_child_and_cleanup_now(&mut *applier, parent_id, orphaned.id);
376                continue;
377            }
378            if let Ok(node) = applier.get_mut(orphaned.id) {
379                node.on_removed_from_parent();
380                node.unmount();
381            }
382            let _ = applier.remove(orphaned.id);
383        }
384        if !deferred.is_empty() {
385            self.with_slots_mut(|slots| {
386                for orphaned in deferred {
387                    slots.requeue_orphaned_node(orphaned);
388                }
389            });
390        }
391    }
392
393    pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
394        let parent_scope = self.current_recranpose_scope();
395        let (group, group_anchor, scope_ref, restored_from_gap) = self.with_slots_mut(|slots| {
396            let StartGroup {
397                group,
398                anchor,
399                restored_from_gap,
400            } = slots.begin_group(key);
401            let scope_slot = slots.use_value_slot(|| RecomposeScope::new(self.runtime_handle()));
402            let scope_ref = slots.read_value::<RecomposeScope>(scope_slot).clone();
403            (group, anchor, scope_ref, restored_from_gap)
404        });
405
406        scope_ref.reactivate();
407        scope_ref.set_group_anchor(group_anchor);
408        scope_ref.set_parent_scope(parent_scope);
409
410        if let Some(options) = self.pending_scope_options().take() {
411            if options.force_recompose {
412                scope_ref.force_recompose();
413            } else if options.force_reuse {
414                scope_ref.force_reuse();
415            }
416        }
417        if restored_from_gap {
418            scope_ref.force_recompose();
419        }
420
421        self.with_slots_mut(|slots| {
422            slots.set_group_scope(group.0, scope_ref.id());
423        });
424
425        let slots_host = self.active_slots_host();
426        scope_ref.set_slots_host(Rc::downgrade(&slots_host));
427
428        {
429            let mut stack = self.scope_stack();
430            stack.push(scope_ref.clone());
431        }
432
433        {
434            let mut stack = self.subcompose_stack();
435            if let Some(frame) = stack.last_mut() {
436                frame.scopes.push(scope_ref.clone());
437            }
438        }
439
440        scope_ref.snapshot_locals(self.current_local_stack());
441        {
442            let parent_hint = self.parent_stack().last().map(|frame| frame.id);
443            scope_ref.set_parent_hint(parent_hint);
444        }
445
446        let result = self.observe_scope(&scope_ref, || f(self));
447
448        scope_ref.mark_composed_once();
449
450        let trimmed = self.with_slots_mut(|slots| slots.trim_to_cursor());
451        if trimmed {
452            scope_ref.force_recompose();
453        }
454        self.drain_orphaned_nodes_from_slots();
455
456        {
457            let mut stack = self.scope_stack();
458            stack.pop();
459        }
460        scope_ref.mark_recomposed();
461        self.with_slots_mut(|slots| slots.end_group());
462        self.flush_pending_commands_if_large();
463        result
464    }
465
466    pub fn cranpose_with_reuse<R>(
467        &self,
468        key: Key,
469        options: RecomposeOptions,
470        f: impl FnOnce(&Composer) -> R,
471    ) -> R {
472        self.pending_scope_options().replace(options);
473        self.with_group(key, f)
474    }
475
476    pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
477        let hashed = hash_key(key);
478        self.with_group(hashed, f)
479    }
480
481    pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
482        self.with_slots_mut(|slots| slots.remember(init))
483    }
484
485    pub fn use_value_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> usize {
486        self.with_slots_mut(|slots| slots.use_value_slot(init))
487    }
488
489    pub fn with_slot_value<T: 'static, R>(&self, idx: usize, f: impl FnOnce(&T) -> R) -> R {
490        self.with_slots(|slots| f(slots.read_value(idx)))
491    }
492
493    pub fn with_slot_value_mut<T: 'static, R>(&self, idx: usize, f: impl FnOnce(&mut T) -> R) -> R {
494        self.with_slots_mut(|slots| f(slots.read_value_mut(idx)))
495    }
496
497    pub fn write_slot_value<T: 'static>(&self, idx: usize, value: T) {
498        self.with_slots_mut(|slots| slots.write_value(idx, value));
499    }
500
501    pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
502        MutableState::with_runtime(initial, self.runtime_handle())
503    }
504
505    pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
506    where
507        T: Clone + 'static,
508        I: IntoIterator<Item = T>,
509    {
510        SnapshotStateList::with_runtime(values, self.runtime_handle())
511    }
512
513    pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
514    where
515        K: Clone + Eq + Hash + 'static,
516        V: Clone + 'static,
517        I: IntoIterator<Item = (K, V)>,
518    {
519        SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
520    }
521
522    pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
523        let stack = self.core.local_stack.borrow();
524        for context in stack.iter().rev() {
525            if let Some(entry) = context.values.get(&local.key) {
526                let typed = entry
527                    .clone()
528                    .downcast::<LocalStateEntry<T>>()
529                    .expect("composition local type mismatch");
530                return typed.value();
531            }
532        }
533        local.default_value()
534    }
535
536    pub fn read_static_composition_local<T: Clone + 'static>(
537        &self,
538        local: &StaticCompositionLocal<T>,
539    ) -> T {
540        let stack = self.core.local_stack.borrow();
541        for context in stack.iter().rev() {
542            if let Some(entry) = context.values.get(&local.key) {
543                let typed = entry
544                    .clone()
545                    .downcast::<StaticLocalEntry<T>>()
546                    .expect("static composition local type mismatch");
547                return typed.value();
548            }
549        }
550        local.default_value()
551    }
552
553    pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
554        self.core.scope_stack.borrow().last().cloned()
555    }
556
557    pub fn phase(&self) -> crate::Phase {
558        self.core.phase.get()
559    }
560
561    pub(crate) fn set_phase(&self, phase: crate::Phase) {
562        self.core.phase.set(phase);
563    }
564
565    pub fn enter_phase(&self, phase: crate::Phase) {
566        self.set_phase(phase);
567    }
568
569    pub(crate) fn subcompose<R>(
570        &self,
571        state: &mut SubcomposeState,
572        slot_id: SlotId,
573        content: impl FnOnce(&Composer) -> R,
574    ) -> (R, Vec<NodeId>) {
575        match self.phase() {
576            crate::Phase::Measure | crate::Phase::Layout => {}
577            current => panic!(
578                "subcompose() may only be called during measure or layout; current phase: {:?}",
579                current
580            ),
581        }
582
583        self.subcompose_stack().push(SubcomposeFrame::default());
584        struct StackGuard {
585            core: Rc<ComposerCore>,
586            leaked: bool,
587        }
588        impl Drop for StackGuard {
589            fn drop(&mut self) {
590                if !self.leaked {
591                    self.core.subcompose_stack.borrow_mut().pop();
592                }
593            }
594        }
595        let mut guard = StackGuard {
596            core: self.clone_core(),
597            leaked: false,
598        };
599
600        let slot_host = state.get_or_create_slots(slot_id);
601        {
602            let mut slots = slot_host.borrow_mut();
603            slots.reset();
604        }
605        let result = self.with_slot_override(slot_host.clone(), |composer| {
606            composer.with_group(slot_id.raw(), |composer| content(composer))
607        });
608        {
609            let mut slots = slot_host.borrow_mut();
610            slots.finalize_current_group();
611            slots.flush();
612        }
613
614        let frame = {
615            let mut stack = guard.core.subcompose_stack.borrow_mut();
616            let frame = stack.pop().expect("subcompose stack underflow");
617            guard.leaked = true;
618            frame
619        };
620        let nodes = frame.nodes;
621        let scopes = frame.scopes;
622        state.register_active(slot_id, &nodes, &scopes);
623        (result, nodes)
624    }
625
626    pub fn subcompose_measurement<R>(
627        &self,
628        state: &mut SubcomposeState,
629        slot_id: SlotId,
630        content: impl FnOnce(&Composer) -> R,
631    ) -> (R, Vec<NodeId>) {
632        let (result, nodes) = self.subcompose(state, slot_id, content);
633        let roots = nodes
634            .into_iter()
635            .filter(|&id| self.node_has_no_parent(id))
636            .collect();
637
638        (result, roots)
639    }
640
641    pub fn subcompose_in<R>(
642        &self,
643        slots: &Rc<SlotsHost>,
644        root: Option<NodeId>,
645        f: impl FnOnce(&Composer) -> R,
646    ) -> Result<R, NodeError> {
647        let runtime_handle = self.runtime_handle();
648        slots.borrow_mut().reset();
649        let phase = self.phase();
650        let locals = self.current_local_stack();
651        let core = Rc::new(ComposerCore::new(
652            Rc::clone(slots),
653            Rc::clone(&self.core.applier),
654            runtime_handle.clone(),
655            self.observer(),
656            root,
657        ));
658        core.phase.set(phase);
659        *core.local_stack.borrow_mut() = locals;
660        let composer = Composer::from_core(core);
661        let (result, commands, side_effects) = composer.install(|composer| {
662            let output = f(composer);
663            let commands = composer.take_commands();
664            let side_effects = composer.take_side_effects();
665            (output, commands, side_effects)
666        });
667        {
668            let mut applier = self.borrow_applier();
669            commands.apply(&mut *applier)?;
670            for update in runtime_handle.take_updates() {
671                update.apply(&mut *applier)?;
672            }
673        }
674        runtime_handle.drain_ui();
675        for effect in side_effects {
676            effect();
677        }
678        runtime_handle.drain_ui();
679        {
680            let mut slots_mut = slots.borrow_mut();
681            slots_mut.finalize_current_group();
682            slots_mut.flush();
683        }
684        Ok(result)
685    }
686
687    /// Subcomposes content using an isolated SlotsHost without resetting it.
688    /// Unlike `subcompose_in`, this preserves existing slot state across calls,
689    /// allowing efficient reuse during measurement passes. This is critical for
690    /// lazy lists where items need stable slot positions.
691    pub fn subcompose_slot<R>(
692        &self,
693        slots: &Rc<SlotsHost>,
694        root: Option<NodeId>,
695        f: impl FnOnce(&Composer) -> R,
696    ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
697        let runtime_handle = self.runtime_handle();
698        slots.borrow_mut().reset();
699        let phase = self.phase();
700        let locals = self.current_local_stack();
701        let core = Rc::new(ComposerCore::new(
702            Rc::clone(slots),
703            Rc::clone(&self.core.applier),
704            runtime_handle.clone(),
705            self.observer(),
706            root,
707        ));
708        core.phase.set(phase);
709        *core.local_stack.borrow_mut() = locals;
710        let composer = Composer::from_core(core);
711        composer.subcompose_stack().push(SubcomposeFrame::default());
712        struct StackGuard {
713            core: Rc<ComposerCore>,
714            leaked: bool,
715        }
716        impl Drop for StackGuard {
717            fn drop(&mut self) {
718                if !self.leaked {
719                    self.core.subcompose_stack.borrow_mut().pop();
720                }
721            }
722        }
723        let mut guard = StackGuard {
724            core: composer.clone_core(),
725            leaked: false,
726        };
727        let root_group_key = crate::location_key(file!(), line!(), column!());
728        let (result, commands, side_effects) = composer.install(|composer| {
729            let output = composer.with_group(root_group_key, |composer| f(composer));
730            if root.is_some() {
731                composer.pop_parent();
732            }
733            let commands = composer.take_commands();
734            let side_effects = composer.take_side_effects();
735            (output, commands, side_effects)
736        });
737        let frame = {
738            let mut stack = guard.core.subcompose_stack.borrow_mut();
739            let frame = stack.pop().expect("subcompose stack underflow");
740            guard.leaked = true;
741            frame
742        };
743
744        {
745            let mut applier = self.borrow_applier();
746            commands.apply(&mut *applier)?;
747            for update in runtime_handle.take_updates() {
748                update.apply(&mut *applier)?;
749            }
750        }
751        runtime_handle.drain_ui();
752        for effect in side_effects {
753            effect();
754        }
755        runtime_handle.drain_ui();
756        {
757            let mut slots_mut = slots.borrow_mut();
758            let _ = slots_mut.finalize_current_group();
759            slots_mut.flush();
760        }
761        Ok((result, frame.scopes))
762    }
763
764    pub(crate) fn skipped_group_root_nodes(&self, nodes: &[NodeId]) -> Vec<NodeId> {
765        let node_set: std::collections::HashSet<NodeId> = nodes.iter().copied().collect();
766        let mut applier = self.borrow_applier();
767        nodes
768            .iter()
769            .copied()
770            .filter(|id| {
771                let parent = applier.get_mut(*id).ok().and_then(|node| node.parent());
772                parent.is_none_or(|parent_id| !node_set.contains(&parent_id))
773            })
774            .collect()
775    }
776
777    pub fn skip_current_group(&self) {
778        let nodes = self.with_slots(|slots| slots.nodes_in_current_group());
779        let root_nodes = self.skipped_group_root_nodes(&nodes);
780        self.with_slots_mut(|slots| slots.skip_current_group());
781        for id in root_nodes {
782            self.attach_to_parent_with_mode(id, true);
783        }
784    }
785
786    pub fn runtime_handle(&self) -> RuntimeHandle {
787        self.core.runtime.clone()
788    }
789
790    pub fn set_recranpose_callback<F>(&self, callback: F)
791    where
792        F: FnMut(&Composer) + 'static,
793    {
794        if let Some(scope) = self.current_recranpose_scope() {
795            let observer = self.observer();
796            let scope_weak = scope.downgrade();
797            let mut callback = callback;
798            scope.set_recompose(Box::new(move |composer: &Composer| {
799                if let Some(inner) = scope_weak.upgrade() {
800                    let scope_instance = RecomposeScope { inner };
801                    observer.observe_reads(
802                        scope_instance.clone(),
803                        move |scope_ref| scope_ref.invalidate(),
804                        || {
805                            callback(composer);
806                        },
807                    );
808                }
809            }));
810        }
811    }
812
813    pub fn set_recranpose_fn(&self, callback: fn(&Composer)) {
814        if let Some(scope) = self.current_recranpose_scope() {
815            scope.set_recompose_fn(callback);
816        }
817    }
818
819    pub fn with_composition_locals<R>(
820        &self,
821        provided: Vec<ProvidedValue>,
822        f: impl FnOnce(&Composer) -> R,
823    ) -> R {
824        if provided.is_empty() {
825            return f(self);
826        }
827        let mut context = LocalContext::default();
828        for value in provided {
829            let (key, entry) = value.into_entry(self);
830            context.values.insert(key, entry);
831        }
832        {
833            let mut stack = self.local_stack();
834            Rc::make_mut(&mut *stack).push(context);
835        }
836        let result = f(self);
837        {
838            let mut stack = self.local_stack();
839            Rc::make_mut(&mut *stack).pop();
840        }
841        result
842    }
843}