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