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