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(|inner| crate::RecomposeScope { inner }.owner_chain_deactivation_epoch())
640 .unwrap_or(0)
641 }
642}
643
644fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
645 match core.subcompose_stack.borrow_mut().pop() {
646 Some(frame) => frame,
647 None => {
648 log::error!("subcompose stack underflow while finishing {operation}");
649 SubcomposeFrame::default()
650 }
651 }
652}
653
654struct SubcomposeStackGuard {
655 core: Rc<ComposerCore>,
656 leaked: bool,
657}
658
659impl Drop for SubcomposeStackGuard {
660 fn drop(&mut self) {
661 if !self.leaked {
662 self.core.subcompose_stack.borrow_mut().pop();
663 }
664 }
665}
666
667impl ComposerCore {
668 pub(crate) fn new(
669 shared_state: Rc<ComposerRuntimeState>,
670 slots: Rc<SlotsHost>,
671 applier: Rc<dyn ApplierHost>,
672 runtime: RuntimeHandle,
673 observer: SnapshotStateObserver,
674 root: Option<NodeId>,
675 initial_parent_frame: InitialParentFrame,
676 ) -> Self {
677 let parent_stack = if let Some(root_id) = root {
678 vec![ParentFrame {
679 id: root_id,
680 previous: ChildList::new(),
681 new_children: ChildList::new(),
682 new_children_membership: None,
683 attach_mode: ParentAttachMode::DeferredSync,
684 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
685 }]
686 } else {
687 Vec::new()
688 };
689
690 Self {
691 shared_state,
692 slots,
693 slot_hosts: RefCell::new(Vec::new()),
694 applier,
695 runtime,
696 observer,
697 parent_stack: RefCell::new(parent_stack),
698 subcompose_stack: RefCell::new(Vec::new()),
699 root: Cell::new(root),
700 commands: RefCell::new(CommandQueue::default()),
701 scope_stack: RefCell::new(Vec::new()),
702 subcomposition_owner_scope: RefCell::new(None),
703 local_stack: RefCell::new(empty_local_stack()),
704 side_effects: RefCell::new(Vec::new()),
705 pending_scope_options: RefCell::new(None),
706 phase: Cell::new(crate::Phase::Compose),
707 last_node_reused: Cell::new(None),
708 recompose_parent_hint: Cell::new(None),
709 recompose_child_cursor: Cell::new(None),
710 root_render_requested: Cell::new(false),
711 _not_send: PhantomData,
712 }
713 }
714}
715
716#[derive(Clone)]
717pub struct Composer {
718 pub(crate) core: Rc<ComposerCore>,
719}
720
721pub struct BranchGroupGuard {
722 composer: Composer,
723 fold_token: Option<usize>,
724}
725
726impl Drop for BranchGroupGuard {
727 fn drop(&mut self) {
728 let Some(token) = self.fold_token else {
729 return;
730 };
731 if !self
732 .composer
733 .active_slots_host()
734 .try_close_branch_fold(token)
735 {
736 log::error!("a branch fold guard closed while its slot host was busy");
737 }
738 }
739}
740
741pub(crate) enum EmittedNode {
742 Fresh(Box<dyn Node>),
743 Recycled(RecycledNode),
744}
745
746impl Composer {
747 pub(crate) fn new_with_shared_state(
748 shared_state: Rc<ComposerRuntimeState>,
749 slots: Rc<SlotsHost>,
750 applier: Rc<dyn ApplierHost>,
751 runtime: RuntimeHandle,
752 observer: SnapshotStateObserver,
753 root: Option<NodeId>,
754 ) -> Self {
755 Self::new_with_shared_state_with_parent_frame(
756 shared_state,
757 slots,
758 applier,
759 runtime,
760 observer,
761 root,
762 InitialParentFrame::SyntheticRoot,
763 )
764 }
765
766 fn new_with_shared_state_with_parent_frame(
767 shared_state: Rc<ComposerRuntimeState>,
768 slots: Rc<SlotsHost>,
769 applier: Rc<dyn ApplierHost>,
770 runtime: RuntimeHandle,
771 observer: SnapshotStateObserver,
772 root: Option<NodeId>,
773 initial_parent_frame: InitialParentFrame,
774 ) -> Self {
775 shared_state.bind_applier_host(&applier);
776 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
777 let core = Rc::new(ComposerCore::new(
778 shared_state,
779 slots,
780 applier,
781 runtime,
782 observer,
783 root,
784 initial_parent_frame,
785 ));
786 Self { core }
787 }
788
789 pub fn new(
790 slots: Rc<SlotsHost>,
791 applier: Rc<dyn ApplierHost>,
792 runtime: RuntimeHandle,
793 observer: SnapshotStateObserver,
794 root: Option<NodeId>,
795 ) -> Self {
796 Self::new_with_shared_state_with_parent_frame(
797 slots
798 .runtime_state()
799 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
800 slots,
801 applier,
802 runtime,
803 observer,
804 root,
805 InitialParentFrame::RealParent,
806 )
807 }
808
809 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
810 Self { core }
811 }
812
813 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
814 Rc::clone(&self.core)
815 }
816
817 fn observer(&self) -> SnapshotStateObserver {
818 self.core.observer.clone()
819 }
820
821 pub(crate) fn request_root_render(&self) {
822 self.core.root_render_requested.set(true);
823 }
824
825 pub(crate) fn take_root_render_request(&self) -> bool {
826 self.core.root_render_requested.replace(false)
827 }
828
829 pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
830 let observer = self.observer();
831 let scope_clone = scope.clone();
832 observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
833 }
834
835 pub fn active_slots_host(&self) -> Rc<SlotsHost> {
836 self.core
837 .slot_hosts
838 .borrow()
839 .last()
840 .cloned()
841 .unwrap_or_else(|| Rc::clone(&self.core.slots))
842 }
843
844 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
845 let host = self.active_slots_host();
846 let slots = host.borrow();
847 f(&slots)
848 }
849
850 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
851 let host = self.active_slots_host();
852 let mut slots = host.borrow_mut();
853 f(&mut slots)
854 }
855
856 pub(crate) fn with_slot_session_mut<R>(
857 &self,
858 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
859 ) -> R {
860 self.active_slots_host().with_write_session(f)
861 }
862
863 pub(crate) fn try_with_slot_host_pass<R>(
864 &self,
865 slots: Rc<SlotsHost>,
866 mode: crate::slot::SlotPassMode,
867 f: impl FnOnce(&Composer) -> R,
868 ) -> Result<(R, SlotPassOutcome), NodeError> {
869 let mut guard = self.begin_slot_host_pass(&slots, mode);
870 let result = f(self);
871 let outcome = self.finish_slot_host_pass(&guard.host)?;
872 guard.close();
873 Ok((result, outcome))
874 }
875
876 pub(crate) fn with_slot_host_pass<R>(
877 &self,
878 slots: Rc<SlotsHost>,
879 mode: crate::slot::SlotPassMode,
880 f: impl FnOnce(&Composer) -> R,
881 ) -> (R, SlotPassOutcome) {
882 let mut guard = self.begin_slot_host_pass(&slots, mode);
883 let result = f(self);
884 let outcome = match self.finish_slot_host_pass(&guard.host) {
885 Ok(outcome) => outcome,
886 Err(err) => {
887 log::error!("slot host pass finalization failed: {err}");
888 SlotPassOutcome::default()
889 }
890 };
891 guard.close();
892 (result, outcome)
893 }
894
895 pub(crate) fn with_slot_override<R>(
896 &self,
897 slots: Rc<SlotsHost>,
898 f: impl FnOnce(&Composer) -> R,
899 ) -> (R, SlotPassOutcome) {
900 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
901 }
902
903 fn begin_slot_host_pass(
904 &self,
905 slots: &Rc<SlotsHost>,
906 mode: crate::slot::SlotPassMode,
907 ) -> SlotHostPassGuard {
908 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
909 slots.begin_pass(mode);
910 {
911 let mut stack = self.core.slot_hosts.borrow_mut();
912 if let Some(parent) = stack.last()
913 && !Rc::ptr_eq(parent, &slots)
914 {
915 parent.note_nested_host(&slots);
916 }
917 stack.push(Rc::clone(&slots));
918 }
919 SlotHostPassGuard {
920 core: self.clone_core(),
921 host: slots,
922 active: true,
923 }
924 }
925
926 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
927 let finished = {
928 let mut applier = self.core.applier.borrow_dyn();
929 slots.finish_pass(&mut *applier)
930 }?;
931 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
932 self.wake_sites_whose_movable_arrived();
933 self.evict_retained_subtrees_for_host(slots)?;
934 slots.complete_pass_cleanup(&finished.outcome);
935 Ok(finished.outcome)
936 }
937
938 fn wake_sites_whose_movable_arrived(&self) {
939 for host in self.core.shared_state.hosts_awaiting_movables() {
940 self.wake_sites_in_host(&host);
941 }
942 }
943
944 fn wake_sites_in_host(&self, slots: &Rc<SlotsHost>) {
945 let pending = self.core.shared_state.take_pending_movables(slots);
946 if pending.is_empty() {
947 return;
948 }
949 let mut waiting = Vec::new();
950 for site in pending {
951 if !slots.borrow().group_is_active(site.placeholder) {
952 continue;
953 }
954 let retain_key = RetainKey::for_group(None, site.key);
955 if !self.core.shared_state.movable_retained_anywhere(retain_key) {
956 waiting.push(site);
957 continue;
958 }
959 match site.parent_scope.and_then(|id| self.scope_for_id(id)) {
960 Some(scope) => {
961 scope.force_recompose();
962 scope.invalidate();
963 }
964 None => log::error!(
965 "movable content {:?} arrived for a site whose scope is gone",
966 site.key
967 ),
968 }
969 }
970 self.core.shared_state.keep_pending_movables(slots, waiting);
971 }
972
973 pub(crate) fn forget_movables(&self, ids: &[Key]) -> Result<(), NodeError> {
974 for id in ids {
975 let Some((host, subtree)) = self.core.shared_state.take_retained_movable(*id) else {
976 continue;
977 };
978 self.dispose_detached_subtree_in_host(&host, subtree)?;
979 host.flush_pending_drops();
980 }
981 Ok(())
982 }
983
984 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
985 self.core.parent_stack.borrow_mut()
986 }
987
988 pub(crate) fn current_parent_hint(&self) -> Option<NodeId> {
989 let stack = self.core.parent_stack.borrow();
990 let stack_hint = stack
991 .last()
992 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
993 stack_hint.or_else(|| self.core.recompose_parent_hint.get())
994 }
995
996 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
997 self.core.subcompose_stack.borrow_mut()
998 }
999
1000 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
1001 self.core.commands.borrow_mut()
1002 }
1003
1004 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
1005 self.commands_mut().push(Command::BubbleDirty {
1006 node_id: id,
1007 bubble: DirtyBubble::SEMANTICS,
1008 });
1009 }
1010
1011 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
1012 self.core.scope_stack.borrow_mut()
1013 }
1014
1015 fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
1016 self.core.shared_state.scope_for_id(scope_id)
1017 }
1018
1019 fn register_scope(&self, scope: &RecomposeScope) {
1020 self.core.shared_state.register_scope(scope);
1021 }
1022
1023 fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
1024 self.core.shared_state.remove_scope(scope_id)
1025 }
1026
1027 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
1028 self.core.local_stack.borrow_mut()
1029 }
1030
1031 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
1032 self.core.local_stack.borrow().clone()
1033 }
1034
1035 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
1036 self.core.side_effects.borrow_mut()
1037 }
1038
1039 fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
1040 self.core.pending_scope_options.borrow_mut()
1041 }
1042
1043 pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
1044 self.core.applier.borrow_dyn()
1045 }
1046
1047 pub fn record_rebound_slot_children(&self, children: &[NodeId]) {
1058 let mut applier = self.borrow_applier();
1059 for &child in children {
1060 applier.record_structural_change(child);
1061 }
1062 }
1063
1064 pub fn register_virtual_node(
1071 &self,
1072 node_id: NodeId,
1073 node: Box<dyn Node>,
1074 ) -> Result<(), NodeError> {
1075 let mut applier = self.borrow_applier();
1076 applier.insert_with_id(node_id, node)
1077 }
1078
1079 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
1082 let mut applier = self.borrow_applier();
1083 match applier.get_mut(node_id) {
1084 Ok(node) => node.parent().is_none(),
1085 Err(_) => true,
1086 }
1087 }
1088
1089 pub fn node_parent(&self, node_id: NodeId) -> Result<Option<NodeId>, NodeError> {
1093 self.borrow_applier()
1094 .get_mut(node_id)
1095 .map(|node| node.parent())
1096 }
1097
1098 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
1103 let mut applier = self.borrow_applier();
1104 match applier.get_mut(node_id) {
1105 Ok(node) => {
1106 let mut children = SmallVec::<[NodeId; 8]>::new();
1107 node.collect_children_into(&mut children);
1108 children
1109 }
1110 Err(_) => SmallVec::<[NodeId; 8]>::new(),
1111 }
1112 }
1113
1114 pub fn nodes_need_measure(&self, node_ids: &[NodeId]) -> bool {
1115 let mut applier = self.borrow_applier();
1116 node_ids.iter().any(|node_id| {
1117 applier
1118 .get_mut(*node_id)
1119 .is_ok_and(|node| node.needs_measure())
1120 })
1121 }
1122
1123 pub fn nodes_need_layout(&self, node_ids: &[NodeId]) -> bool {
1131 let mut applier = self.borrow_applier();
1132 node_ids.iter().any(|node_id| {
1133 applier
1134 .get_mut(*node_id)
1135 .is_ok_and(|node| node.needs_layout())
1136 })
1137 }
1138
1139 pub fn record_subcompose_child(&self, child_id: NodeId) {
1149 let mut parent_stack = self.parent_stack();
1150 if let Some(frame) = parent_stack.last_mut()
1151 && matches!(frame.attach_mode, ParentAttachMode::DeferredSync)
1152 {
1153 if let Some(membership) = frame.new_children_membership.as_mut() {
1154 if membership.insert(child_id) {
1155 frame.new_children.push(child_id);
1156 }
1157 } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
1158 let mut membership = HashSet::default();
1159 membership.reserve(frame.new_children.len() + 1);
1160 membership.extend(frame.new_children.iter().copied());
1161 if membership.insert(child_id) {
1162 frame.new_children.push(child_id);
1163 }
1164 frame.new_children_membership = Some(membership);
1165 } else if !frame.new_children.contains(&child_id) {
1166 frame.new_children.push(child_id);
1167 }
1168 }
1169 }
1170
1171 pub fn clear_node_children(&self, node_id: NodeId) {
1177 let mut applier = self.borrow_applier();
1178 if let Ok(node) = applier.get_mut(node_id) {
1179 node.update_children(&[]);
1180 }
1181 }
1182
1183 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
1184 let _composer_guard = composer_context::enter(self);
1185 runtime::push_active_runtime(&self.core.runtime);
1186 struct Guard;
1187 impl Drop for Guard {
1188 fn drop(&mut self) {
1189 runtime::pop_active_runtime();
1190 }
1191 }
1192 let guard = Guard;
1193 let result = f(self);
1194 drop(guard);
1195 result
1196 }
1197
1198 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
1199 let queued = self.core.commands.borrow().len();
1200 if queued < COMMAND_FLUSH_THRESHOLD {
1201 return Ok(());
1202 }
1203 self.apply_pending_commands()
1204 }
1205
1206 fn resolve_group_entry(
1207 &self,
1208 seed: crate::slot::GroupKeySeed,
1209 parent_scope_id: Option<ScopeId>,
1210 ) -> GroupEntry {
1211 let host = self.active_slots_host();
1212 let key = self.with_slot_session_mut(|slots| slots.reserve_group_key(seed));
1213 let retain_key = RetainKey::for_group(parent_scope_id, key);
1214 let restored = self
1215 .core
1216 .shared_state
1217 .take_retained(&host, retain_key, |subtree| {
1218 self.with_slot_session_mut(|slots| slots.retained_restore_ready(key, subtree))
1219 })
1220 .or_else(|| self.take_movable_from_another_table(&host, retain_key, key));
1221 if restored.is_some() || !key.is_movable() {
1222 return GroupEntry {
1223 key,
1224 restored,
1225 placeholder_for: None,
1226 };
1227 }
1228 let attached_elsewhere = self.movable_attached_elsewhere(&host, key);
1229 if !attached_elsewhere {
1230 return GroupEntry {
1231 key,
1232 restored: None,
1233 placeholder_for: None,
1234 };
1235 }
1236 let id = key.explicit_key.unwrap_or_default();
1237 let placeholder = self.with_slot_session_mut(|slots| {
1238 slots.reserve_group_key(crate::slot::GroupKeySeed::movable_placeholder(id))
1239 });
1240 GroupEntry {
1241 key: placeholder,
1242 restored: None,
1243 placeholder_for: Some(key),
1244 }
1245 }
1246
1247 fn take_movable_from_another_table(
1252 &self,
1253 host: &Rc<SlotsHost>,
1254 retain_key: RetainKey,
1255 key: crate::slot::GroupKey,
1256 ) -> Option<crate::slot::DetachedSubtree> {
1257 if !key.is_movable() {
1258 return None;
1259 }
1260 let (source, mut subtree) = self
1261 .core
1262 .shared_state
1263 .take_retained_movable_elsewhere(host, retain_key)?;
1264 source
1265 .borrow_mut()
1266 .invalidate_detached_subtree_anchors(&subtree);
1267 if self.with_slot_session_mut(|slots| slots.retained_restore_ready(key, &mut subtree)) {
1268 return Some(subtree);
1269 }
1270 log::error!(
1271 "movable content {key:?} could not be taken over by the slot table that asked for it"
1272 );
1273 if let Err(error) = self.dispose_detached_subtree_in_host(host, subtree) {
1274 log::error!("disposing movable content that could not move failed: {error}");
1275 }
1276 None
1277 }
1278
1279 fn movable_attached_elsewhere(&self, host: &Rc<SlotsHost>, key: crate::slot::GroupKey) -> bool {
1282 if self.with_slot_session_mut(|slots| slots.movable_attached_elsewhere(key)) {
1283 return true;
1284 }
1285 let Some(id) = key.movable_id() else {
1286 return false;
1287 };
1288 self.core
1289 .shared_state
1290 .host_holding_movable(id)
1291 .is_some_and(|holder| !Rc::ptr_eq(&holder, host))
1292 }
1293
1294 fn scope_for_started_group(
1295 &self,
1296 group: crate::slot::ActiveGroupId,
1297 scope_id: Option<ScopeId>,
1298 ) -> RecomposeScope {
1299 if let Some(scope) = scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
1300 return scope;
1301 }
1302 let scope = RecomposeScope::new(self.runtime_handle());
1303 self.register_scope(&scope);
1304 self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
1305 scope
1306 }
1307
1308 fn enter_group_scope(&self, scope_ref: &RecomposeScope, entry: GroupScopeEntry<'_>) {
1309 let GroupScopeEntry {
1310 parent_scope,
1311 options,
1312 start_kind,
1313 host,
1314 restored_scopes,
1315 } = entry;
1316 let lifetime_owner_scope = if parent_scope.is_none() {
1317 self.core.subcomposition_owner_scope.borrow().clone()
1318 } else {
1319 None
1320 };
1321 scope_ref.reactivate();
1322 scope_ref.set_parent_scope(parent_scope);
1323 scope_ref.set_lifetime_owner_scope(lifetime_owner_scope);
1324 scope_ref.set_retention_mode(options.retention);
1325
1326 if options.force_recompose {
1327 scope_ref.force_recompose();
1328 } else if options.force_reuse {
1329 scope_ref.force_reuse();
1330 }
1331 if matches!(start_kind, GroupStartKind::Restored) {
1332 scope_ref.force_recompose();
1333 }
1334
1335 scope_ref.set_slots_host(host);
1336
1337 {
1338 let mut stack = self.scope_stack();
1339 stack.push(scope_ref.clone());
1340 }
1341
1342 {
1343 let mut stack = self.subcompose_stack();
1344 if let Some(frame) = stack.last_mut() {
1345 frame.scopes.push(scope_ref.clone());
1346 }
1347 }
1348
1349 scope_ref.snapshot_locals(self.current_local_stack());
1350 let parent_hint = self.current_parent_hint();
1351 if let Some(restored_scopes) = restored_scopes {
1352 self.reparent_restored_scopes(scope_ref, &restored_scopes, parent_hint);
1353 }
1354 scope_ref.set_parent_hint(parent_hint);
1355 }
1356
1357 fn reparent_restored_scopes(
1358 &self,
1359 root: &RecomposeScope,
1360 restored_scopes: &[ScopeId],
1361 parent_hint: Option<NodeId>,
1362 ) {
1363 let old_hint = root.parent_hint();
1364 for scope in restored_scopes
1365 .iter()
1366 .filter_map(|scope_id| self.scope_for_id(*scope_id))
1367 {
1368 if scope.parent_hint() == old_hint {
1369 scope.set_parent_hint(parent_hint);
1370 }
1371 scope.reactivate();
1372 }
1373 }
1374
1375 #[inline(never)]
1376 fn with_group_in_active_pass_dyn(
1377 &self,
1378 key: crate::slot::GroupKeySeed,
1379 f: &mut dyn FnMut(&Composer),
1380 ) {
1381 struct GroupGuard {
1382 composer: Composer,
1383 scope: RecomposeScope,
1384 }
1385
1386 impl Drop for GroupGuard {
1387 fn drop(&mut self) {
1388 self.composer
1389 .close_current_group_body_for_scope(&self.scope);
1390 self.scope.mark_recomposed();
1391 self.composer
1392 .with_slot_session_mut(|slots| slots.end_group());
1393 if let Err(err) = self.composer.flush_pending_commands_if_large() {
1394 log::error!("mid-composition command flush failed: {err}");
1395 }
1396 }
1397 }
1398
1399 let parent_scope = self.current_recompose_scope();
1400 let options = self.pending_scope_options().take().unwrap_or_default();
1401 let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
1402 let host = self.active_slots_host();
1403 let GroupEntry {
1404 key: reserved_key,
1405 restored,
1406 placeholder_for,
1407 } = self.resolve_group_entry(key, parent_scope_id);
1408 let restored_scopes = restored
1409 .as_ref()
1410 .map(crate::slot::DetachedSubtree::scope_ids);
1411 let parent_node = self.current_parent_hint();
1412 let (group, anchor, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
1413 let GroupStart {
1414 group,
1415 anchor,
1416 scope_id,
1417 kind,
1418 } = slots.begin_group(reserved_key, restored, parent_node);
1419 (group, anchor, scope_id, kind)
1420 });
1421 let scope_ref = self.scope_for_started_group(group, start_scope_id);
1422 self.enter_group_scope(
1423 &scope_ref,
1424 GroupScopeEntry {
1425 parent_scope,
1426 options,
1427 start_kind,
1428 host: &host,
1429 restored_scopes,
1430 },
1431 );
1432 if let Some(movable_key) = placeholder_for {
1433 self.core.shared_state.record_pending_movable(
1434 &host,
1435 PendingMovable {
1436 key: movable_key,
1437 placeholder: anchor,
1438 parent_scope: parent_scope_id,
1439 },
1440 );
1441 }
1442
1443 let guard = GroupGuard {
1444 composer: self.clone(),
1445 scope: scope_ref.clone(),
1446 };
1447 if placeholder_for.is_none() {
1448 self.observe_scope(&scope_ref, || f(self));
1449 }
1450 scope_ref.mark_composed_once();
1451 drop(guard);
1452 }
1453
1454 fn with_group_seed_dyn(&self, key: crate::slot::GroupKeySeed, f: &mut dyn FnMut(&Composer)) {
1455 let host = self.active_slots_host();
1456 if host.has_active_pass() {
1457 self.with_group_in_active_pass_dyn(key, f);
1458 return;
1459 }
1460 self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1461 composer.with_group_in_active_pass_dyn(key, f)
1462 });
1463 }
1464
1465 pub(crate) fn with_group_seed<R>(
1466 &self,
1467 key: crate::slot::GroupKeySeed,
1468 f: impl FnOnce(&Composer) -> R,
1469 ) -> R {
1470 let mut f = Some(f);
1471 let mut result = None;
1472 self.with_group_seed_dyn(key, &mut |composer| {
1473 let f = f.take().expect("group body must run at most once");
1474 result = Some(f(composer));
1475 });
1476 result.expect("group body must run exactly once")
1477 }
1478
1479 pub(crate) fn with_movable_group(&self, id: Key, f: impl FnOnce(&Composer)) {
1480 let mut f = Some(f);
1481 self.with_group_seed_dyn(crate::slot::GroupKeySeed::movable(id), &mut |composer| {
1482 if let Some(f) = f.take() {
1483 f(composer);
1484 }
1485 });
1486 }
1487
1488 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1489 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1490 }
1491
1492 pub fn cranpose_with_reuse<R>(
1493 &self,
1494 key: Key,
1495 mut options: RecomposeOptions,
1496 f: impl FnOnce(&Composer) -> R,
1497 ) -> R {
1498 options.retention = RetentionMode::RetainWhenInactive;
1499 self.pending_scope_options().replace(options);
1500 self.with_group(key, f)
1501 }
1502
1503 #[track_caller]
1504 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1505 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1506 self.with_group_seed(seed, f)
1507 }
1508
1509 #[doc(hidden)]
1510 pub fn __branch_group_deferred(&self, key: Key) -> BranchGroupGuard {
1511 BranchGroupGuard {
1512 composer: self.clone(),
1513 fold_token: self.active_slots_host().try_push_branch_fold(key),
1514 }
1515 }
1516
1517 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1518 for node_id in nodes {
1519 self.commands_mut().push(Command::callback(move |applier| {
1520 crate::slot::dispose_detached_node_now(applier, node_id)
1521 }));
1522 }
1523 }
1524
1525 fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1526 for scope_id in scope_ids {
1527 if let Some(scope) = self.scope_for_id(scope_id) {
1528 scope.deactivate();
1529 }
1530 }
1531 }
1532
1533 fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1534 for scope_id in scope_ids {
1535 if let Some(scope) = self.remove_scope(scope_id) {
1536 scope.deactivate();
1537 }
1538 }
1539 }
1540
1541 fn detached_root_parent_commands(
1542 &self,
1543 subtree: &crate::slot::DetachedSubtree,
1544 context: &'static str,
1545 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1546 let mut root_nodes = Vec::new();
1547 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1548 let mut roots = Vec::with_capacity(root_nodes.len());
1549 for root in root_nodes {
1550 let parent_id = {
1551 let mut applier = self.borrow_applier();
1552 applier.get_mut(root)?.parent()
1553 };
1554 roots.push((root, parent_id));
1555 }
1556 Ok(roots)
1557 }
1558
1559 fn retain_detached_subtree_in_host(
1560 &self,
1561 slots_host: &Rc<SlotsHost>,
1562 parent_scope: Option<ScopeId>,
1563 subtree: crate::slot::DetachedSubtree,
1564 ) -> Result<(), NodeError> {
1565 let Some(root_key) = subtree.root_key_checked() else {
1566 log::error!("retention rejected detached subtree without a root group");
1567 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1568 return Ok(());
1569 };
1570 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1571 self.deactivate_scope_ids(subtree.scope_ids_iter());
1572 for (root, parent_id) in root_detaches {
1573 if let Some(parent_id) = parent_id {
1574 self.commands_mut().push(Command::DetachChild {
1575 parent_id,
1576 child_id: root,
1577 });
1578 }
1579 }
1580 let evicted = self.core.shared_state.insert_retained(
1581 slots_host,
1582 RetainKey::for_group(parent_scope, root_key),
1583 subtree,
1584 root_key.is_movable(),
1585 );
1586 for subtree in evicted {
1587 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1588 }
1589 Ok(())
1590 }
1591
1592 fn evict_retained_subtrees_for_host(
1593 &self,
1594 slots_host: &Rc<SlotsHost>,
1595 ) -> Result<(), NodeError> {
1596 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1597 for subtree in evicted {
1598 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1599 }
1600 Ok(())
1601 }
1602
1603 fn dispose_detached_subtree_in_host(
1604 &self,
1605 slots_host: &Rc<SlotsHost>,
1606 subtree: crate::slot::DetachedSubtree,
1607 ) -> Result<(), NodeError> {
1608 let root_nodes = self
1609 .detached_root_parent_commands(&subtree, "disposal")?
1610 .into_iter()
1611 .map(|(root, _)| root);
1612 self.dispose_scope_ids(subtree.scope_ids_iter());
1613 self.dispose_detached_nodes(root_nodes);
1614 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1615 table.invalidate_detached_subtree_anchors(&subtree);
1616 lifecycle.queue_subtree_disposal(subtree);
1617 });
1618 Ok(())
1619 }
1620
1621 fn handle_detached_children_in_host(
1622 &self,
1623 slots_host: &Rc<SlotsHost>,
1624 parent_scope: Option<ScopeId>,
1625 detached: Vec<crate::slot::DetachedSubtree>,
1626 ) -> Result<(), NodeError> {
1627 for mut subtree in detached {
1628 for movable in subtree.split_off_nested_movables() {
1629 self.retain_detached_subtree_in_host(slots_host, None, movable)?;
1630 }
1631 if subtree
1632 .root_key_checked()
1633 .is_some_and(crate::slot::GroupKey::is_movable)
1634 {
1635 self.retain_detached_subtree_in_host(slots_host, None, subtree)?;
1636 continue;
1637 }
1638 let retention_mode = subtree
1639 .root_scope_id()
1640 .and_then(|scope_id| self.scope_for_id(scope_id))
1641 .map(|scope| scope.retention_mode())
1642 .unwrap_or_default();
1643 match retention_mode {
1644 RetentionMode::DisposeWhenInactive => {
1645 self.dispose_detached_subtree_in_host(slots_host, subtree)?
1646 }
1647 RetentionMode::RetainWhenInactive => {
1648 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1649 }
1650 }
1651 }
1652 Ok(())
1653 }
1654
1655 fn handle_detached_children(
1656 &self,
1657 parent_scope: Option<ScopeId>,
1658 detached: Vec<crate::slot::DetachedSubtree>,
1659 ) {
1660 let host = self.active_slots_host();
1661 if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1662 log::error!("detached subtree handling failed while closing a group: {err}");
1663 }
1664 }
1665
1666 fn handle_finished_group_result(
1667 &self,
1668 parent_scope: Option<ScopeId>,
1669 result: FinishGroupResult,
1670 ) {
1671 let FinishGroupResult {
1672 detached_children,
1673 direct_nodes,
1674 root_nodes,
1675 was_skipped,
1676 } = result;
1677 if was_skipped {
1678 self.attach_root_nodes(root_nodes);
1679 }
1680 self.dispose_detached_nodes(direct_nodes);
1681 self.handle_detached_children(parent_scope, detached_children);
1682 }
1683
1684 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1685 let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1686 self.handle_finished_group_result(Some(scope.id()), result);
1687 if let Some(popped) = self.scope_stack().pop() {
1688 debug_assert_eq!(
1689 popped.id(),
1690 scope.id(),
1691 "closed scope must match the active scope stack"
1692 );
1693 } else {
1694 log::error!("scope stack underflow while closing scope {}", scope.id());
1695 }
1696 }
1697
1698 #[track_caller]
1699 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1700 self.remember_at(crate::caller_location_key(), init)
1701 }
1702
1703 #[doc(hidden)]
1704 pub fn remember_at<T: 'static>(
1705 &self,
1706 source: crate::Key,
1707 init: impl FnOnce() -> T,
1708 ) -> Owned<T> {
1709 self.with_slot_session_mut(|slots| {
1710 slots.remember_with_kind(PayloadKind::Remember, source, init)
1711 })
1712 }
1713
1714 #[track_caller]
1715 pub(crate) fn remember_internal<T: 'static>(
1716 &self,
1717 source_salt: crate::Key,
1718 init: impl FnOnce() -> T,
1719 ) -> Owned<T> {
1720 let source = crate::caller_location_key() ^ source_salt;
1721 self.with_slot_session_mut(|slots| {
1722 slots.remember_with_kind(PayloadKind::Internal, source, init)
1723 })
1724 }
1725
1726 #[track_caller]
1727 pub(crate) fn remember_effect<T: Default + 'static>(&self) -> Owned<T> {
1728 let source = crate::caller_location_key();
1729 self.with_slot_session_mut(|slots| slots.remember_effect::<T>(source))
1730 }
1731
1732 #[track_caller]
1733 pub fn use_value_slot<'pass, T: 'static>(
1734 &'pass self,
1735 init: impl FnOnce() -> T,
1736 ) -> ValueSlotHandle<'pass, T> {
1737 let source = crate::caller_location_key();
1738 let slot = self.with_slot_session_mut(|slots| {
1739 slots.value_slot_with_kind(PayloadKind::Internal, source, init)
1740 });
1741 ValueSlotHandle::new(slot)
1742 }
1743
1744 #[doc(hidden)]
1745 #[track_caller]
1746 pub fn __use_param_slot<'pass, T: 'static>(
1747 &'pass self,
1748 init: impl FnOnce() -> T,
1749 ) -> ValueSlotHandle<'pass, T> {
1750 let source = crate::caller_location_key();
1751 let slot = self.with_slot_session_mut(|slots| {
1752 slots.value_slot_with_kind(PayloadKind::Param, source, init)
1753 });
1754 ValueSlotHandle::new(slot)
1755 }
1756
1757 #[doc(hidden)]
1758 #[track_caller]
1759 pub fn __use_return_slot<'pass, T: 'static>(
1760 &'pass self,
1761 init: impl FnOnce() -> T,
1762 ) -> ValueSlotHandle<'pass, T> {
1763 let source = crate::caller_location_key();
1764 let slot = self.with_slot_session_mut(|slots| {
1765 slots.value_slot_with_kind(PayloadKind::Return, source, init)
1766 });
1767 ValueSlotHandle::new(slot)
1768 }
1769
1770 #[doc(hidden)]
1771 pub fn __invalidate_return_consumer_scope(&self) {
1772 let Some(scope) = self.current_recompose_scope() else {
1773 self.request_root_render();
1774 return;
1775 };
1776
1777 if let Some(target) = scope.callback_promotion_target() {
1778 target.invalidate();
1779 } else {
1780 self.request_root_render();
1781 }
1782 }
1783
1784 pub fn with_slot_value<'pass, T: 'static, R>(
1785 &'pass self,
1786 handle: ValueSlotHandle<'pass, T>,
1787 f: impl FnOnce(&T) -> R,
1788 ) -> R {
1789 self.with_slots(|slots| f(slots.read_value(handle.slot())))
1790 }
1791
1792 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1793 &'pass self,
1794 handle: ValueSlotHandle<'pass, T>,
1795 f: impl FnOnce(&mut T) -> R,
1796 ) -> R {
1797 self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1798 }
1799
1800 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1801 MutableState::with_runtime(initial, self.runtime_handle())
1802 }
1803
1804 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1805 where
1806 T: Clone + 'static,
1807 I: IntoIterator<Item = T>,
1808 {
1809 SnapshotStateList::with_runtime(values, self.runtime_handle())
1810 }
1811
1812 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1813 where
1814 K: Clone + Eq + Hash + 'static,
1815 V: Clone + 'static,
1816 I: IntoIterator<Item = (K, V)>,
1817 {
1818 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1819 }
1820
1821 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1822 let stack = self.core.local_stack.borrow();
1823 for context in stack.iter().rev() {
1824 if let Some(entry) = context.values.get(&local.key) {
1825 match entry.clone().downcast::<LocalStateEntry<T>>() {
1826 Ok(typed) => return typed.value(),
1827 Err(_) => {
1828 log::error!(
1829 "composition local entry type mismatch for key {:?}",
1830 local.key
1831 );
1832 return local.default_value();
1833 }
1834 }
1835 }
1836 }
1837 local.default_value()
1838 }
1839
1840 pub fn read_static_composition_local<T: Clone + 'static>(
1841 &self,
1842 local: &StaticCompositionLocal<T>,
1843 ) -> T {
1844 let stack = self.core.local_stack.borrow();
1845 for context in stack.iter().rev() {
1846 if let Some(entry) = context.values.get(&local.key) {
1847 match entry.clone().downcast::<StaticLocalEntry<T>>() {
1848 Ok(typed) => return typed.value(),
1849 Err(_) => {
1850 log::error!(
1851 "static composition local entry type mismatch for key {:?}",
1852 local.key
1853 );
1854 return local.default_value();
1855 }
1856 }
1857 }
1858 }
1859 local.default_value()
1860 }
1861
1862 pub fn current_recompose_scope(&self) -> Option<RecomposeScope> {
1863 self.core.scope_stack.borrow().last().cloned()
1864 }
1865
1866 pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1867 let stack = self.core.scope_stack.borrow();
1868 stack
1869 .iter()
1870 .rev()
1871 .find(|scope| scope.has_recompose_callback())
1872 .cloned()
1873 .or_else(|| stack.last().cloned())
1874 }
1875
1876 pub fn phase(&self) -> crate::Phase {
1877 self.core.phase.get()
1878 }
1879
1880 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1881 self.core.phase.set(phase);
1882 }
1883
1884 pub fn enter_phase(&self, phase: crate::Phase) {
1885 self.set_phase(phase);
1886 }
1887
1888 pub(crate) fn subcompose<R>(
1889 &self,
1890 state: &mut SubcomposeState,
1891 slot_id: SlotId,
1892 content: impl FnOnce(&Composer) -> R,
1893 ) -> (R, Vec<NodeId>) {
1894 match self.phase() {
1895 crate::Phase::Measure | crate::Phase::Layout => {}
1896 current => panic!(
1897 "subcompose() may only be called during measure or layout; current phase: {:?}",
1898 current
1899 ),
1900 }
1901
1902 self.subcompose_stack().push(SubcomposeFrame::default());
1903 let mut guard = SubcomposeStackGuard {
1904 core: self.clone_core(),
1905 leaked: false,
1906 };
1907
1908 let slot_host = state.get_or_create_slots(slot_id);
1909 let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1910 composer.with_group(slot_id.raw(), |composer| content(composer))
1911 });
1912
1913 let frame = {
1914 let frame = take_subcompose_frame(&guard.core, "subcompose");
1915 guard.leaked = true;
1916 frame
1917 };
1918 let nodes = frame.nodes;
1919 let scopes = frame.scopes;
1920 state.register_active(slot_id, &nodes, &scopes);
1921 (result, nodes)
1922 }
1923
1924 pub fn subcompose_measurement<R>(
1925 &self,
1926 state: &mut SubcomposeState,
1927 slot_id: SlotId,
1928 content: impl FnOnce(&Composer) -> R,
1929 ) -> (R, Vec<NodeId>) {
1930 let (result, nodes) = self.subcompose(state, slot_id, content);
1931 let roots = nodes
1932 .into_iter()
1933 .filter(|&id| self.node_has_no_parent(id))
1934 .collect();
1935
1936 (result, roots)
1937 }
1938
1939 fn spin_up_subcompose_core(
1940 &self,
1941 slots: &Rc<SlotsHost>,
1942 root: Option<NodeId>,
1943 runtime_handle: &RuntimeHandle,
1944 locals: LocalStackSnapshot,
1945 ) -> Rc<ComposerCore> {
1946 let phase = self.phase();
1947 let shared_state = slots
1948 .runtime_state()
1949 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1950 let core = Rc::new(ComposerCore::new(
1951 shared_state,
1952 Rc::clone(slots),
1953 Rc::clone(&self.core.applier),
1954 runtime_handle.clone(),
1955 self.observer(),
1956 root,
1957 InitialParentFrame::RealParent,
1958 ));
1959 core.phase.set(phase);
1960 *core.local_stack.borrow_mut() = locals;
1961 core
1962 }
1963
1964 fn flush_subcompose_pass(
1965 &self,
1966 commands: CommandQueue,
1967 runtime_handle: &RuntimeHandle,
1968 compact_applier: bool,
1969 side_effects: Vec<Box<dyn FnOnce()>>,
1970 ) -> Result<(), NodeError> {
1971 {
1972 let mut applier = self.borrow_applier();
1973 commands.apply(&mut *applier)?;
1974 for update in runtime_handle.take_updates() {
1975 update.apply(&mut *applier)?;
1976 }
1977 }
1978 if compact_applier {
1979 self.core.applier.compact();
1980 self.core.applier.borrow_dyn().clear_recycled_nodes();
1981 }
1982 runtime_handle.drain_ui();
1983 for effect in side_effects {
1984 effect();
1985 }
1986 runtime_handle.drain_ui();
1987 Ok(())
1988 }
1989
1990 pub fn subcompose_in<R>(
1991 &self,
1992 slots: &Rc<SlotsHost>,
1993 root: Option<NodeId>,
1994 f: impl FnOnce(&Composer) -> R,
1995 ) -> Result<R, NodeError> {
1996 let runtime_handle = self.runtime_handle();
1997 let locals = self.current_local_stack();
1998 let core = self.spin_up_subcompose_core(slots, root, &runtime_handle, locals);
1999 let composer = Composer::from_core(core);
2000 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
2001 let (output, outcome) = composer.try_with_slot_host_pass(
2002 Rc::clone(slots),
2003 crate::slot::SlotPassMode::Compose,
2004 |composer| f(composer),
2005 )?;
2006 let commands = composer.take_commands();
2007 let side_effects = composer.take_side_effects();
2008 Ok((output, commands, side_effects, outcome.compacted))
2009 })?;
2010 self.flush_subcompose_pass(commands, &runtime_handle, compact_applier, side_effects)?;
2011 Ok(result)
2012 }
2013
2014 pub fn capture_composition_context(&self) -> CapturedCompositionContext {
2025 CapturedCompositionContext {
2026 locals: self.current_local_stack(),
2027 owner_scope: self
2028 .current_recompose_scope()
2029 .map(|scope| scope.downgrade()),
2030 }
2031 }
2032
2033 pub fn subcompose_slot<R>(
2038 &self,
2039 slots: &Rc<SlotsHost>,
2040 root: Option<NodeId>,
2041 f: impl FnOnce(&Composer) -> R,
2042 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2043 let context = self.capture_composition_context();
2044 self.subcompose_slot_with_context(slots, root, &context, f)
2045 }
2046
2047 pub fn subcompose_slot_with_context<R>(
2051 &self,
2052 slots: &Rc<SlotsHost>,
2053 root: Option<NodeId>,
2054 context: &CapturedCompositionContext,
2055 f: impl FnOnce(&Composer) -> R,
2056 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2057 let runtime_handle = self.runtime_handle();
2058 let locals = context.locals.clone();
2059 let core = self.spin_up_subcompose_core(slots, root, &runtime_handle, locals);
2060 *core.subcomposition_owner_scope.borrow_mut() = context
2061 .owner_scope
2062 .as_ref()
2063 .and_then(Weak::upgrade)
2064 .map(|inner| RecomposeScope { inner });
2065 let composer = Composer::from_core(core);
2066 composer.subcompose_stack().push(SubcomposeFrame::default());
2067 let mut guard = SubcomposeStackGuard {
2068 core: composer.clone_core(),
2069 leaked: false,
2070 };
2071 let root_group_key = crate::location_key(file!(), line!(), column!());
2072 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
2073 let (output, outcome) = composer.try_with_slot_host_pass(
2074 Rc::clone(slots),
2075 crate::slot::SlotPassMode::Compose,
2076 |composer| {
2077 let output = composer.with_group(root_group_key, |composer| f(composer));
2078 if root.is_some() {
2079 composer.pop_parent();
2080 }
2081 output
2082 },
2083 )?;
2084 let commands = composer.take_commands();
2085 let side_effects = composer.take_side_effects();
2086 Ok((output, commands, side_effects, outcome.compacted))
2087 })?;
2088 let frame = {
2089 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
2090 guard.leaked = true;
2091 frame
2092 };
2093
2094 self.flush_subcompose_pass(commands, &runtime_handle, compact_applier, side_effects)?;
2095 Ok((result, frame.scopes))
2096 }
2097
2098 fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
2099 for id in root_nodes {
2100 self.attach_to_parent(id);
2101 }
2102 }
2103
2104 pub fn skip_current_group(&self) {
2105 self.with_slot_session_mut(|slots| slots.skip_group());
2106 }
2107
2108 pub fn runtime_handle(&self) -> RuntimeHandle {
2109 self.core.runtime.clone()
2110 }
2111
2112 pub fn set_recompose_callback<F>(&self, callback: F)
2113 where
2114 F: FnMut(&Composer) + 'static,
2115 {
2116 self.set_recompose_callback_boxed(Box::new(callback));
2117 }
2118
2119 #[inline(never)]
2120 fn set_recompose_callback_boxed(&self, mut callback: Box<dyn FnMut(&Composer)>) {
2121 if let Some(scope) = self.current_recompose_scope() {
2122 let observer = self.observer();
2123 let scope_weak = scope.downgrade();
2124 scope.set_recompose(Box::new(move |composer: &Composer| {
2125 if let Some(inner) = scope_weak.upgrade() {
2126 let scope_instance = RecomposeScope { inner };
2127 observer.observe_reads(
2128 scope_instance.clone(),
2129 move |scope_ref| scope_ref.invalidate(),
2130 || {
2131 callback(composer);
2132 },
2133 );
2134 }
2135 }));
2136 }
2137 }
2138
2139 pub fn set_recompose_fn(&self, callback: fn(&Composer)) {
2140 if let Some(scope) = self.current_recompose_scope() {
2141 scope.set_recompose_fn(callback);
2142 }
2143 }
2144
2145 pub fn with_composition_locals<R>(
2146 &self,
2147 provided: Vec<ProvidedValue>,
2148 site: crate::Key,
2149 f: impl FnOnce(&Composer) -> R,
2150 ) -> R {
2151 if provided.is_empty() {
2152 return f(self);
2153 }
2154 let mut context = LocalContext::default();
2155 for value in provided.into_iter().rev() {
2156 if context.values.contains_key(value.key()) {
2157 continue;
2158 }
2159 let (key, entry) = value.into_entry(self, site);
2160 context.values.insert(key, entry);
2161 }
2162 {
2163 let mut stack = self.local_stack();
2164 Rc::make_mut(&mut *stack).push(context);
2165 }
2166 let result = f(self);
2167 {
2168 let mut stack = self.local_stack();
2169 Rc::make_mut(&mut *stack).pop();
2170 }
2171 result
2172 }
2173}