1use std::{
2 any::Any,
3 cell::{Cell, RefCell, RefMut},
4 hash::Hash,
5 marker::PhantomData,
6 rc::{Rc, Weak},
7};
8
9use smallvec::SmallVec;
10
11use crate::{
12 AnchorId, Applier, ApplierGuard, ApplierHost, COMMAND_FLUSH_THRESHOLD, ChildList, Command,
13 CommandQueue, CompositionLocal, ComputedLocalEntry, DirtyBubble, Key, LocalKey,
14 LocalStackSnapshot, LocalStateEntry, MutableState, Node, NodeError, NodeId, Owned,
15 ProvidedValue, RecomposeOptions, RecomposeScope, RecomposeScopeInner, RecycledNode,
16 RetentionMode, RuntimeHandle, ScopeId, SlotId, SlotPassOutcome, SlotTable, SlotsHost,
17 SnapshotStateList, SnapshotStateMap, StaticCompositionLocal, SubcomposeState, composer_context,
18 retention::RetainKey,
19 runtime,
20 slot::{FinishGroupResult, GroupStart, GroupStartKind, PayloadKind, RootNodeIds, ValueSlotId},
21};
22
23mod composable_group;
24mod runtime_state;
25mod subcompose_slot;
26
27pub use composable_group::{ComposableGroup, ParameterChange};
28pub(crate) use runtime_state::ComposerRuntimeState;
29use subcompose_slot::{measure_slot_state, subcompose_slot_at};
30
31pub struct ValueSlotHandle<'pass, T: 'static> {
34 slot: ValueSlotId,
35 record: Option<usize>,
36 _pass: PhantomData<&'pass Composer>,
37 _value: PhantomData<fn() -> T>,
38}
39
40impl<T: 'static> Copy for ValueSlotHandle<'_, T> {}
41
42impl<T: 'static> Clone for ValueSlotHandle<'_, T> {
43 fn clone(&self) -> Self {
44 *self
45 }
46}
47
48impl<T: 'static> ValueSlotHandle<'_, T> {
49 pub(crate) fn new(slot: ValueSlotId, record: Option<usize>) -> Self {
50 Self {
51 slot,
52 record,
53 _pass: PhantomData,
54 _value: PhantomData,
55 }
56 }
57}
58
59fn slots_storage_key(host: &Rc<SlotsHost>) -> usize {
60 host.storage_key()
61}
62
63fn bind_slots_host_to_runtime_state(
64 state: &Rc<ComposerRuntimeState>,
65 host: &Rc<SlotsHost>,
66) -> Rc<SlotsHost> {
67 if host.runtime_state_ptr() == Some(Rc::as_ptr(state)) {
70 debug_assert!(
71 state
72 .host_for_storage_key(host.storage_key())
73 .is_some_and(|registered| Rc::ptr_eq(®istered, host)),
74 "a slot host bound to a runtime state must be in its registry"
75 );
76 return Rc::clone(host);
77 }
78 if host.runtime_state().is_some() {
79 if host.rebind_orphaned_runtime_state(state) {
80 state.bind_slots_host(host);
81 return Rc::clone(host);
82 }
83 log::error!(
84 "slot host already belongs to a different composer runtime state; using a fresh slot host"
85 );
86 let replacement = Rc::new(SlotsHost::new(SlotTable::new()));
87 state.bind_slots_host(&replacement);
88 return replacement;
89 }
90 state.bind_slots_host(host);
91 Rc::clone(host)
92}
93
94struct GroupEntry {
95 key: crate::slot::GroupKey,
96 restored: Option<Box<crate::slot::DetachedSubtree>>,
98 placeholder_for: Option<crate::slot::GroupKey>,
99}
100
101struct GroupScopeEntry<'a> {
102 options: RecomposeOptions,
103 start_kind: GroupStartKind,
104 host: &'a SlotsHost,
105 group: crate::slot::ActiveGroupId,
106 parent_hint: Option<NodeId>,
107}
108
109struct SlotHostPassGuard {
110 core: Rc<ComposerCore>,
111 host: Rc<SlotsHost>,
112 fold_base: usize,
114 active: bool,
115}
116
117impl SlotHostPassGuard {
118 fn close(&mut self) {
119 if !self.active {
120 return;
121 }
122 if self.host.has_active_pass() {
123 self.host.abandon_active_pass();
124 }
125 match self.core.slot_hosts.borrow_mut().pop() {
126 Some(host) if Rc::ptr_eq(&host, &self.host) => {}
127 Some(_) => {
128 log::error!("slot host stack mismatch while closing slot host pass");
129 }
130 None => {
131 log::error!("slot host stack underflow while closing slot host pass");
132 }
133 }
134 self.core.branch_folds.end_pass(self.fold_base);
135 self.active = false;
136 }
137}
138
139impl Drop for SlotHostPassGuard {
140 fn drop(&mut self) {
141 self.close();
142 }
143}
144
145pub(crate) struct PendingMovable {
146 pub(crate) key: crate::slot::GroupKey,
147 pub(crate) placeholder: AnchorId,
148 pub(crate) parent_scope: Option<Weak<RecomposeScopeInner>>,
149}
150
151fn reparent_restored_scopes(
152 host: &SlotsHost,
153 group: crate::slot::ActiveGroupId,
154 old_hint: Option<NodeId>,
155 parent_hint: Option<NodeId>,
156) {
157 host.with_write_session(|slots| {
158 slots.for_each_subtree_scope(group, |scope| {
159 if scope.parent_hint() == old_hint {
160 scope.set_parent_hint(parent_hint);
161 }
162 scope.reactivate();
163 });
164 });
165}
166
167fn movable_retain_key(id: Key) -> RetainKey {
168 RetainKey::for_group(
169 None,
170 crate::slot::GroupKey::new(crate::slot::MOVABLE_STATIC_KEY, Some(id), 0),
171 )
172}
173
174const SPARE_COMMAND_QUEUES: usize = 8;
176
177const SPARE_COMMAND_CAPACITY: usize = 1024;
180
181fn spare_commands() -> CommandQueue {
182 crate::thread_state::try_runtimes(|state| state.spare_commands.borrow_mut().pop())
183 .flatten()
184 .unwrap_or_default()
185}
186
187#[derive(Default)]
190pub(crate) struct ComposerStacks {
191 slot_hosts: Vec<Rc<SlotsHost>>,
192 parent_stack: Vec<ParentFrame>,
193 scope_stack: Vec<RecomposeScope>,
194}
195
196const SPARE_STACK_CAPACITY: usize = 256;
199
200fn spare_composer_stacks() -> ComposerStacks {
201 crate::thread_state::try_runtimes(|state| state.spare_composer_stacks.take())
202 .flatten()
203 .unwrap_or_default()
204}
205
206impl ComposerStacks {
207 fn holds_storage(&self) -> bool {
208 self.slot_hosts.capacity() > 0
209 || self.parent_stack.capacity() > 0
210 || self.scope_stack.capacity() > 0
211 }
212}
213
214fn recycle_composer_stacks(mut stacks: ComposerStacks) {
216 let ComposerStacks {
217 slot_hosts,
218 parent_stack,
219 scope_stack,
220 } = &mut stacks;
221 if slot_hosts.capacity() > SPARE_STACK_CAPACITY
222 || parent_stack.capacity() > SPARE_STACK_CAPACITY
223 || scope_stack.capacity() > SPARE_STACK_CAPACITY
224 {
225 return;
226 }
227 slot_hosts.clear();
228 parent_stack.clear();
229 scope_stack.clear();
230 crate::thread_state::try_runtimes(|state| state.spare_composer_stacks.set(Some(stacks)));
231}
232
233pub(crate) fn recycle_commands(commands: CommandQueue) {
235 if commands.capacity() > SPARE_COMMAND_CAPACITY {
236 return;
237 }
238 crate::thread_state::try_runtimes(|state| {
239 let mut spare = state.spare_commands.borrow_mut();
240 if spare.len() < SPARE_COMMAND_QUEUES {
241 spare.push(commands);
242 }
243 });
244}
245
246pub(crate) struct ParentFrame {
247 pub(crate) id: NodeId,
248 pub(crate) previous: ChildList,
249 pub(crate) new_children: ChildList,
250 pub(crate) attach_mode: ParentAttachMode,
251 pub(crate) synthetic_root: bool,
252}
253
254#[derive(Clone, Copy)]
255pub(crate) enum InitialParentFrame {
256 SyntheticRoot,
257 RealParent,
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
261pub(crate) enum ParentAttachMode {
262 ImmediateAppend,
263 DeferredSync,
264}
265
266#[derive(Default)]
267pub(crate) struct SubcomposeFrame {
268 pub(crate) nodes: Vec<NodeId>,
269 pub(crate) scopes: Vec<RecomposeScope>,
270}
271
272pub(crate) struct LocalFrame {
275 values: SmallVec<[(LocalKey, Rc<dyn Any>); 2]>,
276 parent: LocalStackSnapshot,
277}
278
279pub(crate) fn provided_entry(stack: &LocalStackSnapshot, key: &LocalKey) -> Option<Rc<dyn Any>> {
281 let mut frame = stack.as_deref();
282 while let Some(current) = frame {
283 if let Some((_, entry)) = current.values.iter().find(|(provided, _)| provided == key) {
284 return Some(Rc::clone(entry));
285 }
286 frame = current.parent.as_deref();
287 }
288 None
289}
290
291pub(crate) struct ComposerCore {
292 pub(crate) shared_state: Rc<ComposerRuntimeState>,
293 pub(crate) branch_folds: Rc<crate::slot::BranchFolds>,
296 pub(crate) slots: Rc<SlotsHost>,
297 slot_hosts: RefCell<Vec<Rc<SlotsHost>>>,
298 pub(crate) applier: Rc<dyn ApplierHost>,
299 pub(crate) runtime: RuntimeHandle,
300 pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
301 pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
302 pub(crate) root: Cell<Option<NodeId>>,
303 pub(crate) commands: RefCell<CommandQueue>,
304 pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
305 subcomposition_owner_scope: RefCell<Option<RecomposeScope>>,
306 pub(crate) local_stack: RefCell<LocalStackSnapshot>,
307 pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
308 pub(crate) pending_scope_options: Cell<Option<RecomposeOptions>>,
309 pub(crate) phase: Cell<crate::Phase>,
310 pub(crate) recompose_parent_hint: Cell<Option<NodeId>>,
311 pub(crate) recompose_child_cursor: Cell<crate::recompose::RecomposeChildCursor>,
312 pub(crate) restarted_scope: RefCell<Option<RecomposeScope>>,
316 pub(crate) root_render_requested: Cell<bool>,
317 locals_changed: Cell<bool>,
320 pub(crate) _not_send: PhantomData<*const ()>,
321}
322
323#[derive(Clone, Default)]
327pub struct CapturedCompositionContext {
328 locals: LocalStackSnapshot,
329 owner_scope: Option<Weak<RecomposeScopeInner>>,
330 locals_changed: bool,
331}
332
333impl CapturedCompositionContext {
334 pub fn locals_changed(&self) -> bool {
339 self.locals_changed
340 }
341
342 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
346 self.owner_scope
347 .as_ref()
348 .and_then(Weak::upgrade)
349 .map_or(0, |inner| {
350 RecomposeScope { inner }.owner_chain_deactivation_epoch()
351 })
352 }
353}
354
355fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
356 match core.subcompose_stack.borrow_mut().pop() {
357 Some(frame) => frame,
358 None => {
359 log::error!("subcompose stack underflow while finishing {operation}");
360 SubcomposeFrame::default()
361 }
362 }
363}
364
365struct SubcomposeStackGuard {
366 core: Rc<ComposerCore>,
367 leaked: bool,
368}
369
370impl Drop for SubcomposeStackGuard {
371 fn drop(&mut self) {
372 if !self.leaked {
373 self.core.subcompose_stack.borrow_mut().pop();
374 }
375 }
376}
377
378impl Drop for ComposerCore {
379 fn drop(&mut self) {
382 let commands = std::mem::take(self.commands.get_mut());
383 if commands.len() == 0 && commands.capacity() > 0 {
384 recycle_commands(commands);
385 }
386 let stacks = self.take_stacks();
387 if stacks.holds_storage() {
388 recycle_composer_stacks(stacks);
389 }
390 }
391}
392
393impl ComposerCore {
394 fn new(parts: ComposerParts) -> Self {
395 let ComposerParts {
396 shared_state,
397 slots,
398 applier,
399 runtime,
400 root,
401 initial_parent_frame,
402 } = parts;
403 let ComposerStacks {
404 slot_hosts,
405 mut parent_stack,
406 scope_stack,
407 } = spare_composer_stacks();
408 push_root_frame(&mut parent_stack, root, initial_parent_frame);
409 Self {
410 branch_folds: Rc::clone(&shared_state.branch_folds),
411 shared_state,
412 slots,
413 slot_hosts: RefCell::new(slot_hosts),
414 applier,
415 runtime,
416 parent_stack: RefCell::new(parent_stack),
417 subcompose_stack: RefCell::new(Vec::new()),
418 root: Cell::new(root),
419 commands: RefCell::new(spare_commands()),
420 scope_stack: RefCell::new(scope_stack),
421 subcomposition_owner_scope: RefCell::new(None),
422 local_stack: RefCell::new(None),
423 side_effects: RefCell::new(Vec::new()),
424 pending_scope_options: Cell::new(None),
425 phase: Cell::new(crate::Phase::Compose),
426 recompose_parent_hint: Cell::new(None),
427 recompose_child_cursor: Cell::new(crate::recompose::RecomposeChildCursor::Unknown),
428 restarted_scope: RefCell::new(None),
429 root_render_requested: Cell::new(false),
430 locals_changed: Cell::new(false),
431 _not_send: PhantomData,
432 }
433 }
434
435 fn take_stacks(&mut self) -> ComposerStacks {
437 let mut stacks = ComposerStacks {
438 slot_hosts: std::mem::take(self.slot_hosts.get_mut()),
439 parent_stack: std::mem::take(self.parent_stack.get_mut()),
440 scope_stack: std::mem::take(self.scope_stack.get_mut()),
441 };
442 stacks.slot_hosts.clear();
443 stacks.parent_stack.clear();
444 stacks.scope_stack.clear();
445 stacks
446 }
447
448 fn restart(&mut self, parts: ComposerParts) {
452 let Self {
453 shared_state,
454 branch_folds,
455 slots,
456 slot_hosts,
457 applier,
458 runtime,
459 parent_stack,
460 subcompose_stack,
461 root,
462 commands,
463 scope_stack,
464 subcomposition_owner_scope,
465 local_stack,
466 side_effects,
467 pending_scope_options,
468 phase,
469 recompose_parent_hint,
470 recompose_child_cursor,
471 restarted_scope,
472 root_render_requested,
473 locals_changed,
474 _not_send: _,
475 } = self;
476 if !Rc::ptr_eq(shared_state, &parts.shared_state) {
477 *branch_folds = Rc::clone(&parts.shared_state.branch_folds);
478 *shared_state = parts.shared_state;
479 }
480 *slots = parts.slots;
481 *applier = parts.applier;
482 *runtime = parts.runtime;
483 slot_hosts.get_mut().clear();
484 let parent_stack = parent_stack.get_mut();
485 parent_stack.clear();
486 push_root_frame(parent_stack, parts.root, parts.initial_parent_frame);
487 subcompose_stack.get_mut().clear();
488 root.set(parts.root);
489 let commands = commands.get_mut();
490 if commands.len() > 0 || commands.capacity() == 0 {
491 *commands = spare_commands();
492 }
493 scope_stack.get_mut().clear();
494 *subcomposition_owner_scope.get_mut() = None;
495 *local_stack.get_mut() = None;
496 side_effects.get_mut().clear();
497 pending_scope_options.set(None);
498 phase.set(crate::Phase::Compose);
499 recompose_parent_hint.set(None);
500 recompose_child_cursor.set(crate::recompose::RecomposeChildCursor::Unknown);
501 *restarted_scope.get_mut() = None;
502 root_render_requested.set(false);
503 locals_changed.set(false);
504 }
505
506 fn park(&mut self) {
509 self.slot_hosts.get_mut().clear();
510 self.parent_stack.get_mut().clear();
511 self.scope_stack.get_mut().clear();
512 self.subcompose_stack.get_mut().clear();
513 *self.subcomposition_owner_scope.get_mut() = None;
514 *self.local_stack.get_mut() = None;
515 self.side_effects.get_mut().clear();
516 }
517}
518
519fn push_root_frame(
521 parent_stack: &mut Vec<ParentFrame>,
522 root: Option<NodeId>,
523 initial_parent_frame: InitialParentFrame,
524) {
525 if let Some(root_id) = root {
526 parent_stack.push(ParentFrame {
527 id: root_id,
528 previous: ChildList::new(),
529 new_children: ChildList::new(),
530 attach_mode: ParentAttachMode::DeferredSync,
531 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
532 });
533 }
534}
535
536#[derive(Clone, Copy)]
540struct SubcomposeSite<'a> {
541 applier: &'a Rc<dyn ApplierHost>,
542 runtime: &'a RuntimeHandle,
543 phase: crate::Phase,
544 locals_changed: bool,
545}
546
547impl SubcomposeSite<'_> {
548 fn spin_up_core(
551 self,
552 shared_state: Rc<ComposerRuntimeState>,
553 slots: &Rc<SlotsHost>,
554 root: Option<NodeId>,
555 locals: LocalStackSnapshot,
556 ) -> Rc<ComposerCore> {
557 let core = Rc::new(ComposerCore::new(ComposerParts {
558 shared_state,
559 slots: Rc::clone(slots),
560 applier: Rc::clone(self.applier),
561 runtime: self.runtime.clone(),
562 root,
563 initial_parent_frame: InitialParentFrame::RealParent,
564 }));
565 core.phase.set(self.phase);
566 core.locals_changed.set(self.locals_changed);
567 *core.local_stack.borrow_mut() = locals;
568 core
569 }
570
571 fn flush(
576 self,
577 mut commands: CommandQueue,
578 compact_applier: bool,
579 side_effects: Vec<Box<dyn FnOnce()>>,
580 ) -> Result<(), NodeError> {
581 {
582 let mut applier = self.applier.borrow_dyn();
583 commands.apply(&mut *applier)?;
584 for update in self.runtime.take_updates() {
585 update.apply(&mut *applier)?;
586 }
587 }
588 recycle_commands(commands);
589 if compact_applier {
590 self.applier.compact();
591 self.applier.borrow_dyn().clear_recycled_nodes();
592 }
593 composer_context::without_composer(|| {
594 for effect in side_effects {
595 effect();
596 }
597 });
598 Ok(())
599 }
600}
601
602struct ComposerParts {
605 shared_state: Rc<ComposerRuntimeState>,
606 slots: Rc<SlotsHost>,
607 applier: Rc<dyn ApplierHost>,
608 runtime: RuntimeHandle,
609 root: Option<NodeId>,
610 initial_parent_frame: InitialParentFrame,
611}
612
613#[derive(Clone)]
617pub struct Composer {
618 pub(crate) core: Rc<ComposerCore>,
619}
620
621#[doc(hidden)]
624pub struct BranchGroupGuard<'a> {
625 folds: &'a crate::slot::BranchFolds,
626 token: crate::slot::FoldToken,
627}
628
629impl BranchGroupGuard<'_> {
630 #[doc(hidden)]
633 #[inline]
634 pub fn advance(&mut self, key: Key) {
635 self.folds.advance(&mut self.token, key);
636 }
637}
638
639impl Drop for BranchGroupGuard<'_> {
640 #[inline]
641 fn drop(&mut self) {
642 self.folds.close(self.token);
643 }
644}
645
646#[doc(hidden)]
653pub struct ScopedBranchGroupGuard {
654 open: std::mem::ManuallyDrop<Option<ScopedBranchGroup>>,
657}
658
659struct ScopedBranchGroup {
661 folds: Rc<crate::slot::BranchFolds>,
662 token: crate::slot::FoldToken,
663}
664
665#[doc(hidden)]
668#[inline]
669pub fn __branch_group_scope_deferred(key: Key) -> ScopedBranchGroupGuard {
670 let open = composer_context::with_current_core(|core| ScopedBranchGroup {
671 token: core.branch_folds.push(key),
672 folds: Rc::clone(&core.branch_folds),
673 });
674 ScopedBranchGroupGuard {
675 open: std::mem::ManuallyDrop::new(open),
676 }
677}
678
679#[doc(hidden)]
684#[inline]
685pub fn __branch_group_scope_advance(guard: &mut ScopedBranchGroupGuard, key: Key) {
686 if let Some(group) = guard.open.as_mut() {
687 group.folds.advance(&mut group.token, key);
688 }
689}
690
691impl Drop for ScopedBranchGroupGuard {
692 #[inline(always)]
693 fn drop(&mut self) {
694 if let Some(group) = self.open.take() {
695 close_scoped_branch_group(group);
696 }
697 }
698}
699
700#[inline(never)]
701fn close_scoped_branch_group(group: ScopedBranchGroup) {
702 group.folds.close(group.token);
703}
704
705pub(crate) enum EmittedNode {
706 Fresh(Box<dyn Node>),
707 Recycled(RecycledNode),
708}
709
710impl Composer {
711 pub(crate) fn new_with_shared_state(
712 shared_state: Rc<ComposerRuntimeState>,
713 slots: Rc<SlotsHost>,
714 applier: Rc<dyn ApplierHost>,
715 runtime: RuntimeHandle,
716 root: Option<NodeId>,
717 ) -> Self {
718 Self::new_with_shared_state_with_parent_frame(
719 shared_state,
720 slots,
721 applier,
722 runtime,
723 root,
724 InitialParentFrame::SyntheticRoot,
725 )
726 }
727
728 fn new_with_shared_state_with_parent_frame(
729 shared_state: Rc<ComposerRuntimeState>,
730 slots: Rc<SlotsHost>,
731 applier: Rc<dyn ApplierHost>,
732 runtime: RuntimeHandle,
733 root: Option<NodeId>,
734 initial_parent_frame: InitialParentFrame,
735 ) -> Self {
736 shared_state.bind_applier_host(&applier);
737 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
738 let core = Rc::new(ComposerCore::new(ComposerParts {
739 shared_state,
740 slots,
741 applier,
742 runtime,
743 root,
744 initial_parent_frame,
745 }));
746 Self { core }
747 }
748
749 pub(crate) fn for_pass(
753 parked: Option<Self>,
754 shared_state: Rc<ComposerRuntimeState>,
755 slots: Rc<SlotsHost>,
756 applier: Rc<dyn ApplierHost>,
757 runtime: RuntimeHandle,
758 root: Option<NodeId>,
759 ) -> Self {
760 let Some(mut composer) = parked.filter(|parked| Rc::strong_count(&parked.core) == 1) else {
761 return Self::new_with_shared_state(shared_state, slots, applier, runtime, root);
762 };
763 let Some(core) = Rc::get_mut(&mut composer.core) else {
764 return Self::new_with_shared_state(shared_state, slots, applier, runtime, root);
765 };
766 shared_state.bind_applier_host(&applier);
767 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
768 core.restart(ComposerParts {
769 shared_state,
770 slots,
771 applier,
772 runtime,
773 root,
774 initial_parent_frame: InitialParentFrame::SyntheticRoot,
775 });
776 composer
777 }
778
779 pub(crate) fn park(mut self) -> Option<Self> {
783 Rc::get_mut(&mut self.core)?.park();
784 Some(self)
785 }
786
787 pub fn new(
790 slots: Rc<SlotsHost>,
791 applier: Rc<dyn ApplierHost>,
792 runtime: RuntimeHandle,
793 root: Option<NodeId>,
794 ) -> Self {
795 Self::new_with_shared_state_with_parent_frame(
796 slots
797 .runtime_state()
798 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
799 slots,
800 applier,
801 runtime,
802 root,
803 InitialParentFrame::RealParent,
804 )
805 }
806
807 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
808 Self { core }
809 }
810
811 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
812 Rc::clone(&self.core)
813 }
814
815 pub(crate) fn request_root_render(&self) {
816 self.core.root_render_requested.set(true);
817 }
818
819 pub(crate) fn take_root_render_request(&self) -> bool {
820 self.core.root_render_requested.replace(false)
821 }
822
823 pub fn active_slots_host(&self) -> Rc<SlotsHost> {
826 self.core
827 .slot_hosts
828 .borrow()
829 .last()
830 .cloned()
831 .unwrap_or_else(|| Rc::clone(&self.core.slots))
832 }
833
834 fn with_active_slots_host<R>(&self, f: impl FnOnce(&SlotsHost) -> R) -> R {
835 let hosts = self.core.slot_hosts.borrow();
836 f(hosts.last().unwrap_or(&self.core.slots))
837 }
838
839 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
840 self.with_active_slots_host(|host| f(&host.borrow()))
841 }
842
843 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
844 self.with_active_slots_host(|host| f(&mut host.borrow_mut()))
845 }
846
847 pub(crate) fn with_slot_session_mut<R>(
848 &self,
849 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
850 ) -> R {
851 self.with_active_slots_host(|host| host.with_write_session(f))
852 }
853
854 pub(crate) fn try_with_slot_host_pass<R>(
855 &self,
856 slots: Rc<SlotsHost>,
857 mode: crate::slot::SlotPassMode,
858 f: impl FnOnce(&Self) -> R,
859 ) -> Result<(R, SlotPassOutcome), NodeError> {
860 let mut guard = self.begin_slot_host_pass(&slots, mode);
861 let result = f(self);
862 let outcome = self.finish_slot_host_pass(&guard.host)?;
863 guard.close();
864 Ok((result, outcome))
865 }
866
867 pub(crate) fn with_slot_host_pass<R>(
868 &self,
869 slots: Rc<SlotsHost>,
870 mode: crate::slot::SlotPassMode,
871 f: impl FnOnce(&Self) -> R,
872 ) -> (R, SlotPassOutcome) {
873 let mut guard = self.begin_slot_host_pass(&slots, mode);
874 let result = f(self);
875 let outcome = match self.finish_slot_host_pass(&guard.host) {
876 Ok(outcome) => outcome,
877 Err(err) => {
878 log::error!("slot host pass finalization failed: {err}");
879 SlotPassOutcome::default()
880 }
881 };
882 guard.close();
883 (result, outcome)
884 }
885
886 pub(crate) fn with_slot_override<R>(
887 &self,
888 slots: Rc<SlotsHost>,
889 f: impl FnOnce(&Self) -> R,
890 ) -> (R, SlotPassOutcome) {
891 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
892 }
893
894 fn begin_slot_host_pass(
895 &self,
896 slots: &Rc<SlotsHost>,
897 mode: crate::slot::SlotPassMode,
898 ) -> SlotHostPassGuard {
899 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
900 let fold_base = self.core.branch_folds.len();
901 slots.begin_pass(mode, &self.core.branch_folds);
902 {
903 let mut stack = self.core.slot_hosts.borrow_mut();
904 if let Some(parent) = stack.last()
905 && !Rc::ptr_eq(parent, &slots)
906 {
907 parent.note_nested_host(&slots);
908 }
909 stack.push(Rc::clone(&slots));
910 }
911 SlotHostPassGuard {
912 core: self.clone_core(),
913 host: slots,
914 fold_base,
915 active: true,
916 }
917 }
918
919 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
920 let finished = {
921 let mut applier = self.core.applier.borrow_dyn();
922 slots.finish_pass(&mut *applier)
923 }?;
924 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
925 self.wake_sites_whose_movable_arrived();
926 self.evict_retained_subtrees_for_host(slots)?;
927 slots.complete_pass_cleanup(&finished.outcome);
928 Ok(finished.outcome)
929 }
930
931 fn wake_sites_whose_movable_arrived(&self) {
932 for host in self.core.shared_state.hosts_awaiting_movables() {
933 self.wake_sites_in_host(&host);
934 }
935 }
936
937 fn wake_sites_in_host(&self, slots: &Rc<SlotsHost>) {
938 let pending = self.core.shared_state.take_pending_movables(slots);
939 if pending.is_empty() {
940 return;
941 }
942 let mut waiting = Vec::new();
943 for site in pending {
944 if !slots.borrow().group_is_active(site.placeholder) {
945 continue;
946 }
947 let retain_key = RetainKey::for_group(None, site.key);
948 if !self.core.shared_state.movable_retained_anywhere(retain_key) {
949 waiting.push(site);
950 continue;
951 }
952 match site.parent_scope.as_ref().and_then(RecomposeScope::upgrade) {
953 Some(scope) => {
954 scope.force_recompose();
955 scope.invalidate();
956 }
957 None => log::error!(
958 "movable content {:?} arrived for a site whose scope is gone",
959 site.key
960 ),
961 }
962 }
963 self.core.shared_state.keep_pending_movables(slots, waiting);
964 }
965
966 pub(crate) fn forget_movables(&self, ids: &[Key]) -> Result<(), NodeError> {
967 for id in ids {
968 let Some((host, subtree)) = self.core.shared_state.take_retained_movable(*id) else {
969 continue;
970 };
971 self.dispose_detached_subtree_in_host(&host, subtree)?;
972 host.flush_pending_drops();
973 }
974 Ok(())
975 }
976
977 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
978 self.core.parent_stack.borrow_mut()
979 }
980
981 pub(crate) fn current_parent_hint(&self) -> Option<NodeId> {
982 let stack = self.core.parent_stack.borrow();
983 let stack_hint = stack
984 .last()
985 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
986 stack_hint.or_else(|| self.core.recompose_parent_hint.get())
987 }
988
989 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
990 self.core.subcompose_stack.borrow_mut()
991 }
992
993 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
994 self.core.commands.borrow_mut()
995 }
996
997 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
998 self.commands_mut().push(Command::BubbleDirty {
999 node_id: id,
1000 bubble: DirtyBubble::SEMANTICS,
1001 });
1002 }
1003
1004 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
1005 self.core.scope_stack.borrow_mut()
1006 }
1007
1008 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
1009 self.core.local_stack.borrow_mut()
1010 }
1011
1012 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
1013 self.core.local_stack.borrow().clone()
1014 }
1015
1016 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
1017 self.core.side_effects.borrow_mut()
1018 }
1019
1020 pub(crate) fn borrow_applier(&self) -> ApplierGuard<'_, dyn Applier> {
1021 self.core.applier.borrow_dyn()
1022 }
1023
1024 pub fn record_rebound_slot_children(&self, children: &[NodeId]) {
1035 let mut applier = self.borrow_applier();
1036 for &child in children {
1037 applier.record_structural_change(child);
1038 }
1039 }
1040
1041 pub fn create_virtual_node(&self, node: Box<dyn Node>) -> NodeId {
1046 let mut applier = self.borrow_applier();
1047 let node_id = applier.create_virtual(node);
1048 if let Ok(node) = applier.get_mut(node_id) {
1049 node.set_node_id(node_id);
1050 }
1051 node_id
1052 }
1053
1054 pub fn dispose_subcomposed_nodes(
1066 &self,
1067 disposal: crate::subcompose::SubcomposeDisposal,
1068 ) -> Result<(), NodeError> {
1069 if disposal.nodes.is_empty() && disposal.slot_hosts.is_empty() {
1070 return Ok(());
1071 }
1072 self.apply_pending_commands()?;
1073 Self::dispose_measure_subcomposed_nodes(&self.core.applier, disposal)
1074 }
1075
1076 pub fn dispose_measure_subcomposed_nodes(
1080 applier: &Rc<dyn ApplierHost>,
1081 disposal: crate::subcompose::SubcomposeDisposal,
1082 ) -> Result<(), NodeError> {
1083 if disposal.nodes.is_empty() && disposal.slot_hosts.is_empty() {
1084 return Ok(());
1085 }
1086 let disposal = crate::NodeDisposal::from_subcomposed(disposal, &*applier.borrow_dyn());
1087 applier.dispose_nodes(disposal)
1088 }
1089
1090 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
1093 let mut applier = self.borrow_applier();
1094 match applier.get_mut(node_id) {
1095 Ok(node) => node.parent().is_none(),
1096 Err(_) => true,
1097 }
1098 }
1099
1100 pub fn node_parent(&self, node_id: NodeId) -> Result<Option<NodeId>, NodeError> {
1104 self.borrow_applier()
1105 .get_mut(node_id)
1106 .map(|node| node.parent())
1107 }
1108
1109 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
1114 let mut applier = self.borrow_applier();
1115 match applier.get_mut(node_id) {
1116 Ok(node) => {
1117 let mut children = SmallVec::<[NodeId; 8]>::new();
1118 node.collect_children_into(&mut children);
1119 children
1120 }
1121 Err(_) => SmallVec::<[NodeId; 8]>::new(),
1122 }
1123 }
1124
1125 pub fn visit_node_children(&self, node_id: NodeId, mut visit: impl FnMut(NodeId, bool)) {
1131 let applier = self.borrow_applier();
1132 let Ok(node) = applier.get_ref(node_id) else {
1133 return;
1134 };
1135 node.for_each_child(&mut |child| {
1136 let needs_relayout = applier.get_ref(child).is_ok_and(Node::layout_dirty);
1137 visit(child, needs_relayout);
1138 });
1139 }
1140
1141 pub fn nodes_need_relayout(&self, node_ids: impl IntoIterator<Item = NodeId>) -> bool {
1150 let applier = self.borrow_applier();
1151 node_ids
1152 .into_iter()
1153 .any(|node_id| applier.get_ref(node_id).is_ok_and(Node::layout_dirty))
1154 }
1155
1156 pub fn clear_node_children(&self, node_id: NodeId) {
1162 let mut applier = self.borrow_applier();
1163 if let Ok(node) = applier.get_mut(node_id) {
1164 node.update_children(&[]);
1165 }
1166 }
1167
1168 pub fn install<R>(&self, f: impl FnOnce(&Self) -> R) -> R {
1171 let _composer_guard = composer_context::enter(self);
1172 runtime::push_active_runtime(&self.core.runtime);
1173 struct Guard;
1174 impl Drop for Guard {
1175 fn drop(&mut self) {
1176 runtime::pop_active_runtime();
1177 }
1178 }
1179 let guard = Guard;
1180 let result = f(self);
1181 drop(guard);
1182 result
1183 }
1184
1185 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
1186 let queued = self.core.commands.borrow().len();
1187 if queued < COMMAND_FLUSH_THRESHOLD {
1188 return Ok(());
1189 }
1190 self.apply_pending_commands()
1191 }
1192
1193 fn resolve_group_entry(
1194 &self,
1195 host: &Rc<SlotsHost>,
1196 seed: crate::slot::GroupKeySeed,
1197 parent_scope_id: Option<ScopeId>,
1198 ) -> GroupEntry {
1199 let key = host.with_write_session(|slots| slots.reserve_group_key(seed));
1200 let retain_key = RetainKey::for_group(parent_scope_id, key);
1201 let restored = self
1202 .core
1203 .shared_state
1204 .take_retained(host, retain_key, |subtree| {
1205 host.with_write_session(|slots| slots.retained_restore_ready(key, subtree))
1206 })
1207 .or_else(|| self.take_movable_from_another_table(host, retain_key, key))
1208 .map(Box::new);
1209 if restored.is_some() || !key.is_movable() {
1210 return GroupEntry {
1211 key,
1212 restored,
1213 placeholder_for: None,
1214 };
1215 }
1216 let attached_elsewhere = self.movable_attached_elsewhere(host, key);
1217 if !attached_elsewhere {
1218 return GroupEntry {
1219 key,
1220 restored: None,
1221 placeholder_for: None,
1222 };
1223 }
1224 let id = key.explicit_key.unwrap_or_default();
1225 let placeholder = host.with_write_session(|slots| {
1226 slots.reserve_group_key(crate::slot::GroupKeySeed::movable_placeholder(id))
1227 });
1228 GroupEntry {
1229 key: placeholder,
1230 restored: None,
1231 placeholder_for: Some(key),
1232 }
1233 }
1234
1235 fn take_movable_from_another_table(
1240 &self,
1241 host: &Rc<SlotsHost>,
1242 retain_key: RetainKey,
1243 key: crate::slot::GroupKey,
1244 ) -> Option<crate::slot::DetachedSubtree> {
1245 if !key.is_movable() {
1246 return None;
1247 }
1248 let (source, mut subtree) = self
1249 .core
1250 .shared_state
1251 .take_retained_movable_elsewhere(host, retain_key)?;
1252 source
1253 .borrow_mut()
1254 .invalidate_detached_subtree_anchors(&subtree);
1255 if host.with_write_session(|slots| slots.retained_restore_ready(key, &mut subtree)) {
1256 return Some(subtree);
1257 }
1258 log::error!(
1259 "movable content {key:?} could not be taken over by the slot table that asked for it"
1260 );
1261 if let Err(error) = self.dispose_detached_subtree_in_host(host, subtree) {
1262 log::error!("disposing movable content that could not move failed: {error}");
1263 }
1264 None
1265 }
1266
1267 fn movable_attached_elsewhere(&self, host: &Rc<SlotsHost>, key: crate::slot::GroupKey) -> bool {
1270 if host.with_write_session(|slots| slots.movable_attached_elsewhere(key)) {
1271 return true;
1272 }
1273 let Some(id) = key.movable_id() else {
1274 return false;
1275 };
1276 self.core
1277 .shared_state
1278 .host_holding_movable(id)
1279 .is_some_and(|holder| !Rc::ptr_eq(&holder, host))
1280 }
1281
1282 fn enter_group_scope(&self, scope_ref: &RecomposeScope, entry: GroupScopeEntry<'_>) {
1283 let GroupScopeEntry {
1284 options,
1285 start_kind,
1286 host,
1287 group,
1288 parent_hint,
1289 } = entry;
1290 let reused = !scope_ref.is_active() && scope_ref.has_composed_once();
1295 scope_ref.reactivate();
1296 {
1297 let mut stack = self.scope_stack();
1298 match stack.last() {
1299 Some(parent_scope) => scope_ref.set_parent_scope(Some(parent_scope)),
1300 None => scope_ref.set_lifetime_owner_scope(
1301 self.core.subcomposition_owner_scope.borrow().as_ref(),
1302 ),
1303 }
1304 stack.push(scope_ref.clone());
1305 }
1306 scope_ref.set_retention_mode(options.retention);
1307
1308 let restored = matches!(start_kind, GroupStartKind::Restored);
1311 let recompose = options.force_recompose | self.core.locals_changed.get() | restored;
1312 if recompose | (reused & !options.force_reuse) {
1313 scope_ref.force_recompose();
1314 } else if options.force_reuse {
1315 scope_ref.force_reuse();
1316 }
1317
1318 scope_ref.set_slots_host(host);
1319
1320 {
1321 let mut stack = self.subcompose_stack();
1322 if let Some(frame) = stack.last_mut() {
1323 frame.scopes.push(scope_ref.clone());
1324 }
1325 }
1326
1327 scope_ref.snapshot_locals(&self.core.local_stack.borrow());
1328 if restored {
1329 reparent_restored_scopes(host, group, scope_ref.parent_hint(), parent_hint);
1330 }
1331 scope_ref.set_parent_hint(parent_hint);
1332 }
1333
1334 fn start_group(
1335 &self,
1336 host: &Rc<SlotsHost>,
1337 seed: crate::slot::GroupKeySeed,
1338 parent_node: Option<NodeId>,
1339 ) -> (
1340 GroupStart<crate::slot::ActiveGroupId>,
1341 Option<crate::slot::GroupKey>,
1342 ) {
1343 if !self.core.shared_state.retains_any() {
1344 let started = host.with_write_session(|slots| {
1345 let key = slots.reserve_group_key(seed);
1346 (!key.is_movable()).then(|| slots.begin_group(key, None, parent_node))
1347 });
1348 if let Some(started) = started {
1349 return (started, None);
1350 }
1351 }
1352 let parent_scope_id = self
1353 .core
1354 .scope_stack
1355 .borrow()
1356 .last()
1357 .map(RecomposeScope::id);
1358 let GroupEntry {
1359 key,
1360 restored,
1361 placeholder_for,
1362 } = self.resolve_group_entry(host, seed, parent_scope_id);
1363 let started =
1364 host.with_write_session(|slots| slots.begin_group(key, restored, parent_node));
1365 (started, placeholder_for)
1366 }
1367
1368 #[doc(hidden)]
1369 pub fn __branch_group_deferred(&self, key: Key) -> BranchGroupGuard<'_> {
1370 let folds = &*self.core.branch_folds;
1371 BranchGroupGuard {
1372 token: folds.push(key),
1373 folds,
1374 }
1375 }
1376
1377 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1378 for node_id in nodes {
1379 self.commands_mut().push(Command::callback(move |applier| {
1380 crate::slot::dispose_detached_node_now(applier, node_id)
1381 }));
1382 }
1383 }
1384
1385 fn detached_root_parent_commands(
1386 &self,
1387 subtree: &crate::slot::DetachedSubtree,
1388 context: &'static str,
1389 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1390 let mut root_nodes = Vec::new();
1391 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1392 let mut roots = Vec::with_capacity(root_nodes.len());
1393 for root in root_nodes {
1394 let parent_id = {
1395 let mut applier = self.borrow_applier();
1396 applier.get_mut(root)?.parent()
1397 };
1398 roots.push((root, parent_id));
1399 }
1400 Ok(roots)
1401 }
1402
1403 fn retain_detached_subtree_in_host(
1404 &self,
1405 slots_host: &Rc<SlotsHost>,
1406 parent_scope: Option<ScopeId>,
1407 subtree: crate::slot::DetachedSubtree,
1408 ) -> Result<(), NodeError> {
1409 let Some(root_key) = subtree.root_key_checked() else {
1410 log::error!("retention rejected detached subtree without a root group");
1411 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1412 return Ok(());
1413 };
1414 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1415 subtree.scopes().for_each(RecomposeScope::deactivate);
1416 for (root, parent_id) in root_detaches {
1417 if let Some(parent_id) = parent_id {
1418 self.commands_mut().push(Command::DetachChild {
1419 parent_id,
1420 child_id: root,
1421 });
1422 }
1423 }
1424 let evicted = self.core.shared_state.insert_retained(
1425 slots_host,
1426 RetainKey::for_group(parent_scope, root_key),
1427 subtree,
1428 root_key.is_movable(),
1429 );
1430 for subtree in evicted {
1431 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1432 }
1433 Ok(())
1434 }
1435
1436 fn evict_retained_subtrees_for_host(
1437 &self,
1438 slots_host: &Rc<SlotsHost>,
1439 ) -> Result<(), NodeError> {
1440 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1441 for subtree in evicted {
1442 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1443 }
1444 Ok(())
1445 }
1446
1447 fn dispose_detached_subtree_in_host(
1448 &self,
1449 slots_host: &Rc<SlotsHost>,
1450 subtree: crate::slot::DetachedSubtree,
1451 ) -> Result<(), NodeError> {
1452 let root_nodes = self
1453 .detached_root_parent_commands(&subtree, "disposal")?
1454 .into_iter()
1455 .map(|(root, _)| root);
1456 subtree.scopes().for_each(RecomposeScope::deactivate);
1457 self.dispose_detached_nodes(root_nodes);
1458 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1459 table.invalidate_detached_subtree_anchors(&subtree);
1460 lifecycle.queue_subtree_disposal(subtree);
1461 });
1462 Ok(())
1463 }
1464
1465 fn handle_detached_children_in_host(
1466 &self,
1467 slots_host: &Rc<SlotsHost>,
1468 parent_scope: Option<ScopeId>,
1469 detached: Vec<crate::slot::DetachedSubtree>,
1470 ) -> Result<(), NodeError> {
1471 for mut subtree in detached {
1472 for movable in subtree.split_off_nested_movables() {
1473 self.retain_detached_subtree_in_host(slots_host, None, movable)?;
1474 }
1475 if subtree
1476 .root_key_checked()
1477 .is_some_and(crate::slot::GroupKey::is_movable)
1478 {
1479 self.retain_detached_subtree_in_host(slots_host, None, subtree)?;
1480 continue;
1481 }
1482 let retention_mode = subtree
1483 .root_scope()
1484 .map(RecomposeScope::retention_mode)
1485 .unwrap_or_default();
1486 match retention_mode {
1487 RetentionMode::DisposeWhenInactive => {
1488 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1489 }
1490 RetentionMode::RetainWhenInactive => {
1491 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?;
1492 }
1493 }
1494 }
1495 Ok(())
1496 }
1497
1498 fn handle_finished_group_result(
1499 &self,
1500 host: &Rc<SlotsHost>,
1501 parent_scope: Option<ScopeId>,
1502 result: FinishGroupResult,
1503 ) {
1504 let FinishGroupResult {
1505 detached_children,
1506 direct_nodes,
1507 root_nodes,
1508 was_skipped,
1509 } = result;
1510 if was_skipped {
1511 self.attach_root_nodes(root_nodes);
1512 }
1513 self.dispose_detached_nodes(direct_nodes);
1514 if detached_children.is_empty() {
1515 return;
1516 }
1517 if let Err(err) =
1518 self.handle_detached_children_in_host(host, parent_scope, detached_children)
1519 {
1520 log::error!("detached subtree handling failed while closing a group: {err}");
1521 }
1522 }
1523
1524 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1525 self.close_group_body(&self.active_slots_host(), scope);
1526 }
1527
1528 fn close_group_body(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
1529 #[expect(
1530 clippy::redundant_closure_for_method_calls,
1531 reason = "the method path is not general over the session lifetime"
1532 )]
1533 let result = host.with_write_session(|slots| slots.finish_group_body_changes());
1534 self.close_finished_group(host, scope, result);
1535 }
1536
1537 fn close_finished_group(
1540 &self,
1541 host: &Rc<SlotsHost>,
1542 scope: &RecomposeScope,
1543 result: Option<FinishGroupResult>,
1544 ) {
1545 if let Some(result) = result {
1546 self.handle_finished_group_result(host, Some(scope.id()), result);
1547 }
1548 self.pop_closed_scope(scope);
1549 }
1550
1551 fn pop_closed_scope(&self, scope: &RecomposeScope) {
1552 if let Some(popped) = self.scope_stack().pop() {
1553 debug_assert_eq!(
1554 popped.id(),
1555 scope.id(),
1556 "closed scope must match the active scope stack"
1557 );
1558 } else {
1559 log::error!("scope stack underflow while closing scope {}", scope.id());
1560 }
1561 }
1562
1563 #[track_caller]
1566 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1567 self.remember_at(crate::caller_location_key(), init)
1568 }
1569
1570 #[doc(hidden)]
1571 pub fn remember_at<T: 'static>(&self, source: Key, init: impl FnOnce() -> T) -> Owned<T> {
1572 self.with_slot_session_mut(|slots| {
1573 slots.remember_with_kind(PayloadKind::Remember, source, init)
1574 })
1575 }
1576
1577 #[track_caller]
1578 pub(crate) fn remember_internal<T: 'static>(
1579 &self,
1580 source_salt: Key,
1581 init: impl FnOnce() -> T,
1582 ) -> Owned<T> {
1583 let source = crate::caller_location_key().scoped(source_salt);
1584 self.with_slot_session_mut(|slots| {
1585 slots.remember_with_kind(PayloadKind::Internal, source, init)
1586 })
1587 }
1588
1589 #[track_caller]
1590 pub(crate) fn remember_effect<T: Default + 'static>(&self, site: Key) -> Owned<T> {
1593 self.with_slot_session_mut(|slots| slots.remember_effect::<T>(site))
1594 }
1595
1596 #[track_caller]
1599 pub fn use_value_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> ValueSlotHandle<'_, T> {
1600 let source = crate::caller_location_key();
1601 let (slot, record) = self.with_slot_session_mut(|slots| {
1602 slots.located_value_slot(PayloadKind::Internal, source, init)
1603 });
1604 ValueSlotHandle::new(slot, record)
1605 }
1606
1607 #[doc(hidden)]
1608 pub fn __use_param_slot<T: 'static>(
1609 &self,
1610 source: Key,
1611 init: impl FnOnce() -> T,
1612 ) -> ValueSlotHandle<'_, T> {
1613 let (slot, record) = self.with_slot_session_mut(|slots| {
1614 slots.located_value_slot(PayloadKind::Param, source, init)
1615 });
1616 ValueSlotHandle::new(slot, record)
1617 }
1618
1619 #[doc(hidden)]
1622 pub fn __update_param_slot<T: 'static, R>(
1623 &self,
1624 source: Key,
1625 init: impl FnOnce() -> T,
1626 update: impl FnOnce(&mut T) -> R,
1627 ) -> (ValueSlotHandle<'_, T>, R) {
1628 self.update_value_slot(PayloadKind::Param, source, init, update)
1629 }
1630
1631 #[doc(hidden)]
1634 pub fn __update_return_slot<T: 'static, R>(
1635 &self,
1636 source: Key,
1637 init: impl FnOnce() -> T,
1638 read: impl FnOnce(&mut T) -> R,
1639 ) -> (ValueSlotHandle<'_, T>, R) {
1640 self.update_value_slot(PayloadKind::Return, source, init, read)
1641 }
1642
1643 fn update_value_slot<T: 'static, R>(
1644 &self,
1645 kind: PayloadKind,
1646 source: Key,
1647 init: impl FnOnce() -> T,
1648 update: impl FnOnce(&mut T) -> R,
1649 ) -> (ValueSlotHandle<'_, T>, R) {
1650 self.with_slot_session_mut(|slots| {
1651 let (slot, record, value) = slots.value_slot_and_value(kind, source, init);
1652 (ValueSlotHandle::new(slot, record), update(value))
1653 })
1654 }
1655
1656 #[doc(hidden)]
1657 #[track_caller]
1658 pub fn __use_return_slot<T: 'static>(
1659 &self,
1660 init: impl FnOnce() -> T,
1661 ) -> ValueSlotHandle<'_, T> {
1662 let source = crate::caller_location_key();
1663 let (slot, record) = self.with_slot_session_mut(|slots| {
1664 slots.located_value_slot(PayloadKind::Return, source, init)
1665 });
1666 ValueSlotHandle::new(slot, record)
1667 }
1668
1669 #[doc(hidden)]
1670 pub fn __invalidate_return_consumer_scope(&self) {
1671 let Some(scope) = self.current_recompose_scope() else {
1672 self.request_root_render();
1673 return;
1674 };
1675
1676 if let Some(target) = scope.callback_promotion_target() {
1677 target.invalidate();
1678 } else {
1679 self.request_root_render();
1680 }
1681 }
1682
1683 pub fn with_slot_value<'pass, T: 'static, R>(
1685 &'pass self,
1686 handle: ValueSlotHandle<'pass, T>,
1687 f: impl FnOnce(&T) -> R,
1688 ) -> R {
1689 self.with_slots(|slots| f(slots.value_at(handle.slot, handle.record)))
1690 }
1691
1692 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1694 &'pass self,
1695 handle: ValueSlotHandle<'pass, T>,
1696 f: impl FnOnce(&mut T) -> R,
1697 ) -> R {
1698 self.with_slots_mut(|slots| f(slots.value_at_mut(handle.slot, handle.record)))
1699 }
1700
1701 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1704 MutableState::with_runtime(initial, self.runtime_handle())
1705 }
1706
1707 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1710 where
1711 T: Clone + 'static,
1712 I: IntoIterator<Item = T>,
1713 {
1714 SnapshotStateList::with_runtime(values, self.runtime_handle())
1715 }
1716
1717 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1720 where
1721 K: Clone + Eq + Hash + 'static,
1722 V: Clone + 'static,
1723 I: IntoIterator<Item = (K, V)>,
1724 {
1725 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1726 }
1727
1728 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1731 self.read_provided(
1732 &local.key,
1733 |entry: &LocalStateEntry<T>| entry.value(),
1734 |entry: &ComputedLocalEntry<T>| entry.value(),
1735 )
1736 .unwrap_or_else(|| local.default_value())
1737 }
1738
1739 pub fn read_static_composition_local<T: Clone + 'static>(
1742 &self,
1743 local: &StaticCompositionLocal<T>,
1744 ) -> T {
1745 let provided = local.provided_under(&self.core.local_stack.borrow());
1746 provided.unwrap_or_else(|deferred| deferred.value(local))
1747 }
1748
1749 fn read_provided<V: 'static, C: 'static, T>(
1753 &self,
1754 key: &LocalKey,
1755 value: impl FnOnce(&V) -> T,
1756 computed: impl FnOnce(&C) -> T,
1757 ) -> Option<T> {
1758 let entry = provided_entry(&self.core.local_stack.borrow(), key)?;
1759 match entry.downcast::<V>() {
1760 Ok(entry) => Some(value(&entry)),
1761 Err(entry) => entry
1762 .downcast::<C>()
1763 .inspect_err(|_| {
1764 log::error!("composition local entry type mismatch for key {key:?}");
1765 })
1766 .ok()
1767 .map(|entry| computed(&entry)),
1768 }
1769 }
1770
1771 pub fn current_recompose_scope(&self) -> Option<RecomposeScope> {
1773 self.core.scope_stack.borrow().last().cloned()
1774 }
1775
1776 pub(crate) fn restarted_scope(&self) -> Option<RecomposeScope> {
1779 self.core.restarted_scope.borrow().clone()
1780 }
1781
1782 pub(crate) fn note_state_read(
1786 &self,
1787 state_id: Option<crate::StateId>,
1788 ) -> Option<(RecomposeScope, crate::ScopeRead)> {
1789 let stack = self.core.scope_stack.borrow();
1790 let scope = stack
1791 .iter()
1792 .rev()
1793 .find(|scope| scope.has_recompose_callback())
1794 .or_else(|| stack.last())?;
1795 let read = state_id.map_or(crate::ScopeRead::NotReading, |id| scope.note_read(id));
1796 (!matches!(read, crate::ScopeRead::Watched)).then(|| (scope.clone(), read))
1797 }
1798
1799 pub fn phase(&self) -> crate::Phase {
1801 self.core.phase.get()
1802 }
1803
1804 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1805 self.core.phase.set(phase);
1806 }
1807
1808 pub fn enter_phase(&self, phase: crate::Phase) {
1810 self.set_phase(phase);
1811 }
1812
1813 pub(crate) fn subcompose<R>(
1814 &self,
1815 state: &mut SubcomposeState,
1816 slot_id: SlotId,
1817 content: impl FnOnce(&Self) -> R,
1818 ) -> (R, Vec<NodeId>) {
1819 match self.phase() {
1820 crate::Phase::Measure | crate::Phase::Layout => {}
1821 current => panic!(
1822 "subcompose() may only be called during measure or layout; current phase: {current:?}"
1823 ),
1824 }
1825
1826 self.subcompose_stack().push(SubcomposeFrame::default());
1827 let mut guard = SubcomposeStackGuard {
1828 core: self.clone_core(),
1829 leaked: false,
1830 };
1831
1832 let slot_host = state.get_or_create_slots(slot_id);
1833 let (result, _) = self.with_slot_override(slot_host, |composer| {
1834 composer.with_group(Key(slot_id.raw()), |composer| content(composer))
1835 });
1836
1837 let frame = {
1838 let frame = take_subcompose_frame(&guard.core, "subcompose");
1839 guard.leaked = true;
1840 frame
1841 };
1842 let nodes = frame.nodes;
1843 let scopes = frame.scopes;
1844 state.register_active(slot_id, &nodes, &scopes);
1845 (result, nodes)
1846 }
1847
1848 pub fn subcompose_measurement<R>(
1851 &self,
1852 state: &mut SubcomposeState,
1853 slot_id: SlotId,
1854 content: impl FnOnce(&Self) -> R,
1855 ) -> (R, Vec<NodeId>) {
1856 let (result, nodes) = self.subcompose(state, slot_id, content);
1857 let roots = nodes
1858 .into_iter()
1859 .filter(|&id| self.node_has_no_parent(id))
1860 .collect();
1861
1862 (result, roots)
1863 }
1864
1865 fn subcompose_site(&self) -> SubcomposeSite<'_> {
1867 SubcomposeSite {
1868 applier: &self.core.applier,
1869 runtime: &self.core.runtime,
1870 phase: self.phase(),
1871 locals_changed: self.core.locals_changed.get(),
1872 }
1873 }
1874
1875 pub fn subcompose_in<R>(
1879 &self,
1880 slots: &Rc<SlotsHost>,
1881 root: Option<NodeId>,
1882 f: impl FnOnce(&Self) -> R,
1883 ) -> Result<R, NodeError> {
1884 let _teardown = runtime::enter_state_teardown_scope();
1885 let site = self.subcompose_site();
1886 let shared_state = slots
1887 .runtime_state()
1888 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1889 let core = site.spin_up_core(shared_state, slots, root, self.current_local_stack());
1890 let composer = Self::from_core(core);
1891 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1892 let (output, outcome) = composer.try_with_slot_host_pass(
1893 Rc::clone(slots),
1894 crate::slot::SlotPassMode::Compose,
1895 |composer| f(composer),
1896 )?;
1897 let commands = composer.take_commands();
1898 let side_effects = composer.take_side_effects();
1899 Ok((output, commands, side_effects, outcome.compacted))
1900 })?;
1901 site.flush(commands, compact_applier, side_effects)?;
1902 Ok(result)
1903 }
1904
1905 pub fn capture_composition_context(&self) -> CapturedCompositionContext {
1916 CapturedCompositionContext {
1917 locals: self.current_local_stack(),
1918 owner_scope: self
1919 .current_recompose_scope()
1920 .map(|scope| scope.downgrade()),
1921 locals_changed: self.core.locals_changed.get(),
1922 }
1923 }
1924
1925 pub fn with_captured_locals<R>(
1930 &self,
1931 context: &CapturedCompositionContext,
1932 f: impl FnOnce(&Self) -> R,
1933 ) -> R {
1934 let parent = self.current_local_stack();
1935 self.local_stack().clone_from(&context.locals);
1936 let result = self.with_locals_changed(context.locals_changed, f);
1937 *self.local_stack() = parent;
1938 result
1939 }
1940
1941 pub fn with_locals_changed<R>(&self, changed: bool, f: impl FnOnce(&Self) -> R) -> R {
1945 let outer = self.core.locals_changed.get();
1946 self.core.locals_changed.set(outer || changed);
1947 let result = f(self);
1948 self.core.locals_changed.set(outer);
1949 result
1950 }
1951
1952 pub fn subcompose_slot<R>(
1957 &self,
1958 slots: &Rc<SlotsHost>,
1959 root: Option<NodeId>,
1960 f: impl FnOnce(&Self) -> R,
1961 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1962 let context = self.capture_composition_context();
1963 self.subcompose_slot_with_context(slots, root, &context, f)
1964 }
1965
1966 pub fn subcompose_slot_with_context<R>(
1970 &self,
1971 slots: &Rc<SlotsHost>,
1972 root: Option<NodeId>,
1973 context: &CapturedCompositionContext,
1974 f: impl FnOnce(&Self) -> R,
1975 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1976 subcompose_slot_at(
1977 self.subcompose_site(),
1978 || Rc::clone(&self.core.shared_state),
1979 slots,
1980 root,
1981 context,
1982 f,
1983 )
1984 }
1985
1986 pub fn subcompose_measure_slot<R>(
1991 applier: &Rc<dyn ApplierHost>,
1992 runtime: &RuntimeHandle,
1993 slots: &Rc<SlotsHost>,
1994 root: NodeId,
1995 context: &CapturedCompositionContext,
1996 f: impl FnOnce(&Self) -> R,
1997 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1998 let site = SubcomposeSite {
1999 applier,
2000 runtime,
2001 phase: crate::Phase::Measure,
2002 locals_changed: false,
2003 };
2004 let new_state = || measure_slot_state(applier, context);
2005 subcompose_slot_at(site, new_state, slots, Some(root), context, f)
2006 }
2007
2008 fn attach_root_nodes(&self, root_nodes: RootNodeIds) {
2009 for id in root_nodes {
2010 self.attach_to_parent(id);
2011 }
2012 }
2013
2014 pub fn skip_current_group(&self) {
2017 #[expect(
2018 clippy::redundant_closure_for_method_calls,
2019 reason = "the method path is not general over the session lifetime"
2020 )]
2021 self.with_slot_session_mut(|slots| slots.skip_group());
2022 }
2023
2024 pub fn runtime_handle(&self) -> RuntimeHandle {
2026 self.core.runtime.clone()
2027 }
2028
2029 pub fn set_recompose_callback<F>(&self, callback: F)
2032 where
2033 F: FnMut(&Self) + 'static,
2034 {
2035 if let Some(scope) = self.current_recompose_scope() {
2036 scope.set_recompose_callback(callback);
2037 }
2038 }
2039
2040 pub fn set_recompose_fn(&self, callback: fn(&Self)) {
2042 if let Some(scope) = self.current_recompose_scope() {
2043 scope.set_recompose_fn(callback);
2044 }
2045 }
2046
2047 pub fn with_composition_locals<R>(
2050 &self,
2051 provided: impl IntoIterator<Item = ProvidedValue>,
2052 site: Key,
2053 f: impl FnOnce(&Self) -> R,
2054 ) -> R {
2055 let provided: SmallVec<[ProvidedValue; 2]> = provided.into_iter().collect();
2056 if provided.is_empty() {
2057 return f(self);
2058 }
2059 let mut values = SmallVec::<[(LocalKey, Rc<dyn Any>); 2]>::new();
2060 let mut static_changed = false;
2061 for value in provided.into_iter().rev() {
2062 if values.iter().any(|(key, _)| key == value.key()) {
2063 continue;
2064 }
2065 let (key, entry, changed) = value.into_entry(self, site);
2066 static_changed |= changed;
2067 values.push((key, entry));
2068 }
2069 let parent = self.current_local_stack();
2070 *self.local_stack() = Some(Rc::new(LocalFrame {
2071 values,
2072 parent: parent.clone(),
2073 }));
2074 let result = self.with_locals_changed(static_changed, f);
2075 *self.local_stack() = parent;
2076 result
2077 }
2078}