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