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