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 record_subcompose_child(&self, child_id: NodeId) {
824 let mut parent_stack = self.parent_stack();
825 if let Some(frame) = parent_stack.last_mut() {
826 if matches!(frame.attach_mode, ParentAttachMode::DeferredSync) {
827 if let Some(membership) = frame.new_children_membership.as_mut() {
828 if membership.insert(child_id) {
829 frame.new_children.push(child_id);
830 }
831 } else if frame.new_children.len() >= LARGE_DEFERRED_CHILD_TRACKING_THRESHOLD {
832 let mut membership = HashSet::default();
833 membership.reserve(frame.new_children.len() + 1);
834 membership.extend(frame.new_children.iter().copied());
835 if membership.insert(child_id) {
836 frame.new_children.push(child_id);
837 }
838 frame.new_children_membership = Some(membership);
839 } else if !frame.new_children.contains(&child_id) {
840 frame.new_children.push(child_id);
841 }
842 }
843 }
844 }
845
846 pub fn clear_node_children(&self, node_id: NodeId) {
852 let mut applier = self.borrow_applier();
853 if let Ok(node) = applier.get_mut(node_id) {
854 node.update_children(&[]);
855 }
856 }
857
858 pub fn install<R>(&self, f: impl FnOnce(&Composer) -> R) -> R {
859 let _composer_guard = composer_context::enter(self);
860 runtime::push_active_runtime(&self.core.runtime);
861 struct Guard;
862 impl Drop for Guard {
863 fn drop(&mut self) {
864 runtime::pop_active_runtime();
865 }
866 }
867 let guard = Guard;
868 let result = f(self);
869 drop(guard);
870 result
871 }
872
873 pub(crate) fn flush_pending_commands_if_large(&self) -> Result<(), NodeError> {
874 let queued = self.core.commands.borrow().len();
875 if queued < COMMAND_FLUSH_THRESHOLD {
876 return Ok(());
877 }
878 self.apply_pending_commands()
879 }
880
881 fn with_group_in_active_pass<R>(
882 &self,
883 key: crate::slot::GroupKeySeed,
884 f: impl FnOnce(&Composer) -> R,
885 ) -> R {
886 struct GroupGuard {
887 composer: Composer,
888 scope: RecomposeScope,
889 }
890
891 impl Drop for GroupGuard {
892 fn drop(&mut self) {
893 self.composer
894 .close_current_group_body_for_scope(&self.scope);
895 self.scope.mark_recomposed();
896 self.composer
897 .with_slot_session_mut(|slots| slots.end_group());
898 if let Err(err) = self.composer.flush_pending_commands_if_large() {
899 log::error!("mid-composition command flush failed: {err}");
900 }
901 }
902 }
903
904 let parent_scope = self.current_recranpose_scope();
905 let options = self.pending_scope_options().take().unwrap_or_default();
906 let parent_scope_id = parent_scope.as_ref().map(RecomposeScope::id);
907 let reserved_key = self.with_slot_session_mut(|slots| slots.preview_group_key(key));
908 let host = self.active_slots_host();
909 let restored = self.core.shared_state.take_retained(
910 &host,
911 RetainKey {
912 parent_scope: parent_scope_id,
913 key: reserved_key,
914 },
915 |subtree| {
916 self.with_slot_session_mut(|slots| {
917 slots.retained_restore_ready(reserved_key, subtree)
918 })
919 },
920 );
921 let (group, start_scope_id, start_kind) = self.with_slot_session_mut(|slots| {
922 let GroupStart {
923 group,
924 scope_id,
925 kind,
926 ..
927 } = slots.begin_group(reserved_key, restored);
928 (group, scope_id, kind)
929 });
930 let scope_ref =
931 if let Some(scope) = start_scope_id.and_then(|scope_id| self.scope_for_id(scope_id)) {
932 scope
933 } else {
934 let scope = RecomposeScope::new(self.runtime_handle());
935 self.register_scope(&scope);
936 self.with_slot_session_mut(|slots| slots.set_group_scope(group, scope.id()));
937 scope
938 };
939
940 scope_ref.reactivate();
941 scope_ref.set_parent_scope(parent_scope);
942 scope_ref.set_retention_mode(options.retention);
943
944 if options.force_recompose {
945 scope_ref.force_recompose();
946 } else if options.force_reuse {
947 scope_ref.force_reuse();
948 }
949 if matches!(start_kind, GroupStartKind::Restored) {
950 scope_ref.force_recompose();
951 }
952
953 scope_ref.set_slots_host(&host);
954
955 {
956 let mut stack = self.scope_stack();
957 stack.push(scope_ref.clone());
958 }
959
960 {
961 let mut stack = self.subcompose_stack();
962 if let Some(frame) = stack.last_mut() {
963 frame.scopes.push(scope_ref.clone());
964 }
965 }
966
967 scope_ref.snapshot_locals(self.current_local_stack());
968 {
969 let parent_hint = self.current_parent_hint();
970 scope_ref.set_parent_hint(parent_hint);
971 }
972
973 let guard = GroupGuard {
974 composer: self.clone(),
975 scope: scope_ref.clone(),
976 };
977 let result = self.observe_scope(&scope_ref, || f(self));
978 scope_ref.mark_composed_once();
979 drop(guard);
980 result
981 }
982
983 pub(crate) fn with_group_seed<R>(
984 &self,
985 key: crate::slot::GroupKeySeed,
986 f: impl FnOnce(&Composer) -> R,
987 ) -> R {
988 let host = self.active_slots_host();
989 if host.has_active_pass() {
990 return self.with_group_in_active_pass(key, f);
991 }
992 let (result, _) =
993 self.with_slot_host_pass(host, crate::slot::SlotPassMode::Compose, |composer| {
994 composer.with_group_in_active_pass(key, f)
995 });
996 result
997 }
998
999 pub fn with_group<R>(&self, key: Key, f: impl FnOnce(&Composer) -> R) -> R {
1000 self.with_group_seed(crate::slot::GroupKeySeed::unkeyed(key), f)
1001 }
1002
1003 pub fn cranpose_with_reuse<R>(
1004 &self,
1005 key: Key,
1006 mut options: RecomposeOptions,
1007 f: impl FnOnce(&Composer) -> R,
1008 ) -> R {
1009 options.retention = RetentionMode::RetainWhenInactive;
1010 self.pending_scope_options().replace(options);
1011 self.with_group(key, f)
1012 }
1013
1014 #[track_caller]
1015 pub fn with_key<K: Hash, R>(&self, key: &K, f: impl FnOnce(&Composer) -> R) -> R {
1016 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
1017 self.with_group_seed(seed, f)
1018 }
1019
1020 fn dispose_detached_nodes(&self, nodes: impl IntoIterator<Item = NodeId>) {
1021 for node_id in nodes {
1022 self.commands_mut().push(Command::callback(move |applier| {
1023 crate::slot::dispose_detached_node_now(applier, node_id)
1024 }));
1025 }
1026 }
1027
1028 fn deactivate_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1029 for scope_id in scope_ids {
1030 if let Some(scope) = self.scope_for_id(scope_id) {
1031 scope.deactivate();
1032 }
1033 }
1034 }
1035
1036 fn dispose_scope_ids(&self, scope_ids: impl IntoIterator<Item = ScopeId>) {
1037 for scope_id in scope_ids {
1038 if let Some(scope) = self.remove_scope(scope_id) {
1039 scope.deactivate();
1040 }
1041 }
1042 }
1043
1044 fn detached_root_parent_commands(
1045 &self,
1046 subtree: &crate::slot::DetachedSubtree,
1047 context: &'static str,
1048 ) -> Result<Vec<(NodeId, Option<NodeId>)>, NodeError> {
1049 let mut root_nodes = Vec::new();
1050 subtree.collect_root_nodes_checked_into(&mut root_nodes, context);
1051 let mut roots = Vec::with_capacity(root_nodes.len());
1052 for root in root_nodes {
1053 let parent_id = {
1054 let mut applier = self.borrow_applier();
1055 applier.get_mut(root)?.parent()
1056 };
1057 roots.push((root, parent_id));
1058 }
1059 Ok(roots)
1060 }
1061
1062 fn retain_detached_subtree_in_host(
1063 &self,
1064 slots_host: &Rc<SlotsHost>,
1065 parent_scope: Option<ScopeId>,
1066 subtree: crate::slot::DetachedSubtree,
1067 ) -> Result<(), NodeError> {
1068 let Some(root_key) = subtree.root_key_checked() else {
1072 log::error!("retention rejected detached subtree without a root group");
1073 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1074 return Ok(());
1075 };
1076 let root_detaches = self.detached_root_parent_commands(&subtree, "retention")?;
1077 self.deactivate_scope_ids(subtree.scope_ids_iter());
1078 for (root, parent_id) in root_detaches {
1079 if let Some(parent_id) = parent_id {
1080 self.commands_mut().push(Command::DetachChild {
1081 parent_id,
1082 child_id: root,
1083 });
1084 }
1085 }
1086 let evicted = self.core.shared_state.insert_retained(
1087 slots_host,
1088 RetainKey {
1089 parent_scope,
1090 key: root_key,
1091 },
1092 subtree,
1093 );
1094 for subtree in evicted {
1095 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1096 }
1097 Ok(())
1098 }
1099
1100 fn evict_retained_subtrees_for_host(
1101 &self,
1102 slots_host: &Rc<SlotsHost>,
1103 ) -> Result<(), NodeError> {
1104 let evicted = self.core.shared_state.advance_retention_pass(slots_host);
1105 for subtree in evicted {
1106 self.dispose_detached_subtree_in_host(slots_host, subtree)?;
1107 }
1108 Ok(())
1109 }
1110
1111 fn dispose_detached_subtree_in_host(
1112 &self,
1113 slots_host: &Rc<SlotsHost>,
1114 subtree: crate::slot::DetachedSubtree,
1115 ) -> Result<(), NodeError> {
1116 let root_nodes = self
1117 .detached_root_parent_commands(&subtree, "disposal")?
1118 .into_iter()
1119 .map(|(root, _)| root);
1120 self.dispose_scope_ids(subtree.scope_ids_iter());
1121 self.dispose_detached_nodes(root_nodes);
1122 slots_host.with_table_and_lifecycle_mut(|table, lifecycle| {
1123 table.invalidate_detached_subtree_anchors(&subtree);
1124 lifecycle.queue_subtree_disposal(subtree);
1125 });
1126 Ok(())
1127 }
1128
1129 fn handle_detached_children_in_host(
1130 &self,
1131 slots_host: &Rc<SlotsHost>,
1132 parent_scope: Option<ScopeId>,
1133 detached: Vec<crate::slot::DetachedSubtree>,
1134 ) -> Result<(), NodeError> {
1135 for subtree in detached {
1136 let retention_mode = subtree
1137 .root_scope_id()
1138 .and_then(|scope_id| self.scope_for_id(scope_id))
1139 .map(|scope| scope.retention_mode())
1140 .unwrap_or_default();
1141 match retention_mode {
1142 RetentionMode::DisposeWhenInactive => {
1143 self.dispose_detached_subtree_in_host(slots_host, subtree)?
1144 }
1145 RetentionMode::RetainWhenInactive => {
1146 self.retain_detached_subtree_in_host(slots_host, parent_scope, subtree)?
1147 }
1148 }
1149 }
1150 Ok(())
1151 }
1152
1153 fn handle_detached_children(
1154 &self,
1155 parent_scope: Option<ScopeId>,
1156 detached: Vec<crate::slot::DetachedSubtree>,
1157 ) {
1158 let host = self.active_slots_host();
1159 if let Err(err) = self.handle_detached_children_in_host(&host, parent_scope, detached) {
1160 log::error!("detached subtree handling failed while closing a group: {err}");
1161 }
1162 }
1163
1164 fn handle_finished_group_result(
1165 &self,
1166 parent_scope: Option<ScopeId>,
1167 result: FinishGroupResult,
1168 ) {
1169 let FinishGroupResult {
1170 detached_children,
1171 direct_nodes,
1172 root_nodes,
1173 was_skipped,
1174 } = result;
1175 if was_skipped {
1176 self.attach_root_nodes(root_nodes);
1177 }
1178 self.dispose_detached_nodes(direct_nodes);
1179 self.handle_detached_children(parent_scope, detached_children);
1180 }
1181
1182 pub(crate) fn close_current_group_body_for_scope(&self, scope: &RecomposeScope) {
1183 let result = self.with_slot_session_mut(|slots| slots.finish_group_body());
1184 self.handle_finished_group_result(Some(scope.id()), result);
1185 if let Some(popped) = self.scope_stack().pop() {
1186 debug_assert_eq!(
1187 popped.id(),
1188 scope.id(),
1189 "closed scope must match the active scope stack"
1190 );
1191 } else {
1192 log::error!("scope stack underflow while closing scope {}", scope.id());
1193 }
1194 }
1195
1196 pub fn remember<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1197 self.remember_with_kind(PayloadKind::Remember, init)
1198 }
1199
1200 pub(crate) fn remember_internal<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1201 self.remember_with_kind(PayloadKind::Internal, init)
1202 }
1203
1204 pub(crate) fn remember_effect<T: 'static>(&self, init: impl FnOnce() -> T) -> Owned<T> {
1205 self.remember_with_kind(PayloadKind::Effect, init)
1206 }
1207
1208 fn remember_with_kind<T: 'static>(
1209 &self,
1210 kind: PayloadKind,
1211 init: impl FnOnce() -> T,
1212 ) -> Owned<T> {
1213 self.with_slot_session_mut(|slots| slots.remember_with_kind(kind, init))
1214 }
1215
1216 pub fn use_value_slot<'pass, T: 'static>(
1217 &'pass self,
1218 init: impl FnOnce() -> T,
1219 ) -> ValueSlotHandle<'pass, T> {
1220 let slot = self
1221 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Internal, init));
1222 ValueSlotHandle::new(slot)
1223 }
1224
1225 #[doc(hidden)]
1226 pub fn __use_param_slot<'pass, T: 'static>(
1227 &'pass self,
1228 init: impl FnOnce() -> T,
1229 ) -> ValueSlotHandle<'pass, T> {
1230 let slot = self
1231 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Param, init));
1232 ValueSlotHandle::new(slot)
1233 }
1234
1235 #[doc(hidden)]
1236 pub fn __use_return_slot<'pass, T: 'static>(
1237 &'pass self,
1238 init: impl FnOnce() -> T,
1239 ) -> ValueSlotHandle<'pass, T> {
1240 let slot = self
1241 .with_slot_session_mut(|slots| slots.value_slot_with_kind(PayloadKind::Return, init));
1242 ValueSlotHandle::new(slot)
1243 }
1244
1245 #[doc(hidden)]
1246 pub fn __invalidate_return_consumer_scope(&self) {
1247 let Some(scope) = self.current_recranpose_scope() else {
1248 self.request_root_render();
1249 return;
1250 };
1251
1252 if let Some(target) = scope.callback_promotion_target() {
1253 target.invalidate();
1254 } else {
1255 self.request_root_render();
1256 }
1257 }
1258
1259 pub fn with_slot_value<'pass, T: 'static, R>(
1260 &'pass self,
1261 handle: ValueSlotHandle<'pass, T>,
1262 f: impl FnOnce(&T) -> R,
1263 ) -> R {
1264 self.with_slots(|slots| f(slots.read_value(handle.slot())))
1265 }
1266
1267 pub fn with_slot_value_mut<'pass, T: 'static, R>(
1268 &'pass self,
1269 handle: ValueSlotHandle<'pass, T>,
1270 f: impl FnOnce(&mut T) -> R,
1271 ) -> R {
1272 self.with_slots_mut(|slots| f(slots.read_value_mut(handle.slot())))
1273 }
1274
1275 pub fn mutable_state_of<T: Clone + 'static>(&self, initial: T) -> MutableState<T> {
1276 MutableState::with_runtime(initial, self.runtime_handle())
1277 }
1278
1279 pub fn mutable_state_list_of<T, I>(&self, values: I) -> SnapshotStateList<T>
1280 where
1281 T: Clone + 'static,
1282 I: IntoIterator<Item = T>,
1283 {
1284 SnapshotStateList::with_runtime(values, self.runtime_handle())
1285 }
1286
1287 pub fn mutable_state_map_of<K, V, I>(&self, pairs: I) -> SnapshotStateMap<K, V>
1288 where
1289 K: Clone + Eq + Hash + 'static,
1290 V: Clone + 'static,
1291 I: IntoIterator<Item = (K, V)>,
1292 {
1293 SnapshotStateMap::with_runtime(pairs, self.runtime_handle())
1294 }
1295
1296 pub fn read_composition_local<T: Clone + 'static>(&self, local: &CompositionLocal<T>) -> T {
1297 let stack = self.core.local_stack.borrow();
1298 for context in stack.iter().rev() {
1299 if let Some(entry) = context.values.get(&local.key) {
1300 match entry.clone().downcast::<LocalStateEntry<T>>() {
1301 Ok(typed) => return typed.value(),
1302 Err(_) => {
1303 log::error!(
1304 "composition local entry type mismatch for key {}",
1305 local.key
1306 );
1307 return local.default_value();
1308 }
1309 }
1310 }
1311 }
1312 local.default_value()
1313 }
1314
1315 pub fn read_static_composition_local<T: Clone + 'static>(
1316 &self,
1317 local: &StaticCompositionLocal<T>,
1318 ) -> T {
1319 let stack = self.core.local_stack.borrow();
1320 for context in stack.iter().rev() {
1321 if let Some(entry) = context.values.get(&local.key) {
1322 match entry.clone().downcast::<StaticLocalEntry<T>>() {
1323 Ok(typed) => return typed.value(),
1324 Err(_) => {
1325 log::error!(
1326 "static composition local entry type mismatch for key {}",
1327 local.key
1328 );
1329 return local.default_value();
1330 }
1331 }
1332 }
1333 }
1334 local.default_value()
1335 }
1336
1337 pub fn current_recranpose_scope(&self) -> Option<RecomposeScope> {
1338 self.core.scope_stack.borrow().last().cloned()
1339 }
1340
1341 pub fn phase(&self) -> crate::Phase {
1342 self.core.phase.get()
1343 }
1344
1345 pub(crate) fn set_phase(&self, phase: crate::Phase) {
1346 self.core.phase.set(phase);
1347 }
1348
1349 pub fn enter_phase(&self, phase: crate::Phase) {
1350 self.set_phase(phase);
1351 }
1352
1353 pub(crate) fn subcompose<R>(
1354 &self,
1355 state: &mut SubcomposeState,
1356 slot_id: SlotId,
1357 content: impl FnOnce(&Composer) -> R,
1358 ) -> (R, Vec<NodeId>) {
1359 match self.phase() {
1360 crate::Phase::Measure | crate::Phase::Layout => {}
1361 current => panic!(
1362 "subcompose() may only be called during measure or layout; current phase: {:?}",
1363 current
1364 ),
1365 }
1366
1367 self.subcompose_stack().push(SubcomposeFrame::default());
1368 struct StackGuard {
1369 core: Rc<ComposerCore>,
1370 leaked: bool,
1371 }
1372 impl Drop for StackGuard {
1373 fn drop(&mut self) {
1374 if !self.leaked {
1375 self.core.subcompose_stack.borrow_mut().pop();
1376 }
1377 }
1378 }
1379 let mut guard = StackGuard {
1380 core: self.clone_core(),
1381 leaked: false,
1382 };
1383
1384 let slot_host = state.get_or_create_slots(slot_id);
1385 let (result, _) = self.with_slot_override(slot_host.clone(), |composer| {
1386 composer.with_group(slot_id.raw(), |composer| content(composer))
1387 });
1388
1389 let frame = {
1390 let frame = take_subcompose_frame(&guard.core, "subcompose");
1391 guard.leaked = true;
1392 frame
1393 };
1394 let nodes = frame.nodes;
1395 let scopes = frame.scopes;
1396 state.register_active(slot_id, &nodes, &scopes);
1397 (result, nodes)
1398 }
1399
1400 pub fn subcompose_measurement<R>(
1401 &self,
1402 state: &mut SubcomposeState,
1403 slot_id: SlotId,
1404 content: impl FnOnce(&Composer) -> R,
1405 ) -> (R, Vec<NodeId>) {
1406 let (result, nodes) = self.subcompose(state, slot_id, content);
1407 let roots = nodes
1408 .into_iter()
1409 .filter(|&id| self.node_has_no_parent(id))
1410 .collect();
1411
1412 (result, roots)
1413 }
1414
1415 pub fn subcompose_in<R>(
1416 &self,
1417 slots: &Rc<SlotsHost>,
1418 root: Option<NodeId>,
1419 f: impl FnOnce(&Composer) -> R,
1420 ) -> Result<R, NodeError> {
1421 let runtime_handle = self.runtime_handle();
1422 let phase = self.phase();
1423 let locals = self.current_local_stack();
1424 let shared_state = slots
1425 .runtime_state()
1426 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1427 let core = Rc::new(ComposerCore::new(
1428 shared_state,
1429 Rc::clone(slots),
1430 Rc::clone(&self.core.applier),
1431 runtime_handle.clone(),
1432 self.observer(),
1433 root,
1434 InitialParentFrame::RealParent,
1435 ));
1436 core.phase.set(phase);
1437 *core.local_stack.borrow_mut() = locals;
1438 let composer = Composer::from_core(core);
1439 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1440 let (output, outcome) = composer.try_with_slot_host_pass(
1441 Rc::clone(slots),
1442 crate::slot::SlotPassMode::Compose,
1443 |composer| f(composer),
1444 )?;
1445 let commands = composer.take_commands();
1446 let side_effects = composer.take_side_effects();
1447 Ok((output, commands, side_effects, outcome.compacted))
1448 })?;
1449 {
1450 let mut applier = self.borrow_applier();
1451 commands.apply(&mut *applier)?;
1452 for update in runtime_handle.take_updates() {
1453 update.apply(&mut *applier)?;
1454 }
1455 }
1456 if compact_applier {
1457 self.core.applier.compact();
1458 self.core.applier.borrow_dyn().clear_recycled_nodes();
1459 }
1460 runtime_handle.drain_ui();
1461 for effect in side_effects {
1462 effect();
1463 }
1464 runtime_handle.drain_ui();
1465 Ok(result)
1466 }
1467
1468 pub fn subcompose_slot<R>(
1473 &self,
1474 slots: &Rc<SlotsHost>,
1475 root: Option<NodeId>,
1476 f: impl FnOnce(&Composer) -> R,
1477 ) -> Result<(R, Vec<RecomposeScope>), NodeError> {
1478 let runtime_handle = self.runtime_handle();
1479 let phase = self.phase();
1480 let locals = self.current_local_stack();
1481 let shared_state = slots
1482 .runtime_state()
1483 .unwrap_or_else(|| Rc::clone(&self.core.shared_state));
1484 let core = Rc::new(ComposerCore::new(
1485 shared_state,
1486 Rc::clone(slots),
1487 Rc::clone(&self.core.applier),
1488 runtime_handle.clone(),
1489 self.observer(),
1490 root,
1491 InitialParentFrame::RealParent,
1492 ));
1493 core.phase.set(phase);
1494 *core.local_stack.borrow_mut() = locals;
1495 let composer = Composer::from_core(core);
1496 composer.subcompose_stack().push(SubcomposeFrame::default());
1497 struct StackGuard {
1498 core: Rc<ComposerCore>,
1499 leaked: bool,
1500 }
1501 impl Drop for StackGuard {
1502 fn drop(&mut self) {
1503 if !self.leaked {
1504 self.core.subcompose_stack.borrow_mut().pop();
1505 }
1506 }
1507 }
1508 let mut guard = StackGuard {
1509 core: composer.clone_core(),
1510 leaked: false,
1511 };
1512 let root_group_key = crate::location_key(file!(), line!(), column!());
1513 let (result, commands, side_effects, compact_applier) = composer.install(|composer| {
1514 let (output, outcome) = composer.try_with_slot_host_pass(
1515 Rc::clone(slots),
1516 crate::slot::SlotPassMode::Compose,
1517 |composer| {
1518 let output = composer.with_group(root_group_key, |composer| f(composer));
1519 if root.is_some() {
1520 composer.pop_parent();
1521 }
1522 output
1523 },
1524 )?;
1525 let commands = composer.take_commands();
1526 let side_effects = composer.take_side_effects();
1527 Ok((output, commands, side_effects, outcome.compacted))
1528 })?;
1529 let frame = {
1530 let frame = take_subcompose_frame(&guard.core, "subcompose_slot");
1531 guard.leaked = true;
1532 frame
1533 };
1534
1535 {
1536 let mut applier = self.borrow_applier();
1537 commands.apply(&mut *applier)?;
1538 for update in runtime_handle.take_updates() {
1539 update.apply(&mut *applier)?;
1540 }
1541 }
1542 if compact_applier {
1543 self.core.applier.compact();
1544 self.core.applier.borrow_dyn().clear_recycled_nodes();
1545 }
1546 runtime_handle.drain_ui();
1547 for effect in side_effects {
1548 effect();
1549 }
1550 runtime_handle.drain_ui();
1551 Ok((result, frame.scopes))
1552 }
1553
1554 fn attach_root_nodes(&self, root_nodes: Vec<NodeId>) {
1555 for id in root_nodes {
1556 self.attach_to_parent_with_mode(id, true);
1557 }
1558 }
1559
1560 pub fn skip_current_group(&self) {
1561 self.with_slot_session_mut(|slots| slots.skip_group());
1562 }
1563
1564 pub fn runtime_handle(&self) -> RuntimeHandle {
1565 self.core.runtime.clone()
1566 }
1567
1568 pub fn set_recranpose_callback<F>(&self, callback: F)
1569 where
1570 F: FnMut(&Composer) + 'static,
1571 {
1572 if let Some(scope) = self.current_recranpose_scope() {
1573 let observer = self.observer();
1574 let scope_weak = scope.downgrade();
1575 let mut callback = callback;
1576 scope.set_recompose(Box::new(move |composer: &Composer| {
1577 if let Some(inner) = scope_weak.upgrade() {
1578 let scope_instance = RecomposeScope { inner };
1579 observer.observe_reads(
1580 scope_instance.clone(),
1581 move |scope_ref| scope_ref.invalidate(),
1582 || {
1583 callback(composer);
1584 },
1585 );
1586 }
1587 }));
1588 }
1589 }
1590
1591 pub fn set_recranpose_fn(&self, callback: fn(&Composer)) {
1592 if let Some(scope) = self.current_recranpose_scope() {
1593 scope.set_recompose_fn(callback);
1594 }
1595 }
1596
1597 pub fn with_composition_locals<R>(
1598 &self,
1599 provided: Vec<ProvidedValue>,
1600 f: impl FnOnce(&Composer) -> R,
1601 ) -> R {
1602 if provided.is_empty() {
1603 return f(self);
1604 }
1605 let mut context = LocalContext::default();
1606 for value in provided {
1607 let (key, entry) = value.into_entry(self);
1608 context.values.insert(key, entry);
1609 }
1610 {
1611 let mut stack = self.local_stack();
1612 Rc::make_mut(&mut *stack).push(context);
1613 }
1614 let result = f(self);
1615 {
1616 let mut stack = self.local_stack();
1617 Rc::make_mut(&mut *stack).pop();
1618 }
1619 result
1620 }
1621}