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