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