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