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