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
494impl CapturedCompositionContext {
495 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
499 self.owner_scope
500 .as_ref()
501 .and_then(Weak::upgrade)
502 .map(|inner| crate::RecomposeScope { inner }.owner_chain_deactivation_epoch())
503 .unwrap_or(0)
504 }
505}
506
507fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
508 match core.subcompose_stack.borrow_mut().pop() {
509 Some(frame) => frame,
510 None => {
511 log::error!("subcompose stack underflow while finishing {operation}");
512 SubcomposeFrame::default()
513 }
514 }
515}
516
517impl ComposerCore {
518 pub(crate) fn new(
519 shared_state: Rc<ComposerRuntimeState>,
520 slots: Rc<SlotsHost>,
521 applier: Rc<dyn ApplierHost>,
522 runtime: RuntimeHandle,
523 observer: SnapshotStateObserver,
524 root: Option<NodeId>,
525 initial_parent_frame: InitialParentFrame,
526 ) -> Self {
527 let parent_stack = if let Some(root_id) = root {
528 vec![ParentFrame {
529 id: root_id,
530 previous: ChildList::new(),
531 new_children: ChildList::new(),
532 new_children_membership: None,
533 attach_mode: ParentAttachMode::DeferredSync,
534 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
535 }]
536 } else {
537 Vec::new()
538 };
539
540 Self {
541 shared_state,
542 slots,
543 slot_hosts: RefCell::new(Vec::new()),
544 applier,
545 runtime,
546 observer,
547 parent_stack: RefCell::new(parent_stack),
548 subcompose_stack: RefCell::new(Vec::new()),
549 root: Cell::new(root),
550 commands: RefCell::new(CommandQueue::default()),
551 scope_stack: RefCell::new(Vec::new()),
552 subcomposition_owner_scope: RefCell::new(None),
553 local_stack: RefCell::new(empty_local_stack()),
554 side_effects: RefCell::new(Vec::new()),
555 pending_scope_options: RefCell::new(None),
556 phase: Cell::new(crate::Phase::Compose),
557 last_node_reused: Cell::new(None),
558 recompose_parent_hint: Cell::new(None),
559 root_render_requested: Cell::new(false),
560 _not_send: PhantomData,
561 }
562 }
563}
564
565#[derive(Clone)]
566pub struct Composer {
567 pub(crate) core: Rc<ComposerCore>,
568}
569
570pub struct BranchGroupGuard {
571 composer: Composer,
572 fold_token: Option<usize>,
573}
574
575impl Drop for BranchGroupGuard {
576 fn drop(&mut self) {
577 let Some(token) = self.fold_token else {
578 return;
579 };
580 if !self
581 .composer
582 .active_slots_host()
583 .try_close_branch_fold(token)
584 {
585 log::error!("a branch fold guard closed while its slot host was busy");
586 }
587 }
588}
589
590pub(crate) enum EmittedNode {
591 Fresh(Box<dyn Node>),
592 Recycled(RecycledNode),
593}
594
595impl Composer {
596 pub(crate) fn new_with_shared_state(
597 shared_state: Rc<ComposerRuntimeState>,
598 slots: Rc<SlotsHost>,
599 applier: Rc<dyn ApplierHost>,
600 runtime: RuntimeHandle,
601 observer: SnapshotStateObserver,
602 root: Option<NodeId>,
603 ) -> Self {
604 Self::new_with_shared_state_with_parent_frame(
605 shared_state,
606 slots,
607 applier,
608 runtime,
609 observer,
610 root,
611 InitialParentFrame::SyntheticRoot,
612 )
613 }
614
615 fn new_with_shared_state_with_parent_frame(
616 shared_state: Rc<ComposerRuntimeState>,
617 slots: Rc<SlotsHost>,
618 applier: Rc<dyn ApplierHost>,
619 runtime: RuntimeHandle,
620 observer: SnapshotStateObserver,
621 root: Option<NodeId>,
622 initial_parent_frame: InitialParentFrame,
623 ) -> Self {
624 shared_state.bind_applier_host(&applier);
625 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
626 let core = Rc::new(ComposerCore::new(
627 shared_state,
628 slots,
629 applier,
630 runtime,
631 observer,
632 root,
633 initial_parent_frame,
634 ));
635 Self { core }
636 }
637
638 pub fn new(
639 slots: Rc<SlotsHost>,
640 applier: Rc<dyn ApplierHost>,
641 runtime: RuntimeHandle,
642 observer: SnapshotStateObserver,
643 root: Option<NodeId>,
644 ) -> Self {
645 Self::new_with_shared_state_with_parent_frame(
646 slots
647 .runtime_state()
648 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
649 slots,
650 applier,
651 runtime,
652 observer,
653 root,
654 InitialParentFrame::RealParent,
655 )
656 }
657
658 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
659 Self { core }
660 }
661
662 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
663 Rc::clone(&self.core)
664 }
665
666 fn observer(&self) -> SnapshotStateObserver {
667 self.core.observer.clone()
668 }
669
670 pub(crate) fn request_root_render(&self) {
671 self.core.root_render_requested.set(true);
672 }
673
674 pub(crate) fn take_root_render_request(&self) -> bool {
675 self.core.root_render_requested.replace(false)
676 }
677
678 pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
679 let observer = self.observer();
680 let scope_clone = scope.clone();
681 observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
682 }
683
684 pub fn active_slots_host(&self) -> Rc<SlotsHost> {
685 self.core
686 .slot_hosts
687 .borrow()
688 .last()
689 .cloned()
690 .unwrap_or_else(|| Rc::clone(&self.core.slots))
691 }
692
693 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
694 let host = self.active_slots_host();
695 let slots = host.borrow();
696 f(&slots)
697 }
698
699 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
700 let host = self.active_slots_host();
701 let mut slots = host.borrow_mut();
702 f(&mut slots)
703 }
704
705 pub(crate) fn with_slot_session_mut<R>(
706 &self,
707 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
708 ) -> R {
709 self.active_slots_host().with_write_session(f)
710 }
711
712 pub(crate) fn try_with_slot_host_pass<R>(
713 &self,
714 slots: Rc<SlotsHost>,
715 mode: crate::slot::SlotPassMode,
716 f: impl FnOnce(&Composer) -> R,
717 ) -> Result<(R, SlotPassOutcome), NodeError> {
718 let mut guard = self.begin_slot_host_pass(&slots, mode);
719 let result = f(self);
720 let outcome = self.finish_slot_host_pass(&guard.host)?;
721 guard.close();
722 Ok((result, outcome))
723 }
724
725 pub(crate) fn with_slot_host_pass<R>(
726 &self,
727 slots: Rc<SlotsHost>,
728 mode: crate::slot::SlotPassMode,
729 f: impl FnOnce(&Composer) -> R,
730 ) -> (R, SlotPassOutcome) {
731 let mut guard = self.begin_slot_host_pass(&slots, mode);
732 let result = f(self);
733 let outcome = match self.finish_slot_host_pass(&guard.host) {
734 Ok(outcome) => outcome,
735 Err(err) => {
736 log::error!("slot host pass finalization failed: {err}");
737 SlotPassOutcome::default()
738 }
739 };
740 guard.close();
741 (result, outcome)
742 }
743
744 pub(crate) fn with_slot_override<R>(
745 &self,
746 slots: Rc<SlotsHost>,
747 f: impl FnOnce(&Composer) -> R,
748 ) -> (R, SlotPassOutcome) {
749 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
750 }
751
752 fn begin_slot_host_pass(
753 &self,
754 slots: &Rc<SlotsHost>,
755 mode: crate::slot::SlotPassMode,
756 ) -> SlotHostPassGuard {
757 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
758 slots.begin_pass(mode);
759 {
760 let mut stack = self.core.slot_hosts.borrow_mut();
761 if let Some(parent) = stack.last()
762 && !Rc::ptr_eq(parent, &slots)
763 {
764 parent.note_nested_host(&slots);
765 }
766 stack.push(Rc::clone(&slots));
767 }
768 SlotHostPassGuard {
769 core: self.clone_core(),
770 host: slots,
771 active: true,
772 }
773 }
774
775 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
776 let finished = {
777 let mut applier = self.core.applier.borrow_dyn();
778 slots.finish_pass(&mut *applier)
779 }?;
780 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
781 self.evict_retained_subtrees_for_host(slots)?;
782 slots.complete_pass_cleanup(&finished.outcome);
783 Ok(finished.outcome)
784 }
785
786 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
787 self.core.parent_stack.borrow_mut()
788 }
789
790 fn current_parent_hint(&self) -> Option<NodeId> {
791 let stack = self.core.parent_stack.borrow();
792 let stack_hint = stack
793 .last()
794 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
795 stack_hint.or_else(|| self.core.recompose_parent_hint.get())
796 }
797
798 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
799 self.core.subcompose_stack.borrow_mut()
800 }
801
802 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
803 self.core.commands.borrow_mut()
804 }
805
806 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
807 self.commands_mut().push(Command::BubbleDirty {
808 node_id: id,
809 bubble: DirtyBubble::SEMANTICS,
810 });
811 }
812
813 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
814 self.core.scope_stack.borrow_mut()
815 }
816
817 fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
818 self.core.shared_state.scope_for_id(scope_id)
819 }
820
821 fn register_scope(&self, scope: &RecomposeScope) {
822 self.core.shared_state.register_scope(scope);
823 }
824
825 fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
826 self.core.shared_state.remove_scope(scope_id)
827 }
828
829 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
830 self.core.local_stack.borrow_mut()
831 }
832
833 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
834 self.core.local_stack.borrow().clone()
835 }
836
837 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
838 self.core.side_effects.borrow_mut()
839 }
840
841 fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
842 self.core.pending_scope_options.borrow_mut()
843 }
844
845 pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
846 self.core.applier.borrow_dyn()
847 }
848
849 pub fn record_rebound_slot_children(&self, children: &[NodeId]) {
860 let mut applier = self.borrow_applier();
861 for &child in children {
862 applier.record_structural_change(child);
863 }
864 }
865
866 pub fn register_virtual_node(
873 &self,
874 node_id: NodeId,
875 node: Box<dyn Node>,
876 ) -> Result<(), NodeError> {
877 let mut applier = self.borrow_applier();
878 applier.insert_with_id(node_id, node)
879 }
880
881 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
884 let mut applier = self.borrow_applier();
885 match applier.get_mut(node_id) {
886 Ok(node) => node.parent().is_none(),
887 Err(_) => true,
888 }
889 }
890
891 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
896 let mut applier = self.borrow_applier();
897 match applier.get_mut(node_id) {
898 Ok(node) => {
899 let mut children = SmallVec::<[NodeId; 8]>::new();
900 node.collect_children_into(&mut children);
901 children
902 }
903 Err(_) => SmallVec::<[NodeId; 8]>::new(),
904 }
905 }
906
907 pub fn nodes_need_measure(&self, node_ids: &[NodeId]) -> bool {
908 let mut applier = self.borrow_applier();
909 node_ids.iter().any(|node_id| {
910 applier
911 .get_mut(*node_id)
912 .is_ok_and(|node| node.needs_measure())
913 })
914 }
915
916 pub fn nodes_need_layout(&self, node_ids: &[NodeId]) -> bool {
924 let mut applier = self.borrow_applier();
925 node_ids.iter().any(|node_id| {
926 applier
927 .get_mut(*node_id)
928 .is_ok_and(|node| node.needs_layout())
929 })
930 }
931
932 pub fn record_subcompose_child(&self, child_id: NodeId) {
942 let mut parent_stack = self.parent_stack();
943 if let Some(frame) = parent_stack.last_mut()
944 && matches!(frame.attach_mode, ParentAttachMode::DeferredSync)
945 {
946 if let Some(membership) = frame.new_children_membership.as_mut() {
947 if membership.insert(child_id) {
948 frame.new_children.push(child_id);
949 }
950 } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
951 let mut membership = HashSet::default();
952 membership.reserve(frame.new_children.len() + 1);
953 membership.extend(frame.new_children.iter().copied());
954 if membership.insert(child_id) {
955 frame.new_children.push(child_id);
956 }
957 frame.new_children_membership = Some(membership);
958 } else if !frame.new_children.contains(&child_id) {
959 frame.new_children.push(child_id);
960 }
961 }
962 }
963
964 pub fn clear_node_children(&self, node_id: NodeId) {
970 let mut applier = self.borrow_applier();
971 if let Ok(node) = applier.get_mut(node_id) {
972 node.update_children(&[]);
973 }
974 }
975
976 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
977 let _composer_guard = composer_context::enter(self);
978 runtime::push_active_runtime(&self.core.runtime);
979 struct Guard;
980 impl Drop for Guard {
981 fn drop(&mut self) {
982 runtime::pop_active_runtime();
983 }
984 }
985 let guard = Guard;
986 let result = f(self);
987 drop(guard);
988 result
989 }
990
991 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
992 let queued = self.core.commands.borrow().len();
993 if queued < COMMAND_FLUSH_THRESHOLD {
994 return Ok(());
995 }
996 self.apply_pending_commands()
997 }
998
999 fn with_group_in_active_pass<R>(
1000 &self,
1001 key: crate::slot::GroupKeySeed,
1002 f: impl FnOnce(&Composer) -> R,
1003 ) -> R {
1004 let mut f = Some(f);
1005 let mut result = None;
1006 self.with_group_in_active_pass_dyn(key, &mut |composer| {
1007 let f = f.take().expect("group body must run at most once");
1008 result = Some(f(composer));
1009 });
1010 result.expect("group body must run exactly once")
1011 }
1012
1013 #[inline(never)]
1014 fn with_group_in_active_pass_dyn(
1015 &self,
1016 key: crate::slot::GroupKeySeed,
1017 f: &mut dyn FnMut(&Composer),
1018 ) {
1019 struct GroupGuard {
1020 composer: Composer,
1021 scope: RecomposeScope,
1022 }
1023
1024 impl Drop for GroupGuard {
1025 fn drop(&mut self) {
1026 self.composer
1027 .close_current_group_body_for_scope(&self.scope);
1028 self.scope.mark_recomposed();
1029 self.composer
1030 .with_slot_session_mut(|slots| slots.end_group());
1031 if let Err(err) = self.composer.flush_pending_commands_if_large() {
1032 log::error!("mid-composition command flush failed: {err}");
1033 }
1034 }
1035 }
1036
1037 let parent_scope = self.current_recompose_scope();
1038 let options = self.pending_scope_options().take().unwrap_or_default();
1039 let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
1040 let reserved_key = self.with_slot_session_mut(|slots| slots.reserve_group_key(key));
1041 let host = self.active_slots_host();
1042 let restored = self.core.shared_state.take_retained(
1043 &host,
1044 RetainKey {
1045 parent_scope: parent_scope_id,
1046 key: reserved_key,
1047 },
1048 |subtree| {
1049 self.with_slot_session_mut(|slots| {
1050 slots.retained_restore_ready(reserved_key, subtree)
1051 })
1052 },
1053 );
1054 let (group, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
1055 let GroupStart {
1056 group,
1057 scope_id,
1058 kind,
1059 ..
1060 } = slots.begin_group(reserved_key, restored);
1061 (group, scope_id, kind)
1062 });
1063 let scope_ref =
1064 if let Some(scope) = start_scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
1065 scope
1066 } else {
1067 let scope = RecomposeScope::new(self.runtime_handle());
1068 self.register_scope(&scope);
1069 self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
1070 scope
1071 };
1072
1073 let lifetime_owner_scope = if parent_scope.is_none() {
1074 self.core.subcomposition_owner_scope.borrow().clone()
1075 } else {
1076 None
1077 };
1078 scope_ref.reactivate();
1079 scope_ref.set_parent_scope(parent_scope);
1080 scope_ref.set_lifetime_owner_scope(lifetime_owner_scope);
1081 scope_ref.set_retention_mode(options.retention);
1082
1083 if options.force_recompose {
1084 scope_ref.force_recompose();
1085 } else if options.force_reuse {
1086 scope_ref.force_reuse();
1087 }
1088 if matches!(start_kind, GroupStartKind::Restored) {
1089 scope_ref.force_recompose();
1090 }
1091
1092 scope_ref.set_slots_host(&host);
1093
1094 {
1095 let mut stack = self.scope_stack();
1096 stack.push(scope_ref.clone());
1097 }
1098
1099 {
1100 let mut stack = self.subcompose_stack();
1101 if let Some(frame) = stack.last_mut() {
1102 frame.scopes.push(scope_ref.clone());
1103 }
1104 }
1105
1106 scope_ref.snapshot_locals(self.current_local_stack());
1107 {
1108 let parent_hint = self.current_parent_hint();
1109 scope_ref.set_parent_hint(parent_hint);
1110 }
1111
1112 let guard = GroupGuard {
1113 composer: self.clone(),
1114 scope: scope_ref.clone(),
1115 };
1116 self.observe_scope(&scope_ref, || f(self));
1117 scope_ref.mark_composed_once();
1118 drop(guard);
1119 }
1120
1121 pub(crate) fn with_group_seed<R>(
1122 &self,
1123 key: crate::slot::GroupKeySeed,
1124 f: impl FnOnce(&Composer) -> R,
1125 ) -> R {
1126 let host = self.active_slots_host();
1127 if host.has_active_pass() {
1128 return self.with_group_in_active_pass(key, f);
1129 }
1130 let (result, _) =
1131 self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1132 composer.with_group_in_active_pass(key, f)
1133 });
1134 result
1135 }
1136
1137 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1138 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1139 }
1140
1141 pub fn cranpose_with_reuse<R>(
1142 &self,
1143 key: Key,
1144 mut options: RecomposeOptions,
1145 f: impl FnOnce(&Composer) -> R,
1146 ) -> R {
1147 options.retention = RetentionMode::RetainWhenInactive;
1148 self.pending_scope_options().replace(options);
1149 self.with_group(key, f)
1150 }
1151
1152 #[track_caller]
1153 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1154 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1155 self.with_group_seed(seed, f)
1156 }
1157
1158 #[doc(hidden)]
1159 pub fn __branch_group_deferred(&self, key: Key) -> BranchGroupGuard {
1160 BranchGroupGuard {
1161 composer: self.clone(),
1162 fold_token: self.active_slots_host().try_push_branch_fold(key),
1163 }
1164 }
1165
1166 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1167 for node_id in nodes {
1168 self.commands_mut().push(Command::callback(move |applier| {
1169 crate::slot::dispose_detached_node_now(applier, node_id)
1170 }));
1171 }
1172 }
1173
1174 fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1175 for scope_id in scope_ids {
1176 if let Some(scope) = self.scope_for_id(scope_id) {
1177 scope.deactivate();
1178 }
1179 }
1180 }
1181
1182 fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1183 for scope_id in scope_ids {
1184 if let Some(scope) = self.remove_scope(scope_id) {
1185 scope.deactivate();
1186 }
1187 }
1188 }
1189
1190 fn detached_root_parent_commands(
1191 &self,
1192 subtree: &crate::slot::DetachedSubtree,
1193 context: &'static str,
1194 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1195 let mut root_nodes = Vec::new();
1196 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1197 let mut roots = Vec::with_capacity(root_nodes.len());
1198 for root in root_nodes {
1199 let parent_id = {
1200 let mut applier = self.borrow_applier();
1201 applier.get_mut(root)?.parent()
1202 };
1203 roots.push((root, parent_id));
1204 }
1205 Ok(roots)
1206 }
1207
1208 fn retain_detached_subtree_in_host(
1209 &self,
1210 slots_host: &Rc<SlotsHost>,
1211 parent_scope: Option<ScopeId>,
1212 subtree: crate::slot::DetachedSubtree,
1213 ) -> Result<(), NodeError> {
1214 let Some(root_key) = subtree.root_key_checked() else {
1215 log::error!("retention rejected detached subtree without a root group");
1216 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1217 return Ok(());
1218 };
1219 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1220 self.deactivate_scope_ids(subtree.scope_ids_iter());
1221 for (root, parent_id) in root_detaches {
1222 if let Some(parent_id) = parent_id {
1223 self.commands_mut().push(Command::DetachChild {
1224 parent_id,
1225 child_id: root,
1226 });
1227 }
1228 }
1229 let evicted = self.core.shared_state.insert_retained(
1230 slots_host,
1231 RetainKey {
1232 parent_scope,
1233 key: root_key,
1234 },
1235 subtree,
1236 );
1237 for subtree in evicted {
1238 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1239 }
1240 Ok(())
1241 }
1242
1243 fn evict_retained_subtrees_for_host(
1244 &self,
1245 slots_host: &Rc<SlotsHost>,
1246 ) -> Result<(), NodeError> {
1247 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1248 for subtree in evicted {
1249 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1250 }
1251 Ok(())
1252 }
1253
1254 fn dispose_detached_subtree_in_host(
1255 &self,
1256 slots_host: &Rc<SlotsHost>,
1257 subtree: crate::slot::DetachedSubtree,
1258 ) -> Result<(), NodeError> {
1259 let root_nodes = self
1260 .detached_root_parent_commands(&subtree, "disposal")?
1261 .into_iter()
1262 .map(|(root, _)| root);
1263 self.dispose_scope_ids(subtree.scope_ids_iter());
1264 self.dispose_detached_nodes(root_nodes);
1265 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1266 table.invalidate_detached_subtree_anchors(&subtree);
1267 lifecycle.queue_subtree_disposal(subtree);
1268 });
1269 Ok(())
1270 }
1271
1272 fn handle_detached_children_in_host(
1273 &self,
1274 slots_host: &Rc<SlotsHost>,
1275 parent_scope: Option<ScopeId>,
1276 detached: Vec<crate::slot::DetachedSubtree>,
1277 ) -> Result<(), NodeError> {
1278 for subtree in detached {
1279 let retention_mode = subtree
1280 .root_scope_id()
1281 .and_then(|scope_id| self.scope_for_id(scope_id))
1282 .map(|scope| scope.retention_mode())
1283 .unwrap_or_default();
1284 match retention_mode {
1285 RetentionMode::DisposeWhenInactive => {
1286 self.dispose_detached_subtree_in_host(slots_host, subtree)?
1287 }
1288 RetentionMode::RetainWhenInactive => {
1289 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1290 }
1291 }
1292 }
1293 Ok(())
1294 }
1295
1296 fn handle_detached_children(
1297 &self,
1298 parent_scope: Option<ScopeId>,
1299 detached: Vec<crate::slot::DetachedSubtree>,
1300 ) {
1301 let host = self.active_slots_host();
1302 if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1303 log::error!("detached subtree handling failed while closing a group: {err}");
1304 }
1305 }
1306
1307 fn handle_finished_group_result(
1308 &self,
1309 parent_scope: Option<ScopeId>,
1310 result: FinishGroupResult,
1311 ) {
1312 let FinishGroupResult {
1313 detached_children,
1314 direct_nodes,
1315 root_nodes,
1316 was_skipped,
1317 } = result;
1318 if was_skipped {
1319 self.attach_root_nodes(root_nodes);
1320 }
1321 self.dispose_detached_nodes(direct_nodes);
1322 self.handle_detached_children(parent_scope, detached_children);
1323 }
1324
1325 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1326 let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1327 self.handle_finished_group_result(Some(scope.id()), result);
1328 if let Some(popped) = self.scope_stack().pop() {
1329 debug_assert_eq!(
1330 popped.id(),
1331 scope.id(),
1332 "closed scope must match the active scope stack"
1333 );
1334 } else {
1335 log::error!("scope stack underflow while closing scope {}", scope.id());
1336 }
1337 }
1338
1339 #[track_caller]
1340 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1341 self.remember_at(crate::caller_location_key(), init)
1342 }
1343
1344 #[doc(hidden)]
1345 pub fn remember_at<T: 'static>(
1346 &self,
1347 source: crate::Key,
1348 init: impl FnOnce() -> T,
1349 ) -> Owned<T> {
1350 self.with_slot_session_mut(|slots| {
1351 slots.remember_with_kind(PayloadKind::Remember, source, init)
1352 })
1353 }
1354
1355 #[track_caller]
1356 pub(crate) fn remember_internal<T: 'static>(
1357 &self,
1358 source_salt: crate::Key,
1359 init: impl FnOnce() -> T,
1360 ) -> Owned<T> {
1361 let source = crate::caller_location_key() ^ source_salt;
1362 self.with_slot_session_mut(|slots| {
1363 slots.remember_with_kind(PayloadKind::Internal, source, init)
1364 })
1365 }
1366
1367 #[track_caller]
1368 pub(crate) fn remember_effect<T: Default + 'static>(&self) -> Owned<T> {
1369 let source = crate::caller_location_key();
1370 self.with_slot_session_mut(|slots| slots.remember_effect::<T>(source))
1371 }
1372
1373 #[track_caller]
1374 pub fn use_value_slot<'pass, T: 'static>(
1375 &'pass self,
1376 init: impl FnOnce() -> T,
1377 ) -> ValueSlotHandle<'pass, T> {
1378 let source = crate::caller_location_key();
1379 let slot = self.with_slot_session_mut(|slots| {
1380 slots.value_slot_with_kind(PayloadKind::Internal, source, init)
1381 });
1382 ValueSlotHandle::new(slot)
1383 }
1384
1385 #[doc(hidden)]
1386 #[track_caller]
1387 pub fn __use_param_slot<'pass, T: 'static>(
1388 &'pass self,
1389 init: impl FnOnce() -> T,
1390 ) -> ValueSlotHandle<'pass, T> {
1391 let source = crate::caller_location_key();
1392 let slot = self.with_slot_session_mut(|slots| {
1393 slots.value_slot_with_kind(PayloadKind::Param, source, init)
1394 });
1395 ValueSlotHandle::new(slot)
1396 }
1397
1398 #[doc(hidden)]
1399 #[track_caller]
1400 pub fn __use_return_slot<'pass, T: 'static>(
1401 &'pass self,
1402 init: impl FnOnce() -> T,
1403 ) -> ValueSlotHandle<'pass, T> {
1404 let source = crate::caller_location_key();
1405 let slot = self.with_slot_session_mut(|slots| {
1406 slots.value_slot_with_kind(PayloadKind::Return, source, init)
1407 });
1408 ValueSlotHandle::new(slot)
1409 }
1410
1411 #[doc(hidden)]
1412 pub fn __invalidate_return_consumer_scope(&self) {
1413 let Some(scope) = self.current_recompose_scope() else {
1414 self.request_root_render();
1415 return;
1416 };
1417
1418 if let Some(target) = scope.callback_promotion_target() {
1419 target.invalidate();
1420 } else {
1421 self.request_root_render();
1422 }
1423 }
1424
1425 pub fn with_slot_value<'pass, T: 'static, R>(
1426 &'pass self,
1427 handle: ValueSlotHandle<'pass, T>,
1428 f: impl FnOnce(&T) -> R,
1429 ) -> R {
1430 self.with_slots(|slots| f(slots.read_value(handle.slot())))
1431 }
1432
1433 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1434 &'pass self,
1435 handle: ValueSlotHandle<'pass, T>,
1436 f: impl FnOnce(&mut T) -> R,
1437 ) -> R {
1438 self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1439 }
1440
1441 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1442 MutableState::with_runtime(initial, self.runtime_handle())
1443 }
1444
1445 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1446 where
1447 T: Clone + 'static,
1448 I: IntoIterator<Item = T>,
1449 {
1450 SnapshotStateList::with_runtime(values, self.runtime_handle())
1451 }
1452
1453 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1454 where
1455 K: Clone + Eq + Hash + 'static,
1456 V: Clone + 'static,
1457 I: IntoIterator<Item = (K, V)>,
1458 {
1459 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1460 }
1461
1462 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1463 let stack = self.core.local_stack.borrow();
1464 for context in stack.iter().rev() {
1465 if let Some(entry) = context.values.get(&local.key) {
1466 match entry.clone().downcast::<LocalStateEntry<T>>() {
1467 Ok(typed) => return typed.value(),
1468 Err(_) => {
1469 log::error!(
1470 "composition local entry type mismatch for key {:?}",
1471 local.key
1472 );
1473 return local.default_value();
1474 }
1475 }
1476 }
1477 }
1478 local.default_value()
1479 }
1480
1481 pub fn read_static_composition_local<T: Clone + 'static>(
1482 &self,
1483 local: &StaticCompositionLocal<T>,
1484 ) -> T {
1485 let stack = self.core.local_stack.borrow();
1486 for context in stack.iter().rev() {
1487 if let Some(entry) = context.values.get(&local.key) {
1488 match entry.clone().downcast::<StaticLocalEntry<T>>() {
1489 Ok(typed) => return typed.value(),
1490 Err(_) => {
1491 log::error!(
1492 "static composition local entry type mismatch for key {:?}",
1493 local.key
1494 );
1495 return local.default_value();
1496 }
1497 }
1498 }
1499 }
1500 local.default_value()
1501 }
1502
1503 pub fn current_recompose_scope(&self) -> Option<RecomposeScope> {
1504 self.core.scope_stack.borrow().last().cloned()
1505 }
1506
1507 pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1508 let stack = self.core.scope_stack.borrow();
1509 stack
1510 .iter()
1511 .rev()
1512 .find(|scope| scope.has_recompose_callback())
1513 .cloned()
1514 .or_else(|| stack.last().cloned())
1515 }
1516
1517 pub fn phase(&self) -> crate::Phase {
1518 self.core.phase.get()
1519 }
1520
1521 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1522 self.core.phase.set(phase);
1523 }
1524
1525 pub fn enter_phase(&self, phase: crate::Phase) {
1526 self.set_phase(phase);
1527 }
1528
1529 pub(crate) fn subcompose<R>(
1530 &self,
1531 state: &mut SubcomposeState,
1532 slot_id: SlotId,
1533 content: impl FnOnce(&Composer) -> R,
1534 ) -> (R, Vec<NodeId>) {
1535 match self.phase() {
1536 crate::Phase::Measure | crate::Phase::Layout => {}
1537 current => panic!(
1538 "subcompose() may only be called during measure or layout; current phase: {:?}",
1539 current
1540 ),
1541 }
1542
1543 self.subcompose_stack().push(SubcomposeFrame::default());
1544 struct StackGuard {
1545 core: Rc<ComposerCore>,
1546 leaked: bool,
1547 }
1548 impl Drop for StackGuard {
1549 fn drop(&mut self) {
1550 if !self.leaked {
1551 self.core.subcompose_stack.borrow_mut().pop();
1552 }
1553 }
1554 }
1555 let mut guard = StackGuard {
1556 core: self.clone_core(),
1557 leaked: false,
1558 };
1559
1560 let slot_host = state.get_or_create_slots(slot_id);
1561 let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1562 composer.with_group(slot_id.raw(), |composer| content(composer))
1563 });
1564
1565 let frame = {
1566 let frame = take_subcompose_frame(&guard.core, "subcompose");
1567 guard.leaked = true;
1568 frame
1569 };
1570 let nodes = frame.nodes;
1571 let scopes = frame.scopes;
1572 state.register_active(slot_id, &nodes, &scopes);
1573 (result, nodes)
1574 }
1575
1576 pub fn subcompose_measurement<R>(
1577 &self,
1578 state: &mut SubcomposeState,
1579 slot_id: SlotId,
1580 content: impl FnOnce(&Composer) -> R,
1581 ) -> (R, Vec<NodeId>) {
1582 let (result, nodes) = self.subcompose(state, slot_id, content);
1583 let roots = nodes
1584 .into_iter()
1585 .filter(|&id| self.node_has_no_parent(id))
1586 .collect();
1587
1588 (result, roots)
1589 }
1590
1591 pub fn subcompose_in<R>(
1592 &self,
1593 slots: &Rc<SlotsHost>,
1594 root: Option<NodeId>,
1595 f: impl FnOnce(&Composer) -> R,
1596 ) -> Result<R, NodeError> {
1597 let runtime_handle = self.runtime_handle();
1598 let phase = self.phase();
1599 let locals = self.current_local_stack();
1600 let shared_state = slots
1601 .runtime_state()
1602 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1603 let core = Rc::new(ComposerCore::new(
1604 shared_state,
1605 Rc::clone(slots),
1606 Rc::clone(&self.core.applier),
1607 runtime_handle.clone(),
1608 self.observer(),
1609 root,
1610 InitialParentFrame::RealParent,
1611 ));
1612 core.phase.set(phase);
1613 *core.local_stack.borrow_mut() = locals;
1614 let composer = Composer::from_core(core);
1615 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1616 let (output, outcome) = composer.try_with_slot_host_pass(
1617 Rc::clone(slots),
1618 crate::slot::SlotPassMode::Compose,
1619 |composer| f(composer),
1620 )?;
1621 let commands = composer.take_commands();
1622 let side_effects = composer.take_side_effects();
1623 Ok((output, commands, side_effects, outcome.compacted))
1624 })?;
1625 {
1626 let mut applier = self.borrow_applier();
1627 commands.apply(&mut *applier)?;
1628 for update in runtime_handle.take_updates() {
1629 update.apply(&mut *applier)?;
1630 }
1631 }
1632 if compact_applier {
1633 self.core.applier.compact();
1634 self.core.applier.borrow_dyn().clear_recycled_nodes();
1635 }
1636 runtime_handle.drain_ui();
1637 for effect in side_effects {
1638 effect();
1639 }
1640 runtime_handle.drain_ui();
1641 Ok(result)
1642 }
1643
1644 pub fn capture_composition_context(&self) -> CapturedCompositionContext {
1655 CapturedCompositionContext {
1656 locals: self.current_local_stack(),
1657 owner_scope: self
1658 .current_recompose_scope()
1659 .map(|scope| scope.downgrade()),
1660 }
1661 }
1662
1663 pub fn subcompose_slot<R>(
1668 &self,
1669 slots: &Rc<SlotsHost>,
1670 root: Option<NodeId>,
1671 f: impl FnOnce(&Composer) -> R,
1672 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1673 let context = self.capture_composition_context();
1674 self.subcompose_slot_with_context(slots, root, &context, f)
1675 }
1676
1677 pub fn subcompose_slot_with_context<R>(
1681 &self,
1682 slots: &Rc<SlotsHost>,
1683 root: Option<NodeId>,
1684 context: &CapturedCompositionContext,
1685 f: impl FnOnce(&Composer) -> R,
1686 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1687 let runtime_handle = self.runtime_handle();
1688 let phase = self.phase();
1689 let locals = context.locals.clone();
1690 let shared_state = slots
1691 .runtime_state()
1692 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1693 let core = Rc::new(ComposerCore::new(
1694 shared_state,
1695 Rc::clone(slots),
1696 Rc::clone(&self.core.applier),
1697 runtime_handle.clone(),
1698 self.observer(),
1699 root,
1700 InitialParentFrame::RealParent,
1701 ));
1702 core.phase.set(phase);
1703 *core.local_stack.borrow_mut() = locals;
1704 *core.subcomposition_owner_scope.borrow_mut() = context
1705 .owner_scope
1706 .as_ref()
1707 .and_then(Weak::upgrade)
1708 .map(|inner| RecomposeScope { inner });
1709 let composer = Composer::from_core(core);
1710 composer.subcompose_stack().push(SubcomposeFrame::default());
1711 struct StackGuard {
1712 core: Rc<ComposerCore>,
1713 leaked: bool,
1714 }
1715 impl Drop for StackGuard {
1716 fn drop(&mut self) {
1717 if !self.leaked {
1718 self.core.subcompose_stack.borrow_mut().pop();
1719 }
1720 }
1721 }
1722 let mut guard = StackGuard {
1723 core: composer.clone_core(),
1724 leaked: false,
1725 };
1726 let root_group_key = crate::location_key(file!(), line!(), column!());
1727 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1728 let (output, outcome) = composer.try_with_slot_host_pass(
1729 Rc::clone(slots),
1730 crate::slot::SlotPassMode::Compose,
1731 |composer| {
1732 let output = composer.with_group(root_group_key, |composer| f(composer));
1733 if root.is_some() {
1734 composer.pop_parent();
1735 }
1736 output
1737 },
1738 )?;
1739 let commands = composer.take_commands();
1740 let side_effects = composer.take_side_effects();
1741 Ok((output, commands, side_effects, outcome.compacted))
1742 })?;
1743 let frame = {
1744 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1745 guard.leaked = true;
1746 frame
1747 };
1748
1749 {
1750 let mut applier = self.borrow_applier();
1751 commands.apply(&mut *applier)?;
1752 for update in runtime_handle.take_updates() {
1753 update.apply(&mut *applier)?;
1754 }
1755 }
1756 if compact_applier {
1757 self.core.applier.compact();
1758 self.core.applier.borrow_dyn().clear_recycled_nodes();
1759 }
1760 runtime_handle.drain_ui();
1761 for effect in side_effects {
1762 effect();
1763 }
1764 runtime_handle.drain_ui();
1765 Ok((result, frame.scopes))
1766 }
1767
1768 fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
1769 for id in root_nodes {
1770 self.attach_to_parent_with_mode(id, true);
1771 }
1772 }
1773
1774 pub fn skip_current_group(&self) {
1775 self.with_slot_session_mut(|slots| slots.skip_group());
1776 }
1777
1778 pub fn runtime_handle(&self) -> RuntimeHandle {
1779 self.core.runtime.clone()
1780 }
1781
1782 pub fn set_recompose_callback<F>(&self, callback: F)
1783 where
1784 F: FnMut(&Composer) + 'static,
1785 {
1786 self.set_recompose_callback_boxed(Box::new(callback));
1787 }
1788
1789 #[inline(never)]
1790 fn set_recompose_callback_boxed(&self, mut callback: Box<dyn FnMut(&Composer)>) {
1791 if let Some(scope) = self.current_recompose_scope() {
1792 let observer = self.observer();
1793 let scope_weak = scope.downgrade();
1794 scope.set_recompose(Box::new(move |composer: &Composer| {
1795 if let Some(inner) = scope_weak.upgrade() {
1796 let scope_instance = RecomposeScope { inner };
1797 observer.observe_reads(
1798 scope_instance.clone(),
1799 move |scope_ref| scope_ref.invalidate(),
1800 || {
1801 callback(composer);
1802 },
1803 );
1804 }
1805 }));
1806 }
1807 }
1808
1809 pub fn set_recompose_fn(&self, callback: fn(&Composer)) {
1810 if let Some(scope) = self.current_recompose_scope() {
1811 scope.set_recompose_fn(callback);
1812 }
1813 }
1814
1815 pub fn with_composition_locals<R>(
1816 &self,
1817 provided: Vec<ProvidedValue>,
1818 site: crate::Key,
1819 f: impl FnOnce(&Composer) -> R,
1820 ) -> R {
1821 if provided.is_empty() {
1822 return f(self);
1823 }
1824 let mut context = LocalContext::default();
1825 for value in provided.into_iter().rev() {
1826 if context.values.contains_key(value.key()) {
1827 continue;
1828 }
1829 let (key, entry) = value.into_entry(self, site);
1830 context.values.insert(key, entry);
1831 }
1832 {
1833 let mut stack = self.local_stack();
1834 Rc::make_mut(&mut *stack).push(context);
1835 }
1836 let result = f(self);
1837 {
1838 let mut stack = self.local_stack();
1839 Rc::make_mut(&mut *stack).pop();
1840 }
1841 result
1842 }
1843}