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, ComputedLocalEntry, DirtyBubble, Key, LocalKey,
14 LocalStackSnapshot, LocalStateEntry, MutableState, Node, NodeError, NodeId, Owned,
15 ProvidedValue, RecomposeOptions, RecomposeScope, RecomposeScopeInner, RecycledNode,
16 RetentionMode, RetentionPolicy, RuntimeHandle, ScopeId, SlotId, SlotPassOutcome, SlotTable,
17 SlotsHost, SnapshotStateList, SnapshotStateMap, StaticCompositionLocal, SubcomposeState,
18 collections::map::HashMap,
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> {
28 slot: ValueSlotId,
29 record: Option<usize>,
30 _pass: PhantomData<&'pass Composer>,
31 _value: PhantomData<fn() -> T>,
32}
33
34impl<T: 'static> Copy for ValueSlotHandle<'_, T> {}
35
36impl<T: 'static> Clone for ValueSlotHandle<'_, T> {
37 fn clone(&self) -> Self {
38 *self
39 }
40}
41
42impl<T: 'static> ValueSlotHandle<'_, T> {
43 pub(crate) fn new(slot: ValueSlotId, record: Option<usize>) -> Self {
44 Self {
45 slot,
46 record,
47 _pass: PhantomData,
48 _value: PhantomData,
49 }
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 host.runtime_state_ptr() == Some(Rc::as_ptr(state)) {
64 debug_assert!(
65 state
66 .host_for_storage_key(host.storage_key())
67 .is_some_and(|registered| Rc::ptr_eq(®istered, host)),
68 "a slot host bound to a runtime state must be in its registry"
69 );
70 return Rc::clone(host);
71 }
72 if host.runtime_state().is_some() {
73 if host.rebind_orphaned_runtime_state(state) {
74 state.bind_slots_host(host);
75 return Rc::clone(host);
76 }
77 log::error!(
78 "slot host already belongs to a different composer runtime state; using a fresh slot host"
79 );
80 let replacement = Rc::new(SlotsHost::new(SlotTable::new()));
81 state.bind_slots_host(&replacement);
82 return replacement;
83 }
84 state.bind_slots_host(host);
85 Rc::clone(host)
86}
87
88struct GroupEntry {
89 key: crate::slot::GroupKey,
90 restored: Option<Box<crate::slot::DetachedSubtree>>,
92 placeholder_for: Option<crate::slot::GroupKey>,
93}
94
95struct GroupScopeEntry<'a> {
96 options: RecomposeOptions,
97 start_kind: GroupStartKind,
98 host: &'a SlotsHost,
99 group: crate::slot::ActiveGroupId,
100 parent_hint: Option<NodeId>,
101}
102
103struct SlotHostPassGuard {
104 core: Rc<ComposerCore>,
105 host: Rc<SlotsHost>,
106 fold_base: usize,
108 active: bool,
109}
110
111impl SlotHostPassGuard {
112 fn close(&mut self) {
113 if !self.active {
114 return;
115 }
116 if self.host.has_active_pass() {
117 self.host.abandon_active_pass();
118 }
119 match self.core.slot_hosts.borrow_mut().pop() {
120 Some(host) if Rc::ptr_eq(&host, &self.host) => {}
121 Some(_) => {
122 log::error!("slot host stack mismatch while closing slot host pass");
123 }
124 None => {
125 log::error!("slot host stack underflow while closing slot host pass");
126 }
127 }
128 self.core.branch_folds.end_pass(self.fold_base);
129 self.active = false;
130 }
131}
132
133impl Drop for SlotHostPassGuard {
134 fn drop(&mut self) {
135 self.close();
136 }
137}
138
139pub(crate) struct PendingMovable {
140 pub(crate) key: crate::slot::GroupKey,
141 pub(crate) placeholder: AnchorId,
142 pub(crate) parent_scope: Option<Weak<RecomposeScopeInner>>,
143}
144
145fn reparent_restored_scopes(
146 host: &SlotsHost,
147 group: crate::slot::ActiveGroupId,
148 old_hint: Option<NodeId>,
149 parent_hint: Option<NodeId>,
150) {
151 host.with_write_session(|slots| {
152 slots.for_each_subtree_scope(group, |scope| {
153 if scope.parent_hint() == old_hint {
154 scope.set_parent_hint(parent_hint);
155 }
156 scope.reactivate();
157 });
158 });
159}
160
161fn movable_retain_key(id: Key) -> RetainKey {
162 RetainKey::for_group(
163 None,
164 crate::slot::GroupKey::new(crate::slot::MOVABLE_STATIC_KEY, Some(id), 0),
165 )
166}
167
168pub(crate) struct ComposerRuntimeState {
169 retention_by_host: RefCell<HashMap<usize, RetentionManager>>,
170 pending_movables_by_host: RefCell<HashMap<usize, Vec<PendingMovable>>>,
171 retention_policy: Cell<RetentionPolicy>,
172 live_hosts: RefCell<HashMap<usize, std::rc::Weak<SlotsHost>>>,
173 applier_host: RefCell<Option<std::rc::Weak<dyn ApplierHost>>>,
174 branch_folds: Rc<crate::slot::BranchFolds>,
175}
176
177const SPARE_COMMAND_QUEUES: usize = 8;
179
180const SPARE_COMMAND_CAPACITY: usize = 1024;
183
184fn spare_commands() -> CommandQueue {
185 crate::thread_state::try_runtimes(|state| state.spare_commands.borrow_mut().pop())
186 .flatten()
187 .unwrap_or_default()
188}
189
190#[derive(Default)]
193pub(crate) struct ComposerStacks {
194 slot_hosts: Vec<Rc<SlotsHost>>,
195 parent_stack: Vec<ParentFrame>,
196 scope_stack: Vec<RecomposeScope>,
197}
198
199const SPARE_STACK_CAPACITY: usize = 256;
202
203fn spare_composer_stacks() -> ComposerStacks {
204 crate::thread_state::try_runtimes(|state| state.spare_composer_stacks.take())
205 .flatten()
206 .unwrap_or_default()
207}
208
209impl ComposerStacks {
210 fn holds_storage(&self) -> bool {
211 self.slot_hosts.capacity() > 0
212 || self.parent_stack.capacity() > 0
213 || self.scope_stack.capacity() > 0
214 }
215}
216
217fn recycle_composer_stacks(mut stacks: ComposerStacks) {
219 let ComposerStacks {
220 slot_hosts,
221 parent_stack,
222 scope_stack,
223 } = &mut stacks;
224 if slot_hosts.capacity() > SPARE_STACK_CAPACITY
225 || parent_stack.capacity() > SPARE_STACK_CAPACITY
226 || scope_stack.capacity() > SPARE_STACK_CAPACITY
227 {
228 return;
229 }
230 slot_hosts.clear();
231 parent_stack.clear();
232 scope_stack.clear();
233 crate::thread_state::try_runtimes(|state| state.spare_composer_stacks.set(Some(stacks)));
234}
235
236pub(crate) fn recycle_commands(commands: CommandQueue) {
238 if commands.capacity() > SPARE_COMMAND_CAPACITY {
239 return;
240 }
241 crate::thread_state::try_runtimes(|state| {
242 let mut spare = state.spare_commands.borrow_mut();
243 if spare.len() < SPARE_COMMAND_QUEUES {
244 spare.push(commands);
245 }
246 });
247}
248
249impl Default for ComposerRuntimeState {
250 fn default() -> Self {
251 Self {
252 retention_by_host: RefCell::new(HashMap::default()),
253 pending_movables_by_host: RefCell::new(HashMap::default()),
254 retention_policy: Cell::new(RetentionPolicy::default()),
255 live_hosts: RefCell::new(HashMap::default()),
256 applier_host: RefCell::new(None),
257 branch_folds: Rc::default(),
258 }
259 }
260}
261
262impl ComposerRuntimeState {
263 pub(crate) fn clear_host_storage_key(&self, host_key: usize, table: &mut SlotTable) {
264 self.retention_by_host.borrow_mut().remove(&host_key);
265 self.pending_movables_by_host.borrow_mut().remove(&host_key);
266 self.live_hosts.borrow_mut().remove(&host_key);
267 table.release_scopes();
268 }
269
270 pub(crate) fn force_recompose_retained_scopes(&self, host_key: usize) {
271 if let Some(retention) = self.retention_by_host.borrow().get(&host_key) {
272 for scope in retention
273 .subtrees()
274 .flat_map(crate::slot::DetachedSubtree::scopes)
275 {
276 scope.force_recompose();
277 }
278 }
279 }
280
281 pub(crate) fn bind_applier_host(&self, applier: &Rc<dyn ApplierHost>) {
282 *self.applier_host.borrow_mut() = Some(Rc::downgrade(applier));
283 }
284
285 pub(crate) fn has_live_applier_host(&self) -> bool {
286 self.applier_host
287 .borrow()
288 .as_ref()
289 .and_then(std::rc::Weak::upgrade)
290 .is_some()
291 }
292
293 pub(crate) fn bind_slots_host(self: &Rc<Self>, host: &Rc<SlotsHost>) {
294 host.bind_runtime_state(self);
295 self.live_hosts
296 .borrow_mut()
297 .insert(host.storage_key(), Rc::downgrade(host));
298 }
299
300 pub(crate) fn retains_any(&self) -> bool {
301 !self.retention_by_host.borrow().is_empty()
302 }
303
304 pub(crate) fn set_retention_policy(&self, policy: RetentionPolicy) {
305 self.retention_policy.set(policy);
306 }
307
308 pub(crate) fn retention_policy(&self) -> RetentionPolicy {
309 self.retention_policy.get()
310 }
311
312 pub(crate) fn take_retained(
313 &self,
314 host: &Rc<SlotsHost>,
315 key: RetainKey,
316 preflight: impl FnOnce(&mut crate::slot::DetachedSubtree) -> bool,
317 ) -> Option<crate::slot::DetachedSubtree> {
318 let host_key = slots_storage_key(host);
319 let mut retention = self.retention_by_host.borrow_mut();
320 let subtree = retention
321 .get_mut(&host_key)?
322 .take_after_restore_preflight(key, preflight);
323 if retention
324 .get(&host_key)
325 .is_some_and(|manager| manager.is_empty() && manager.evictions_total() == 0)
326 {
327 retention.remove(&host_key);
328 }
329 subtree
330 }
331
332 pub(crate) fn insert_retained(
333 &self,
334 host: &Rc<SlotsHost>,
335 key: RetainKey,
336 subtree: crate::slot::DetachedSubtree,
337 pinned: bool,
338 ) -> Vec<crate::slot::DetachedSubtree> {
339 let policy = self.retention_policy();
340 let mut retention_by_host = self.retention_by_host.borrow_mut();
341 let manager = retention_by_host
342 .entry(slots_storage_key(host))
343 .or_insert_with(|| RetentionManager::new(policy));
344 manager.set_policy(policy);
345 if pinned {
346 manager.insert_pinned(key, subtree)
347 } else {
348 manager.insert(key, subtree)
349 }
350 }
351
352 pub(crate) fn take_retained_movable(
353 &self,
354 id: Key,
355 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
356 self.take_retained_movable_by_key(movable_retain_key(id))
357 }
358
359 fn take_retained_movable_by_key(
360 &self,
361 key: RetainKey,
362 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
363 let mut retention_by_host = self.retention_by_host.borrow_mut();
364 let (host_key, manager) = retention_by_host
365 .iter_mut()
366 .find(|(_, manager)| manager.contains(key))?;
367 let host = self.host_for_storage_key(*host_key)?;
368 let subtree = manager.take(key)?;
369 Some((host, subtree))
370 }
371
372 pub(crate) fn take_retained_movable_elsewhere(
375 &self,
376 besides: &Rc<SlotsHost>,
377 key: RetainKey,
378 ) -> Option<(Rc<SlotsHost>, crate::slot::DetachedSubtree)> {
379 let besides_key = slots_storage_key(besides);
380 let mut retention_by_host = self.retention_by_host.borrow_mut();
381 let (host_key, manager) = retention_by_host
382 .iter_mut()
383 .find(|(host_key, manager)| **host_key != besides_key && manager.contains(key))?;
384 let host = self.host_for_storage_key(*host_key)?;
385 let subtree = manager.take(key)?;
386 Some((host, subtree))
387 }
388
389 pub(crate) fn movable_retained_anywhere(&self, key: RetainKey) -> bool {
393 self.retention_by_host
394 .borrow()
395 .values()
396 .any(|manager| manager.contains(key))
397 }
398
399 pub(crate) fn host_holding_movable(&self, id: Key) -> Option<Rc<SlotsHost>> {
401 self.live_hosts
402 .borrow()
403 .values()
404 .filter_map(std::rc::Weak::upgrade)
405 .find(|host| host.borrow().movable_is_attached(id))
406 }
407
408 pub(crate) fn record_pending_movable(&self, host: &Rc<SlotsHost>, pending: PendingMovable) {
409 let mut by_host = self.pending_movables_by_host.borrow_mut();
410 let sites = by_host.entry(slots_storage_key(host)).or_default();
411 if sites
412 .iter()
413 .all(|site| site.placeholder != pending.placeholder)
414 {
415 sites.push(pending);
416 }
417 }
418
419 pub(crate) fn take_pending_movables(&self, host: &Rc<SlotsHost>) -> Vec<PendingMovable> {
420 self.pending_movables_by_host
421 .borrow_mut()
422 .remove(&slots_storage_key(host))
423 .unwrap_or_default()
424 }
425
426 pub(crate) fn hosts_awaiting_movables(&self) -> Vec<Rc<SlotsHost>> {
430 let by_host = self.pending_movables_by_host.borrow();
431 if by_host.is_empty() {
432 return Vec::new();
433 }
434 let waiting = by_host
435 .iter()
436 .filter(|(_, sites)| !sites.is_empty())
437 .map(|(host_key, _)| *host_key)
438 .collect::<Vec<_>>();
439 waiting
440 .into_iter()
441 .filter_map(|host_key| self.host_for_storage_key(host_key))
442 .collect()
443 }
444
445 pub(crate) fn keep_pending_movables(&self, host: &Rc<SlotsHost>, sites: Vec<PendingMovable>) {
446 if sites.is_empty() {
447 return;
448 }
449 self.pending_movables_by_host
450 .borrow_mut()
451 .insert(slots_storage_key(host), sites);
452 }
453
454 pub(crate) fn advance_retention_pass(
455 &self,
456 host: &Rc<SlotsHost>,
457 ) -> Vec<crate::slot::DetachedSubtree> {
458 let mut retention_by_host = self.retention_by_host.borrow_mut();
459 if retention_by_host.is_empty() {
460 return Vec::new();
461 }
462 let host_key = slots_storage_key(host);
463 let policy = self.retention_policy();
464 let Some(manager) = retention_by_host.get_mut(&host_key) else {
465 return Vec::new();
466 };
467 manager.set_policy(policy);
468 manager.advance_pass()
469 }
470
471 pub(crate) fn fill_slot_debug_snapshot(
472 &self,
473 host: &SlotsHost,
474 snapshot: &mut crate::SlotDebugSnapshot,
475 ) {
476 let retention = self.retention_debug_stats(host.storage_key());
477 snapshot.retained_subtree_count = retention.subtree_count;
478 snapshot.retained_group_count = retention.group_count;
479 snapshot.retained_payload_count = retention.payload_count;
480 snapshot.retained_node_count = retention.node_count;
481 snapshot.retained_scope_count = retention.scope_count;
482 }
483
484 pub(crate) fn slot_retention_debug_stats(
485 &self,
486 host: &SlotsHost,
487 ) -> crate::slot::SlotRetentionDebugStats {
488 let retention = self.retention_debug_stats(host.storage_key());
489 crate::slot::SlotRetentionDebugStats {
490 retained_subtree_count: retention.subtree_count,
491 retained_group_count: retention.group_count,
492 retained_payload_count: retention.payload_count,
493 retained_node_count: retention.node_count,
494 retained_scope_count: retention.scope_count,
495 retained_anchor_count: retention.anchor_count,
496 retained_heap_bytes: retention.heap_bytes,
497 retained_evictions_total: retention.evictions_total,
498 }
499 }
500
501 pub(crate) fn compact_table_identity_storage_for_host(
502 &self,
503 host: &SlotsHost,
504 table: &mut SlotTable,
505 compact_anchors: bool,
506 compact_payloads: bool,
507 ) {
508 if !compact_anchors && !compact_payloads {
509 return;
510 }
511
512 let host_key = host.storage_key();
513 let mut retention = self.retention_by_host.borrow_mut();
514 if let Some(retained) = retention.get_mut(&host_key) {
515 if compact_anchors {
516 table.compact_anchor_registry_storage(Some(&mut *retained));
517 }
518 if compact_payloads {
519 table.compact_payload_anchor_registry_storage(Some(&mut *retained));
520 }
521 } else {
522 if compact_anchors {
523 table.compact_anchor_registry_storage(None);
524 }
525 if compact_payloads {
526 table.compact_payload_anchor_registry_storage(None);
527 }
528 }
529 }
530
531 pub(crate) fn clear_host(&self, host: &SlotsHost, table: &mut SlotTable) {
532 let host_key = host.storage_key();
533 debug_assert!(
534 self.host_retention_is_empty(host),
535 "host retention must be drained before clearing host ownership"
536 );
537 self.clear_host_storage_key(host_key, table);
538 }
539
540 fn deactivate_and_queue_subtrees(
541 &self,
542 retention: RetentionManager,
543 table: &mut SlotTable,
544 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
545 ) {
546 for subtree in retention.into_subtrees() {
547 for scope in subtree.scopes() {
548 scope.deactivate();
549 }
550 table.invalidate_detached_subtree_anchors(&subtree);
551 lifecycle.queue_subtree_disposal(subtree);
552 }
553 }
554
555 pub(crate) fn dispose_retained_subtrees_for_host(
556 &self,
557 host_key: usize,
558 table: &mut SlotTable,
559 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
560 ) -> Result<(), NodeError> {
561 let retention = self.retention_by_host.borrow_mut().remove(&host_key);
562 let applier_host = self
563 .applier_host
564 .borrow()
565 .as_ref()
566 .and_then(std::rc::Weak::upgrade);
567 if let Some(applier_host) = applier_host {
568 let mut disposal = crate::NodeDisposal::default();
569 if let Some(retention) = retention {
570 for subtree in retention.into_subtrees() {
571 subtree.scopes().for_each(RecomposeScope::deactivate);
572 table.invalidate_detached_subtree_anchors(&subtree);
573 disposal.retain_subtree(subtree);
574 }
575 }
576 applier_host.dispose_nodes(disposal)?;
577 } else if let Some(retention) = retention {
578 self.deactivate_and_queue_subtrees(retention, table, lifecycle);
579 }
580 Ok(())
581 }
582
583 pub(crate) fn abandon_retained_subtrees_for_host(
584 &self,
585 host_key: usize,
586 table: &mut SlotTable,
587 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
588 ) {
589 let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
590 self.clear_host_storage_key(host_key, table);
591 return;
592 };
593 self.deactivate_and_queue_subtrees(retention, table, lifecycle);
594 self.clear_host_storage_key(host_key, table);
595 }
596
597 pub(crate) fn host_retention_is_empty(&self, host: &SlotsHost) -> bool {
598 self.retention_by_host
599 .borrow()
600 .get(&host.storage_key())
601 .is_none_or(RetentionManager::is_empty)
602 }
603
604 #[cfg(any(test, debug_assertions))]
605 pub(crate) fn debug_verify_host(&self, host: &SlotsHost, table: &SlotTable) {
606 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
607 retention.debug_verify(table);
608 }
609 }
610
611 #[cfg(test)]
612 pub(crate) fn validate_host_retention(
613 &self,
614 host: &SlotsHost,
615 table: &SlotTable,
616 ) -> Result<(), crate::slot::SlotInvariantError> {
617 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
618 retention.validate(table)?;
619 }
620 Ok(())
621 }
622
623 pub(crate) fn host_for_storage_key(&self, storage_key: usize) -> Option<Rc<SlotsHost>> {
624 self.live_hosts
625 .borrow()
626 .get(&storage_key)
627 .and_then(std::rc::Weak::upgrade)
628 }
629
630 #[cold]
631 #[inline(never)]
632 fn retention_debug_stats(&self, host_key: usize) -> crate::retention::RetentionDebugStats {
633 self.retention_by_host
634 .borrow()
635 .get(&host_key)
636 .map(RetentionManager::debug_stats)
637 .unwrap_or_default()
638 }
639}
640
641pub(crate) struct ParentFrame {
642 pub(crate) id: NodeId,
643 pub(crate) previous: ChildList,
644 pub(crate) new_children: ChildList,
645 pub(crate) attach_mode: ParentAttachMode,
646 pub(crate) synthetic_root: bool,
647}
648
649#[derive(Clone, Copy)]
650pub(crate) enum InitialParentFrame {
651 SyntheticRoot,
652 RealParent,
653}
654
655#[derive(Clone, Copy, Debug, PartialEq, Eq)]
656pub(crate) enum ParentAttachMode {
657 ImmediateAppend,
658 DeferredSync,
659}
660
661#[derive(Default)]
662pub(crate) struct SubcomposeFrame {
663 pub(crate) nodes: Vec<NodeId>,
664 pub(crate) scopes: Vec<RecomposeScope>,
665}
666
667pub(crate) struct LocalFrame {
670 values: SmallVec<[(LocalKey, Rc<dyn Any>); 2]>,
671 parent: LocalStackSnapshot,
672}
673
674pub(crate) fn provided_entry(stack: &LocalStackSnapshot, key: &LocalKey) -> Option<Rc<dyn Any>> {
676 let mut frame = stack.as_deref();
677 while let Some(current) = frame {
678 if let Some((_, entry)) = current.values.iter().find(|(provided, _)| provided == key) {
679 return Some(Rc::clone(entry));
680 }
681 frame = current.parent.as_deref();
682 }
683 None
684}
685
686pub(crate) struct ComposerCore {
687 pub(crate) shared_state: Rc<ComposerRuntimeState>,
688 pub(crate) branch_folds: Rc<crate::slot::BranchFolds>,
691 pub(crate) slots: Rc<SlotsHost>,
692 slot_hosts: RefCell<Vec<Rc<SlotsHost>>>,
693 pub(crate) applier: Rc<dyn ApplierHost>,
694 pub(crate) runtime: RuntimeHandle,
695 pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
696 pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
697 pub(crate) root: Cell<Option<NodeId>>,
698 pub(crate) commands: RefCell<CommandQueue>,
699 pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
700 subcomposition_owner_scope: RefCell<Option<RecomposeScope>>,
701 pub(crate) local_stack: RefCell<LocalStackSnapshot>,
702 pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
703 pub(crate) pending_scope_options: Cell<Option<RecomposeOptions>>,
704 pub(crate) phase: Cell<crate::Phase>,
705 pub(crate) recompose_parent_hint: Cell<Option<NodeId>>,
706 pub(crate) recompose_child_cursor: Cell<crate::recompose::RecomposeChildCursor>,
707 pub(crate) root_render_requested: Cell<bool>,
708 locals_changed: Cell<bool>,
711 pub(crate) _not_send: PhantomData<*const ()>,
712}
713
714#[derive(Clone, Default)]
718pub struct CapturedCompositionContext {
719 locals: LocalStackSnapshot,
720 owner_scope: Option<Weak<RecomposeScopeInner>>,
721 locals_changed: bool,
722}
723
724impl CapturedCompositionContext {
725 pub fn locals_changed(&self) -> bool {
730 self.locals_changed
731 }
732
733 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
737 self.owner_scope
738 .as_ref()
739 .and_then(Weak::upgrade)
740 .map_or(0, |inner| {
741 crate::RecomposeScope { inner }.owner_chain_deactivation_epoch()
742 })
743 }
744}
745
746fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
747 match core.subcompose_stack.borrow_mut().pop() {
748 Some(frame) => frame,
749 None => {
750 log::error!("subcompose stack underflow while finishing {operation}");
751 SubcomposeFrame::default()
752 }
753 }
754}
755
756struct SubcomposeStackGuard {
757 core: Rc<ComposerCore>,
758 leaked: bool,
759}
760
761impl Drop for SubcomposeStackGuard {
762 fn drop(&mut self) {
763 if !self.leaked {
764 self.core.subcompose_stack.borrow_mut().pop();
765 }
766 }
767}
768
769impl Drop for ComposerCore {
770 fn drop(&mut self) {
773 let commands = std::mem::take(self.commands.get_mut());
774 if commands.len() == 0 && commands.capacity() > 0 {
775 recycle_commands(commands);
776 }
777 let stacks = self.take_stacks();
778 if stacks.holds_storage() {
779 recycle_composer_stacks(stacks);
780 }
781 }
782}
783
784impl ComposerCore {
785 fn new(parts: ComposerParts) -> Self {
786 let ComposerParts {
787 shared_state,
788 slots,
789 applier,
790 runtime,
791 root,
792 initial_parent_frame,
793 } = parts;
794 let ComposerStacks {
795 slot_hosts,
796 mut parent_stack,
797 scope_stack,
798 } = spare_composer_stacks();
799 push_root_frame(&mut parent_stack, root, initial_parent_frame);
800 Self {
801 branch_folds: Rc::clone(&shared_state.branch_folds),
802 shared_state,
803 slots,
804 slot_hosts: RefCell::new(slot_hosts),
805 applier,
806 runtime,
807 parent_stack: RefCell::new(parent_stack),
808 subcompose_stack: RefCell::new(Vec::new()),
809 root: Cell::new(root),
810 commands: RefCell::new(spare_commands()),
811 scope_stack: RefCell::new(scope_stack),
812 subcomposition_owner_scope: RefCell::new(None),
813 local_stack: RefCell::new(None),
814 side_effects: RefCell::new(Vec::new()),
815 pending_scope_options: Cell::new(None),
816 phase: Cell::new(crate::Phase::Compose),
817 recompose_parent_hint: Cell::new(None),
818 recompose_child_cursor: Cell::new(crate::recompose::RecomposeChildCursor::Unknown),
819 root_render_requested: Cell::new(false),
820 locals_changed: Cell::new(false),
821 _not_send: PhantomData,
822 }
823 }
824
825 fn take_stacks(&mut self) -> ComposerStacks {
827 let mut stacks = ComposerStacks {
828 slot_hosts: std::mem::take(self.slot_hosts.get_mut()),
829 parent_stack: std::mem::take(self.parent_stack.get_mut()),
830 scope_stack: std::mem::take(self.scope_stack.get_mut()),
831 };
832 stacks.slot_hosts.clear();
833 stacks.parent_stack.clear();
834 stacks.scope_stack.clear();
835 stacks
836 }
837
838 fn restart(&mut self, parts: ComposerParts) {
842 let Self {
843 shared_state,
844 branch_folds,
845 slots,
846 slot_hosts,
847 applier,
848 runtime,
849 parent_stack,
850 subcompose_stack,
851 root,
852 commands,
853 scope_stack,
854 subcomposition_owner_scope,
855 local_stack,
856 side_effects,
857 pending_scope_options,
858 phase,
859 recompose_parent_hint,
860 recompose_child_cursor,
861 root_render_requested,
862 locals_changed,
863 _not_send: _,
864 } = self;
865 if !Rc::ptr_eq(shared_state, &parts.shared_state) {
866 *branch_folds = Rc::clone(&parts.shared_state.branch_folds);
867 *shared_state = parts.shared_state;
868 }
869 *slots = parts.slots;
870 *applier = parts.applier;
871 *runtime = parts.runtime;
872 slot_hosts.get_mut().clear();
873 let parent_stack = parent_stack.get_mut();
874 parent_stack.clear();
875 push_root_frame(parent_stack, parts.root, parts.initial_parent_frame);
876 subcompose_stack.get_mut().clear();
877 root.set(parts.root);
878 let commands = commands.get_mut();
879 if commands.len() > 0 || commands.capacity() == 0 {
880 *commands = spare_commands();
881 }
882 scope_stack.get_mut().clear();
883 *subcomposition_owner_scope.get_mut() = None;
884 *local_stack.get_mut() = None;
885 side_effects.get_mut().clear();
886 pending_scope_options.set(None);
887 phase.set(crate::Phase::Compose);
888 recompose_parent_hint.set(None);
889 recompose_child_cursor.set(crate::recompose::RecomposeChildCursor::Unknown);
890 root_render_requested.set(false);
891 locals_changed.set(false);
892 }
893
894 fn park(&mut self) {
897 self.slot_hosts.get_mut().clear();
898 self.parent_stack.get_mut().clear();
899 self.scope_stack.get_mut().clear();
900 self.subcompose_stack.get_mut().clear();
901 *self.subcomposition_owner_scope.get_mut() = None;
902 *self.local_stack.get_mut() = None;
903 self.side_effects.get_mut().clear();
904 }
905}
906
907fn push_root_frame(
909 parent_stack: &mut Vec<ParentFrame>,
910 root: Option<NodeId>,
911 initial_parent_frame: InitialParentFrame,
912) {
913 if let Some(root_id) = root {
914 parent_stack.push(ParentFrame {
915 id: root_id,
916 previous: ChildList::new(),
917 new_children: ChildList::new(),
918 attach_mode: ParentAttachMode::DeferredSync,
919 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
920 });
921 }
922}
923
924#[derive(Clone, Copy)]
928struct SubcomposeSite<'a> {
929 applier: &'a Rc<dyn ApplierHost>,
930 runtime: &'a RuntimeHandle,
931 phase: crate::Phase,
932 locals_changed: bool,
933}
934
935impl SubcomposeSite<'_> {
936 fn spin_up_core(
939 self,
940 shared_state: Rc<ComposerRuntimeState>,
941 slots: &Rc<SlotsHost>,
942 root: Option<NodeId>,
943 locals: LocalStackSnapshot,
944 ) -> Rc<ComposerCore> {
945 let core = Rc::new(ComposerCore::new(ComposerParts {
946 shared_state,
947 slots: Rc::clone(slots),
948 applier: Rc::clone(self.applier),
949 runtime: self.runtime.clone(),
950 root,
951 initial_parent_frame: InitialParentFrame::RealParent,
952 }));
953 core.phase.set(self.phase);
954 core.locals_changed.set(self.locals_changed);
955 *core.local_stack.borrow_mut() = locals;
956 core
957 }
958
959 fn flush(
964 self,
965 mut commands: CommandQueue,
966 compact_applier: bool,
967 side_effects: Vec<Box<dyn FnOnce()>>,
968 ) -> Result<(), NodeError> {
969 {
970 let mut applier = self.applier.borrow_dyn();
971 commands.apply(&mut *applier)?;
972 for update in self.runtime.take_updates() {
973 update.apply(&mut *applier)?;
974 }
975 }
976 recycle_commands(commands);
977 if compact_applier {
978 self.applier.compact();
979 self.applier.borrow_dyn().clear_recycled_nodes();
980 }
981 composer_context::without_composer(|| {
982 for effect in side_effects {
983 effect();
984 }
985 });
986 Ok(())
987 }
988}
989
990fn subcompose_slot_at<R>(
994 site: SubcomposeSite<'_>,
995 fallback_state: impl FnOnce() -> Rc<ComposerRuntimeState>,
996 slots: &Rc<SlotsHost>,
997 root: Option<NodeId>,
998 context: &CapturedCompositionContext,
999 f: impl FnOnce(&Composer) -> R,
1000) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1001 let _teardown = crate::runtime::enter_state_teardown_scope();
1002 let shared_state = slots.runtime_state().unwrap_or_else(fallback_state);
1003 let core = site.spin_up_core(shared_state, slots, root, context.locals.clone());
1004 *core.subcomposition_owner_scope.borrow_mut() = context
1005 .owner_scope
1006 .as_ref()
1007 .and_then(RecomposeScope::upgrade);
1008 let composer = Composer::from_core(core);
1009 composer.subcompose_stack().push(SubcomposeFrame::default());
1010 let mut guard = SubcomposeStackGuard {
1011 core: composer.clone_core(),
1012 leaked: false,
1013 };
1014 let root_group_key = crate::location_key!();
1015 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1016 let (output, outcome) = composer.try_with_slot_host_pass(
1017 Rc::clone(slots),
1018 crate::slot::SlotPassMode::Compose,
1019 |composer| {
1020 let output = composer.with_group(root_group_key, |composer| f(composer));
1021 if root.is_some() {
1022 composer.pop_parent();
1023 }
1024 output
1025 },
1026 )?;
1027 let commands = composer.take_commands();
1028 let side_effects = composer.take_side_effects();
1029 Ok((output, commands, side_effects, outcome.compacted))
1030 })?;
1031 let frame = {
1032 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1033 guard.leaked = true;
1034 frame
1035 };
1036 site.flush(commands, compact_applier, side_effects)?;
1037 Ok((result, frame.scopes))
1038}
1039
1040struct ComposerParts {
1043 shared_state: Rc<ComposerRuntimeState>,
1044 slots: Rc<SlotsHost>,
1045 applier: Rc<dyn ApplierHost>,
1046 runtime: RuntimeHandle,
1047 root: Option<NodeId>,
1048 initial_parent_frame: InitialParentFrame,
1049}
1050
1051#[derive(Clone)]
1055pub struct Composer {
1056 pub(crate) core: Rc<ComposerCore>,
1057}
1058
1059#[doc(hidden)]
1062pub struct BranchGroupGuard<'a> {
1063 folds: &'a crate::slot::BranchFolds,
1064 token: crate::slot::FoldToken,
1065}
1066
1067impl BranchGroupGuard<'_> {
1068 #[doc(hidden)]
1071 #[inline]
1072 pub fn advance(&mut self, key: Key) {
1073 self.folds.advance(&mut self.token, key);
1074 }
1075}
1076
1077impl Drop for BranchGroupGuard<'_> {
1078 #[inline]
1079 fn drop(&mut self) {
1080 self.folds.close(self.token);
1081 }
1082}
1083
1084#[doc(hidden)]
1091pub struct ScopedBranchGroupGuard {
1092 open: std::mem::ManuallyDrop<Option<ScopedBranchGroup>>,
1095}
1096
1097struct ScopedBranchGroup {
1099 folds: Rc<crate::slot::BranchFolds>,
1100 token: crate::slot::FoldToken,
1101}
1102
1103#[doc(hidden)]
1106#[inline]
1107pub fn __branch_group_scope_deferred(key: Key) -> ScopedBranchGroupGuard {
1108 let open = crate::composer_context::with_current_core(|core| ScopedBranchGroup {
1109 token: core.branch_folds.push(key),
1110 folds: Rc::clone(&core.branch_folds),
1111 });
1112 ScopedBranchGroupGuard {
1113 open: std::mem::ManuallyDrop::new(open),
1114 }
1115}
1116
1117#[doc(hidden)]
1122#[inline]
1123pub fn __branch_group_scope_advance(guard: &mut ScopedBranchGroupGuard, key: Key) {
1124 if let Some(group) = guard.open.as_mut() {
1125 group.folds.advance(&mut group.token, key);
1126 }
1127}
1128
1129impl Drop for ScopedBranchGroupGuard {
1130 #[inline(always)]
1131 fn drop(&mut self) {
1132 if let Some(group) = self.open.take() {
1133 close_scoped_branch_group(group);
1134 }
1135 }
1136}
1137
1138#[inline(never)]
1139fn close_scoped_branch_group(group: ScopedBranchGroup) {
1140 group.folds.close(group.token);
1141}
1142
1143#[doc(hidden)]
1146pub struct ComposableGroup<'a> {
1147 composer: &'a Composer,
1148 host: Rc<SlotsHost>,
1149 scope: RecomposeScope,
1150 pass: Option<SlotHostPassGuard>,
1151 runs_body: bool,
1152 completed: bool,
1153 skipped: Cell<bool>,
1154}
1155
1156impl ComposableGroup<'_> {
1157 pub fn scope(&self) -> &RecomposeScope {
1159 &self.scope
1160 }
1161
1162 pub fn set_recompose_callback<F>(&self, callback: F)
1165 where
1166 F: FnMut(&Composer) + 'static,
1167 {
1168 self.scope.set_recompose_callback(callback);
1169 }
1170
1171 pub fn skip(&self) {
1174 self.skipped.set(true);
1175 }
1176
1177 #[inline]
1179 pub fn close(mut self) {
1180 self.completed = true;
1181 }
1182}
1183
1184impl Drop for ComposableGroup<'_> {
1185 fn drop(&mut self) {
1186 if self.completed {
1187 self.scope.mark_composed_once();
1188 }
1189 self.scope
1190 .end_reads(self.completed & self.runs_body & !self.skipped.get());
1191 if self.skipped.get() {
1192 self.composer
1193 .close_skipped_group_in_active_pass(&self.host, &self.scope);
1194 } else {
1195 self.composer
1196 .close_group_in_active_pass(&self.host, &self.scope);
1197 }
1198 if let Some(mut pass) = self.pass.take() {
1199 if self.completed
1200 && let Err(err) = self.composer.finish_slot_host_pass(&pass.host)
1201 {
1202 log::error!("slot host pass finalization failed: {err}");
1203 }
1204 pass.close();
1205 }
1206 }
1207}
1208
1209pub(crate) enum EmittedNode {
1210 Fresh(Box<dyn Node>),
1211 Recycled(RecycledNode),
1212}
1213
1214impl Composer {
1215 pub(crate) fn new_with_shared_state(
1216 shared_state: Rc<ComposerRuntimeState>,
1217 slots: Rc<SlotsHost>,
1218 applier: Rc<dyn ApplierHost>,
1219 runtime: RuntimeHandle,
1220 root: Option<NodeId>,
1221 ) -> Self {
1222 Self::new_with_shared_state_with_parent_frame(
1223 shared_state,
1224 slots,
1225 applier,
1226 runtime,
1227 root,
1228 InitialParentFrame::SyntheticRoot,
1229 )
1230 }
1231
1232 fn new_with_shared_state_with_parent_frame(
1233 shared_state: Rc<ComposerRuntimeState>,
1234 slots: Rc<SlotsHost>,
1235 applier: Rc<dyn ApplierHost>,
1236 runtime: RuntimeHandle,
1237 root: Option<NodeId>,
1238 initial_parent_frame: InitialParentFrame,
1239 ) -> Self {
1240 shared_state.bind_applier_host(&applier);
1241 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
1242 let core = Rc::new(ComposerCore::new(ComposerParts {
1243 shared_state,
1244 slots,
1245 applier,
1246 runtime,
1247 root,
1248 initial_parent_frame,
1249 }));
1250 Self { core }
1251 }
1252
1253 pub(crate) fn for_pass(
1257 parked: Option<Composer>,
1258 shared_state: Rc<ComposerRuntimeState>,
1259 slots: Rc<SlotsHost>,
1260 applier: Rc<dyn ApplierHost>,
1261 runtime: RuntimeHandle,
1262 root: Option<NodeId>,
1263 ) -> Self {
1264 let Some(mut composer) = parked.filter(|parked| Rc::strong_count(&parked.core) == 1) else {
1265 return Self::new_with_shared_state(shared_state, slots, applier, runtime, root);
1266 };
1267 let Some(core) = Rc::get_mut(&mut composer.core) else {
1268 return Self::new_with_shared_state(shared_state, slots, applier, runtime, root);
1269 };
1270 shared_state.bind_applier_host(&applier);
1271 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
1272 core.restart(ComposerParts {
1273 shared_state,
1274 slots,
1275 applier,
1276 runtime,
1277 root,
1278 initial_parent_frame: InitialParentFrame::SyntheticRoot,
1279 });
1280 composer
1281 }
1282
1283 pub(crate) fn park(mut self) -> Option<Composer> {
1287 Rc::get_mut(&mut self.core)?.park();
1288 Some(self)
1289 }
1290
1291 pub fn new(
1294 slots: Rc<SlotsHost>,
1295 applier: Rc<dyn ApplierHost>,
1296 runtime: RuntimeHandle,
1297 root: Option<NodeId>,
1298 ) -> Self {
1299 Self::new_with_shared_state_with_parent_frame(
1300 slots
1301 .runtime_state()
1302 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
1303 slots,
1304 applier,
1305 runtime,
1306 root,
1307 InitialParentFrame::RealParent,
1308 )
1309 }
1310
1311 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
1312 Self { core }
1313 }
1314
1315 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
1316 Rc::clone(&self.core)
1317 }
1318
1319 pub(crate) fn request_root_render(&self) {
1320 self.core.root_render_requested.set(true);
1321 }
1322
1323 pub(crate) fn take_root_render_request(&self) -> bool {
1324 self.core.root_render_requested.replace(false)
1325 }
1326
1327 pub fn active_slots_host(&self) -> Rc<SlotsHost> {
1330 self.core
1331 .slot_hosts
1332 .borrow()
1333 .last()
1334 .cloned()
1335 .unwrap_or_else(|| Rc::clone(&self.core.slots))
1336 }
1337
1338 fn with_active_slots_host<R>(&self, f: impl FnOnce(&SlotsHost) -> R) -> R {
1339 let hosts = self.core.slot_hosts.borrow();
1340 f(hosts.last().unwrap_or(&self.core.slots))
1341 }
1342
1343 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
1344 self.with_active_slots_host(|host| f(&host.borrow()))
1345 }
1346
1347 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
1348 self.with_active_slots_host(|host| f(&mut host.borrow_mut()))
1349 }
1350
1351 pub(crate) fn with_slot_session_mut<R>(
1352 &self,
1353 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
1354 ) -> R {
1355 self.with_active_slots_host(|host| host.with_write_session(f))
1356 }
1357
1358 pub(crate) fn try_with_slot_host_pass<R>(
1359 &self,
1360 slots: Rc<SlotsHost>,
1361 mode: crate::slot::SlotPassMode,
1362 f: impl FnOnce(&Composer) -> R,
1363 ) -> Result<(R, SlotPassOutcome), NodeError> {
1364 let mut guard = self.begin_slot_host_pass(&slots, mode);
1365 let result = f(self);
1366 let outcome = self.finish_slot_host_pass(&guard.host)?;
1367 guard.close();
1368 Ok((result, outcome))
1369 }
1370
1371 pub(crate) fn with_slot_host_pass<R>(
1372 &self,
1373 slots: Rc<SlotsHost>,
1374 mode: crate::slot::SlotPassMode,
1375 f: impl FnOnce(&Composer) -> R,
1376 ) -> (R, SlotPassOutcome) {
1377 let mut guard = self.begin_slot_host_pass(&slots, mode);
1378 let result = f(self);
1379 let outcome = match self.finish_slot_host_pass(&guard.host) {
1380 Ok(outcome) => outcome,
1381 Err(err) => {
1382 log::error!("slot host pass finalization failed: {err}");
1383 SlotPassOutcome::default()
1384 }
1385 };
1386 guard.close();
1387 (result, outcome)
1388 }
1389
1390 pub(crate) fn with_slot_override<R>(
1391 &self,
1392 slots: Rc<SlotsHost>,
1393 f: impl FnOnce(&Composer) -> R,
1394 ) -> (R, SlotPassOutcome) {
1395 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
1396 }
1397
1398 fn begin_slot_host_pass(
1399 &self,
1400 slots: &Rc<SlotsHost>,
1401 mode: crate::slot::SlotPassMode,
1402 ) -> SlotHostPassGuard {
1403 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
1404 let fold_base = self.core.branch_folds.len();
1405 slots.begin_pass(mode, &self.core.branch_folds);
1406 {
1407 let mut stack = self.core.slot_hosts.borrow_mut();
1408 if let Some(parent) = stack.last()
1409 && !Rc::ptr_eq(parent, &slots)
1410 {
1411 parent.note_nested_host(&slots);
1412 }
1413 stack.push(Rc::clone(&slots));
1414 }
1415 SlotHostPassGuard {
1416 core: self.clone_core(),
1417 host: slots,
1418 fold_base,
1419 active: true,
1420 }
1421 }
1422
1423 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
1424 let finished = {
1425 let mut applier = self.core.applier.borrow_dyn();
1426 slots.finish_pass(&mut *applier)
1427 }?;
1428 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
1429 self.wake_sites_whose_movable_arrived();
1430 self.evict_retained_subtrees_for_host(slots)?;
1431 slots.complete_pass_cleanup(&finished.outcome);
1432 Ok(finished.outcome)
1433 }
1434
1435 fn wake_sites_whose_movable_arrived(&self) {
1436 for host in self.core.shared_state.hosts_awaiting_movables() {
1437 self.wake_sites_in_host(&host);
1438 }
1439 }
1440
1441 fn wake_sites_in_host(&self, slots: &Rc<SlotsHost>) {
1442 let pending = self.core.shared_state.take_pending_movables(slots);
1443 if pending.is_empty() {
1444 return;
1445 }
1446 let mut waiting = Vec::new();
1447 for site in pending {
1448 if !slots.borrow().group_is_active(site.placeholder) {
1449 continue;
1450 }
1451 let retain_key = RetainKey::for_group(None, site.key);
1452 if !self.core.shared_state.movable_retained_anywhere(retain_key) {
1453 waiting.push(site);
1454 continue;
1455 }
1456 match site.parent_scope.as_ref().and_then(RecomposeScope::upgrade) {
1457 Some(scope) => {
1458 scope.force_recompose();
1459 scope.invalidate();
1460 }
1461 None => log::error!(
1462 "movable content {:?} arrived for a site whose scope is gone",
1463 site.key
1464 ),
1465 }
1466 }
1467 self.core.shared_state.keep_pending_movables(slots, waiting);
1468 }
1469
1470 pub(crate) fn forget_movables(&self, ids: &[Key]) -> Result<(), NodeError> {
1471 for id in ids {
1472 let Some((host, subtree)) = self.core.shared_state.take_retained_movable(*id) else {
1473 continue;
1474 };
1475 self.dispose_detached_subtree_in_host(&host, subtree)?;
1476 host.flush_pending_drops();
1477 }
1478 Ok(())
1479 }
1480
1481 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
1482 self.core.parent_stack.borrow_mut()
1483 }
1484
1485 pub(crate) fn current_parent_hint(&self) -> Option<NodeId> {
1486 let stack = self.core.parent_stack.borrow();
1487 let stack_hint = stack
1488 .last()
1489 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
1490 stack_hint.or_else(|| self.core.recompose_parent_hint.get())
1491 }
1492
1493 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
1494 self.core.subcompose_stack.borrow_mut()
1495 }
1496
1497 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
1498 self.core.commands.borrow_mut()
1499 }
1500
1501 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
1502 self.commands_mut().push(Command::BubbleDirty {
1503 node_id: id,
1504 bubble: DirtyBubble::SEMANTICS,
1505 });
1506 }
1507
1508 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
1509 self.core.scope_stack.borrow_mut()
1510 }
1511
1512 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
1513 self.core.local_stack.borrow_mut()
1514 }
1515
1516 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
1517 self.core.local_stack.borrow().clone()
1518 }
1519
1520 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
1521 self.core.side_effects.borrow_mut()
1522 }
1523
1524 pub(crate) fn borrow_applier(&self) -> ApplierGuard<'_, dyn Applier> {
1525 self.core.applier.borrow_dyn()
1526 }
1527
1528 pub fn record_rebound_slot_children(&self, children: &[NodeId]) {
1539 let mut applier = self.borrow_applier();
1540 for &child in children {
1541 applier.record_structural_change(child);
1542 }
1543 }
1544
1545 pub fn create_virtual_node(&self, node: Box<dyn Node>) -> NodeId {
1550 let mut applier = self.borrow_applier();
1551 let node_id = applier.create_virtual(node);
1552 if let Ok(node) = applier.get_mut(node_id) {
1553 node.set_node_id(node_id);
1554 }
1555 node_id
1556 }
1557
1558 pub fn dispose_subcomposed_nodes(
1570 &self,
1571 disposal: crate::subcompose::SubcomposeDisposal,
1572 ) -> Result<(), NodeError> {
1573 if disposal.nodes.is_empty() && disposal.slot_hosts.is_empty() {
1574 return Ok(());
1575 }
1576 self.apply_pending_commands()?;
1577 Self::dispose_measure_subcomposed_nodes(&self.core.applier, disposal)
1578 }
1579
1580 pub fn dispose_measure_subcomposed_nodes(
1584 applier: &Rc<dyn ApplierHost>,
1585 disposal: crate::subcompose::SubcomposeDisposal,
1586 ) -> Result<(), NodeError> {
1587 if disposal.nodes.is_empty() && disposal.slot_hosts.is_empty() {
1588 return Ok(());
1589 }
1590 let disposal = crate::NodeDisposal::from_subcomposed(disposal, &*applier.borrow_dyn());
1591 applier.dispose_nodes(disposal)
1592 }
1593
1594 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
1597 let mut applier = self.borrow_applier();
1598 match applier.get_mut(node_id) {
1599 Ok(node) => node.parent().is_none(),
1600 Err(_) => true,
1601 }
1602 }
1603
1604 pub fn node_parent(&self, node_id: NodeId) -> Result<Option<NodeId>, NodeError> {
1608 self.borrow_applier()
1609 .get_mut(node_id)
1610 .map(|node| node.parent())
1611 }
1612
1613 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
1618 let mut applier = self.borrow_applier();
1619 match applier.get_mut(node_id) {
1620 Ok(node) => {
1621 let mut children = SmallVec::<[NodeId; 8]>::new();
1622 node.collect_children_into(&mut children);
1623 children
1624 }
1625 Err(_) => SmallVec::<[NodeId; 8]>::new(),
1626 }
1627 }
1628
1629 pub fn visit_node_children(&self, node_id: NodeId, mut visit: impl FnMut(NodeId, bool)) {
1635 let applier = self.borrow_applier();
1636 let Ok(node) = applier.get_ref(node_id) else {
1637 return;
1638 };
1639 node.for_each_child(&mut |child| {
1640 let needs_relayout = applier.get_ref(child).is_ok_and(Node::layout_dirty);
1641 visit(child, needs_relayout);
1642 });
1643 }
1644
1645 pub fn nodes_need_relayout(&self, node_ids: impl IntoIterator<Item = NodeId>) -> bool {
1654 let applier = self.borrow_applier();
1655 node_ids
1656 .into_iter()
1657 .any(|node_id| applier.get_ref(node_id).is_ok_and(Node::layout_dirty))
1658 }
1659
1660 pub fn clear_node_children(&self, node_id: NodeId) {
1666 let mut applier = self.borrow_applier();
1667 if let Ok(node) = applier.get_mut(node_id) {
1668 node.update_children(&[]);
1669 }
1670 }
1671
1672 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
1675 let _composer_guard = composer_context::enter(self);
1676 runtime::push_active_runtime(&self.core.runtime);
1677 struct Guard;
1678 impl Drop for Guard {
1679 fn drop(&mut self) {
1680 runtime::pop_active_runtime();
1681 }
1682 }
1683 let guard = Guard;
1684 let result = f(self);
1685 drop(guard);
1686 result
1687 }
1688
1689 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
1690 let queued = self.core.commands.borrow().len();
1691 if queued < COMMAND_FLUSH_THRESHOLD {
1692 return Ok(());
1693 }
1694 self.apply_pending_commands()
1695 }
1696
1697 fn resolve_group_entry(
1698 &self,
1699 host: &Rc<SlotsHost>,
1700 seed: crate::slot::GroupKeySeed,
1701 parent_scope_id: Option<ScopeId>,
1702 ) -> GroupEntry {
1703 let key = host.with_write_session(|slots| slots.reserve_group_key(seed));
1704 let retain_key = RetainKey::for_group(parent_scope_id, key);
1705 let restored = self
1706 .core
1707 .shared_state
1708 .take_retained(host, retain_key, |subtree| {
1709 host.with_write_session(|slots| slots.retained_restore_ready(key, subtree))
1710 })
1711 .or_else(|| self.take_movable_from_another_table(host, retain_key, key))
1712 .map(Box::new);
1713 if restored.is_some() || !key.is_movable() {
1714 return GroupEntry {
1715 key,
1716 restored,
1717 placeholder_for: None,
1718 };
1719 }
1720 let attached_elsewhere = self.movable_attached_elsewhere(host, key);
1721 if !attached_elsewhere {
1722 return GroupEntry {
1723 key,
1724 restored: None,
1725 placeholder_for: None,
1726 };
1727 }
1728 let id = key.explicit_key.unwrap_or_default();
1729 let placeholder = host.with_write_session(|slots| {
1730 slots.reserve_group_key(crate::slot::GroupKeySeed::movable_placeholder(id))
1731 });
1732 GroupEntry {
1733 key: placeholder,
1734 restored: None,
1735 placeholder_for: Some(key),
1736 }
1737 }
1738
1739 fn take_movable_from_another_table(
1744 &self,
1745 host: &Rc<SlotsHost>,
1746 retain_key: RetainKey,
1747 key: crate::slot::GroupKey,
1748 ) -> Option<crate::slot::DetachedSubtree> {
1749 if !key.is_movable() {
1750 return None;
1751 }
1752 let (source, mut subtree) = self
1753 .core
1754 .shared_state
1755 .take_retained_movable_elsewhere(host, retain_key)?;
1756 source
1757 .borrow_mut()
1758 .invalidate_detached_subtree_anchors(&subtree);
1759 if host.with_write_session(|slots| slots.retained_restore_ready(key, &mut subtree)) {
1760 return Some(subtree);
1761 }
1762 log::error!(
1763 "movable content {key:?} could not be taken over by the slot table that asked for it"
1764 );
1765 if let Err(error) = self.dispose_detached_subtree_in_host(host, subtree) {
1766 log::error!("disposing movable content that could not move failed: {error}");
1767 }
1768 None
1769 }
1770
1771 fn movable_attached_elsewhere(&self, host: &Rc<SlotsHost>, key: crate::slot::GroupKey) -> bool {
1774 if host.with_write_session(|slots| slots.movable_attached_elsewhere(key)) {
1775 return true;
1776 }
1777 let Some(id) = key.movable_id() else {
1778 return false;
1779 };
1780 self.core
1781 .shared_state
1782 .host_holding_movable(id)
1783 .is_some_and(|holder| !Rc::ptr_eq(&holder, host))
1784 }
1785
1786 fn scope_for_started_group(
1787 &self,
1788 host: &SlotsHost,
1789 group: crate::slot::ActiveGroupId,
1790 scope: Option<RecomposeScope>,
1791 ) -> RecomposeScope {
1792 if let Some(scope) = scope {
1793 return scope;
1794 }
1795 let scope = RecomposeScope::new(self.runtime_handle());
1796 host.with_write_session(|slots| slots.set_group_scope(group, scope.clone()));
1797 scope
1798 }
1799
1800 fn enter_group_scope(&self, scope_ref: &RecomposeScope, entry: GroupScopeEntry<'_>) {
1801 let GroupScopeEntry {
1802 options,
1803 start_kind,
1804 host,
1805 group,
1806 parent_hint,
1807 } = entry;
1808 let reused = !scope_ref.is_active() && scope_ref.has_composed_once();
1813 scope_ref.reactivate();
1814 {
1815 let mut stack = self.scope_stack();
1816 match stack.last() {
1817 Some(parent_scope) => scope_ref.set_parent_scope(Some(parent_scope)),
1818 None => scope_ref.set_lifetime_owner_scope(
1819 self.core.subcomposition_owner_scope.borrow().as_ref(),
1820 ),
1821 }
1822 stack.push(scope_ref.clone());
1823 }
1824 scope_ref.set_retention_mode(options.retention);
1825
1826 let restored = matches!(start_kind, GroupStartKind::Restored);
1829 let recompose = options.force_recompose | self.core.locals_changed.get() | restored;
1830 if recompose | (reused & !options.force_reuse) {
1831 scope_ref.force_recompose();
1832 } else if options.force_reuse {
1833 scope_ref.force_reuse();
1834 }
1835
1836 scope_ref.set_slots_host(host);
1837
1838 {
1839 let mut stack = self.subcompose_stack();
1840 if let Some(frame) = stack.last_mut() {
1841 frame.scopes.push(scope_ref.clone());
1842 }
1843 }
1844
1845 scope_ref.snapshot_locals(&self.core.local_stack.borrow());
1846 if restored {
1847 reparent_restored_scopes(host, group, scope_ref.parent_hint(), parent_hint);
1848 }
1849 scope_ref.set_parent_hint(parent_hint);
1850 }
1851
1852 fn start_group(
1853 &self,
1854 host: &Rc<SlotsHost>,
1855 seed: crate::slot::GroupKeySeed,
1856 parent_node: Option<NodeId>,
1857 ) -> (
1858 GroupStart<crate::slot::ActiveGroupId>,
1859 Option<crate::slot::GroupKey>,
1860 ) {
1861 if !self.core.shared_state.retains_any() {
1862 let started = host.with_write_session(|slots| {
1863 let key = slots.reserve_group_key(seed);
1864 (!key.is_movable()).then(|| slots.begin_group(key, None, parent_node))
1865 });
1866 if let Some(started) = started {
1867 return (started, None);
1868 }
1869 }
1870 let parent_scope_id = self
1871 .core
1872 .scope_stack
1873 .borrow()
1874 .last()
1875 .map(RecomposeScope::id);
1876 let GroupEntry {
1877 key,
1878 restored,
1879 placeholder_for,
1880 } = self.resolve_group_entry(host, seed, parent_scope_id);
1881 let started =
1882 host.with_write_session(|slots| slots.begin_group(key, restored, parent_node));
1883 (started, placeholder_for)
1884 }
1885
1886 #[inline(never)]
1887 fn open_group_in_active_pass(
1888 &self,
1889 host: &Rc<SlotsHost>,
1890 key: crate::slot::GroupKeySeed,
1891 ) -> (RecomposeScope, bool) {
1892 let options = self.core.pending_scope_options.take().unwrap_or_default();
1893 let parent_node = self.current_parent_hint();
1894 let (
1895 GroupStart {
1896 group,
1897 anchor,
1898 scope,
1899 kind,
1900 },
1901 placeholder_for,
1902 ) = self.start_group(host, key, parent_node);
1903 let scope_ref = self.scope_for_started_group(host, group, scope);
1904 if let Some(movable_key) = placeholder_for {
1905 self.core.shared_state.record_pending_movable(
1906 host,
1907 PendingMovable {
1908 key: movable_key,
1909 placeholder: anchor,
1910 parent_scope: self
1911 .core
1912 .scope_stack
1913 .borrow()
1914 .last()
1915 .map(RecomposeScope::downgrade),
1916 },
1917 );
1918 }
1919 self.enter_group_scope(
1920 &scope_ref,
1921 GroupScopeEntry {
1922 options,
1923 start_kind: kind,
1924 host,
1925 group,
1926 parent_hint: parent_node,
1927 },
1928 );
1929 (scope_ref, placeholder_for.is_none())
1930 }
1931
1932 #[inline(never)]
1933 fn close_group_in_active_pass(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
1934 let result = host.with_write_session(|slots| {
1935 let result = slots.finish_group_body_changes();
1936 slots.end_group();
1937 result
1938 });
1939 self.close_finished_group(host, scope, result);
1940 self.settle_closed_group(scope);
1941 }
1942
1943 #[inline(never)]
1946 fn close_skipped_group_in_active_pass(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
1947 host.with_write_session(|slots| slots.skip_and_end_group(|id| self.attach_to_parent(id)));
1950 self.pop_closed_scope(scope);
1951 self.settle_closed_group(scope);
1952 }
1953
1954 fn settle_closed_group(&self, scope: &RecomposeScope) {
1955 scope.mark_recomposed();
1956 if let Err(err) = self.flush_pending_commands_if_large() {
1957 log::error!("mid-composition command flush failed: {err}");
1958 }
1959 }
1960
1961 #[inline(never)]
1962 fn open_group(&self, key: crate::slot::GroupKeySeed) -> ComposableGroup<'_> {
1963 let host = self.active_slots_host();
1964 let (host, pass) = if host.has_active_pass() {
1965 (host, None)
1966 } else {
1967 let pass = self.begin_slot_host_pass(&host, crate::slot::SlotPassMode::Compose);
1968 (self.active_slots_host(), Some(pass))
1969 };
1970 let (scope, runs_body) = self.open_group_in_active_pass(&host, key);
1971 scope.begin_reads();
1972 ComposableGroup {
1973 composer: self,
1974 host,
1975 scope,
1976 pass,
1977 runs_body,
1978 completed: false,
1979 skipped: Cell::new(false),
1980 }
1981 }
1982
1983 #[doc(hidden)]
1984 pub fn __open_composable_group(&self, key: Key) -> ComposableGroup<'_> {
1985 self.open_group(crate::slot::GroupKeySeed::unkeyed(key))
1986 }
1987
1988 pub(crate) fn with_group_seed<R>(
1989 &self,
1990 key: crate::slot::GroupKeySeed,
1991 f: impl FnOnce(&Composer) -> R,
1992 ) -> R {
1993 let group = self.open_group(key);
1994 debug_assert!(group.runs_body, "only movable content waits for a body");
1995 let result = f(self);
1996 group.close();
1997 result
1998 }
1999
2000 pub(crate) fn with_movable_group(&self, id: Key, f: impl FnOnce(&Composer)) {
2001 let group = self.open_group(crate::slot::GroupKeySeed::movable(id));
2002 if group.runs_body {
2003 f(self);
2004 }
2005 group.close();
2006 }
2007
2008 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
2011 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
2012 }
2013
2014 pub fn cranpose_with_reuse<R>(
2018 &self,
2019 key: Key,
2020 mut options: RecomposeOptions,
2021 f: impl FnOnce(&Composer) -> R,
2022 ) -> R {
2023 options.retention = RetentionMode::RetainWhenInactive;
2024 self.core.pending_scope_options.set(Some(options));
2025 self.with_group(key, f)
2026 }
2027
2028 #[track_caller]
2031 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
2032 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
2033 self.with_group_seed(seed, f)
2034 }
2035
2036 #[doc(hidden)]
2037 pub fn __branch_group_deferred(&self, key: Key) -> BranchGroupGuard<'_> {
2038 let folds = &*self.core.branch_folds;
2039 BranchGroupGuard {
2040 token: folds.push(key),
2041 folds,
2042 }
2043 }
2044
2045 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
2046 for node_id in nodes {
2047 self.commands_mut().push(Command::callback(move |applier| {
2048 crate::slot::dispose_detached_node_now(applier, node_id)
2049 }));
2050 }
2051 }
2052
2053 fn detached_root_parent_commands(
2054 &self,
2055 subtree: &crate::slot::DetachedSubtree,
2056 context: &'static str,
2057 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
2058 let mut root_nodes = Vec::new();
2059 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
2060 let mut roots = Vec::with_capacity(root_nodes.len());
2061 for root in root_nodes {
2062 let parent_id = {
2063 let mut applier = self.borrow_applier();
2064 applier.get_mut(root)?.parent()
2065 };
2066 roots.push((root, parent_id));
2067 }
2068 Ok(roots)
2069 }
2070
2071 fn retain_detached_subtree_in_host(
2072 &self,
2073 slots_host: &Rc<SlotsHost>,
2074 parent_scope: Option<ScopeId>,
2075 subtree: crate::slot::DetachedSubtree,
2076 ) -> Result<(), NodeError> {
2077 let Some(root_key) = subtree.root_key_checked() else {
2078 log::error!("retention rejected detached subtree without a root group");
2079 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
2080 return Ok(());
2081 };
2082 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
2083 subtree.scopes().for_each(RecomposeScope::deactivate);
2084 for (root, parent_id) in root_detaches {
2085 if let Some(parent_id) = parent_id {
2086 self.commands_mut().push(Command::DetachChild {
2087 parent_id,
2088 child_id: root,
2089 });
2090 }
2091 }
2092 let evicted = self.core.shared_state.insert_retained(
2093 slots_host,
2094 RetainKey::for_group(parent_scope, root_key),
2095 subtree,
2096 root_key.is_movable(),
2097 );
2098 for subtree in evicted {
2099 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
2100 }
2101 Ok(())
2102 }
2103
2104 fn evict_retained_subtrees_for_host(
2105 &self,
2106 slots_host: &Rc<SlotsHost>,
2107 ) -> Result<(), NodeError> {
2108 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
2109 for subtree in evicted {
2110 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
2111 }
2112 Ok(())
2113 }
2114
2115 fn dispose_detached_subtree_in_host(
2116 &self,
2117 slots_host: &Rc<SlotsHost>,
2118 subtree: crate::slot::DetachedSubtree,
2119 ) -> Result<(), NodeError> {
2120 let root_nodes = self
2121 .detached_root_parent_commands(&subtree, "disposal")?
2122 .into_iter()
2123 .map(|(root, _)| root);
2124 subtree.scopes().for_each(RecomposeScope::deactivate);
2125 self.dispose_detached_nodes(root_nodes);
2126 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
2127 table.invalidate_detached_subtree_anchors(&subtree);
2128 lifecycle.queue_subtree_disposal(subtree);
2129 });
2130 Ok(())
2131 }
2132
2133 fn handle_detached_children_in_host(
2134 &self,
2135 slots_host: &Rc<SlotsHost>,
2136 parent_scope: Option<ScopeId>,
2137 detached: Vec<crate::slot::DetachedSubtree>,
2138 ) -> Result<(), NodeError> {
2139 for mut subtree in detached {
2140 for movable in subtree.split_off_nested_movables() {
2141 self.retain_detached_subtree_in_host(slots_host, None, movable)?;
2142 }
2143 if subtree
2144 .root_key_checked()
2145 .is_some_and(crate::slot::GroupKey::is_movable)
2146 {
2147 self.retain_detached_subtree_in_host(slots_host, None, subtree)?;
2148 continue;
2149 }
2150 let retention_mode = subtree
2151 .root_scope()
2152 .map(RecomposeScope::retention_mode)
2153 .unwrap_or_default();
2154 match retention_mode {
2155 RetentionMode::DisposeWhenInactive => {
2156 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
2157 }
2158 RetentionMode::RetainWhenInactive => {
2159 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?;
2160 }
2161 }
2162 }
2163 Ok(())
2164 }
2165
2166 fn handle_finished_group_result(
2167 &self,
2168 host: &Rc<SlotsHost>,
2169 parent_scope: Option<ScopeId>,
2170 result: FinishGroupResult,
2171 ) {
2172 let FinishGroupResult {
2173 detached_children,
2174 direct_nodes,
2175 root_nodes,
2176 was_skipped,
2177 } = result;
2178 if was_skipped {
2179 self.attach_root_nodes(root_nodes);
2180 }
2181 self.dispose_detached_nodes(direct_nodes);
2182 if detached_children.is_empty() {
2183 return;
2184 }
2185 if let Err(err) =
2186 self.handle_detached_children_in_host(host, parent_scope, detached_children)
2187 {
2188 log::error!("detached subtree handling failed while closing a group: {err}");
2189 }
2190 }
2191
2192 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
2193 self.close_group_body(&self.active_slots_host(), scope);
2194 }
2195
2196 fn close_group_body(&self, host: &Rc<SlotsHost>, scope: &RecomposeScope) {
2197 #[expect(
2198 clippy::redundant_closure_for_method_calls,
2199 reason = "the method path is not general over the session lifetime"
2200 )]
2201 let result = host.with_write_session(|slots| slots.finish_group_body_changes());
2202 self.close_finished_group(host, scope, result);
2203 }
2204
2205 fn close_finished_group(
2208 &self,
2209 host: &Rc<SlotsHost>,
2210 scope: &RecomposeScope,
2211 result: Option<FinishGroupResult>,
2212 ) {
2213 if let Some(result) = result {
2214 self.handle_finished_group_result(host, Some(scope.id()), result);
2215 }
2216 self.pop_closed_scope(scope);
2217 }
2218
2219 fn pop_closed_scope(&self, scope: &RecomposeScope) {
2220 if let Some(popped) = self.scope_stack().pop() {
2221 debug_assert_eq!(
2222 popped.id(),
2223 scope.id(),
2224 "closed scope must match the active scope stack"
2225 );
2226 } else {
2227 log::error!("scope stack underflow while closing scope {}", scope.id());
2228 }
2229 }
2230
2231 #[track_caller]
2234 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
2235 self.remember_at(crate::caller_location_key(), init)
2236 }
2237
2238 #[doc(hidden)]
2239 pub fn remember_at<T: 'static>(
2240 &self,
2241 source: crate::Key,
2242 init: impl FnOnce() -> T,
2243 ) -> Owned<T> {
2244 self.with_slot_session_mut(|slots| {
2245 slots.remember_with_kind(PayloadKind::Remember, source, init)
2246 })
2247 }
2248
2249 #[track_caller]
2250 pub(crate) fn remember_internal<T: 'static>(
2251 &self,
2252 source_salt: crate::Key,
2253 init: impl FnOnce() -> T,
2254 ) -> Owned<T> {
2255 let source = crate::caller_location_key() ^ source_salt;
2256 self.with_slot_session_mut(|slots| {
2257 slots.remember_with_kind(PayloadKind::Internal, source, init)
2258 })
2259 }
2260
2261 #[track_caller]
2262 pub(crate) fn remember_effect<T: Default + 'static>(&self, site: Key) -> Owned<T> {
2265 self.with_slot_session_mut(|slots| slots.remember_effect::<T>(site))
2266 }
2267
2268 #[track_caller]
2271 pub fn use_value_slot<T: 'static>(&self, init: impl FnOnce() -> T) -> ValueSlotHandle<'_, T> {
2272 let source = crate::caller_location_key();
2273 let (slot, record) = self.with_slot_session_mut(|slots| {
2274 slots.located_value_slot(PayloadKind::Internal, source, init)
2275 });
2276 ValueSlotHandle::new(slot, record)
2277 }
2278
2279 #[doc(hidden)]
2280 pub fn __use_param_slot<T: 'static>(
2281 &self,
2282 source: Key,
2283 init: impl FnOnce() -> T,
2284 ) -> ValueSlotHandle<'_, T> {
2285 let (slot, record) = self.with_slot_session_mut(|slots| {
2286 slots.located_value_slot(PayloadKind::Param, source, init)
2287 });
2288 ValueSlotHandle::new(slot, record)
2289 }
2290
2291 #[doc(hidden)]
2294 pub fn __update_param_slot<T: 'static, R>(
2295 &self,
2296 source: Key,
2297 init: impl FnOnce() -> T,
2298 update: impl FnOnce(&mut T) -> R,
2299 ) -> (ValueSlotHandle<'_, T>, R) {
2300 self.update_value_slot(PayloadKind::Param, source, init, update)
2301 }
2302
2303 #[doc(hidden)]
2306 pub fn __update_return_slot<T: 'static, R>(
2307 &self,
2308 source: Key,
2309 init: impl FnOnce() -> T,
2310 read: impl FnOnce(&mut T) -> R,
2311 ) -> (ValueSlotHandle<'_, T>, R) {
2312 self.update_value_slot(PayloadKind::Return, source, init, read)
2313 }
2314
2315 fn update_value_slot<T: 'static, R>(
2316 &self,
2317 kind: PayloadKind,
2318 source: Key,
2319 init: impl FnOnce() -> T,
2320 update: impl FnOnce(&mut T) -> R,
2321 ) -> (ValueSlotHandle<'_, T>, R) {
2322 self.with_slot_session_mut(|slots| {
2323 let (slot, record, value) = slots.value_slot_and_value(kind, source, init);
2324 (ValueSlotHandle::new(slot, record), update(value))
2325 })
2326 }
2327
2328 #[doc(hidden)]
2329 #[track_caller]
2330 pub fn __use_return_slot<T: 'static>(
2331 &self,
2332 init: impl FnOnce() -> T,
2333 ) -> ValueSlotHandle<'_, T> {
2334 let source = crate::caller_location_key();
2335 let (slot, record) = self.with_slot_session_mut(|slots| {
2336 slots.located_value_slot(PayloadKind::Return, source, init)
2337 });
2338 ValueSlotHandle::new(slot, record)
2339 }
2340
2341 #[doc(hidden)]
2342 pub fn __invalidate_return_consumer_scope(&self) {
2343 let Some(scope) = self.current_recompose_scope() else {
2344 self.request_root_render();
2345 return;
2346 };
2347
2348 if let Some(target) = scope.callback_promotion_target() {
2349 target.invalidate();
2350 } else {
2351 self.request_root_render();
2352 }
2353 }
2354
2355 pub fn with_slot_value<'pass, T: 'static, R>(
2357 &'pass self,
2358 handle: ValueSlotHandle<'pass, T>,
2359 f: impl FnOnce(&T) -> R,
2360 ) -> R {
2361 self.with_slots(|slots| f(slots.value_at(handle.slot, handle.record)))
2362 }
2363
2364 pub fn with_slot_value_mut<'pass, T: 'static, R>(
2366 &'pass self,
2367 handle: ValueSlotHandle<'pass, T>,
2368 f: impl FnOnce(&mut T) -> R,
2369 ) -> R {
2370 self.with_slots_mut(|slots| f(slots.value_at_mut(handle.slot, handle.record)))
2371 }
2372
2373 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
2376 MutableState::with_runtime(initial, self.runtime_handle())
2377 }
2378
2379 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
2382 where
2383 T: Clone + 'static,
2384 I: IntoIterator<Item = T>,
2385 {
2386 SnapshotStateList::with_runtime(values, self.runtime_handle())
2387 }
2388
2389 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
2392 where
2393 K: Clone + Eq + Hash + 'static,
2394 V: Clone + 'static,
2395 I: IntoIterator<Item = (K, V)>,
2396 {
2397 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
2398 }
2399
2400 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
2403 self.read_provided(
2404 &local.key,
2405 |entry: &LocalStateEntry<T>| entry.value(),
2406 |entry: &ComputedLocalEntry<T>| entry.value(),
2407 )
2408 .unwrap_or_else(|| local.default_value())
2409 }
2410
2411 pub fn read_static_composition_local<T: Clone + 'static>(
2414 &self,
2415 local: &StaticCompositionLocal<T>,
2416 ) -> T {
2417 let provided = local.provided_under(&self.core.local_stack.borrow());
2418 provided.unwrap_or_else(|deferred| deferred.value(local))
2419 }
2420
2421 fn read_provided<V: 'static, C: 'static, T>(
2425 &self,
2426 key: &LocalKey,
2427 value: impl FnOnce(&V) -> T,
2428 computed: impl FnOnce(&C) -> T,
2429 ) -> Option<T> {
2430 let entry = provided_entry(&self.core.local_stack.borrow(), key)?;
2431 match entry.downcast::<V>() {
2432 Ok(entry) => Some(value(&entry)),
2433 Err(entry) => entry
2434 .downcast::<C>()
2435 .inspect_err(|_| {
2436 log::error!("composition local entry type mismatch for key {key:?}");
2437 })
2438 .ok()
2439 .map(|entry| computed(&entry)),
2440 }
2441 }
2442
2443 pub fn current_recompose_scope(&self) -> Option<RecomposeScope> {
2445 self.core.scope_stack.borrow().last().cloned()
2446 }
2447
2448 pub(crate) fn note_state_read(
2452 &self,
2453 state_id: Option<crate::StateId>,
2454 ) -> Option<(RecomposeScope, crate::ScopeRead)> {
2455 let stack = self.core.scope_stack.borrow();
2456 let scope = stack
2457 .iter()
2458 .rev()
2459 .find(|scope| scope.has_recompose_callback())
2460 .or_else(|| stack.last())?;
2461 let read = state_id.map_or(crate::ScopeRead::NotReading, |id| scope.note_read(id));
2462 (!matches!(read, crate::ScopeRead::Watched)).then(|| (scope.clone(), read))
2463 }
2464
2465 pub fn phase(&self) -> crate::Phase {
2467 self.core.phase.get()
2468 }
2469
2470 pub(crate) fn set_phase(&self, phase: crate::Phase) {
2471 self.core.phase.set(phase);
2472 }
2473
2474 pub fn enter_phase(&self, phase: crate::Phase) {
2476 self.set_phase(phase);
2477 }
2478
2479 pub(crate) fn subcompose<R>(
2480 &self,
2481 state: &mut SubcomposeState,
2482 slot_id: SlotId,
2483 content: impl FnOnce(&Composer) -> R,
2484 ) -> (R, Vec<NodeId>) {
2485 match self.phase() {
2486 crate::Phase::Measure | crate::Phase::Layout => {}
2487 current => panic!(
2488 "subcompose() may only be called during measure or layout; current phase: {current:?}"
2489 ),
2490 }
2491
2492 self.subcompose_stack().push(SubcomposeFrame::default());
2493 let mut guard = SubcomposeStackGuard {
2494 core: self.clone_core(),
2495 leaked: false,
2496 };
2497
2498 let slot_host = state.get_or_create_slots(slot_id);
2499 let (result, _) = self.with_slot_override(slot_host, |composer| {
2500 composer.with_group(slot_id.raw(), |composer| content(composer))
2501 });
2502
2503 let frame = {
2504 let frame = take_subcompose_frame(&guard.core, "subcompose");
2505 guard.leaked = true;
2506 frame
2507 };
2508 let nodes = frame.nodes;
2509 let scopes = frame.scopes;
2510 state.register_active(slot_id, &nodes, &scopes);
2511 (result, nodes)
2512 }
2513
2514 pub fn subcompose_measurement<R>(
2517 &self,
2518 state: &mut SubcomposeState,
2519 slot_id: SlotId,
2520 content: impl FnOnce(&Composer) -> R,
2521 ) -> (R, Vec<NodeId>) {
2522 let (result, nodes) = self.subcompose(state, slot_id, content);
2523 let roots = nodes
2524 .into_iter()
2525 .filter(|&id| self.node_has_no_parent(id))
2526 .collect();
2527
2528 (result, roots)
2529 }
2530
2531 fn subcompose_site(&self) -> SubcomposeSite<'_> {
2533 SubcomposeSite {
2534 applier: &self.core.applier,
2535 runtime: &self.core.runtime,
2536 phase: self.phase(),
2537 locals_changed: self.core.locals_changed.get(),
2538 }
2539 }
2540
2541 pub fn subcompose_in<R>(
2545 &self,
2546 slots: &Rc<SlotsHost>,
2547 root: Option<NodeId>,
2548 f: impl FnOnce(&Composer) -> R,
2549 ) -> Result<R, NodeError> {
2550 let _teardown = crate::runtime::enter_state_teardown_scope();
2551 let site = self.subcompose_site();
2552 let shared_state = slots
2553 .runtime_state()
2554 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
2555 let core = site.spin_up_core(shared_state, slots, root, self.current_local_stack());
2556 let composer = Composer::from_core(core);
2557 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
2558 let (output, outcome) = composer.try_with_slot_host_pass(
2559 Rc::clone(slots),
2560 crate::slot::SlotPassMode::Compose,
2561 |composer| f(composer),
2562 )?;
2563 let commands = composer.take_commands();
2564 let side_effects = composer.take_side_effects();
2565 Ok((output, commands, side_effects, outcome.compacted))
2566 })?;
2567 site.flush(commands, compact_applier, side_effects)?;
2568 Ok(result)
2569 }
2570
2571 pub fn capture_composition_context(&self) -> CapturedCompositionContext {
2582 CapturedCompositionContext {
2583 locals: self.current_local_stack(),
2584 owner_scope: self
2585 .current_recompose_scope()
2586 .map(|scope| scope.downgrade()),
2587 locals_changed: self.core.locals_changed.get(),
2588 }
2589 }
2590
2591 pub fn with_locals_changed<R>(&self, changed: bool, f: impl FnOnce(&Composer) -> R) -> R {
2595 let outer = self.core.locals_changed.get();
2596 self.core.locals_changed.set(outer || changed);
2597 let result = f(self);
2598 self.core.locals_changed.set(outer);
2599 result
2600 }
2601
2602 pub fn subcompose_slot<R>(
2607 &self,
2608 slots: &Rc<SlotsHost>,
2609 root: Option<NodeId>,
2610 f: impl FnOnce(&Composer) -> R,
2611 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2612 let context = self.capture_composition_context();
2613 self.subcompose_slot_with_context(slots, root, &context, f)
2614 }
2615
2616 pub fn subcompose_slot_with_context<R>(
2620 &self,
2621 slots: &Rc<SlotsHost>,
2622 root: Option<NodeId>,
2623 context: &CapturedCompositionContext,
2624 f: impl FnOnce(&Composer) -> R,
2625 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2626 subcompose_slot_at(
2627 self.subcompose_site(),
2628 || Rc::clone(&self.core.shared_state),
2629 slots,
2630 root,
2631 context,
2632 f,
2633 )
2634 }
2635
2636 pub fn subcompose_measure_slot<R>(
2641 applier: &Rc<dyn ApplierHost>,
2642 runtime: &RuntimeHandle,
2643 slots: &Rc<SlotsHost>,
2644 root: NodeId,
2645 context: &CapturedCompositionContext,
2646 f: impl FnOnce(&Composer) -> R,
2647 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
2648 let site = SubcomposeSite {
2649 applier,
2650 runtime,
2651 phase: crate::Phase::Measure,
2652 locals_changed: false,
2653 };
2654 let new_state = || {
2655 let state = Rc::new(ComposerRuntimeState::default());
2656 state.bind_applier_host(applier);
2657 state
2658 };
2659 subcompose_slot_at(site, new_state, slots, Some(root), context, f)
2660 }
2661
2662 fn attach_root_nodes(&self, root_nodes: RootNodeIds) {
2663 for id in root_nodes {
2664 self.attach_to_parent(id);
2665 }
2666 }
2667
2668 pub fn skip_current_group(&self) {
2671 #[expect(
2672 clippy::redundant_closure_for_method_calls,
2673 reason = "the method path is not general over the session lifetime"
2674 )]
2675 self.with_slot_session_mut(|slots| slots.skip_group());
2676 }
2677
2678 pub fn runtime_handle(&self) -> RuntimeHandle {
2680 self.core.runtime.clone()
2681 }
2682
2683 pub fn set_recompose_callback<F>(&self, callback: F)
2686 where
2687 F: FnMut(&Composer) + 'static,
2688 {
2689 if let Some(scope) = self.current_recompose_scope() {
2690 scope.set_recompose_callback(callback);
2691 }
2692 }
2693
2694 pub fn set_recompose_fn(&self, callback: fn(&Composer)) {
2696 if let Some(scope) = self.current_recompose_scope() {
2697 scope.set_boxed_recompose(Box::new(callback), None);
2698 }
2699 }
2700
2701 pub fn with_composition_locals<R>(
2704 &self,
2705 provided: impl IntoIterator<Item = ProvidedValue>,
2706 site: crate::Key,
2707 f: impl FnOnce(&Composer) -> R,
2708 ) -> R {
2709 let provided: SmallVec<[ProvidedValue; 2]> = provided.into_iter().collect();
2710 if provided.is_empty() {
2711 return f(self);
2712 }
2713 let mut values = SmallVec::<[(LocalKey, Rc<dyn Any>); 2]>::new();
2714 let mut static_changed = false;
2715 for value in provided.into_iter().rev() {
2716 if values.iter().any(|(key, _)| key == value.key()) {
2717 continue;
2718 }
2719 let (key, entry, changed) = value.into_entry(self, site);
2720 static_changed |= changed;
2721 values.push((key, entry));
2722 }
2723 let parent = self.current_local_stack();
2724 *self.local_stack() = Some(Rc::new(LocalFrame {
2725 values,
2726 parent: parent.clone(),
2727 }));
2728 let result = self.with_locals_changed(static_changed, f);
2729 *self.local_stack() = parent;
2730 result
2731 }
2732}