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