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