1use std::{
2 any::Any,
3 cell::{Cell, RefCell, RefMut},
4 hash::Hash,
5 marker::PhantomData,
6 rc::{Rc, Weak},
7};
8
9use smallvec::SmallVec;
10
11use crate::{
12 AnchorId, Applier, 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 GroupEntry {
83 key: crate::slot::GroupKey,
84 restored: Option<crate::slot::DetachedSubtree>,
85 placeholder_for: Option<crate::slot::GroupKey>,
86}
87
88struct GroupScopeEntry<'a> {
89 parent_scope: Option<RecomposeScope>,
90 options: RecomposeOptions,
91 start_kind: GroupStartKind,
92 host: &'a Rc<SlotsHost>,
93 restored_scopes: Option<Vec<ScopeId>>,
94}
95
96struct SlotHostPassGuard {
97 core: Rc<ComposerCore>,
98 host: Rc<SlotsHost>,
99 active: bool,
100}
101
102impl SlotHostPassGuard {
103 fn close(&mut self) {
104 if !self.active {
105 return;
106 }
107 if self.host.has_active_pass() {
108 self.host.abandon_active_pass();
109 }
110 match self.core.slot_hosts.borrow_mut().pop() {
111 Some(host) if Rc::ptr_eq(&host, &self.host) => {}
112 Some(_) => {
113 log::error!("slot host stack mismatch while closing slot host pass");
114 }
115 None => {
116 log::error!("slot host stack underflow while closing slot host pass");
117 }
118 }
119 self.active = false;
120 }
121}
122
123impl Drop for SlotHostPassGuard {
124 fn drop(&mut self) {
125 self.close();
126 }
127}
128
129pub(crate) struct PendingMovable {
130 pub(crate) key: crate::slot::GroupKey,
131 pub(crate) placeholder: AnchorId,
132 pub(crate) parent_scope: Option<ScopeId>,
133}
134
135fn movable_retain_key(id: Key) -> RetainKey {
136 RetainKey::for_group(
137 None,
138 crate::slot::GroupKey::new(crate::slot::MOVABLE_STATIC_KEY, Some(id), 0),
139 )
140}
141
142pub(crate) struct ComposerRuntimeState {
143 scope_registry: RefCell<HashMap<ScopeId, RecomposeScope>>,
144 retention_by_host: RefCell<HashMap<usize, RetentionManager>>,
145 pending_movables_by_host: RefCell<HashMap<usize, Vec<PendingMovable>>>,
146 retention_policy: Cell<RetentionPolicy>,
147 live_hosts: RefCell<HashMap<usize, std::rc::Weak<SlotsHost>>>,
148 applier_host: RefCell<Option<std::rc::Weak<dyn ApplierHost>>>,
149}
150
151impl Default for ComposerRuntimeState {
152 fn default() -> Self {
153 Self {
154 scope_registry: RefCell::new(HashMap::default()),
155 retention_by_host: RefCell::new(HashMap::default()),
156 pending_movables_by_host: RefCell::new(HashMap::default()),
157 retention_policy: Cell::new(RetentionPolicy::default()),
158 live_hosts: RefCell::new(HashMap::default()),
159 applier_host: RefCell::new(None),
160 }
161 }
162}
163
164impl ComposerRuntimeState {
165 pub(crate) fn clear_host_storage_key(&self, host_key: usize) {
166 self.retention_by_host.borrow_mut().remove(&host_key);
167 self.pending_movables_by_host.borrow_mut().remove(&host_key);
168 self.live_hosts.borrow_mut().remove(&host_key);
169 let removed_scopes = {
170 let mut removed = Vec::new();
171 self.scope_registry.borrow_mut().retain(|_, scope| {
172 if scope.slots_storage_key() == Some(host_key) {
173 removed.push(scope.clone());
174 false
175 } else {
176 true
177 }
178 });
179 removed
180 };
181 for scope in removed_scopes {
182 scope.deactivate();
183 }
184 }
185
186 pub(crate) fn force_recompose_host_scopes(&self, host_key: usize) {
187 for scope in self.scope_registry.borrow().values() {
188 if scope.slots_storage_key() == Some(host_key) {
189 scope.force_recompose();
190 }
191 }
192 }
193
194 pub(crate) fn bind_applier_host(&self, applier: &Rc<dyn ApplierHost>) {
195 *self.applier_host.borrow_mut() = Some(Rc::downgrade(applier));
196 }
197
198 pub(crate) fn has_live_applier_host(&self) -> bool {
199 self.applier_host
200 .borrow()
201 .as_ref()
202 .and_then(std::rc::Weak::upgrade)
203 .is_some()
204 }
205
206 pub(crate) fn bind_slots_host(self: &Rc<Self>, host: &Rc<SlotsHost>) {
207 host.bind_runtime_state(self);
208 self.live_hosts
209 .borrow_mut()
210 .insert(host.storage_key(), Rc::downgrade(host));
211 }
212
213 pub(crate) fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
214 self.scope_registry.borrow().get(&scope_id).cloned()
215 }
216
217 pub(crate) fn register_scope(&self, scope: &RecomposeScope) {
218 self.scope_registry
219 .borrow_mut()
220 .insert(scope.id(), scope.clone());
221 }
222
223 pub(crate) fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
224 self.scope_registry.borrow_mut().remove(&scope_id)
225 }
226
227 pub(crate) fn set_retention_policy(&self, policy: RetentionPolicy) {
228 self.retention_policy.set(policy);
229 }
230
231 pub(crate) fn retention_policy(&self) -> RetentionPolicy {
232 self.retention_policy.get()
233 }
234
235 pub(crate) fn scope_registry_len(&self) -> usize {
236 self.scope_registry.borrow().len()
237 }
238
239 pub(crate) fn take_retained(
240 &self,
241 host: &Rc<SlotsHost>,
242 key: RetainKey,
243 preflight: impl FnOnce(&mut crate::slot::DetachedSubtree) -> bool,
244 ) -> Option<crate::slot::DetachedSubtree> {
245 let host_key = slots_storage_key(host);
246 let mut retention = self.retention_by_host.borrow_mut();
247 let subtree = retention
248 .get_mut(&host_key)?
249 .take_after_restore_preflight(key, preflight);
250 if retention
251 .get(&host_key)
252 .is_some_and(|manager| manager.is_empty() && manager.evictions_total() == 0)
253 {
254 retention.remove(&host_key);
255 }
256 subtree
257 }
258
259 pub(crate) fn insert_retained(
260 &self,
261 host: &Rc<SlotsHost>,
262 key: RetainKey,
263 subtree: crate::slot::DetachedSubtree,
264 pinned: bool,
265 ) -> Vec<crate::slot::DetachedSubtree> {
266 let policy = self.retention_policy();
267 let mut retention_by_host = self.retention_by_host.borrow_mut();
268 let manager = retention_by_host
269 .entry(slots_storage_key(host))
270 .or_insert_with(|| RetentionManager::new(policy));
271 manager.set_policy(policy);
272 if pinned {
273 manager.insert_pinned(key, subtree)
274 } else {
275 manager.insert(key, subtree)
276 }
277 }
278
279 pub(crate) fn take_retained_movable(
280 &self,
281 id: Key,
282 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
283 self.take_retained_movable_by_key(movable_retain_key(id))
284 }
285
286 fn take_retained_movable_by_key(
287 &self,
288 key: RetainKey,
289 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
290 let mut retention_by_host = self.retention_by_host.borrow_mut();
291 let (host_key, manager) = retention_by_host
292 .iter_mut()
293 .find(|(_, manager)| manager.contains(key))?;
294 let host = self.host_for_storage_key(*host_key)?;
295 let subtree = manager.take(key)?;
296 Some((host, subtree))
297 }
298
299 pub(crate) fn take_retained_movable_elsewhere(
302 &self,
303 besides: &Rc<SlotsHost>,
304 key: RetainKey,
305 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
306 let besides_key = slots_storage_key(besides);
307 let mut retention_by_host = self.retention_by_host.borrow_mut();
308 let (host_key, manager) = retention_by_host
309 .iter_mut()
310 .find(|(host_key, manager)| **host_key != besides_key && manager.contains(key))?;
311 let host = self.host_for_storage_key(*host_key)?;
312 let subtree = manager.take(key)?;
313 Some((host, subtree))
314 }
315
316 pub(crate) fn movable_retained_anywhere(&self, key: RetainKey) -> bool {
320 self.retention_by_host
321 .borrow()
322 .values()
323 .any(|manager| manager.contains(key))
324 }
325
326 pub(crate) fn host_holding_movable(&self, id: Key) -> Option<Rc<SlotsHost>> {
328 self.live_hosts
329 .borrow()
330 .values()
331 .filter_map(std::rc::Weak::upgrade)
332 .find(|host| host.borrow().movable_is_attached(id))
333 }
334
335 pub(crate) fn record_pending_movable(&self, host: &Rc<SlotsHost>, pending: PendingMovable) {
336 let mut by_host = self.pending_movables_by_host.borrow_mut();
337 let sites = by_host.entry(slots_storage_key(host)).or_default();
338 if sites
339 .iter()
340 .all(|site| site.placeholder != pending.placeholder)
341 {
342 sites.push(pending);
343 }
344 }
345
346 pub(crate) fn take_pending_movables(&self, host: &Rc<SlotsHost>) -> Vec<PendingMovable> {
347 self.pending_movables_by_host
348 .borrow_mut()
349 .remove(&slots_storage_key(host))
350 .unwrap_or_default()
351 }
352
353 pub(crate) fn hosts_awaiting_movables(&self) -> Vec<Rc<SlotsHost>> {
357 let waiting = self
358 .pending_movables_by_host
359 .borrow()
360 .iter()
361 .filter(|(_, sites)| !sites.is_empty())
362 .map(|(host_key, _)| *host_key)
363 .collect::<Vec<_>>();
364 waiting
365 .into_iter()
366 .filter_map(|host_key| self.host_for_storage_key(host_key))
367 .collect()
368 }
369
370 pub(crate) fn keep_pending_movables(&self, host: &Rc<SlotsHost>, sites: Vec<PendingMovable>) {
371 if sites.is_empty() {
372 return;
373 }
374 self.pending_movables_by_host
375 .borrow_mut()
376 .insert(slots_storage_key(host), sites);
377 }
378
379 pub(crate) fn advance_retention_pass(
380 &self,
381 host: &Rc<SlotsHost>,
382 ) -> Vec<crate::slot::DetachedSubtree> {
383 let host_key = slots_storage_key(host);
384 let policy = self.retention_policy();
385 let mut retention_by_host = self.retention_by_host.borrow_mut();
386 let Some(manager) = retention_by_host.get_mut(&host_key) else {
387 return Vec::new();
388 };
389 manager.set_policy(policy);
390 manager.advance_pass()
391 }
392
393 pub(crate) fn fill_slot_debug_snapshot(
394 &self,
395 host: &SlotsHost,
396 snapshot: &mut crate::SlotDebugSnapshot,
397 ) {
398 let retention = self.retention_debug_stats(host.storage_key());
399 snapshot.runtime_scope_registry_count = Some(self.scope_registry_len());
400 snapshot.retained_subtree_count = retention.subtree_count;
401 snapshot.retained_group_count = retention.group_count;
402 snapshot.retained_payload_count = retention.payload_count;
403 snapshot.retained_node_count = retention.node_count;
404 snapshot.retained_scope_count = retention.scope_count;
405 }
406
407 pub(crate) fn slot_retention_debug_stats(
408 &self,
409 host: &SlotsHost,
410 ) -> crate::slot::SlotRetentionDebugStats {
411 let retention = self.retention_debug_stats(host.storage_key());
412 crate::slot::SlotRetentionDebugStats {
413 retained_subtree_count: retention.subtree_count,
414 retained_group_count: retention.group_count,
415 retained_payload_count: retention.payload_count,
416 retained_node_count: retention.node_count,
417 retained_scope_count: retention.scope_count,
418 retained_anchor_count: retention.anchor_count,
419 retained_heap_bytes: retention.heap_bytes,
420 retained_evictions_total: retention.evictions_total,
421 }
422 }
423
424 pub(crate) fn compact_table_identity_storage_for_host(
425 &self,
426 host: &SlotsHost,
427 table: &mut SlotTable,
428 compact_anchors: bool,
429 compact_payloads: bool,
430 ) {
431 if !compact_anchors && !compact_payloads {
432 return;
433 }
434
435 let host_key = host.storage_key();
436 let mut retention = self.retention_by_host.borrow_mut();
437 if let Some(retained) = retention.get_mut(&host_key) {
438 if compact_anchors {
439 table.compact_anchor_registry_storage(Some(&mut *retained));
440 }
441 if compact_payloads {
442 table.compact_payload_anchor_registry_storage(Some(&mut *retained));
443 }
444 } else {
445 if compact_anchors {
446 table.compact_anchor_registry_storage(None);
447 }
448 if compact_payloads {
449 table.compact_payload_anchor_registry_storage(None);
450 }
451 }
452 }
453
454 pub(crate) fn clear_host(&self, host: &SlotsHost) {
455 let host_key = host.storage_key();
456 debug_assert!(
457 self.host_retention_is_empty(host),
458 "host retention must be drained before clearing host ownership"
459 );
460 self.clear_host_storage_key(host_key);
461 }
462
463 fn deactivate_and_queue_subtrees(
464 &self,
465 retention: RetentionManager,
466 table: &mut SlotTable,
467 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
468 ) {
469 for subtree in retention.into_subtrees() {
470 for scope_id in subtree.scope_ids() {
471 if let Some(scope) = self.remove_scope(scope_id) {
472 scope.deactivate();
473 }
474 }
475 table.invalidate_detached_subtree_anchors(&subtree);
476 lifecycle.queue_subtree_disposal(subtree);
477 }
478 }
479
480 pub(crate) fn dispose_retained_subtrees_for_host(
481 &self,
482 host_key: usize,
483 table: &mut SlotTable,
484 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
485 ) -> Result<(), NodeError> {
486 let applier_host = self
487 .applier_host
488 .borrow()
489 .as_ref()
490 .and_then(std::rc::Weak::upgrade);
491 if let Some(applier_host) = applier_host.as_ref() {
492 let retention_by_host = self.retention_by_host.borrow();
493 let Some(retention) = retention_by_host.get(&host_key) else {
494 return Ok(());
495 };
496 let mut applier = applier_host.borrow_dyn();
497 for subtree in retention.subtrees() {
498 crate::slot::dispose_detached_subtree_now(&mut *applier, subtree)?;
499 }
500 }
501 let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
502 return Ok(());
503 };
504 self.deactivate_and_queue_subtrees(retention, table, lifecycle);
505 Ok(())
506 }
507
508 pub(crate) fn abandon_retained_subtrees_for_host(
509 &self,
510 host_key: usize,
511 table: &mut SlotTable,
512 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
513 ) {
514 let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
515 self.clear_host_storage_key(host_key);
516 return;
517 };
518 self.deactivate_and_queue_subtrees(retention, table, lifecycle);
519 self.clear_host_storage_key(host_key);
520 }
521
522 pub(crate) fn host_retention_is_empty(&self, host: &SlotsHost) -> bool {
523 self.retention_by_host
524 .borrow()
525 .get(&host.storage_key())
526 .is_none_or(RetentionManager::is_empty)
527 }
528
529 #[cfg(any(test, debug_assertions))]
530 pub(crate) fn debug_verify_host(&self, host: &SlotsHost, table: &SlotTable) {
531 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
532 retention.debug_verify(table);
533 }
534 }
535
536 #[cfg(test)]
537 pub(crate) fn validate_host_retention(
538 &self,
539 host: &SlotsHost,
540 table: &SlotTable,
541 ) -> Result<(), crate::slot::SlotInvariantError> {
542 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
543 retention.validate(table)?;
544 }
545 Ok(())
546 }
547
548 pub(crate) fn host_for_storage_key(&self, storage_key: usize) -> Option<Rc<SlotsHost>> {
549 self.live_hosts
550 .borrow()
551 .get(&storage_key)
552 .and_then(std::rc::Weak::upgrade)
553 }
554
555 fn retention_debug_stats(&self, host_key: usize) -> crate::retention::RetentionDebugStats {
556 self.retention_by_host
557 .borrow()
558 .get(&host_key)
559 .map(RetentionManager::debug_stats)
560 .unwrap_or_default()
561 }
562}
563
564pub(crate) struct ParentFrame {
565 pub(crate) id: NodeId,
566 pub(crate) previous: ChildList,
567 pub(crate) new_children: ChildList,
568 pub(crate) new_children_membership: Option<HashSet<NodeId>>,
569 pub(crate) attach_mode: ParentAttachMode,
570 pub(crate) synthetic_root: bool,
571}
572
573#[derive(Clone, Copy)]
574pub(crate) enum InitialParentFrame {
575 SyntheticRoot,
576 RealParent,
577}
578
579const LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD: usize = 16;
580
581#[derive(Clone, Copy, Debug, PartialEq, Eq)]
582pub(crate) enum ParentAttachMode {
583 ImmediateAppend,
584 DeferredSync,
585}
586
587#[derive(Default)]
588pub(crate) struct SubcomposeFrame {
589 pub(crate) nodes: Vec<NodeId>,
590 pub(crate) scopes: Vec<RecomposeScope>,
591}
592
593#[derive(Default, Clone)]
594pub(crate) struct LocalContext {
595 pub(crate) values: HashMap<LocalKey, Rc<dyn Any>>,
596}
597
598pub(crate) struct ComposerCore {
599 pub(crate) shared_state: Rc<ComposerRuntimeState>,
600 pub(crate) slots: Rc<SlotsHost>,
601 slot_hosts: RefCell<Vec<Rc<SlotsHost>>>,
602 pub(crate) applier: Rc<dyn ApplierHost>,
603 pub(crate) runtime: RuntimeHandle,
604 pub(crate) observer: SnapshotStateObserver,
605 pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
606 pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
607 pub(crate) root: Cell<Option<NodeId>>,
608 pub(crate) commands: RefCell<CommandQueue>,
609 pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
610 subcomposition_owner_scope: RefCell<Option<RecomposeScope>>,
611 pub(crate) local_stack: RefCell<LocalStackSnapshot>,
612 pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
613 pub(crate) pending_scope_options: RefCell<Option<RecomposeOptions>>,
614 pub(crate) phase: Cell<crate::Phase>,
615 pub(crate) last_node_reused: Cell<Option<bool>>,
616 pub(crate) recompose_parent_hint: Cell<Option<NodeId>>,
617 pub(crate) recompose_child_cursor: Cell<Option<usize>>,
618 pub(crate) root_render_requested: Cell<bool>,
619 pub(crate) _not_send: PhantomData<*const ()>,
620}
621
622#[derive(Clone)]
626pub struct CapturedCompositionContext {
627 locals: LocalStackSnapshot,
628 owner_scope: Option<Weak<RecomposeScopeInner>>,
629}
630
631impl CapturedCompositionContext {
632 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
636 self.owner_scope
637 .as_ref()
638 .and_then(Weak::upgrade)
639 .map_or(0, |inner| {
640 crate::RecomposeScope { inner }.owner_chain_deactivation_epoch()
641 })
642 }
643}
644
645fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
646 match core.subcompose_stack.borrow_mut().pop() {
647 Some(frame) => frame,
648 None => {
649 log::error!("subcompose stack underflow while finishing {operation}");
650 SubcomposeFrame::default()
651 }
652 }
653}
654
655struct SubcomposeStackGuard {
656 core: Rc<ComposerCore>,
657 leaked: bool,
658}
659
660impl Drop for SubcomposeStackGuard {
661 fn drop(&mut self) {
662 if !self.leaked {
663 self.core.subcompose_stack.borrow_mut().pop();
664 }
665 }
666}
667
668impl ComposerCore {
669 pub(crate) fn new(
670 shared_state: Rc<ComposerRuntimeState>,
671 slots: Rc<SlotsHost>,
672 applier: Rc<dyn ApplierHost>,
673 runtime: RuntimeHandle,
674 observer: SnapshotStateObserver,
675 root: Option<NodeId>,
676 initial_parent_frame: InitialParentFrame,
677 ) -> Self {
678 let parent_stack = if let Some(root_id) = root {
679 vec![ParentFrame {
680 id: root_id,
681 previous: ChildList::new(),
682 new_children: ChildList::new(),
683 new_children_membership: None,
684 attach_mode: ParentAttachMode::DeferredSync,
685 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
686 }]
687 } else {
688 Vec::new()
689 };
690
691 Self {
692 shared_state,
693 slots,
694 slot_hosts: RefCell::new(Vec::new()),
695 applier,
696 runtime,
697 observer,
698 parent_stack: RefCell::new(parent_stack),
699 subcompose_stack: RefCell::new(Vec::new()),
700 root: Cell::new(root),
701 commands: RefCell::new(CommandQueue::default()),
702 scope_stack: RefCell::new(Vec::new()),
703 subcomposition_owner_scope: RefCell::new(None),
704 local_stack: RefCell::new(empty_local_stack()),
705 side_effects: RefCell::new(Vec::new()),
706 pending_scope_options: RefCell::new(None),
707 phase: Cell::new(crate::Phase::Compose),
708 last_node_reused: Cell::new(None),
709 recompose_parent_hint: Cell::new(None),
710 recompose_child_cursor: Cell::new(None),
711 root_render_requested: Cell::new(false),
712 _not_send: PhantomData,
713 }
714 }
715}
716
717#[derive(Clone)]
718pub struct Composer {
719 pub(crate) core: Rc<ComposerCore>,
720}
721
722pub struct BranchGroupGuard {
723 composer: Composer,
724 fold_token: Option<usize>,
725}
726
727impl Drop for BranchGroupGuard {
728 fn drop(&mut self) {
729 let Some(token) = self.fold_token else {
730 return;
731 };
732 if !self
733 .composer
734 .active_slots_host()
735 .try_close_branch_fold(token)
736 {
737 log::error!("a branch fold guard closed while its slot host was busy");
738 }
739 }
740}
741
742pub(crate) enum EmittedNode {
743 Fresh(Box<dyn Node>),
744 Recycled(RecycledNode),
745}
746
747impl Composer {
748 pub(crate) fn new_with_shared_state(
749 shared_state: Rc<ComposerRuntimeState>,
750 slots: Rc<SlotsHost>,
751 applier: Rc<dyn ApplierHost>,
752 runtime: RuntimeHandle,
753 observer: SnapshotStateObserver,
754 root: Option<NodeId>,
755 ) -> Self {
756 Self::new_with_shared_state_with_parent_frame(
757 shared_state,
758 slots,
759 applier,
760 runtime,
761 observer,
762 root,
763 InitialParentFrame::SyntheticRoot,
764 )
765 }
766
767 fn new_with_shared_state_with_parent_frame(
768 shared_state: Rc<ComposerRuntimeState>,
769 slots: Rc<SlotsHost>,
770 applier: Rc<dyn ApplierHost>,
771 runtime: RuntimeHandle,
772 observer: SnapshotStateObserver,
773 root: Option<NodeId>,
774 initial_parent_frame: InitialParentFrame,
775 ) -> Self {
776 shared_state.bind_applier_host(&applier);
777 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
778 let core = Rc::new(ComposerCore::new(
779 shared_state,
780 slots,
781 applier,
782 runtime,
783 observer,
784 root,
785 initial_parent_frame,
786 ));
787 Self { core }
788 }
789
790 pub fn new(
791 slots: Rc<SlotsHost>,
792 applier: Rc<dyn ApplierHost>,
793 runtime: RuntimeHandle,
794 observer: SnapshotStateObserver,
795 root: Option<NodeId>,
796 ) -> Self {
797 Self::new_with_shared_state_with_parent_frame(
798 slots
799 .runtime_state()
800 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
801 slots,
802 applier,
803 runtime,
804 observer,
805 root,
806 InitialParentFrame::RealParent,
807 )
808 }
809
810 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
811 Self { core }
812 }
813
814 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
815 Rc::clone(&self.core)
816 }
817
818 fn observer(&self) -> SnapshotStateObserver {
819 self.core.observer.clone()
820 }
821
822 pub(crate) fn request_root_render(&self) {
823 self.core.root_render_requested.set(true);
824 }
825
826 pub(crate) fn take_root_render_request(&self) -> bool {
827 self.core.root_render_requested.replace(false)
828 }
829
830 pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
831 let observer = self.observer();
832 let scope_clone = scope.clone();
833 observer.observe_reads(scope_clone, super::RecomposeScope::invalidate, block)
834 }
835
836 pub fn active_slots_host(&self) -> Rc<SlotsHost> {
837 self.core
838 .slot_hosts
839 .borrow()
840 .last()
841 .cloned()
842 .unwrap_or_else(|| Rc::clone(&self.core.slots))
843 }
844
845 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
846 let host = self.active_slots_host();
847 let slots = host.borrow();
848 f(&slots)
849 }
850
851 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
852 let host = self.active_slots_host();
853 let mut slots = host.borrow_mut();
854 f(&mut slots)
855 }
856
857 pub(crate) fn with_slot_session_mut<R>(
858 &self,
859 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
860 ) -> R {
861 self.active_slots_host().with_write_session(f)
862 }
863
864 pub(crate) fn try_with_slot_host_pass<R>(
865 &self,
866 slots: Rc<SlotsHost>,
867 mode: crate::slot::SlotPassMode,
868 f: impl FnOnce(&Composer) -> R,
869 ) -> Result<(R, SlotPassOutcome), NodeError> {
870 let mut guard = self.begin_slot_host_pass(&slots, mode);
871 let result = f(self);
872 let outcome = self.finish_slot_host_pass(&guard.host)?;
873 guard.close();
874 Ok((result, outcome))
875 }
876
877 pub(crate) fn with_slot_host_pass<R>(
878 &self,
879 slots: Rc<SlotsHost>,
880 mode: crate::slot::SlotPassMode,
881 f: impl FnOnce(&Composer) -> R,
882 ) -> (R, SlotPassOutcome) {
883 let mut guard = self.begin_slot_host_pass(&slots, mode);
884 let result = f(self);
885 let outcome = match self.finish_slot_host_pass(&guard.host) {
886 Ok(outcome) => outcome,
887 Err(err) => {
888 log::error!("slot host pass finalization failed: {err}");
889 SlotPassOutcome::default()
890 }
891 };
892 guard.close();
893 (result, outcome)
894 }
895
896 pub(crate) fn with_slot_override<R>(
897 &self,
898 slots: Rc<SlotsHost>,
899 f: impl FnOnce(&Composer) -> R,
900 ) -> (R, SlotPassOutcome) {
901 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
902 }
903
904 fn begin_slot_host_pass(
905 &self,
906 slots: &Rc<SlotsHost>,
907 mode: crate::slot::SlotPassMode,
908 ) -> SlotHostPassGuard {
909 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
910 slots.begin_pass(mode);
911 {
912 let mut stack = self.core.slot_hosts.borrow_mut();
913 if let Some(parent) = stack.last()
914 && !Rc::ptr_eq(parent, &slots)
915 {
916 parent.note_nested_host(&slots);
917 }
918 stack.push(Rc::clone(&slots));
919 }
920 SlotHostPassGuard {
921 core: self.clone_core(),
922 host: slots,
923 active: true,
924 }
925 }
926
927 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
928 let finished = {
929 let mut applier = self.core.applier.borrow_dyn();
930 slots.finish_pass(&mut *applier)
931 }?;
932 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
933 self.wake_sites_whose_movable_arrived();
934 self.evict_retained_subtrees_for_host(slots)?;
935 slots.complete_pass_cleanup(&finished.outcome);
936 Ok(finished.outcome)
937 }
938
939 fn wake_sites_whose_movable_arrived(&self) {
940 for host in self.core.shared_state.hosts_awaiting_movables() {
941 self.wake_sites_in_host(&host);
942 }
943 }
944
945 fn wake_sites_in_host(&self, slots: &Rc<SlotsHost>) {
946 let pending = self.core.shared_state.take_pending_movables(slots);
947 if pending.is_empty() {
948 return;
949 }
950 let mut waiting = Vec::new();
951 for site in pending {
952 if !slots.borrow().group_is_active(site.placeholder) {
953 continue;
954 }
955 let retain_key = RetainKey::for_group(None, site.key);
956 if !self.core.shared_state.movable_retained_anywhere(retain_key) {
957 waiting.push(site);
958 continue;
959 }
960 match site.parent_scope.and_then(|id| self.scope_for_id(id)) {
961 Some(scope) => {
962 scope.force_recompose();
963 scope.invalidate();
964 }
965 None => log::error!(
966 "movable content {:?} arrived for a site whose scope is gone",
967 site.key
968 ),
969 }
970 }
971 self.core.shared_state.keep_pending_movables(slots, waiting);
972 }
973
974 pub(crate) fn forget_movables(&self, ids: &[Key]) -> Result<(), NodeError> {
975 for id in ids {
976 let Some((host, subtree)) = self.core.shared_state.take_retained_movable(*id) else {
977 continue;
978 };
979 self.dispose_detached_subtree_in_host(&host, subtree)?;
980 host.flush_pending_drops();
981 }
982 Ok(())
983 }
984
985 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
986 self.core.parent_stack.borrow_mut()
987 }
988
989 pub(crate) fn current_parent_hint(&self) -> Option<NodeId> {
990 let stack = self.core.parent_stack.borrow();
991 let stack_hint = stack
992 .last()
993 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
994 stack_hint.or_else(|| self.core.recompose_parent_hint.get())
995 }
996
997 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
998 self.core.subcompose_stack.borrow_mut()
999 }
1000
1001 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
1002 self.core.commands.borrow_mut()
1003 }
1004
1005 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
1006 self.commands_mut().push(Command::BubbleDirty {
1007 node_id: id,
1008 bubble: DirtyBubble::SEMANTICS,
1009 });
1010 }
1011
1012 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
1013 self.core.scope_stack.borrow_mut()
1014 }
1015
1016 fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
1017 self.core.shared_state.scope_for_id(scope_id)
1018 }
1019
1020 fn register_scope(&self, scope: &RecomposeScope) {
1021 self.core.shared_state.register_scope(scope);
1022 }
1023
1024 fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
1025 self.core.shared_state.remove_scope(scope_id)
1026 }
1027
1028 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
1029 self.core.local_stack.borrow_mut()
1030 }
1031
1032 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
1033 self.core.local_stack.borrow().clone()
1034 }
1035
1036 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
1037 self.core.side_effects.borrow_mut()
1038 }
1039
1040 fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
1041 self.core.pending_scope_options.borrow_mut()
1042 }
1043
1044 pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
1045 self.core.applier.borrow_dyn()
1046 }
1047
1048 pub fn record_rebound_slot_children(&self, children: &[NodeId]) {
1059 let mut applier = self.borrow_applier();
1060 for &child in children {
1061 applier.record_structural_change(child);
1062 }
1063 }
1064
1065 pub fn register_virtual_node(
1072 &self,
1073 node_id: NodeId,
1074 node: Box<dyn Node>,
1075 ) -> Result<(), NodeError> {
1076 let mut applier = self.borrow_applier();
1077 applier.insert_with_id(node_id, node)
1078 }
1079
1080 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
1083 let mut applier = self.borrow_applier();
1084 match applier.get_mut(node_id) {
1085 Ok(node) => node.parent().is_none(),
1086 Err(_) => true,
1087 }
1088 }
1089
1090 pub fn node_parent(&self, node_id: NodeId) -> Result<Option<NodeId>, NodeError> {
1094 self.borrow_applier()
1095 .get_mut(node_id)
1096 .map(|node| node.parent())
1097 }
1098
1099 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
1104 let mut applier = self.borrow_applier();
1105 match applier.get_mut(node_id) {
1106 Ok(node) => {
1107 let mut children = SmallVec::<[NodeId; 8]>::new();
1108 node.collect_children_into(&mut children);
1109 children
1110 }
1111 Err(_) => SmallVec::<[NodeId; 8]>::new(),
1112 }
1113 }
1114
1115 pub fn nodes_need_measure(&self, node_ids: &[NodeId]) -> bool {
1116 let mut applier = self.borrow_applier();
1117 node_ids.iter().any(|node_id| {
1118 applier
1119 .get_mut(*node_id)
1120 .is_ok_and(|node| node.needs_measure())
1121 })
1122 }
1123
1124 pub fn nodes_need_layout(&self, node_ids: &[NodeId]) -> bool {
1132 let mut applier = self.borrow_applier();
1133 node_ids.iter().any(|node_id| {
1134 applier
1135 .get_mut(*node_id)
1136 .is_ok_and(|node| node.needs_layout())
1137 })
1138 }
1139
1140 pub fn record_subcompose_child(&self, child_id: NodeId) {
1150 let mut parent_stack = self.parent_stack();
1151 if let Some(frame) = parent_stack.last_mut()
1152 && matches!(frame.attach_mode, ParentAttachMode::DeferredSync)
1153 {
1154 if let Some(membership) = frame.new_children_membership.as_mut() {
1155 if membership.insert(child_id) {
1156 frame.new_children.push(child_id);
1157 }
1158 } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
1159 let mut membership = HashSet::default();
1160 membership.reserve(frame.new_children.len() + 1);
1161 membership.extend(frame.new_children.iter().copied());
1162 if membership.insert(child_id) {
1163 frame.new_children.push(child_id);
1164 }
1165 frame.new_children_membership = Some(membership);
1166 } else if !frame.new_children.contains(&child_id) {
1167 frame.new_children.push(child_id);
1168 }
1169 }
1170 }
1171
1172 pub fn clear_node_children(&self, node_id: NodeId) {
1178 let mut applier = self.borrow_applier();
1179 if let Ok(node) = applier.get_mut(node_id) {
1180 node.update_children(&[]);
1181 }
1182 }
1183
1184 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
1185 let _composer_guard = composer_context::enter(self);
1186 runtime::push_active_runtime(&self.core.runtime);
1187 struct Guard;
1188 impl Drop for Guard {
1189 fn drop(&mut self) {
1190 runtime::pop_active_runtime();
1191 }
1192 }
1193 let guard = Guard;
1194 let result = f(self);
1195 drop(guard);
1196 result
1197 }
1198
1199 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
1200 let queued = self.core.commands.borrow().len();
1201 if queued < COMMAND_FLUSH_THRESHOLD {
1202 return Ok(());
1203 }
1204 self.apply_pending_commands()
1205 }
1206
1207 fn resolve_group_entry(
1208 &self,
1209 seed: crate::slot::GroupKeySeed,
1210 parent_scope_id: Option<ScopeId>,
1211 ) -> GroupEntry {
1212 let host = self.active_slots_host();
1213 let key = self.with_slot_session_mut(|slots| slots.reserve_group_key(seed));
1214 let retain_key = RetainKey::for_group(parent_scope_id, key);
1215 let restored = self
1216 .core
1217 .shared_state
1218 .take_retained(&host, retain_key, |subtree| {
1219 self.with_slot_session_mut(|slots| slots.retained_restore_ready(key, subtree))
1220 })
1221 .or_else(|| self.take_movable_from_another_table(&host, retain_key, key));
1222 if restored.is_some() || !key.is_movable() {
1223 return GroupEntry {
1224 key,
1225 restored,
1226 placeholder_for: None,
1227 };
1228 }
1229 let attached_elsewhere = self.movable_attached_elsewhere(&host, key);
1230 if !attached_elsewhere {
1231 return GroupEntry {
1232 key,
1233 restored: None,
1234 placeholder_for: None,
1235 };
1236 }
1237 let id = key.explicit_key.unwrap_or_default();
1238 let placeholder = self.with_slot_session_mut(|slots| {
1239 slots.reserve_group_key(crate::slot::GroupKeySeed::movable_placeholder(id))
1240 });
1241 GroupEntry {
1242 key: placeholder,
1243 restored: None,
1244 placeholder_for: Some(key),
1245 }
1246 }
1247
1248 fn take_movable_from_another_table(
1253 &self,
1254 host: &Rc<SlotsHost>,
1255 retain_key: RetainKey,
1256 key: crate::slot::GroupKey,
1257 ) -> Option<crate::slot::DetachedSubtree> {
1258 if !key.is_movable() {
1259 return None;
1260 }
1261 let (source, mut subtree) = self
1262 .core
1263 .shared_state
1264 .take_retained_movable_elsewhere(host, retain_key)?;
1265 source
1266 .borrow_mut()
1267 .invalidate_detached_subtree_anchors(&subtree);
1268 if self.with_slot_session_mut(|slots| slots.retained_restore_ready(key, &mut subtree)) {
1269 return Some(subtree);
1270 }
1271 log::error!(
1272 "movable content {key:?} could not be taken over by the slot table that asked for it"
1273 );
1274 if let Err(error) = self.dispose_detached_subtree_in_host(host, subtree) {
1275 log::error!("disposing movable content that could not move failed: {error}");
1276 }
1277 None
1278 }
1279
1280 fn movable_attached_elsewhere(&self, host: &Rc<SlotsHost>, key: crate::slot::GroupKey) -> bool {
1283 if self.with_slot_session_mut(|slots| slots.movable_attached_elsewhere(key)) {
1284 return true;
1285 }
1286 let Some(id) = key.movable_id() else {
1287 return false;
1288 };
1289 self.core
1290 .shared_state
1291 .host_holding_movable(id)
1292 .is_some_and(|holder| !Rc::ptr_eq(&holder, host))
1293 }
1294
1295 fn scope_for_started_group(
1296 &self,
1297 group: crate::slot::ActiveGroupId,
1298 scope_id: Option<ScopeId>,
1299 ) -> RecomposeScope {
1300 if let Some(scope) = scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
1301 return scope;
1302 }
1303 let scope = RecomposeScope::new(self.runtime_handle());
1304 self.register_scope(&scope);
1305 self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
1306 scope
1307 }
1308
1309 fn enter_group_scope(&self, scope_ref: &RecomposeScope, entry: GroupScopeEntry<'_>) {
1310 let GroupScopeEntry {
1311 parent_scope,
1312 options,
1313 start_kind,
1314 host,
1315 restored_scopes,
1316 } = entry;
1317 let lifetime_owner_scope = if parent_scope.is_none() {
1318 self.core.subcomposition_owner_scope.borrow().clone()
1319 } else {
1320 None
1321 };
1322 scope_ref.reactivate();
1323 scope_ref.set_parent_scope(parent_scope);
1324 scope_ref.set_lifetime_owner_scope(lifetime_owner_scope);
1325 scope_ref.set_retention_mode(options.retention);
1326
1327 if options.force_recompose {
1328 scope_ref.force_recompose();
1329 } else if options.force_reuse {
1330 scope_ref.force_reuse();
1331 }
1332 if matches!(start_kind, GroupStartKind::Restored) {
1333 scope_ref.force_recompose();
1334 }
1335
1336 scope_ref.set_slots_host(host);
1337
1338 {
1339 let mut stack = self.scope_stack();
1340 stack.push(scope_ref.clone());
1341 }
1342
1343 {
1344 let mut stack = self.subcompose_stack();
1345 if let Some(frame) = stack.last_mut() {
1346 frame.scopes.push(scope_ref.clone());
1347 }
1348 }
1349
1350 scope_ref.snapshot_locals(self.current_local_stack());
1351 let parent_hint = self.current_parent_hint();
1352 if let Some(restored_scopes) = restored_scopes {
1353 self.reparent_restored_scopes(scope_ref, &restored_scopes, parent_hint);
1354 }
1355 scope_ref.set_parent_hint(parent_hint);
1356 }
1357
1358 fn reparent_restored_scopes(
1359 &self,
1360 root: &RecomposeScope,
1361 restored_scopes: &[ScopeId],
1362 parent_hint: Option<NodeId>,
1363 ) {
1364 let old_hint = root.parent_hint();
1365 for scope in restored_scopes
1366 .iter()
1367 .filter_map(|scope_id| self.scope_for_id(*scope_id))
1368 {
1369 if scope.parent_hint() == old_hint {
1370 scope.set_parent_hint(parent_hint);
1371 }
1372 scope.reactivate();
1373 }
1374 }
1375
1376 #[inline(never)]
1377 fn with_group_in_active_pass_dyn(
1378 &self,
1379 key: crate::slot::GroupKeySeed,
1380 f: &mut dyn FnMut(&Composer),
1381 ) {
1382 struct GroupGuard {
1383 composer: Composer,
1384 scope: RecomposeScope,
1385 }
1386
1387 impl Drop for GroupGuard {
1388 fn drop(&mut self) {
1389 self.composer
1390 .close_current_group_body_for_scope(&self.scope);
1391 self.scope.mark_recomposed();
1392 #[expect(
1393 clippy::redundant_closure_for_method_calls,
1394 reason = "the method path is not general over the session lifetime"
1395 )]
1396 self.composer
1397 .with_slot_session_mut(|slots| slots.end_group());
1398 if let Err(err) = self.composer.flush_pending_commands_if_large() {
1399 log::error!("mid-composition command flush failed: {err}");
1400 }
1401 }
1402 }
1403
1404 let parent_scope = self.current_recompose_scope();
1405 let options = self.pending_scope_options().take().unwrap_or_default();
1406 let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
1407 let host = self.active_slots_host();
1408 let GroupEntry {
1409 key: reserved_key,
1410 restored,
1411 placeholder_for,
1412 } = self.resolve_group_entry(key, parent_scope_id);
1413 let restored_scopes = restored
1414 .as_ref()
1415 .map(crate::slot::DetachedSubtree::scope_ids);
1416 let parent_node = self.current_parent_hint();
1417 let (group, anchor, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
1418 let GroupStart {
1419 group,
1420 anchor,
1421 scope_id,
1422 kind,
1423 } = slots.begin_group(reserved_key, restored, parent_node);
1424 (group, anchor, scope_id, kind)
1425 });
1426 let scope_ref = self.scope_for_started_group(group, start_scope_id);
1427 self.enter_group_scope(
1428 &scope_ref,
1429 GroupScopeEntry {
1430 parent_scope,
1431 options,
1432 start_kind,
1433 host: &host,
1434 restored_scopes,
1435 },
1436 );
1437 if let Some(movable_key) = placeholder_for {
1438 self.core.shared_state.record_pending_movable(
1439 &host,
1440 PendingMovable {
1441 key: movable_key,
1442 placeholder: anchor,
1443 parent_scope: parent_scope_id,
1444 },
1445 );
1446 }
1447
1448 let guard = GroupGuard {
1449 composer: self.clone(),
1450 scope: scope_ref.clone(),
1451 };
1452 if placeholder_for.is_none() {
1453 self.observe_scope(&scope_ref, || f(self));
1454 }
1455 scope_ref.mark_composed_once();
1456 drop(guard);
1457 }
1458
1459 fn with_group_seed_dyn(&self, key: crate::slot::GroupKeySeed, f: &mut dyn FnMut(&Composer)) {
1460 let host = self.active_slots_host();
1461 if host.has_active_pass() {
1462 self.with_group_in_active_pass_dyn(key, f);
1463 return;
1464 }
1465 self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1466 composer.with_group_in_active_pass_dyn(key, f);
1467 });
1468 }
1469
1470 pub(crate) fn with_group_seed<R>(
1471 &self,
1472 key: crate::slot::GroupKeySeed,
1473 f: impl FnOnce(&Composer) -> R,
1474 ) -> R {
1475 let mut f = Some(f);
1476 let mut result = None;
1477 self.with_group_seed_dyn(key, &mut |composer| {
1478 let f = f.take().expect("group body must run at most once");
1479 result = Some(f(composer));
1480 });
1481 result.expect("group body must run exactly once")
1482 }
1483
1484 pub(crate) fn with_movable_group(&self, id: Key, f: impl FnOnce(&Composer)) {
1485 let mut f = Some(f);
1486 self.with_group_seed_dyn(crate::slot::GroupKeySeed::movable(id), &mut |composer| {
1487 if let Some(f) = f.take() {
1488 f(composer);
1489 }
1490 });
1491 }
1492
1493 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1494 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1495 }
1496
1497 pub fn cranpose_with_reuse<R>(
1498 &self,
1499 key: Key,
1500 mut options: RecomposeOptions,
1501 f: impl FnOnce(&Composer) -> R,
1502 ) -> R {
1503 options.retention = RetentionMode::RetainWhenInactive;
1504 self.pending_scope_options().replace(options);
1505 self.with_group(key, f)
1506 }
1507
1508 #[track_caller]
1509 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1510 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1511 self.with_group_seed(seed, f)
1512 }
1513
1514 #[doc(hidden)]
1515 pub fn __branch_group_deferred(&self, key: Key) -> BranchGroupGuard {
1516 BranchGroupGuard {
1517 composer: self.clone(),
1518 fold_token: self.active_slots_host().try_push_branch_fold(key),
1519 }
1520 }
1521
1522 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1523 for node_id in nodes {
1524 self.commands_mut().push(Command::callback(move |applier| {
1525 crate::slot::dispose_detached_node_now(applier, node_id)
1526 }));
1527 }
1528 }
1529
1530 fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1531 for scope_id in scope_ids {
1532 if let Some(scope) = self.scope_for_id(scope_id) {
1533 scope.deactivate();
1534 }
1535 }
1536 }
1537
1538 fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1539 for scope_id in scope_ids {
1540 if let Some(scope) = self.remove_scope(scope_id) {
1541 scope.deactivate();
1542 }
1543 }
1544 }
1545
1546 fn detached_root_parent_commands(
1547 &self,
1548 subtree: &crate::slot::DetachedSubtree,
1549 context: &'static str,
1550 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1551 let mut root_nodes = Vec::new();
1552 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1553 let mut roots = Vec::with_capacity(root_nodes.len());
1554 for root in root_nodes {
1555 let parent_id = {
1556 let mut applier = self.borrow_applier();
1557 applier.get_mut(root)?.parent()
1558 };
1559 roots.push((root, parent_id));
1560 }
1561 Ok(roots)
1562 }
1563
1564 fn retain_detached_subtree_in_host(
1565 &self,
1566 slots_host: &Rc<SlotsHost>,
1567 parent_scope: Option<ScopeId>,
1568 subtree: crate::slot::DetachedSubtree,
1569 ) -> Result<(), NodeError> {
1570 let Some(root_key) = subtree.root_key_checked() else {
1571 log::error!("retention rejected detached subtree without a root group");
1572 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1573 return Ok(());
1574 };
1575 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1576 self.deactivate_scope_ids(subtree.scope_ids_iter());
1577 for (root, parent_id) in root_detaches {
1578 if let Some(parent_id) = parent_id {
1579 self.commands_mut().push(Command::DetachChild {
1580 parent_id,
1581 child_id: root,
1582 });
1583 }
1584 }
1585 let evicted = self.core.shared_state.insert_retained(
1586 slots_host,
1587 RetainKey::for_group(parent_scope, root_key),
1588 subtree,
1589 root_key.is_movable(),
1590 );
1591 for subtree in evicted {
1592 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1593 }
1594 Ok(())
1595 }
1596
1597 fn evict_retained_subtrees_for_host(
1598 &self,
1599 slots_host: &Rc<SlotsHost>,
1600 ) -> Result<(), NodeError> {
1601 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1602 for subtree in evicted {
1603 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1604 }
1605 Ok(())
1606 }
1607
1608 fn dispose_detached_subtree_in_host(
1609 &self,
1610 slots_host: &Rc<SlotsHost>,
1611 subtree: crate::slot::DetachedSubtree,
1612 ) -> Result<(), NodeError> {
1613 let root_nodes = self
1614 .detached_root_parent_commands(&subtree, "disposal")?
1615 .into_iter()
1616 .map(|(root, _)| root);
1617 self.dispose_scope_ids(subtree.scope_ids_iter());
1618 self.dispose_detached_nodes(root_nodes);
1619 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1620 table.invalidate_detached_subtree_anchors(&subtree);
1621 lifecycle.queue_subtree_disposal(subtree);
1622 });
1623 Ok(())
1624 }
1625
1626 fn handle_detached_children_in_host(
1627 &self,
1628 slots_host: &Rc<SlotsHost>,
1629 parent_scope: Option<ScopeId>,
1630 detached: Vec<crate::slot::DetachedSubtree>,
1631 ) -> Result<(), NodeError> {
1632 for mut subtree in detached {
1633 for movable in subtree.split_off_nested_movables() {
1634 self.retain_detached_subtree_in_host(slots_host, None, movable)?;
1635 }
1636 if subtree
1637 .root_key_checked()
1638 .is_some_and(crate::slot::GroupKey::is_movable)
1639 {
1640 self.retain_detached_subtree_in_host(slots_host, None, subtree)?;
1641 continue;
1642 }
1643 let retention_mode = subtree
1644 .root_scope_id()
1645 .and_then(|scope_id| self.scope_for_id(scope_id))
1646 .map(|scope| scope.retention_mode())
1647 .unwrap_or_default();
1648 match retention_mode {
1649 RetentionMode::DisposeWhenInactive => {
1650 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1651 }
1652 RetentionMode::RetainWhenInactive => {
1653 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?;
1654 }
1655 }
1656 }
1657 Ok(())
1658 }
1659
1660 fn handle_detached_children(
1661 &self,
1662 parent_scope: Option<ScopeId>,
1663 detached: Vec<crate::slot::DetachedSubtree>,
1664 ) {
1665 let host = self.active_slots_host();
1666 if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1667 log::error!("detached subtree handling failed while closing a group: {err}");
1668 }
1669 }
1670
1671 fn handle_finished_group_result(
1672 &self,
1673 parent_scope: Option<ScopeId>,
1674 result: FinishGroupResult,
1675 ) {
1676 let FinishGroupResult {
1677 detached_children,
1678 direct_nodes,
1679 root_nodes,
1680 was_skipped,
1681 } = result;
1682 if was_skipped {
1683 self.attach_root_nodes(root_nodes);
1684 }
1685 self.dispose_detached_nodes(direct_nodes);
1686 self.handle_detached_children(parent_scope, detached_children);
1687 }
1688
1689 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1690 #[expect(
1691 clippy::redundant_closure_for_method_calls,
1692 reason = "the method path is not general over the session lifetime"
1693 )]
1694 let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1695 self.handle_finished_group_result(Some(scope.id()), result);
1696 if let Some(popped) = self.scope_stack().pop() {
1697 debug_assert_eq!(
1698 popped.id(),
1699 scope.id(),
1700 "closed scope must match the active scope stack"
1701 );
1702 } else {
1703 log::error!("scope stack underflow while closing scope {}", scope.id());
1704 }
1705 }
1706
1707 #[track_caller]
1708 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1709 self.remember_at(crate::caller_location_key(), init)
1710 }
1711
1712 #[doc(hidden)]
1713 pub fn remember_at<T: 'static>(
1714 &self,
1715 source: crate::Key,
1716 init: impl FnOnce() -> T,
1717 ) -> Owned<T> {
1718 self.with_slot_session_mut(|slots| {
1719 slots.remember_with_kind(PayloadKind::Remember, source, init)
1720 })
1721 }
1722
1723 #[track_caller]
1724 pub(crate) fn remember_internal<T: 'static>(
1725 &self,
1726 source_salt: crate::Key,
1727 init: impl FnOnce() -> T,
1728 ) -> Owned<T> {
1729 let source = crate::caller_location_key() ^ source_salt;
1730 self.with_slot_session_mut(|slots| {
1731 slots.remember_with_kind(PayloadKind::Internal, source, init)
1732 })
1733 }
1734
1735 #[track_caller]
1736 pub(crate) fn remember_effect<T: Default + 'static>(&self) -> Owned<T> {
1737 let source = crate::caller_location_key();
1738 self.with_slot_session_mut(|slots| slots.remember_effect::<T>(source))
1739 }
1740
1741 #[track_caller]
1742 pub fn use_value_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> ValueSlotHandle<'_, T> {
1743 let source = crate::caller_location_key();
1744 let slot = self.with_slot_session_mut(|slots| {
1745 slots.value_slot_with_kind(PayloadKind::Internal, source, init)
1746 });
1747 ValueSlotHandle::new(slot)
1748 }
1749
1750 #[doc(hidden)]
1751 #[track_caller]
1752 pub fn __use_param_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> ValueSlotHandle<'_, T> {
1753 let source = crate::caller_location_key();
1754 let slot = self.with_slot_session_mut(|slots| {
1755 slots.value_slot_with_kind(PayloadKind::Param, source, init)
1756 });
1757 ValueSlotHandle::new(slot)
1758 }
1759
1760 #[doc(hidden)]
1761 #[track_caller]
1762 pub fn __use_return_slot<T: 'static>(
1763 &self,
1764 init: impl FnOnce() -> T,
1765 ) -> ValueSlotHandle<'_, T> {
1766 let source = crate::caller_location_key();
1767 let slot = self.with_slot_session_mut(|slots| {
1768 slots.value_slot_with_kind(PayloadKind::Return, source, init)
1769 });
1770 ValueSlotHandle::new(slot)
1771 }
1772
1773 #[doc(hidden)]
1774 pub fn __invalidate_return_consumer_scope(&self) {
1775 let Some(scope) = self.current_recompose_scope() else {
1776 self.request_root_render();
1777 return;
1778 };
1779
1780 if let Some(target) = scope.callback_promotion_target() {
1781 target.invalidate();
1782 } else {
1783 self.request_root_render();
1784 }
1785 }
1786
1787 pub fn with_slot_value<'pass, T: 'static, R>(
1788 &'pass self,
1789 handle: ValueSlotHandle<'pass, T>,
1790 f: impl FnOnce(&T) -> R,
1791 ) -> R {
1792 self.with_slots(|slots| f(slots.read_value(handle.slot())))
1793 }
1794
1795 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1796 &'pass self,
1797 handle: ValueSlotHandle<'pass, T>,
1798 f: impl FnOnce(&mut T) -> R,
1799 ) -> R {
1800 self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1801 }
1802
1803 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1804 MutableState::with_runtime(initial, self.runtime_handle())
1805 }
1806
1807 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1808 where
1809 T: Clone + 'static,
1810 I: IntoIterator<Item = T>,
1811 {
1812 SnapshotStateList::with_runtime(values, self.runtime_handle())
1813 }
1814
1815 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1816 where
1817 K: Clone + Eq + Hash + 'static,
1818 V: Clone + 'static,
1819 I: IntoIterator<Item = (K, V)>,
1820 {
1821 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1822 }
1823
1824 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1825 let stack = self.core.local_stack.borrow();
1826 for context in stack.iter().rev() {
1827 if let Some(entry) = context.values.get(&local.key) {
1828 match entry.clone().downcast::<LocalStateEntry<T>>() {
1829 Ok(typed) => return typed.value(),
1830 Err(_) => {
1831 log::error!(
1832 "composition local entry type mismatch for key {:?}",
1833 local.key
1834 );
1835 return local.default_value();
1836 }
1837 }
1838 }
1839 }
1840 local.default_value()
1841 }
1842
1843 pub fn read_static_composition_local<T: Clone + 'static>(
1844 &self,
1845 local: &StaticCompositionLocal<T>,
1846 ) -> T {
1847 let stack = self.core.local_stack.borrow();
1848 for context in stack.iter().rev() {
1849 if let Some(entry) = context.values.get(&local.key) {
1850 match entry.clone().downcast::<StaticLocalEntry<T>>() {
1851 Ok(typed) => return typed.value(),
1852 Err(_) => {
1853 log::error!(
1854 "static composition local entry type mismatch for key {:?}",
1855 local.key
1856 );
1857 return local.default_value();
1858 }
1859 }
1860 }
1861 }
1862 local.default_value()
1863 }
1864
1865 pub fn current_recompose_scope(&self) -> Option<RecomposeScope> {
1866 self.core.scope_stack.borrow().last().cloned()
1867 }
1868
1869 pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1870 let stack = self.core.scope_stack.borrow();
1871 stack
1872 .iter()
1873 .rev()
1874 .find(|scope| scope.has_recompose_callback())
1875 .cloned()
1876 .or_else(|| stack.last().cloned())
1877 }
1878
1879 pub fn phase(&self) -> crate::Phase {
1880 self.core.phase.get()
1881 }
1882
1883 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1884 self.core.phase.set(phase);
1885 }
1886
1887 pub fn enter_phase(&self, phase: crate::Phase) {
1888 self.set_phase(phase);
1889 }
1890
1891 pub(crate) fn subcompose<R>(
1892 &self,
1893 state: &mut SubcomposeState,
1894 slot_id: SlotId,
1895 content: impl FnOnce(&Composer) -> R,
1896 ) -> (R, Vec<NodeId>) {
1897 match self.phase() {
1898 crate::Phase::Measure | crate::Phase::Layout => {}
1899 current => panic!(
1900 "subcompose() may only be called during measure or layout; current phase: {current:?}"
1901 ),
1902 }
1903
1904 self.subcompose_stack().push(SubcomposeFrame::default());
1905 let mut guard = SubcomposeStackGuard {
1906 core: self.clone_core(),
1907 leaked: false,
1908 };
1909
1910 let slot_host = state.get_or_create_slots(slot_id);
1911 let (result, _) = self.with_slot_override(slot_host, |composer| {
1912 composer.with_group(slot_id.raw(), |composer| content(composer))
1913 });
1914
1915 let frame = {
1916 let frame = take_subcompose_frame(&guard.core, "subcompose");
1917 guard.leaked = true;
1918 frame
1919 };
1920 let nodes = frame.nodes;
1921 let scopes = frame.scopes;
1922 state.register_active(slot_id, &nodes, &scopes);
1923 (result, nodes)
1924 }
1925
1926 pub fn subcompose_measurement<R>(
1927 &self,
1928 state: &mut SubcomposeState,
1929 slot_id: SlotId,
1930 content: impl FnOnce(&Composer) -> R,
1931 ) -> (R, Vec<NodeId>) {
1932 let (result, nodes) = self.subcompose(state, slot_id, content);
1933 let roots = nodes
1934 .into_iter()
1935 .filter(|&id| self.node_has_no_parent(id))
1936 .collect();
1937
1938 (result, roots)
1939 }
1940
1941 fn spin_up_subcompose_core(
1942 &self,
1943 slots: &Rc<SlotsHost>,
1944 root: Option<NodeId>,
1945 runtime_handle: &RuntimeHandle,
1946 locals: LocalStackSnapshot,
1947 ) -> Rc<ComposerCore> {
1948 let phase = self.phase();
1949 let shared_state = slots
1950 .runtime_state()
1951 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1952 let core = Rc::new(ComposerCore::new(
1953 shared_state,
1954 Rc::clone(slots),
1955 Rc::clone(&self.core.applier),
1956 runtime_handle.clone(),
1957 self.observer(),
1958 root,
1959 InitialParentFrame::RealParent,
1960 ));
1961 core.phase.set(phase);
1962 *core.local_stack.borrow_mut() = locals;
1963 core
1964 }
1965
1966 fn flush_subcompose_pass(
1967 &self,
1968 commands: CommandQueue,
1969 runtime_handle: &RuntimeHandle,
1970 compact_applier: bool,
1971 side_effects: Vec<Box<dyn FnOnce()>>,
1972 ) -> Result<(), NodeError> {
1973 {
1974 let mut applier = self.borrow_applier();
1975 commands.apply(&mut *applier)?;
1976 for update in runtime_handle.take_updates() {
1977 update.apply(&mut *applier)?;
1978 }
1979 }
1980 if compact_applier {
1981 self.core.applier.compact();
1982 self.core.applier.borrow_dyn().clear_recycled_nodes();
1983 }
1984 composer_context::without_composer(|| {
1985 for effect in side_effects {
1986 effect();
1987 }
1988 });
1989 Ok(())
1990 }
1991
1992 pub fn subcompose_in<R>(
1993 &self,
1994 slots: &Rc<SlotsHost>,
1995 root: Option<NodeId>,
1996 f: impl FnOnce(&Composer) -> R,
1997 ) -> Result<R, NodeError> {
1998 let runtime_handle = self.runtime_handle();
1999 let locals = self.current_local_stack();
2000 let core = self.spin_up_subcompose_core(slots, root, &runtime_handle, locals);
2001 let composer = Composer::from_core(core);
2002 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
2003 let (output, outcome) = composer.try_with_slot_host_pass(
2004 Rc::clone(slots),
2005 crate::slot::SlotPassMode::Compose,
2006 |composer| f(composer),
2007 )?;
2008 let commands = composer.take_commands();
2009 let side_effects = composer.take_side_effects();
2010 Ok((output, commands, side_effects, outcome.compacted))
2011 })?;
2012 self.flush_subcompose_pass(commands, &runtime_handle, compact_applier, side_effects)?;
2013 Ok(result)
2014 }
2015
2016 pub fn capture_composition_context(&self) -> CapturedCompositionContext {
2027 CapturedCompositionContext {
2028 locals: self.current_local_stack(),
2029 owner_scope: self
2030 .current_recompose_scope()
2031 .map(|scope| scope.downgrade()),
2032 }
2033 }
2034
2035 pub fn subcompose_slot<R>(
2040 &self,
2041 slots: &Rc<SlotsHost>,
2042 root: Option<NodeId>,
2043 f: impl FnOnce(&Composer) -> R,
2044 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2045 let context = self.capture_composition_context();
2046 self.subcompose_slot_with_context(slots, root, &context, f)
2047 }
2048
2049 pub fn subcompose_slot_with_context<R>(
2053 &self,
2054 slots: &Rc<SlotsHost>,
2055 root: Option<NodeId>,
2056 context: &CapturedCompositionContext,
2057 f: impl FnOnce(&Composer) -> R,
2058 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2059 let runtime_handle = self.runtime_handle();
2060 let locals = context.locals.clone();
2061 let core = self.spin_up_subcompose_core(slots, root, &runtime_handle, locals);
2062 *core.subcomposition_owner_scope.borrow_mut() = context
2063 .owner_scope
2064 .as_ref()
2065 .and_then(Weak::upgrade)
2066 .map(|inner| RecomposeScope { inner });
2067 let composer = Composer::from_core(core);
2068 composer.subcompose_stack().push(SubcomposeFrame::default());
2069 let mut guard = SubcomposeStackGuard {
2070 core: composer.clone_core(),
2071 leaked: false,
2072 };
2073 let root_group_key = crate::location_key(file!(), line!(), column!());
2074 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
2075 let (output, outcome) = composer.try_with_slot_host_pass(
2076 Rc::clone(slots),
2077 crate::slot::SlotPassMode::Compose,
2078 |composer| {
2079 let output = composer.with_group(root_group_key, |composer| f(composer));
2080 if root.is_some() {
2081 composer.pop_parent();
2082 }
2083 output
2084 },
2085 )?;
2086 let commands = composer.take_commands();
2087 let side_effects = composer.take_side_effects();
2088 Ok((output, commands, side_effects, outcome.compacted))
2089 })?;
2090 let frame = {
2091 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
2092 guard.leaked = true;
2093 frame
2094 };
2095
2096 self.flush_subcompose_pass(commands, &runtime_handle, compact_applier, side_effects)?;
2097 Ok((result, frame.scopes))
2098 }
2099
2100 fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
2101 for id in root_nodes {
2102 self.attach_to_parent(id);
2103 }
2104 }
2105
2106 pub fn skip_current_group(&self) {
2107 #[expect(
2108 clippy::redundant_closure_for_method_calls,
2109 reason = "the method path is not general over the session lifetime"
2110 )]
2111 self.with_slot_session_mut(|slots| slots.skip_group());
2112 }
2113
2114 pub fn runtime_handle(&self) -> RuntimeHandle {
2115 self.core.runtime.clone()
2116 }
2117
2118 pub fn set_recompose_callback<F>(&self, callback: F)
2119 where
2120 F: FnMut(&Composer) + 'static,
2121 {
2122 self.set_recompose_callback_boxed(Box::new(callback));
2123 }
2124
2125 #[inline(never)]
2126 fn set_recompose_callback_boxed(&self, mut callback: Box<dyn FnMut(&Composer)>) {
2127 if let Some(scope) = self.current_recompose_scope() {
2128 let observer = self.observer();
2129 let scope_weak = scope.downgrade();
2130 scope.set_recompose(Box::new(move |composer: &Composer| {
2131 if let Some(inner) = scope_weak.upgrade() {
2132 let scope_instance = RecomposeScope { inner };
2133 observer.observe_reads(
2134 scope_instance,
2135 super::RecomposeScope::invalidate,
2136 || {
2137 callback(composer);
2138 },
2139 );
2140 }
2141 }));
2142 }
2143 }
2144
2145 pub fn set_recompose_fn(&self, callback: fn(&Composer)) {
2146 if let Some(scope) = self.current_recompose_scope() {
2147 scope.set_recompose_fn(callback);
2148 }
2149 }
2150
2151 pub fn with_composition_locals<R>(
2152 &self,
2153 provided: Vec<ProvidedValue>,
2154 site: crate::Key,
2155 f: impl FnOnce(&Composer) -> R,
2156 ) -> R {
2157 if provided.is_empty() {
2158 return f(self);
2159 }
2160 let mut context = LocalContext::default();
2161 for value in provided.into_iter().rev() {
2162 if context.values.contains_key(value.key()) {
2163 continue;
2164 }
2165 let (key, entry) = value.into_entry(self, site);
2166 context.values.insert(key, entry);
2167 }
2168 {
2169 let mut stack = self.local_stack();
2170 Rc::make_mut(&mut *stack).push(context);
2171 }
2172 let result = f(self);
2173 {
2174 let mut stack = self.local_stack();
2175 Rc::make_mut(&mut *stack).pop();
2176 }
2177 result
2178 }
2179}