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