1use crate::collections::map::{HashMap, HashSet};
2use crate::retention::{RetainKey, RetentionManager};
3use crate::slot::{FinishGroupResult, PayloadKind};
4use crate::slot::{GroupStart, GroupStartKind, ValueSlotId};
5use crate::{
6 composer_context, empty_local_stack, explicit_group_key_seed, runtime, Applier, ApplierHost,
7 ChildList, Command, CommandQueue, CompositionLocal, DirtyBubble, Key, LocalKey,
8 LocalStackSnapshot, LocalStateEntry, MutableState, Node, NodeError, NodeId, Owned,
9 ProvidedValue, RecomposeOptions, RecomposeScope, RecycledNode, RetentionMode, RetentionPolicy,
10 RuntimeHandle, ScopeId, SlotId, SlotPassOutcome, SlotTable, SlotsHost, SnapshotStateList,
11 SnapshotStateMap, SnapshotStateObserver, StaticCompositionLocal, StaticLocalEntry,
12 SubcomposeState, COMMAND_FLUSH_THRESHOLD,
13};
14use smallvec::SmallVec;
15use std::any::Any;
16use std::cell::{Cell, RefCell, RefMut};
17use std::hash::Hash;
18use std::marker::PhantomData;
19use std::rc::Rc;
20
21pub struct ValueSlotHandle<'pass, T: 'static> {
22 slot: ValueSlotId,
23 _pass: PhantomData<&'pass Composer>,
24 _value: PhantomData<fn() -> T>,
25}
26
27impl<T: 'static> Copy for ValueSlotHandle<'_, T> {}
28
29impl<T: 'static> Clone for ValueSlotHandle<'_, T> {
30 fn clone(&self) -> Self {
31 *self
32 }
33}
34
35impl<T: 'static> ValueSlotHandle<'_, T> {
36 pub(crate) fn new(slot: ValueSlotId) -> Self {
37 Self {
38 slot,
39 _pass: PhantomData,
40 _value: PhantomData,
41 }
42 }
43
44 pub(crate) fn slot(self) -> ValueSlotId {
45 self.slot
46 }
47}
48
49fn slots_storage_key(host: &Rc<SlotsHost>) -> usize {
50 host.storage_key()
51}
52
53fn bind_slots_host_to_runtime_state(
54 state: &Rc<ComposerRuntimeState>,
55 host: &Rc<SlotsHost>,
56) -> Rc<SlotsHost> {
57 if let Some(bound_state) = host.runtime_state() {
58 if Rc::ptr_eq(&bound_state, state) {
59 state.bind_slots_host(host);
60 return Rc::clone(host);
61 }
62 drop(bound_state);
63 if host.rebind_orphaned_runtime_state(state) {
64 state.bind_slots_host(host);
65 return Rc::clone(host);
66 }
67 log::error!(
68 "slot host already belongs to a different composer runtime state; using a fresh slot host"
69 );
70 let replacement = Rc::new(SlotsHost::new(SlotTable::new()));
71 state.bind_slots_host(&replacement);
72 return replacement;
73 }
74 state.bind_slots_host(host);
75 Rc::clone(host)
76}
77
78struct SlotHostPassGuard {
79 core: Rc<ComposerCore>,
80 host: Rc<SlotsHost>,
81 active: bool,
82}
83
84impl SlotHostPassGuard {
85 fn close(&mut self) {
86 if !self.active {
87 return;
88 }
89 if self.host.has_active_pass() {
90 self.host.abandon_active_pass();
91 }
92 match self.core.slot_hosts.borrow_mut().pop() {
93 Some(host) if Rc::ptr_eq(&host, &self.host) => {}
94 Some(_) => {
95 log::error!("slot host stack mismatch while closing slot host pass");
96 }
97 None => {
98 log::error!("slot host stack underflow while closing slot host pass");
99 }
100 }
101 self.active = false;
102 }
103}
104
105impl Drop for SlotHostPassGuard {
106 fn drop(&mut self) {
107 self.close();
108 }
109}
110
111pub(crate) struct ComposerRuntimeState {
112 scope_registry: RefCell<HashMap<ScopeId, RecomposeScope>>,
113 retention_by_host: RefCell<HashMap<usize, RetentionManager>>,
114 retention_policy: Cell<RetentionPolicy>,
115 live_hosts: RefCell<HashMap<usize, std::rc::Weak<SlotsHost>>>,
116 applier_host: RefCell<Option<std::rc::Weak<dyn ApplierHost>>>,
117}
118
119impl Default for ComposerRuntimeState {
120 fn default() -> Self {
121 Self {
122 scope_registry: RefCell::new(HashMap::default()),
123 retention_by_host: RefCell::new(HashMap::default()),
124 retention_policy: Cell::new(RetentionPolicy::default()),
125 live_hosts: RefCell::new(HashMap::default()),
126 applier_host: RefCell::new(None),
127 }
128 }
129}
130
131impl ComposerRuntimeState {
132 pub(crate) fn clear_host_storage_key(&self, host_key: usize) {
133 self.retention_by_host.borrow_mut().remove(&host_key);
134 self.live_hosts.borrow_mut().remove(&host_key);
135 self.scope_registry
136 .borrow_mut()
137 .retain(|_, scope| scope.slots_storage_key() != Some(host_key));
138 }
139
140 pub(crate) fn bind_applier_host(&self, applier: &Rc<dyn ApplierHost>) {
141 *self.applier_host.borrow_mut() = Some(Rc::downgrade(applier));
142 }
143
144 pub(crate) fn has_live_applier_host(&self) -> bool {
145 self.applier_host
146 .borrow()
147 .as_ref()
148 .and_then(std::rc::Weak::upgrade)
149 .is_some()
150 }
151
152 pub(crate) fn bind_slots_host(self: &Rc<Self>, host: &Rc<SlotsHost>) {
153 host.bind_runtime_state(self);
154 self.live_hosts
155 .borrow_mut()
156 .insert(host.storage_key(), Rc::downgrade(host));
157 }
158
159 pub(crate) fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
160 self.scope_registry.borrow().get(&scope_id).cloned()
161 }
162
163 pub(crate) fn register_scope(&self, scope: &RecomposeScope) {
164 self.scope_registry
165 .borrow_mut()
166 .insert(scope.id(), scope.clone());
167 }
168
169 pub(crate) fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
170 self.scope_registry.borrow_mut().remove(&scope_id)
171 }
172
173 pub(crate) fn set_retention_policy(&self, policy: RetentionPolicy) {
174 self.retention_policy.set(policy);
175 }
176
177 pub(crate) fn retention_policy(&self) -> RetentionPolicy {
178 self.retention_policy.get()
179 }
180
181 pub(crate) fn scope_registry_len(&self) -> usize {
182 self.scope_registry.borrow().len()
183 }
184
185 pub(crate) fn take_retained(
186 &self,
187 host: &Rc<SlotsHost>,
188 key: RetainKey,
189 preflight: impl FnOnce(&crate::slot::DetachedSubtree) -> bool,
190 ) -> Option<crate::slot::DetachedSubtree> {
191 let host_key = slots_storage_key(host);
192 let mut retention = self.retention_by_host.borrow_mut();
193 let subtree = retention
194 .get_mut(&host_key)?
195 .take_after_restore_preflight(key, preflight);
196 if retention
197 .get(&host_key)
198 .is_some_and(|manager| manager.is_empty() && manager.evictions_total() == 0)
199 {
200 retention.remove(&host_key);
201 }
202 subtree
203 }
204
205 pub(crate) fn insert_retained(
206 &self,
207 host: &Rc<SlotsHost>,
208 key: RetainKey,
209 subtree: crate::slot::DetachedSubtree,
210 ) -> Vec<crate::slot::DetachedSubtree> {
211 let policy = self.retention_policy();
212 let mut retention_by_host = self.retention_by_host.borrow_mut();
213 let manager = retention_by_host
214 .entry(slots_storage_key(host))
215 .or_insert_with(|| RetentionManager::new(policy));
216 manager.set_policy(policy);
217 manager.insert(key, subtree)
218 }
219
220 pub(crate) fn advance_retention_pass(
221 &self,
222 host: &Rc<SlotsHost>,
223 ) -> Vec<crate::slot::DetachedSubtree> {
224 let host_key = slots_storage_key(host);
225 let policy = self.retention_policy();
226 let mut retention_by_host = self.retention_by_host.borrow_mut();
227 let Some(manager) = retention_by_host.get_mut(&host_key) else {
228 return Vec::new();
229 };
230 manager.set_policy(policy);
231 manager.advance_pass()
232 }
233
234 pub(crate) fn fill_slot_debug_snapshot(
235 &self,
236 host: &SlotsHost,
237 snapshot: &mut crate::SlotDebugSnapshot,
238 ) {
239 let retention = self.retention_debug_stats(host.storage_key());
240 snapshot.runtime_scope_registry_count = Some(self.scope_registry_len());
241 snapshot.retained_subtree_count = retention.subtree_count;
242 snapshot.retained_group_count = retention.group_count;
243 snapshot.retained_payload_count = retention.payload_count;
244 snapshot.retained_node_count = retention.node_count;
245 snapshot.retained_scope_count = retention.scope_count;
246 }
247
248 pub(crate) fn slot_retention_debug_stats(
249 &self,
250 host: &SlotsHost,
251 ) -> crate::slot::SlotRetentionDebugStats {
252 let retention = self.retention_debug_stats(host.storage_key());
253 crate::slot::SlotRetentionDebugStats {
254 retained_subtree_count: retention.subtree_count,
255 retained_group_count: retention.group_count,
256 retained_payload_count: retention.payload_count,
257 retained_node_count: retention.node_count,
258 retained_scope_count: retention.scope_count,
259 retained_anchor_count: retention.anchor_count,
260 retained_heap_bytes: retention.heap_bytes,
261 retained_evictions_total: retention.evictions_total,
262 }
263 }
264
265 pub(crate) fn compact_table_identity_storage_for_host(
266 &self,
267 host: &SlotsHost,
268 table: &mut SlotTable,
269 compact_anchors: bool,
270 compact_payloads: bool,
271 ) {
272 if !compact_anchors && !compact_payloads {
273 return;
274 }
275
276 let host_key = host.storage_key();
277 let mut retention = self.retention_by_host.borrow_mut();
278 if let Some(retained) = retention.get_mut(&host_key) {
279 if compact_anchors {
280 table.compact_anchor_registry_storage(Some(&mut *retained));
281 }
282 if compact_payloads {
283 table.compact_payload_anchor_registry_storage(Some(&mut *retained));
284 }
285 } else {
286 if compact_anchors {
287 table.compact_anchor_registry_storage(None);
288 }
289 if compact_payloads {
290 table.compact_payload_anchor_registry_storage(None);
291 }
292 }
293 }
294
295 pub(crate) fn clear_host(&self, host: &SlotsHost) {
296 let host_key = host.storage_key();
297 debug_assert!(
298 self.host_retention_is_empty(host),
299 "host retention must be drained before clearing host ownership"
300 );
301 self.clear_host_storage_key(host_key);
302 }
303
304 pub(crate) fn dispose_retained_subtrees_for_host(
305 &self,
306 host_key: usize,
307 table: &mut SlotTable,
308 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
309 ) -> Result<(), NodeError> {
310 let applier_host = self
311 .applier_host
312 .borrow()
313 .as_ref()
314 .and_then(std::rc::Weak::upgrade);
315 if let Some(applier_host) = applier_host.as_ref() {
316 let retention_by_host = self.retention_by_host.borrow();
317 let Some(retention) = retention_by_host.get(&host_key) else {
318 return Ok(());
319 };
320 let mut applier = applier_host.borrow_dyn();
321 for subtree in retention.subtrees() {
322 crate::slot::dispose_detached_subtree_now(&mut *applier, subtree)?;
323 }
324 }
325 let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
326 return Ok(());
327 };
328 for subtree in retention.into_subtrees() {
329 for scope_id in subtree.scope_ids() {
330 if let Some(scope) = self.remove_scope(scope_id) {
331 scope.deactivate();
332 }
333 }
334 table.invalidate_detached_subtree_anchors(&subtree);
335 lifecycle.queue_subtree_disposal(subtree);
336 }
337 Ok(())
338 }
339
340 pub(crate) fn abandon_retained_subtrees_for_host(
341 &self,
342 host_key: usize,
343 table: &mut SlotTable,
344 lifecycle: &mut crate::slot::SlotLifecycleCoordinator,
345 ) {
346 let Some(retention) = self.retention_by_host.borrow_mut().remove(&host_key) else {
347 self.clear_host_storage_key(host_key);
348 return;
349 };
350 for subtree in retention.into_subtrees() {
351 for scope_id in subtree.scope_ids() {
352 if let Some(scope) = self.remove_scope(scope_id) {
353 scope.deactivate();
354 }
355 }
356 table.invalidate_detached_subtree_anchors(&subtree);
357 lifecycle.queue_subtree_disposal(subtree);
358 }
359 self.clear_host_storage_key(host_key);
360 }
361
362 pub(crate) fn host_retention_is_empty(&self, host: &SlotsHost) -> bool {
363 self.retention_by_host
364 .borrow()
365 .get(&host.storage_key())
366 .is_none_or(RetentionManager::is_empty)
367 }
368
369 #[cfg(any(test, debug_assertions))]
370 pub(crate) fn debug_verify_host(&self, host: &SlotsHost, table: &SlotTable) {
371 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
372 retention.debug_verify(table);
373 }
374 }
375
376 #[cfg(test)]
377 pub(crate) fn validate_host_retention(
378 &self,
379 host: &SlotsHost,
380 table: &SlotTable,
381 ) -> Result<(), crate::slot::SlotInvariantError> {
382 if let Some(retention) = self.retention_by_host.borrow().get(&host.storage_key()) {
383 retention.validate(table)?;
384 }
385 Ok(())
386 }
387
388 pub(crate) fn host_for_storage_key(&self, storage_key: usize) -> Option<Rc<SlotsHost>> {
389 self.live_hosts
390 .borrow()
391 .get(&storage_key)
392 .and_then(std::rc::Weak::upgrade)
393 }
394
395 fn retention_debug_stats(&self, host_key: usize) -> crate::retention::RetentionDebugStats {
396 self.retention_by_host
397 .borrow()
398 .get(&host_key)
399 .map(RetentionManager::debug_stats)
400 .unwrap_or_default()
401 }
402}
403
404pub(crate) struct ParentFrame {
405 pub(crate) id: NodeId,
406 pub(crate) previous: ChildList,
407 pub(crate) new_children: ChildList,
408 pub(crate) new_children_membership: Option<HashSet<NodeId>>,
409 pub(crate) attach_mode: ParentAttachMode,
410 pub(crate) synthetic_root: bool,
411}
412
413#[derive(Clone, Copy)]
414pub(crate) enum InitialParentFrame {
415 SyntheticRoot,
416 RealParent,
417}
418
419const LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD: usize = 16;
420
421#[derive(Clone, Copy, Debug, PartialEq, Eq)]
422pub(crate) enum ParentAttachMode {
423 ImmediateAppend,
424 DeferredSync,
425}
426
427#[derive(Default)]
428pub(crate) struct SubcomposeFrame {
429 pub(crate) nodes: Vec<NodeId>,
430 pub(crate) scopes: Vec<RecomposeScope>,
431}
432
433#[derive(Default, Clone)]
434pub(crate) struct LocalContext {
435 pub(crate) values: HashMap<LocalKey, Rc<dyn Any>>,
436}
437
438pub(crate) struct ComposerCore {
439 pub(crate) shared_state: Rc<ComposerRuntimeState>,
440 pub(crate) slots: Rc<SlotsHost>,
441 slot_hosts: RefCell<Vec<Rc<SlotsHost>>>,
442 pub(crate) applier: Rc<dyn ApplierHost>,
443 pub(crate) runtime: RuntimeHandle,
444 pub(crate) observer: SnapshotStateObserver,
445 pub(crate) parent_stack: RefCell<Vec<ParentFrame>>,
446 pub(crate) subcompose_stack: RefCell<Vec<SubcomposeFrame>>,
447 pub(crate) root: Cell<Option<NodeId>>,
448 pub(crate) commands: RefCell<CommandQueue>,
449 pub(crate) scope_stack: RefCell<Vec<RecomposeScope>>,
450 pub(crate) local_stack: RefCell<LocalStackSnapshot>,
451 pub(crate) side_effects: RefCell<Vec<Box<dyn FnOnce()>>>,
452 pub(crate) pending_scope_options: RefCell<Option<RecomposeOptions>>,
453 pub(crate) phase: Cell<crate::Phase>,
454 pub(crate) last_node_reused: Cell<Option<bool>>,
455 pub(crate) recranpose_parent_hint: Cell<Option<NodeId>>,
456 pub(crate) root_render_requested: Cell<bool>,
457 pub(crate) _not_send: PhantomData<*const ()>,
458}
459
460#[derive(Clone)]
464pub struct CapturedCompositionLocals(LocalStackSnapshot);
465
466fn take_subcompose_frame(core: &ComposerCore, operation: &str) -> SubcomposeFrame {
467 match core.subcompose_stack.borrow_mut().pop() {
468 Some(frame) => frame,
469 None => {
470 log::error!("subcompose stack underflow while finishing {operation}");
471 SubcomposeFrame::default()
472 }
473 }
474}
475
476impl ComposerCore {
477 pub(crate) fn new(
478 shared_state: Rc<ComposerRuntimeState>,
479 slots: Rc<SlotsHost>,
480 applier: Rc<dyn ApplierHost>,
481 runtime: RuntimeHandle,
482 observer: SnapshotStateObserver,
483 root: Option<NodeId>,
484 initial_parent_frame: InitialParentFrame,
485 ) -> Self {
486 let parent_stack = if let Some(root_id) = root {
487 vec![ParentFrame {
488 id: root_id,
489 previous: ChildList::new(),
490 new_children: ChildList::new(),
491 new_children_membership: None,
492 attach_mode: ParentAttachMode::DeferredSync,
493 synthetic_root: matches!(initial_parent_frame, InitialParentFrame::SyntheticRoot),
494 }]
495 } else {
496 Vec::new()
497 };
498
499 Self {
500 shared_state,
501 slots,
502 slot_hosts: RefCell::new(Vec::new()),
503 applier,
504 runtime,
505 observer,
506 parent_stack: RefCell::new(parent_stack),
507 subcompose_stack: RefCell::new(Vec::new()),
508 root: Cell::new(root),
509 commands: RefCell::new(CommandQueue::default()),
510 scope_stack: RefCell::new(Vec::new()),
511 local_stack: RefCell::new(empty_local_stack()),
512 side_effects: RefCell::new(Vec::new()),
513 pending_scope_options: RefCell::new(None),
514 phase: Cell::new(crate::Phase::Compose),
515 last_node_reused: Cell::new(None),
516 recranpose_parent_hint: Cell::new(None),
517 root_render_requested: Cell::new(false),
518 _not_send: PhantomData,
519 }
520 }
521}
522
523#[derive(Clone)]
524pub struct Composer {
525 pub(crate) core: Rc<ComposerCore>,
526}
527
528pub(crate) enum EmittedNode {
529 Fresh(Box<dyn Node>),
530 Recycled(RecycledNode),
531}
532
533impl Composer {
534 pub(crate) fn new_with_shared_state(
535 shared_state: Rc<ComposerRuntimeState>,
536 slots: Rc<SlotsHost>,
537 applier: Rc<dyn ApplierHost>,
538 runtime: RuntimeHandle,
539 observer: SnapshotStateObserver,
540 root: Option<NodeId>,
541 ) -> Self {
542 Self::new_with_shared_state_with_parent_frame(
543 shared_state,
544 slots,
545 applier,
546 runtime,
547 observer,
548 root,
549 InitialParentFrame::SyntheticRoot,
550 )
551 }
552
553 fn new_with_shared_state_with_parent_frame(
554 shared_state: Rc<ComposerRuntimeState>,
555 slots: Rc<SlotsHost>,
556 applier: Rc<dyn ApplierHost>,
557 runtime: RuntimeHandle,
558 observer: SnapshotStateObserver,
559 root: Option<NodeId>,
560 initial_parent_frame: InitialParentFrame,
561 ) -> Self {
562 shared_state.bind_applier_host(&applier);
563 let slots = bind_slots_host_to_runtime_state(&shared_state, &slots);
564 let core = Rc::new(ComposerCore::new(
565 shared_state,
566 slots,
567 applier,
568 runtime,
569 observer,
570 root,
571 initial_parent_frame,
572 ));
573 Self { core }
574 }
575
576 pub fn new(
577 slots: Rc<SlotsHost>,
578 applier: Rc<dyn ApplierHost>,
579 runtime: RuntimeHandle,
580 observer: SnapshotStateObserver,
581 root: Option<NodeId>,
582 ) -> Self {
583 Self::new_with_shared_state_with_parent_frame(
584 slots
585 .runtime_state()
586 .unwrap_or_else(|| Rc::new(ComposerRuntimeState::default())),
587 slots,
588 applier,
589 runtime,
590 observer,
591 root,
592 InitialParentFrame::RealParent,
593 )
594 }
595
596 pub(crate) fn from_core(core: Rc<ComposerCore>) -> Self {
597 Self { core }
598 }
599
600 pub(crate) fn clone_core(&self) -> Rc<ComposerCore> {
601 Rc::clone(&self.core)
602 }
603
604 fn observer(&self) -> SnapshotStateObserver {
605 self.core.observer.clone()
606 }
607
608 pub(crate) fn request_root_render(&self) {
609 self.core.root_render_requested.set(true);
610 }
611
612 pub(crate) fn take_root_render_request(&self) -> bool {
613 self.core.root_render_requested.replace(false)
614 }
615
616 pub(crate) fn observe_scope<R>(&self, scope: &RecomposeScope, block: impl FnOnce() -> R) -> R {
617 let observer = self.observer();
618 let scope_clone = scope.clone();
619 observer.observe_reads(scope_clone, move |scope_ref| scope_ref.invalidate(), block)
620 }
621
622 pub(crate) fn active_slots_host(&self) -> Rc<SlotsHost> {
623 self.core
624 .slot_hosts
625 .borrow()
626 .last()
627 .cloned()
628 .unwrap_or_else(|| Rc::clone(&self.core.slots))
629 }
630
631 pub(crate) fn with_slots<R>(&self, f: impl FnOnce(&SlotTable) -> R) -> R {
632 let host = self.active_slots_host();
633 let slots = host.borrow();
634 f(&slots)
635 }
636
637 pub(crate) fn with_slots_mut<R>(&self, f: impl FnOnce(&mut SlotTable) -> R) -> R {
638 let host = self.active_slots_host();
639 let mut slots = host.borrow_mut();
640 f(&mut slots)
641 }
642
643 pub(crate) fn with_slot_session_mut<R>(
644 &self,
645 f: impl FnOnce(&mut crate::slot::SlotWriteSession<'_>) -> R,
646 ) -> R {
647 self.active_slots_host().with_write_session(f)
648 }
649
650 pub(crate) fn try_with_slot_host_pass<R>(
651 &self,
652 slots: Rc<SlotsHost>,
653 mode: crate::slot::SlotPassMode,
654 f: impl FnOnce(&Composer) -> R,
655 ) -> Result<(R, SlotPassOutcome), NodeError> {
656 let mut guard = self.begin_slot_host_pass(&slots, mode);
657 let result = f(self);
658 let outcome = self.finish_slot_host_pass(&guard.host)?;
659 guard.close();
660 Ok((result, outcome))
661 }
662
663 pub(crate) fn with_slot_host_pass<R>(
664 &self,
665 slots: Rc<SlotsHost>,
666 mode: crate::slot::SlotPassMode,
667 f: impl FnOnce(&Composer) -> R,
668 ) -> (R, SlotPassOutcome) {
669 let mut guard = self.begin_slot_host_pass(&slots, mode);
670 let result = f(self);
671 let outcome = match self.finish_slot_host_pass(&guard.host) {
672 Ok(outcome) => outcome,
673 Err(err) => {
674 log::error!("slot host pass finalization failed: {err}");
675 SlotPassOutcome::default()
676 }
677 };
678 guard.close();
679 (result, outcome)
680 }
681
682 pub(crate) fn with_slot_override<R>(
683 &self,
684 slots: Rc<SlotsHost>,
685 f: impl FnOnce(&Composer) -> R,
686 ) -> (R, SlotPassOutcome) {
687 self.with_slot_host_pass(slots, crate::slot::SlotPassMode::Compose, f)
688 }
689
690 fn begin_slot_host_pass(
691 &self,
692 slots: &Rc<SlotsHost>,
693 mode: crate::slot::SlotPassMode,
694 ) -> SlotHostPassGuard {
695 let slots = bind_slots_host_to_runtime_state(&self.core.shared_state, slots);
696 slots.begin_pass(mode);
697 self.core.slot_hosts.borrow_mut().push(Rc::clone(&slots));
698 SlotHostPassGuard {
699 core: self.clone_core(),
700 host: slots,
701 active: true,
702 }
703 }
704
705 fn finish_slot_host_pass(&self, slots: &Rc<SlotsHost>) -> Result<SlotPassOutcome, NodeError> {
706 let finished = {
707 let mut applier = self.core.applier.borrow_dyn();
708 slots.finish_pass(&mut *applier)
709 }?;
710 self.handle_detached_children_in_host(slots, None, finished.detached_root_children)?;
711 self.evict_retained_subtrees_for_host(slots)?;
712 slots.complete_pass_cleanup(&finished.outcome);
713 Ok(finished.outcome)
714 }
715
716 pub(crate) fn parent_stack(&self) -> RefMut<'_, Vec<ParentFrame>> {
717 self.core.parent_stack.borrow_mut()
718 }
719
720 fn current_parent_hint(&self) -> Option<NodeId> {
721 let stack = self.core.parent_stack.borrow();
722 let stack_hint = stack
723 .last()
724 .and_then(|frame| (!frame.synthetic_root).then_some(frame.id));
725 stack_hint.or_else(|| self.core.recranpose_parent_hint.get())
726 }
727
728 pub(crate) fn subcompose_stack(&self) -> RefMut<'_, Vec<SubcomposeFrame>> {
729 self.core.subcompose_stack.borrow_mut()
730 }
731
732 pub(crate) fn commands_mut(&self) -> RefMut<'_, CommandQueue> {
733 self.core.commands.borrow_mut()
734 }
735
736 pub(crate) fn enqueue_semantics_invalidation(&self, id: NodeId) {
737 self.commands_mut().push(Command::BubbleDirty {
738 node_id: id,
739 bubble: DirtyBubble::SEMANTICS,
740 });
741 }
742
743 pub(crate) fn scope_stack(&self) -> RefMut<'_, Vec<RecomposeScope>> {
744 self.core.scope_stack.borrow_mut()
745 }
746
747 fn scope_for_id(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
748 self.core.shared_state.scope_for_id(scope_id)
749 }
750
751 fn register_scope(&self, scope: &RecomposeScope) {
752 self.core.shared_state.register_scope(scope);
753 }
754
755 fn remove_scope(&self, scope_id: ScopeId) -> Option<RecomposeScope> {
756 self.core.shared_state.remove_scope(scope_id)
757 }
758
759 pub(crate) fn local_stack(&self) -> RefMut<'_, LocalStackSnapshot> {
760 self.core.local_stack.borrow_mut()
761 }
762
763 pub(crate) fn current_local_stack(&self) -> LocalStackSnapshot {
764 self.core.local_stack.borrow().clone()
765 }
766
767 pub(crate) fn side_effects_mut(&self) -> RefMut<'_, Vec<Box<dyn FnOnce()>>> {
768 self.core.side_effects.borrow_mut()
769 }
770
771 fn pending_scope_options(&self) -> RefMut<'_, Option<RecomposeOptions>> {
772 self.core.pending_scope_options.borrow_mut()
773 }
774
775 pub(crate) fn borrow_applier(&self) -> RefMut<'_, dyn Applier> {
776 self.core.applier.borrow_dyn()
777 }
778
779 pub fn register_virtual_node(
786 &self,
787 node_id: NodeId,
788 node: Box<dyn Node>,
789 ) -> Result<(), NodeError> {
790 let mut applier = self.borrow_applier();
791 applier.insert_with_id(node_id, node)
792 }
793
794 pub fn node_has_no_parent(&self, node_id: NodeId) -> bool {
797 let mut applier = self.borrow_applier();
798 match applier.get_mut(node_id) {
799 Ok(node) => node.parent().is_none(),
800 Err(_) => true,
801 }
802 }
803
804 pub fn get_node_children(&self, node_id: NodeId) -> SmallVec<[NodeId; 8]> {
809 let mut applier = self.borrow_applier();
810 match applier.get_mut(node_id) {
811 Ok(node) => {
812 let mut children = SmallVec::<[NodeId; 8]>::new();
813 node.collect_children_into(&mut children);
814 children
815 }
816 Err(_) => SmallVec::<[NodeId; 8]>::new(),
817 }
818 }
819
820 pub fn nodes_need_measure(&self, node_ids: &[NodeId]) -> bool {
821 let mut applier = self.borrow_applier();
822 node_ids.iter().any(|node_id| {
823 applier
824 .get_mut(*node_id)
825 .is_ok_and(|node| node.needs_measure())
826 })
827 }
828
829 pub fn record_subcompose_child(&self, child_id: NodeId) {
839 let mut parent_stack = self.parent_stack();
840 if let Some(frame) = parent_stack.last_mut() {
841 if matches!(frame.attach_mode, ParentAttachMode::DeferredSync) {
842 if let Some(membership) = frame.new_children_membership.as_mut() {
843 if membership.insert(child_id) {
844 frame.new_children.push(child_id);
845 }
846 } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
847 let mut membership = HashSet::default();
848 membership.reserve(frame.new_children.len() + 1);
849 membership.extend(frame.new_children.iter().copied());
850 if membership.insert(child_id) {
851 frame.new_children.push(child_id);
852 }
853 frame.new_children_membership = Some(membership);
854 } else if !frame.new_children.contains(&child_id) {
855 frame.new_children.push(child_id);
856 }
857 }
858 }
859 }
860
861 pub fn clear_node_children(&self, node_id: NodeId) {
867 let mut applier = self.borrow_applier();
868 if let Ok(node) = applier.get_mut(node_id) {
869 node.update_children(&[]);
870 }
871 }
872
873 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
874 let _composer_guard = composer_context::enter(self);
875 runtime::push_active_runtime(&self.core.runtime);
876 struct Guard;
877 impl Drop for Guard {
878 fn drop(&mut self) {
879 runtime::pop_active_runtime();
880 }
881 }
882 let guard = Guard;
883 let result = f(self);
884 drop(guard);
885 result
886 }
887
888 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
889 let queued = self.core.commands.borrow().len();
890 if queued < COMMAND_FLUSH_THRESHOLD {
891 return Ok(());
892 }
893 self.apply_pending_commands()
894 }
895
896 fn with_group_in_active_pass<R>(
900 &self,
901 key: crate::slot::GroupKeySeed,
902 f: impl FnOnce(&Composer) -> R,
903 ) -> R {
904 let mut f = Some(f);
905 let mut result = None;
906 self.with_group_in_active_pass_dyn(key, &mut |composer| {
907 let f = f.take().expect("group body must run at most once");
908 result = Some(f(composer));
909 });
910 result.expect("group body must run exactly once")
911 }
912
913 #[inline(never)]
917 fn with_group_in_active_pass_dyn(
918 &self,
919 key: crate::slot::GroupKeySeed,
920 f: &mut dyn FnMut(&Composer),
921 ) {
922 struct GroupGuard {
923 composer: Composer,
924 scope: RecomposeScope,
925 }
926
927 impl Drop for GroupGuard {
928 fn drop(&mut self) {
929 self.composer
930 .close_current_group_body_for_scope(&self.scope);
931 self.scope.mark_recomposed();
932 self.composer
933 .with_slot_session_mut(|slots| slots.end_group());
934 if let Err(err) = self.composer.flush_pending_commands_if_large() {
935 log::error!("mid-composition command flush failed: {err}");
936 }
937 }
938 }
939
940 let parent_scope = self.current_recranpose_scope();
941 let options = self.pending_scope_options().take().unwrap_or_default();
942 let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
943 let reserved_key = self.with_slot_session_mut(|slots| slots.preview_group_key(key));
944 let host = self.active_slots_host();
945 let restored = self.core.shared_state.take_retained(
946 &host,
947 RetainKey {
948 parent_scope: parent_scope_id,
949 key: reserved_key,
950 },
951 |subtree| {
952 self.with_slot_session_mut(|slots| {
953 slots.retained_restore_ready(reserved_key, subtree)
954 })
955 },
956 );
957 let (group, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
958 let GroupStart {
959 group,
960 scope_id,
961 kind,
962 ..
963 } = slots.begin_group(reserved_key, restored);
964 (group, scope_id, kind)
965 });
966 let scope_ref =
967 if let Some(scope) = start_scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
968 scope
969 } else {
970 let scope = RecomposeScope::new(self.runtime_handle());
971 self.register_scope(&scope);
972 self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
973 scope
974 };
975
976 scope_ref.reactivate();
977 scope_ref.set_parent_scope(parent_scope);
978 scope_ref.set_retention_mode(options.retention);
979
980 if options.force_recompose {
981 scope_ref.force_recompose();
982 } else if options.force_reuse {
983 scope_ref.force_reuse();
984 }
985 if matches!(start_kind, GroupStartKind::Restored) {
986 scope_ref.force_recompose();
987 }
988
989 scope_ref.set_slots_host(&host);
990
991 {
992 let mut stack = self.scope_stack();
993 stack.push(scope_ref.clone());
994 }
995
996 {
997 let mut stack = self.subcompose_stack();
998 if let Some(frame) = stack.last_mut() {
999 frame.scopes.push(scope_ref.clone());
1000 }
1001 }
1002
1003 scope_ref.snapshot_locals(self.current_local_stack());
1004 {
1005 let parent_hint = self.current_parent_hint();
1006 scope_ref.set_parent_hint(parent_hint);
1007 }
1008
1009 let guard = GroupGuard {
1010 composer: self.clone(),
1011 scope: scope_ref.clone(),
1012 };
1013 self.observe_scope(&scope_ref, || f(self));
1014 scope_ref.mark_composed_once();
1015 drop(guard);
1016 }
1017
1018 pub(crate) fn with_group_seed<R>(
1019 &self,
1020 key: crate::slot::GroupKeySeed,
1021 f: impl FnOnce(&Composer) -> R,
1022 ) -> R {
1023 let host = self.active_slots_host();
1024 if host.has_active_pass() {
1025 return self.with_group_in_active_pass(key, f);
1026 }
1027 let (result, _) =
1028 self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
1029 composer.with_group_in_active_pass(key, f)
1030 });
1031 result
1032 }
1033
1034 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1035 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1036 }
1037
1038 pub fn cranpose_with_reuse<R>(
1039 &self,
1040 key: Key,
1041 mut options: RecomposeOptions,
1042 f: impl FnOnce(&Composer) -> R,
1043 ) -> R {
1044 options.retention = RetentionMode::RetainWhenInactive;
1045 self.pending_scope_options().replace(options);
1046 self.with_group(key, f)
1047 }
1048
1049 #[track_caller]
1050 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1051 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1052 self.with_group_seed(seed, f)
1053 }
1054
1055 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1056 for node_id in nodes {
1057 self.commands_mut().push(Command::callback(move |applier| {
1058 crate::slot::dispose_detached_node_now(applier, node_id)
1059 }));
1060 }
1061 }
1062
1063 fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1064 for scope_id in scope_ids {
1065 if let Some(scope) = self.scope_for_id(scope_id) {
1066 scope.deactivate();
1067 }
1068 }
1069 }
1070
1071 fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1072 for scope_id in scope_ids {
1073 if let Some(scope) = self.remove_scope(scope_id) {
1074 scope.deactivate();
1075 }
1076 }
1077 }
1078
1079 fn detached_root_parent_commands(
1080 &self,
1081 subtree: &crate::slot::DetachedSubtree,
1082 context: &'static str,
1083 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1084 let mut root_nodes = Vec::new();
1085 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1086 let mut roots = Vec::with_capacity(root_nodes.len());
1087 for root in root_nodes {
1088 let parent_id = {
1089 let mut applier = self.borrow_applier();
1090 applier.get_mut(root)?.parent()
1091 };
1092 roots.push((root, parent_id));
1093 }
1094 Ok(roots)
1095 }
1096
1097 fn retain_detached_subtree_in_host(
1098 &self,
1099 slots_host: &Rc<SlotsHost>,
1100 parent_scope: Option<ScopeId>,
1101 subtree: crate::slot::DetachedSubtree,
1102 ) -> Result<(), NodeError> {
1103 let Some(root_key) = subtree.root_key_checked() else {
1107 log::error!("retention rejected detached subtree without a root group");
1108 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1109 return Ok(());
1110 };
1111 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1112 self.deactivate_scope_ids(subtree.scope_ids_iter());
1113 for (root, parent_id) in root_detaches {
1114 if let Some(parent_id) = parent_id {
1115 self.commands_mut().push(Command::DetachChild {
1116 parent_id,
1117 child_id: root,
1118 });
1119 }
1120 }
1121 let evicted = self.core.shared_state.insert_retained(
1122 slots_host,
1123 RetainKey {
1124 parent_scope,
1125 key: root_key,
1126 },
1127 subtree,
1128 );
1129 for subtree in evicted {
1130 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1131 }
1132 Ok(())
1133 }
1134
1135 fn evict_retained_subtrees_for_host(
1136 &self,
1137 slots_host: &Rc<SlotsHost>,
1138 ) -> Result<(), NodeError> {
1139 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1140 for subtree in evicted {
1141 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1142 }
1143 Ok(())
1144 }
1145
1146 fn dispose_detached_subtree_in_host(
1147 &self,
1148 slots_host: &Rc<SlotsHost>,
1149 subtree: crate::slot::DetachedSubtree,
1150 ) -> Result<(), NodeError> {
1151 let root_nodes = self
1152 .detached_root_parent_commands(&subtree, "disposal")?
1153 .into_iter()
1154 .map(|(root, _)| root);
1155 self.dispose_scope_ids(subtree.scope_ids_iter());
1156 self.dispose_detached_nodes(root_nodes);
1157 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1158 table.invalidate_detached_subtree_anchors(&subtree);
1159 lifecycle.queue_subtree_disposal(subtree);
1160 });
1161 Ok(())
1162 }
1163
1164 fn handle_detached_children_in_host(
1165 &self,
1166 slots_host: &Rc<SlotsHost>,
1167 parent_scope: Option<ScopeId>,
1168 detached: Vec<crate::slot::DetachedSubtree>,
1169 ) -> Result<(), NodeError> {
1170 for subtree in detached {
1171 let retention_mode = subtree
1172 .root_scope_id()
1173 .and_then(|scope_id| self.scope_for_id(scope_id))
1174 .map(|scope| scope.retention_mode())
1175 .unwrap_or_default();
1176 match retention_mode {
1177 RetentionMode::DisposeWhenInactive => {
1178 self.dispose_detached_subtree_in_host(slots_host, subtree)?
1179 }
1180 RetentionMode::RetainWhenInactive => {
1181 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1182 }
1183 }
1184 }
1185 Ok(())
1186 }
1187
1188 fn handle_detached_children(
1189 &self,
1190 parent_scope: Option<ScopeId>,
1191 detached: Vec<crate::slot::DetachedSubtree>,
1192 ) {
1193 let host = self.active_slots_host();
1194 if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1195 log::error!("detached subtree handling failed while closing a group: {err}");
1196 }
1197 }
1198
1199 fn handle_finished_group_result(
1200 &self,
1201 parent_scope: Option<ScopeId>,
1202 result: FinishGroupResult,
1203 ) {
1204 let FinishGroupResult {
1205 detached_children,
1206 direct_nodes,
1207 root_nodes,
1208 was_skipped,
1209 } = result;
1210 if was_skipped {
1211 self.attach_root_nodes(root_nodes);
1212 }
1213 self.dispose_detached_nodes(direct_nodes);
1214 self.handle_detached_children(parent_scope, detached_children);
1215 }
1216
1217 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1218 let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1219 self.handle_finished_group_result(Some(scope.id()), result);
1220 if let Some(popped) = self.scope_stack().pop() {
1221 debug_assert_eq!(
1222 popped.id(),
1223 scope.id(),
1224 "closed scope must match the active scope stack"
1225 );
1226 } else {
1227 log::error!("scope stack underflow while closing scope {}", scope.id());
1228 }
1229 }
1230
1231 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1232 self.remember_with_kind(PayloadKind::Remember, init)
1233 }
1234
1235 pub(crate) fn remember_internal<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1236 self.remember_with_kind(PayloadKind::Internal, init)
1237 }
1238
1239 pub(crate) fn remember_effect<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1240 self.remember_with_kind(PayloadKind::Effect, init)
1241 }
1242
1243 fn remember_with_kind<T: 'static>(
1244 &self,
1245 kind: PayloadKind,
1246 init: impl FnOnce() -> T,
1247 ) -> Owned<T> {
1248 self.with_slot_session_mut(|slots| slots.remember_with_kind(kind, init))
1249 }
1250
1251 pub fn use_value_slot<'pass, T: 'static>(
1252 &'pass self,
1253 init: impl FnOnce() -> T,
1254 ) -> ValueSlotHandle<'pass, T> {
1255 let slot = self
1256 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Internal, init));
1257 ValueSlotHandle::new(slot)
1258 }
1259
1260 #[doc(hidden)]
1261 pub fn __use_param_slot<'pass, T: 'static>(
1262 &'pass self,
1263 init: impl FnOnce() -> T,
1264 ) -> ValueSlotHandle<'pass, T> {
1265 let slot = self
1266 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Param, init));
1267 ValueSlotHandle::new(slot)
1268 }
1269
1270 #[doc(hidden)]
1271 pub fn __use_return_slot<'pass, T: 'static>(
1272 &'pass self,
1273 init: impl FnOnce() -> T,
1274 ) -> ValueSlotHandle<'pass, T> {
1275 let slot = self
1276 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Return, init));
1277 ValueSlotHandle::new(slot)
1278 }
1279
1280 #[doc(hidden)]
1281 pub fn __invalidate_return_consumer_scope(&self) {
1282 let Some(scope) = self.current_recranpose_scope() else {
1283 self.request_root_render();
1284 return;
1285 };
1286
1287 if let Some(target) = scope.callback_promotion_target() {
1288 target.invalidate();
1289 } else {
1290 self.request_root_render();
1291 }
1292 }
1293
1294 pub fn with_slot_value<'pass, T: 'static, R>(
1295 &'pass self,
1296 handle: ValueSlotHandle<'pass, T>,
1297 f: impl FnOnce(&T) -> R,
1298 ) -> R {
1299 self.with_slots(|slots| f(slots.read_value(handle.slot())))
1300 }
1301
1302 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1303 &'pass self,
1304 handle: ValueSlotHandle<'pass, T>,
1305 f: impl FnOnce(&mut T) -> R,
1306 ) -> R {
1307 self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1308 }
1309
1310 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1311 MutableState::with_runtime(initial, self.runtime_handle())
1312 }
1313
1314 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1315 where
1316 T: Clone + 'static,
1317 I: IntoIterator<Item = T>,
1318 {
1319 SnapshotStateList::with_runtime(values, self.runtime_handle())
1320 }
1321
1322 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1323 where
1324 K: Clone + Eq + Hash + 'static,
1325 V: Clone + 'static,
1326 I: IntoIterator<Item = (K, V)>,
1327 {
1328 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1329 }
1330
1331 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1332 let stack = self.core.local_stack.borrow();
1333 for context in stack.iter().rev() {
1334 if let Some(entry) = context.values.get(&local.key) {
1335 match entry.clone().downcast::<LocalStateEntry<T>>() {
1336 Ok(typed) => return typed.value(),
1337 Err(_) => {
1338 log::error!(
1339 "composition local entry type mismatch for key {}",
1340 local.key
1341 );
1342 return local.default_value();
1343 }
1344 }
1345 }
1346 }
1347 local.default_value()
1348 }
1349
1350 pub fn read_static_composition_local<T: Clone + 'static>(
1351 &self,
1352 local: &StaticCompositionLocal<T>,
1353 ) -> T {
1354 let stack = self.core.local_stack.borrow();
1355 for context in stack.iter().rev() {
1356 if let Some(entry) = context.values.get(&local.key) {
1357 match entry.clone().downcast::<StaticLocalEntry<T>>() {
1358 Ok(typed) => return typed.value(),
1359 Err(_) => {
1360 log::error!(
1361 "static composition local entry type mismatch for key {}",
1362 local.key
1363 );
1364 return local.default_value();
1365 }
1366 }
1367 }
1368 }
1369 local.default_value()
1370 }
1371
1372 pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
1373 self.core.scope_stack.borrow().last().cloned()
1374 }
1375
1376 pub(crate) fn current_state_invalidation_scope(&self) -> Option<RecomposeScope> {
1377 let stack = self.core.scope_stack.borrow();
1378 stack
1379 .iter()
1380 .rev()
1381 .find(|scope| scope.has_recompose_callback())
1382 .cloned()
1383 .or_else(|| stack.last().cloned())
1384 }
1385
1386 pub fn phase(&self) -> crate::Phase {
1387 self.core.phase.get()
1388 }
1389
1390 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1391 self.core.phase.set(phase);
1392 }
1393
1394 pub fn enter_phase(&self, phase: crate::Phase) {
1395 self.set_phase(phase);
1396 }
1397
1398 pub(crate) fn subcompose<R>(
1399 &self,
1400 state: &mut SubcomposeState,
1401 slot_id: SlotId,
1402 content: impl FnOnce(&Composer) -> R,
1403 ) -> (R, Vec<NodeId>) {
1404 match self.phase() {
1405 crate::Phase::Measure | crate::Phase::Layout => {}
1406 current => panic!(
1407 "subcompose() may only be called during measure or layout; current phase: {:?}",
1408 current
1409 ),
1410 }
1411
1412 self.subcompose_stack().push(SubcomposeFrame::default());
1413 struct StackGuard {
1414 core: Rc<ComposerCore>,
1415 leaked: bool,
1416 }
1417 impl Drop for StackGuard {
1418 fn drop(&mut self) {
1419 if !self.leaked {
1420 self.core.subcompose_stack.borrow_mut().pop();
1421 }
1422 }
1423 }
1424 let mut guard = StackGuard {
1425 core: self.clone_core(),
1426 leaked: false,
1427 };
1428
1429 let slot_host = state.get_or_create_slots(slot_id);
1430 let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1431 composer.with_group(slot_id.raw(), |composer| content(composer))
1432 });
1433
1434 let frame = {
1435 let frame = take_subcompose_frame(&guard.core, "subcompose");
1436 guard.leaked = true;
1437 frame
1438 };
1439 let nodes = frame.nodes;
1440 let scopes = frame.scopes;
1441 state.register_active(slot_id, &nodes, &scopes);
1442 (result, nodes)
1443 }
1444
1445 pub fn subcompose_measurement<R>(
1446 &self,
1447 state: &mut SubcomposeState,
1448 slot_id: SlotId,
1449 content: impl FnOnce(&Composer) -> R,
1450 ) -> (R, Vec<NodeId>) {
1451 let (result, nodes) = self.subcompose(state, slot_id, content);
1452 let roots = nodes
1453 .into_iter()
1454 .filter(|&id| self.node_has_no_parent(id))
1455 .collect();
1456
1457 (result, roots)
1458 }
1459
1460 pub fn subcompose_in<R>(
1461 &self,
1462 slots: &Rc<SlotsHost>,
1463 root: Option<NodeId>,
1464 f: impl FnOnce(&Composer) -> R,
1465 ) -> Result<R, NodeError> {
1466 let runtime_handle = self.runtime_handle();
1467 let phase = self.phase();
1468 let locals = self.current_local_stack();
1469 let shared_state = slots
1470 .runtime_state()
1471 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1472 let core = Rc::new(ComposerCore::new(
1473 shared_state,
1474 Rc::clone(slots),
1475 Rc::clone(&self.core.applier),
1476 runtime_handle.clone(),
1477 self.observer(),
1478 root,
1479 InitialParentFrame::RealParent,
1480 ));
1481 core.phase.set(phase);
1482 *core.local_stack.borrow_mut() = locals;
1483 let composer = Composer::from_core(core);
1484 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1485 let (output, outcome) = composer.try_with_slot_host_pass(
1486 Rc::clone(slots),
1487 crate::slot::SlotPassMode::Compose,
1488 |composer| f(composer),
1489 )?;
1490 let commands = composer.take_commands();
1491 let side_effects = composer.take_side_effects();
1492 Ok((output, commands, side_effects, outcome.compacted))
1493 })?;
1494 {
1495 let mut applier = self.borrow_applier();
1496 commands.apply(&mut *applier)?;
1497 for update in runtime_handle.take_updates() {
1498 update.apply(&mut *applier)?;
1499 }
1500 }
1501 if compact_applier {
1502 self.core.applier.compact();
1503 self.core.applier.borrow_dyn().clear_recycled_nodes();
1504 }
1505 runtime_handle.drain_ui();
1506 for effect in side_effects {
1507 effect();
1508 }
1509 runtime_handle.drain_ui();
1510 Ok(result)
1511 }
1512
1513 pub fn capture_composition_locals(&self) -> CapturedCompositionLocals {
1525 CapturedCompositionLocals(self.current_local_stack())
1526 }
1527
1528 pub fn subcompose_slot<R>(
1533 &self,
1534 slots: &Rc<SlotsHost>,
1535 root: Option<NodeId>,
1536 f: impl FnOnce(&Composer) -> R,
1537 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1538 self.subcompose_slot_with_locals(slots, root, None, f)
1539 }
1540
1541 pub fn subcompose_slot_with_locals<R>(
1546 &self,
1547 slots: &Rc<SlotsHost>,
1548 root: Option<NodeId>,
1549 locals: Option<&CapturedCompositionLocals>,
1550 f: impl FnOnce(&Composer) -> R,
1551 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1552 let runtime_handle = self.runtime_handle();
1553 let phase = self.phase();
1554 let locals = match locals {
1555 Some(captured) => captured.0.clone(),
1556 None => self.current_local_stack(),
1557 };
1558 let shared_state = slots
1559 .runtime_state()
1560 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1561 let core = Rc::new(ComposerCore::new(
1562 shared_state,
1563 Rc::clone(slots),
1564 Rc::clone(&self.core.applier),
1565 runtime_handle.clone(),
1566 self.observer(),
1567 root,
1568 InitialParentFrame::RealParent,
1569 ));
1570 core.phase.set(phase);
1571 *core.local_stack.borrow_mut() = locals;
1572 let composer = Composer::from_core(core);
1573 composer.subcompose_stack().push(SubcomposeFrame::default());
1574 struct StackGuard {
1575 core: Rc<ComposerCore>,
1576 leaked: bool,
1577 }
1578 impl Drop for StackGuard {
1579 fn drop(&mut self) {
1580 if !self.leaked {
1581 self.core.subcompose_stack.borrow_mut().pop();
1582 }
1583 }
1584 }
1585 let mut guard = StackGuard {
1586 core: composer.clone_core(),
1587 leaked: false,
1588 };
1589 let root_group_key = crate::location_key(file!(), line!(), column!());
1590 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1591 let (output, outcome) = composer.try_with_slot_host_pass(
1592 Rc::clone(slots),
1593 crate::slot::SlotPassMode::Compose,
1594 |composer| {
1595 let output = composer.with_group(root_group_key, |composer| f(composer));
1596 if root.is_some() {
1597 composer.pop_parent();
1598 }
1599 output
1600 },
1601 )?;
1602 let commands = composer.take_commands();
1603 let side_effects = composer.take_side_effects();
1604 Ok((output, commands, side_effects, outcome.compacted))
1605 })?;
1606 let frame = {
1607 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1608 guard.leaked = true;
1609 frame
1610 };
1611
1612 {
1613 let mut applier = self.borrow_applier();
1614 commands.apply(&mut *applier)?;
1615 for update in runtime_handle.take_updates() {
1616 update.apply(&mut *applier)?;
1617 }
1618 }
1619 if compact_applier {
1620 self.core.applier.compact();
1621 self.core.applier.borrow_dyn().clear_recycled_nodes();
1622 }
1623 runtime_handle.drain_ui();
1624 for effect in side_effects {
1625 effect();
1626 }
1627 runtime_handle.drain_ui();
1628 Ok((result, frame.scopes))
1629 }
1630
1631 fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
1632 for id in root_nodes {
1633 self.attach_to_parent_with_mode(id, true);
1634 }
1635 }
1636
1637 pub fn skip_current_group(&self) {
1638 self.with_slot_session_mut(|slots| slots.skip_group());
1639 }
1640
1641 pub fn runtime_handle(&self) -> RuntimeHandle {
1642 self.core.runtime.clone()
1643 }
1644
1645 pub fn set_recranpose_callback<F>(&self, callback: F)
1646 where
1647 F: FnMut(&Composer) + 'static,
1648 {
1649 self.set_recranpose_callback_boxed(Box::new(callback));
1650 }
1651
1652 #[inline(never)]
1656 fn set_recranpose_callback_boxed(&self, mut callback: Box<dyn FnMut(&Composer)>) {
1657 if let Some(scope) = self.current_recranpose_scope() {
1658 let observer = self.observer();
1659 let scope_weak = scope.downgrade();
1660 scope.set_recompose(Box::new(move |composer: &Composer| {
1661 if let Some(inner) = scope_weak.upgrade() {
1662 let scope_instance = RecomposeScope { inner };
1663 observer.observe_reads(
1664 scope_instance.clone(),
1665 move |scope_ref| scope_ref.invalidate(),
1666 || {
1667 callback(composer);
1668 },
1669 );
1670 }
1671 }));
1672 }
1673 }
1674
1675 pub fn set_recranpose_fn(&self, callback: fn(&Composer)) {
1676 if let Some(scope) = self.current_recranpose_scope() {
1677 scope.set_recompose_fn(callback);
1678 }
1679 }
1680
1681 pub fn with_composition_locals<R>(
1682 &self,
1683 provided: Vec<ProvidedValue>,
1684 f: impl FnOnce(&Composer) -> R,
1685 ) -> R {
1686 if provided.is_empty() {
1687 return f(self);
1688 }
1689 let mut context = LocalContext::default();
1690 for value in provided {
1691 let (key, entry) = value.into_entry(self);
1692 context.values.insert(key, entry);
1693 }
1694 {
1695 let mut stack = self.local_stack();
1696 Rc::make_mut(&mut *stack).push(context);
1697 }
1698 let result = f(self);
1699 {
1700 let mut stack = self.local_stack();
1701 Rc::make_mut(&mut *stack).pop();
1702 }
1703 result
1704 }
1705}