1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code)]
3
4pub extern crate self as cranpose_core;
5
6mod applier_host;
7mod blocking;
8mod bubble_passes;
9mod callbacks;
10mod composer;
11pub mod composer_context;
12mod composition;
13mod composition_locals;
14pub mod concurrency;
15mod debug_trace;
16mod effect_key;
17mod emit;
18pub mod env_flags;
19#[cfg(any(feature = "internal", test))]
20mod frame_clock;
21mod hooks;
22mod launched_effect;
23mod node_marks;
24pub mod owned;
25pub mod platform;
26mod recompose;
27mod retention;
28pub mod runtime;
29mod slot;
30pub mod snapshot_double_index_heap;
31pub mod snapshot_id_set;
32pub mod snapshot_pinning;
33pub mod snapshot_state_observer;
34pub mod snapshot_v2;
35mod snapshot_weak_set;
36pub mod source_trace;
37mod stable_index;
38mod state;
39#[doc(hidden)]
40pub use source_trace::__source_scope;
41#[doc(hidden)]
42pub use thread_state::with_or_fresh;
43pub mod subcompose;
44mod thread_state;
45
46#[cfg(feature = "internal")]
47#[doc(hidden)]
48pub mod internal {
49 pub use crate::frame_clock::{FrameCallbackRegistration, FrameClock};
50}
51pub use applier_host::{ApplierGuard, ApplierHost, ConcreteApplierHost, NodeDisposal};
52pub use blocking::{
53 BlockingError, BlockingExecutor, BlockingExecutorConfig, BlockingTask, launchBlocking,
54 withBlocking,
55};
56pub use callbacks::{
57 CallbackHolder, CallbackHolder1, ParamReturnState, ParamSlot, ParamState, ReturnSlot,
58 SharedParam, refresh_param, refresh_shared_param,
59};
60pub use composer::{
61 BranchGroupGuard, CapturedCompositionContext, ComposableGroup, Composer,
62 ScopedBranchGroupGuard, ValueSlotHandle,
63};
64pub(crate) use composer::{ComposerCore, EmittedNode, ParentAttachMode, ParentFrame};
65pub use composition::{Composition, ROOT_RENDER_REPLAY_LIMIT};
66pub use composition_locals::{
67 CompositionLocal, CompositionLocalProvider, ProvidedValue, StaticCompositionLocal,
68 compositionLocalOf, compositionLocalOfWithPolicy, staticCompositionLocalOf,
69 staticCompositionLocalOfWithPolicy,
70};
71pub(crate) use composition_locals::{LocalStateEntry, StaticLocalEntry};
72pub use concurrency::{
73 CollectEvents, CoroutineScope, Delay, EventChannel, EventSender, EventStream, EventStreamNext,
74 ProduceScope, collectAsState, delay, interval, produceState, rememberCoroutineScope,
75 rememberEventStream, spawn_ui_task,
76};
77#[doc(hidden)]
78pub use debug_trace::{
79 debug_label_current_scope, debug_live_recompose_scope_count,
80 debug_recompose_scope_registry_stats, debug_scope_invalidation_sources, debug_scope_label,
81};
82pub use hooks::{
83 derivedStateOf, mutableStateList, mutableStateListOf, mutableStateMap, mutableStateMapOf,
84 mutableStateOf, mutableStateOfNeverEqual, ownedMutableStateOf, ownedMutableStateOfNeverEqual,
85 remember, rememberKeyed, rememberMutableStateOf, rememberMutableStateOfNeverEqual,
86 rememberUpdatedState, try_mutableStateOf,
87};
88#[cfg(feature = "internal")]
89#[doc(hidden)]
90pub use hooks::{withFrameMillis, withFrameNanos};
91pub use launched_effect::{
92 __launched_effect_async_impl, __launched_effect_impl, CancelToken, LaunchedEffect,
93 LaunchedEffectAsync, LaunchedEffectScope, TaskSite,
94};
95pub use node_marks::NodeMarks;
96pub use owned::Owned;
97pub use platform::{Clock, RuntimeScheduler, SchedulerRef, scheduler_ref};
98pub use retention::{RetentionBudget, RetentionEvictionPolicy, RetentionMode, RetentionPolicy};
99#[doc(hidden)]
100pub use runtime::{
101 DefaultScheduler, Runtime, RuntimeHandle, StateId, TaskHandle, UiDispatcher,
102 current_runtime_handle, label_next_ui_task, schedule_frame, schedule_node_update,
103};
104pub use slot::{
105 SlotDebugAnchor, SlotDebugEntry, SlotDebugEntryKind, SlotDebugGroup, SlotDebugScope,
106 SlotDebugSnapshot, SlotRetentionDebugStats, SlotTable, SlotTableDebugStats,
107 SlotTableLocalDebugStats, SlotTableMutationDebugStats,
108};
109#[doc(hidden)]
110pub use snapshot_state_observer::SnapshotStateObserver;
111
112pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
137 let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
138
139 let _applied_guard = AppliedSnapshotFlagGuard::enter();
140 let value = snapshot.enter(block);
141
142 match snapshot.apply() {
143 snapshot_v2::SnapshotApplyResult::Success => Ok(value),
144 snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
145 }
146}
147
148struct AppliedSnapshotFlagGuard {
149 previous: bool,
150}
151
152impl AppliedSnapshotFlagGuard {
153 fn enter() -> Self {
154 let previous = thread_state::cells(|cells| cells.in_applied_snapshot.replace(true));
155 Self { previous }
156 }
157}
158
159impl Drop for AppliedSnapshotFlagGuard {
160 fn drop(&mut self) {
161 thread_state::cells(|cells| cells.in_applied_snapshot.set(self.previous));
162 }
163}
164
165pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
180 run_in_mutable_snapshot(block).ok()
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
184pub struct CompositionPassDebugStats {
185 pub commands_len: usize,
186 pub commands_cap: usize,
187 pub command_payload_len_bytes: usize,
188 pub command_payload_cap_bytes: usize,
189 pub sync_children_len: usize,
190 pub sync_children_cap: usize,
191 pub sync_child_ids_len: usize,
192 pub sync_child_ids_cap: usize,
193 pub side_effects_len: usize,
194 pub side_effects_cap: usize,
195}
196
197#[must_use]
198pub struct EventHandlerScopeGuard {
199 previous: bool,
200}
201
202impl Drop for EventHandlerScopeGuard {
203 fn drop(&mut self) {
204 thread_state::cells(|cells| cells.in_event_handler.set(self.previous));
205 }
206}
207
208pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
209 let previous = thread_state::cells(|cells| cells.in_event_handler.replace(true));
210 EventHandlerScopeGuard { previous }
211}
212
213pub fn in_event_handler() -> bool {
215 thread_state::cells(|cells| cells.in_event_handler.get())
216}
217
218pub fn in_applied_snapshot() -> bool {
220 thread_state::cells(|cells| cells.in_applied_snapshot.get())
221}
222
223use std::{
224 any::{Any, TypeId},
225 cell::{Cell, Ref, RefCell, RefMut},
226 cmp::Reverse,
227 collections::BinaryHeap,
228 hash::{Hash, Hasher},
229 rc::{Rc, Weak},
230 sync::OnceLock,
231};
232
233#[cfg(test)]
234pub use runtime::{TestRuntime, TestScheduler};
235use smallvec::SmallVec;
236
237use crate::collections::map::{HashMap, HashSet};
238
239pub type Key = u64;
240pub type NodeId = usize;
241
242#[cfg(any(test, debug_assertions))]
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub(crate) struct LocationKeyDebugInfo {
245 file: String,
246 line: u32,
247 column: u32,
248}
249
250#[cfg(any(test, debug_assertions))]
251fn register_location_key_debug_info(key: Key, file: &str, line: u32, column: u32) {
252 let info = || LocationKeyDebugInfo {
253 file: file.to_owned(),
254 line,
255 column,
256 };
257 let collision = thread_state::runtimes(|state| {
258 let mut registry = state.location_keys.borrow_mut();
259 match registry.entry(key) {
260 std::collections::hash_map::Entry::Vacant(entry) => {
261 thread_state::watch_thread_exit();
262 entry.insert(info());
263 None
264 }
265 std::collections::hash_map::Entry::Occupied(entry) => {
266 let existing = entry.get();
267 (existing.file != file || existing.line != line || existing.column != column)
268 .then(|| (existing.clone(), info()))
269 }
270 }
271 });
272 if let Some((existing, incoming)) = collision {
273 thread_state::cells(|cells| {
274 let count = &cells.location_key_collisions;
275 count.set(count.get().saturating_add(1));
276 });
277 log::error!("location key collision: key={key} first={existing:?} second={incoming:?}");
278 }
279}
280
281#[cfg(all(debug_assertions, not(test)))]
282fn location_key_diagnostics_enabled() -> bool {
283 crate::env_flag!("CRANPOSE_LOCATION_KEY_DIAGNOSTICS")
284}
285
286#[cfg(test)]
287pub(crate) fn register_location_key_debug_info_for_test(
288 key: Key,
289 file: &str,
290 line: u32,
291 column: u32,
292) {
293 register_location_key_debug_info(key, file, line, column);
294}
295
296#[cfg(test)]
297pub(crate) fn location_key_debug_collision_count_for_test() -> usize {
298 thread_state::cells(|cells| cells.location_key_collisions.get())
299}
300
301#[cfg(test)]
302pub(crate) fn location_key_debug_info_for_test(key: Key) -> Option<LocationKeyDebugInfo> {
303 thread_state::runtimes(|state| state.location_keys.borrow().get(&key).cloned())
304}
305
306#[cfg(test)]
307pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
308 true
309}
310
311#[cfg(all(debug_assertions, not(test)))]
312pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
313 crate::env_flag!("CRANPOSE_VALIDATE_SLOTS")
314}
315
316const fn source_location_hash(file: &str, line: u32, column: u32) -> u64 {
317 position_location_hash(file_location_hash(file), line, column)
318}
319
320const fn file_location_hash(file: &str) -> u64 {
321 fnv1a_location_key_bytes(0xcbf2_9ce4_8422_2325u64, file.as_bytes())
322}
323
324fn static_file_location_hash(cells: &thread_state::ThreadCells, file: &'static str) -> u64 {
329 let address = file.as_ptr() as usize;
330 let cached = &cells.file_hashes[(address >> 4) % thread_state::FILE_HASH_SLOTS];
331 let (cached_address, cached_len, hash) = cached.get();
332 if cached_address == address && cached_len == file.len() {
333 return hash;
334 }
335 let hash = file_location_hash(file);
336 cached.set((address, file.len(), hash));
337 hash
338}
339
340const fn position_location_hash(mut hash: u64, line: u32, column: u32) -> u64 {
341 hash = fnv1a_location_key_bytes(hash, &[0xff]);
342 hash = fnv1a_location_key_bytes(hash, &line.to_le_bytes());
343 hash = fnv1a_location_key_bytes(hash, &[0xfe]);
344 hash = fnv1a_location_key_bytes(hash, &column.to_le_bytes());
345 hash
346}
347
348const fn fnv1a_location_key_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
349 let mut index = 0;
350 while index < bytes.len() {
351 hash = (hash ^ bytes[index] as u64).wrapping_mul(0x0000_0100_0000_01b3);
352 index += 1;
353 }
354 hash
355}
356
357const fn avalanche_location_key(mut value: u64) -> u64 {
358 value ^= value >> 33;
359 value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
360 value ^= value >> 33;
361 value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
362 value ^ (value >> 33)
363}
364
365#[doc(hidden)]
366#[track_caller]
367pub fn caller_location_key() -> Key {
368 let caller = std::panic::Location::caller();
369 let file = caller.file();
370 if cfg!(feature = "hot-reload")
371 && let Some(key) = hot_call_site_key(file, caller.line(), caller.column())
372 {
373 return key;
374 }
375 call_site_key(caller)
376}
377
378fn call_site_key(caller: &'static std::panic::Location<'static>) -> Key {
384 let address = std::ptr::from_ref(caller).addr();
385 thread_state::cells(|cells| {
386 let cached = &cells.call_site_keys[(address >> 3) % thread_state::CALL_SITE_KEY_SLOTS];
387 let (cached_address, key) = cached.get();
388 if cached_address == address {
389 return key;
390 }
391 let file = caller.file();
392 let key = registered_location_key(
393 static_file_location_hash(cells, file),
394 file,
395 caller.line(),
396 caller.column(),
397 );
398 cached.set((address, key));
399 key
400 })
401}
402
403#[derive(Clone, Copy)]
404pub(crate) struct HotOrigin {
405 file: &'static str,
406 line: u32,
407 end: u32,
408 identity: Key,
409}
410
411impl HotOrigin {
412 pub(crate) const NONE: Self = Self {
413 file: "",
414 line: 0,
415 end: 0,
416 identity: 0,
417 };
418}
419
420#[doc(hidden)]
422pub struct HotOriginGuard {
423 previous: HotOrigin,
424}
425
426impl Drop for HotOriginGuard {
427 fn drop(&mut self) {
428 let previous = self.previous;
429 thread_state::cells(|cells| cells.hot_origin.set(previous));
430 }
431}
432
433#[doc(hidden)]
440pub fn hot_origin(file: &'static str, line: u32, end: u32, identity: Key) -> HotOriginGuard {
441 let next = HotOrigin {
442 file,
443 line,
444 end,
445 identity,
446 };
447 HotOriginGuard {
448 previous: thread_state::cells(|cells| cells.hot_origin.replace(next)),
449 }
450}
451
452fn hot_call_site_key(file: &str, line: u32, column: u32) -> Option<Key> {
453 let origin = thread_state::cells(|cells| cells.hot_origin.get());
454 if origin.file.is_empty() || !(origin.line..=origin.end).contains(&line) || origin.file != file
455 {
456 return None;
457 }
458 let relative = line.wrapping_sub(origin.line);
459 let hash = fnv1a_location_key_bytes(origin.identity, &relative.to_le_bytes());
460 Some(avalanche_location_key(fnv1a_location_key_bytes(
461 hash ^ 0xfc,
462 &column.to_le_bytes(),
463 )))
464}
465
466#[doc(hidden)]
469pub const fn hot_branch_key(file: &str, path_hash: u64) -> Key {
470 let hash = file_location_hash(file);
471 avalanche_location_key(fnv1a_location_key_bytes(
472 hash ^ 0xfd,
473 &path_hash.to_le_bytes(),
474 ))
475}
476
477#[doc(hidden)]
480pub const fn hot_definition_key(file: &str, module: &str, name: &str) -> Key {
481 let mut hash = file_location_hash(file);
482 hash = fnv1a_location_key_bytes(hash ^ 0xfe, module.as_bytes());
483 avalanche_location_key(fnv1a_location_key_bytes(hash ^ 0xff, name.as_bytes()))
484}
485
486#[doc(hidden)]
487#[track_caller]
488pub fn composable_identity_key(definition: Key) -> Key {
489 (definition.wrapping_mul(0x0000_0100_0000_01b3) ^ caller_location_key())
490 .wrapping_mul(0x0000_0100_0000_01b3)
491}
492
493#[doc(hidden)]
494pub fn composable_definition_key(
495 file: &str,
496 line: u32,
497 column: u32,
498 marker: std::any::TypeId,
499) -> Key {
500 let mut hasher = std::collections::hash_map::DefaultHasher::new();
501 std::hash::Hash::hash(&marker, &mut hasher);
502 location_key(file, line, column) ^ avalanche_location_key(std::hash::Hasher::finish(&hasher))
503}
504
505#[doc(hidden)]
508pub const fn const_location_key(file: &str, line: u32, column: u32) -> Key {
509 avalanche_location_key(source_location_hash(file, line, column))
510}
511
512#[doc(hidden)]
515#[inline(always)]
516pub fn noted_location_key(key: Key, file: &str, line: u32, column: u32) -> Key {
517 note_location_key(key, file, line, column);
518 key
519}
520
521#[doc(hidden)]
522pub fn cached_composable_definition_key(
523 cell: &OnceLock<Key>,
524 file: &str,
525 line: u32,
526 column: u32,
527 marker: TypeId,
528) -> Key {
529 *cell.get_or_init(|| composable_definition_key(file, line, column, marker))
530}
531
532pub fn location_key(file: &str, line: u32, column: u32) -> Key {
533 registered_location_key(file_location_hash(file), file, line, column)
534}
535
536fn registered_location_key(file_hash: u64, file: &str, line: u32, column: u32) -> Key {
537 let key = avalanche_location_key(position_location_hash(file_hash, line, column));
538 note_location_key(key, file, line, column);
539 key
540}
541
542#[cfg(any(test, debug_assertions))]
544fn note_location_key(key: Key, file: &str, line: u32, column: u32) {
545 #[cfg(test)]
546 register_location_key_debug_info(key, file, line, column);
547 #[cfg(all(debug_assertions, not(test)))]
548 if location_key_diagnostics_enabled() {
549 register_location_key_debug_info(key, file, line, column);
550 }
551}
552
553#[cfg(not(any(test, debug_assertions)))]
554fn note_location_key(_key: Key, _file: &str, _line: u32, _column: u32) {}
555
556#[doc(hidden)]
557#[inline]
558pub fn __branch_group_scope_deferred(key: Key) -> Option<ScopedBranchGroupGuard> {
559 composer_context::with_current_core(|core| {
560 ScopedBranchGroupGuard::open(&core.branch_folds, key)
561 })
562}
563
564#[doc(hidden)]
567pub const fn branch_location_key(file: &str, line: u32, column: u32, branch: u32) -> Key {
568 let mut hash = source_location_hash(file, line, column);
569 hash = fnv1a_location_key_bytes(hash, &[0xfd]);
570 hash = fnv1a_location_key_bytes(hash, &branch.to_le_bytes());
571 avalanche_location_key(hash)
572}
573
574#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
580pub struct AnchorId {
581 id: u32,
582 generation: u32,
583}
584
585impl AnchorId {
586 pub(crate) const INVALID: AnchorId = AnchorId {
587 id: 0,
588 generation: 0,
589 };
590
591 pub(crate) fn new(id: usize) -> Self {
592 Self {
593 id: crate::slot::checked_usize_to_u32(id, "anchor id"),
594 generation: 1,
595 }
596 }
597
598 pub fn is_valid(&self) -> bool {
600 self.id != 0
601 }
602}
603
604pub(crate) type ScopeId = usize;
605pub(crate) type FrameCallbackId = u64;
606type LocalStackSnapshot = Option<Rc<composer::LocalFrame>>;
611
612#[derive(Clone)]
613pub(crate) struct LocalKey(Rc<()>);
614
615impl LocalKey {
616 fn new() -> Self {
617 Self(Rc::new(()))
618 }
619
620 pub(crate) fn entry_source(&self) -> Key {
621 avalanche_location_key(Rc::as_ptr(&self.0) as usize as u64)
622 }
623}
624
625impl std::fmt::Debug for LocalKey {
626 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
627 f.debug_tuple("LocalKey")
628 .field(&(Rc::as_ptr(&self.0) as usize))
629 .finish()
630 }
631}
632
633impl PartialEq for LocalKey {
634 fn eq(&self, other: &Self) -> bool {
635 Rc::ptr_eq(&self.0, &other.0)
636 }
637}
638
639impl Eq for LocalKey {}
640
641impl Hash for LocalKey {
642 fn hash<H: Hasher>(&self, state: &mut H) {
643 Rc::as_ptr(&self.0).hash(state);
644 }
645}
646
647struct RecomposeCallback {
651 body: Box<dyn FnMut(&Composer) + 'static>,
652 stateless_body: Option<fn() -> TypeId>,
653}
654
655fn scope_activity_epoch() -> u64 {
656 thread_state::cells(|cells| cells.scope_activity_epoch.get())
657}
658
659fn note_scope_activity_change() {
660 thread_state::cells(|cells| {
661 let epoch = &cells.scope_activity_epoch;
662 epoch.set(epoch.get() + 1);
663 });
664}
665
666fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
671 let value = cell.take();
672 let result = read(&value);
673 cell.set(value);
674 result
675}
676
677fn update_cell<T: Default, R>(cell: &Cell<T>, update: impl FnOnce(&mut T) -> R) -> R {
679 let mut value = cell.take();
680 let result = update(&mut value);
681 cell.set(value);
682 result
683}
684
685type ScopeLink = Cell<Option<Weak<RecomposeScopeInner>>>;
686
687fn link_scope(link: &ScopeLink, target: Option<&RecomposeScope>) {
688 update_cell(link, |link| {
689 if link.as_ref().map(Weak::as_ptr) != target.map(|scope| Rc::as_ptr(&scope.inner)) {
690 *link = target.map(RecomposeScope::downgrade);
691 note_scope_activity_change();
692 }
693 });
694}
695
696const NO_PARENT_HINT: NodeId = NodeId::MAX;
699
700pub(crate) struct RecomposeScopeInner {
701 runtime: runtime::ScopeRuntime,
702 invalid: Cell<bool>,
703 enqueued: Cell<bool>,
704 active: Cell<bool>,
705 active_epoch: Cell<u64>,
706 deactivations: Cell<u64>,
707 composed_once: Cell<bool>,
708 pending_recompose: Cell<bool>,
709 force_reuse: Cell<bool>,
710 force_recompose: Cell<bool>,
711 derivation: Cell<bool>,
712 retention_mode: Cell<RetentionMode>,
713 parent_hint: Cell<NodeId>,
715 group_anchor: Cell<AnchorId>,
716 recompose: Cell<Option<RecomposeCallback>>,
717 parent_scope: ScopeLink,
718 lifetime_owner_scope: ScopeLink,
719 local_stack: Cell<LocalStackSnapshot>,
720 #[cfg(feature = "inspection")]
721 source_trace: RefCell<Rc<[source_trace::SourceLocation]>>,
722 #[cfg(all(feature = "inspection", debug_assertions))]
723 recompositions: std::cell::OnceCell<source_trace::RecompositionCounter>,
724 slots_storage_key: Cell<usize>,
725 slots_runtime_state: Cell<Option<std::rc::Weak<crate::composer::ComposerRuntimeState>>>,
726 state_subscriptions: Cell<StateIds>,
727 invalidation_sources: Cell<StateIds>,
728 unknown_invalidation_source: Cell<bool>,
729}
730
731type StateIds = SmallVec<[StateId; 2]>;
732
733enum ScopeOwner {
734 Root,
735 Live(Rc<RecomposeScopeInner>),
736 Dropped,
737}
738
739impl RecomposeScopeInner {
740 fn owner(&self) -> ScopeOwner {
741 let upgrade = |owner: &Option<Weak<RecomposeScopeInner>>| {
742 owner.as_ref().map(|owner| {
743 owner
744 .upgrade()
745 .map_or(ScopeOwner::Dropped, ScopeOwner::Live)
746 })
747 };
748 read_cell(&self.parent_scope, upgrade)
749 .or_else(|| read_cell(&self.lifetime_owner_scope, upgrade))
750 .unwrap_or(ScopeOwner::Root)
751 }
752
753 fn is_effectively_active(&self, epoch: u64) -> bool {
754 if self.active_epoch.get() == epoch {
755 return true;
756 }
757 if !self.active.get() {
758 return false;
759 }
760 let active = match self.owner() {
761 ScopeOwner::Root => true,
762 ScopeOwner::Dropped => false,
763 ScopeOwner::Live(owner) => owner.is_effectively_active(epoch),
764 };
765 if active {
766 self.active_epoch.set(epoch);
767 }
768 active
769 }
770
771 fn new(runtime: runtime::ScopeRuntime) -> Self {
772 runtime.increment_live_recompose_scope_count();
773 Self {
774 runtime,
775 invalid: Cell::new(false),
776 enqueued: Cell::new(false),
777 active: Cell::new(true),
778 active_epoch: Cell::new(0),
779 deactivations: Cell::new(0),
780 composed_once: Cell::new(false),
781 pending_recompose: Cell::new(false),
782 force_reuse: Cell::new(false),
783 force_recompose: Cell::new(false),
784 derivation: Cell::new(false),
785 retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
786 parent_hint: Cell::new(NO_PARENT_HINT),
787 group_anchor: Cell::new(AnchorId::INVALID),
788 recompose: Cell::new(None),
789 parent_scope: Cell::new(None),
790 lifetime_owner_scope: Cell::new(None),
791 local_stack: Cell::new(None),
792 #[cfg(feature = "inspection")]
793 source_trace: RefCell::new(Rc::from([])),
794 #[cfg(all(feature = "inspection", debug_assertions))]
795 recompositions: std::cell::OnceCell::new(),
796 slots_storage_key: Cell::new(0),
797 slots_runtime_state: Cell::new(None),
798 state_subscriptions: Cell::new(StateIds::new()),
799 invalidation_sources: Cell::new(StateIds::new()),
800 unknown_invalidation_source: Cell::new(false),
801 }
802 }
803
804 fn id(&self) -> ScopeId {
805 std::ptr::from_ref(self).addr()
806 }
807}
808
809fn push_unique_state_id(ids: &mut StateIds, state_id: StateId) {
810 if !ids.contains(&state_id) {
811 ids.push(state_id);
812 }
813}
814
815impl Drop for RecomposeScopeInner {
816 fn drop(&mut self) {
817 note_scope_activity_change();
818 let id = self.id();
819 self.runtime.decrement_live_recompose_scope_count();
820 let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
821 for state_id in subscriptions {
822 self.runtime.unregister_state_scope(state_id, id);
823 }
824 #[cfg(debug_assertions)]
825 {
826 thread_state::try_runtimes(|state| {
827 state.scope_labels.borrow_mut().remove(&id);
828 state.scope_invalidation_sources.borrow_mut().remove(&id);
829 });
830 }
831 if self.enqueued.replace(false) {
832 self.runtime.mark_scope_recomposed();
833 }
834 }
835}
836
837#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
838pub struct RecomposeScopeRegistryDebugStats {
839 pub len: usize,
840 pub capacity: usize,
841}
842
843#[derive(Clone)]
844pub struct RecomposeScope {
845 inner: Rc<RecomposeScopeInner>,
846}
847
848impl PartialEq for RecomposeScope {
849 fn eq(&self, other: &Self) -> bool {
850 Rc::ptr_eq(&self.inner, &other.inner)
851 }
852}
853
854impl Eq for RecomposeScope {}
855
856impl Hash for RecomposeScope {
857 fn hash<H: Hasher>(&self, state: &mut H) {
858 self.id().hash(state);
859 }
860}
861
862impl RecomposeScope {
863 pub(crate) fn mark_derivation(&self) {
867 self.inner.derivation.set(true);
868 }
869
870 pub(crate) fn is_derivation(&self) -> bool {
871 self.inner.derivation.get()
872 }
873
874 fn new(runtime: RuntimeHandle) -> Self {
875 Self {
876 inner: Rc::new(RecomposeScopeInner::new(runtime.scope_runtime())),
877 }
878 }
879
880 pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
881 Rc::downgrade(&self.inner)
882 }
883
884 pub(crate) fn upgrade(weak: &Weak<RecomposeScopeInner>) -> Option<Self> {
885 weak.upgrade().map(|inner| Self { inner })
886 }
887
888 pub fn id(&self) -> ScopeId {
889 self.inner.id()
890 }
891
892 pub fn is_invalid(&self) -> bool {
893 self.inner.invalid.get()
894 }
895
896 pub(crate) fn is_enqueued(&self) -> bool {
897 self.inner.enqueued.get()
898 }
899
900 pub fn is_active(&self) -> bool {
901 self.inner.active.get()
902 }
903
904 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
911 let mut total = self.inner.deactivations.get();
912 let mut owner = self.inner.owner();
913 while let ScopeOwner::Live(scope) = owner {
914 total = total.wrapping_add(scope.deactivations.get());
915 owner = scope.owner();
916 }
917 total
918 }
919
920 pub(crate) fn is_effectively_active(&self) -> bool {
921 self.inner.is_effectively_active(scope_activity_epoch())
922 }
923
924 fn record_state_subscription(&self, state_id: StateId) {
925 update_cell(&self.inner.state_subscriptions, |ids| {
926 push_unique_state_id(ids, state_id);
927 });
928 }
929
930 fn record_unknown_invalidation_source(&self) {
931 self.inner.unknown_invalidation_source.set(true);
932 update_cell(&self.inner.invalidation_sources, StateIds::clear);
933 }
934
935 fn record_state_invalidation_source(&self, state_id: StateId) {
936 if !self.inner.unknown_invalidation_source.get() {
937 update_cell(&self.inner.invalidation_sources, |ids| {
938 push_unique_state_id(ids, state_id);
939 });
940 }
941 }
942
943 fn enqueue_invalidation(&self) {
944 self.inner.invalid.set(true);
945 if !self.is_effectively_active() {
946 return;
947 }
948 if !self.inner.enqueued.replace(true) {
949 self.inner.runtime.register_invalid_scope(self.downgrade());
950 }
951 }
952
953 fn invalidate(&self) {
954 self.record_unknown_invalidation_source();
955 self.enqueue_invalidation();
956 }
957
958 pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
959 self.record_state_invalidation_source(state_id);
960 self.enqueue_invalidation();
961 }
962
963 fn mark_recomposed(&self) {
964 self.inner.invalid.set(false);
965 self.inner.force_reuse.set(false);
966 self.inner.force_recompose.set(false);
967 self.inner.unknown_invalidation_source.set(false);
968 update_cell(&self.inner.invalidation_sources, StateIds::clear);
969 if self.inner.enqueued.replace(false) {
970 self.inner.runtime.mark_scope_recomposed();
971 }
972 let pending = self.inner.pending_recompose.replace(false);
973 if pending {
974 if self.inner.active.get() {
975 self.invalidate();
976 } else {
977 self.inner.invalid.set(true);
978 }
979 }
980 }
981
982 fn set_boxed_recompose(
983 &self,
984 body: Box<dyn FnMut(&Composer) + 'static>,
985 stateless_body: Option<fn() -> TypeId>,
986 ) {
987 #[cfg(feature = "inspection")]
988 self.inner
989 .source_trace
990 .replace(source_trace::current_source_trace());
991 self.inner.recompose.set(Some(RecomposeCallback {
992 body,
993 stateless_body,
994 }));
995 }
996
997 fn reruns_stateless(&self, body: TypeId) -> bool {
998 read_cell(&self.inner.recompose, |callback| {
999 callback
1000 .as_ref()
1001 .and_then(|callback| callback.stateless_body)
1002 .is_some_and(|current_body| current_body() == body)
1003 })
1004 }
1005
1006 fn run_recompose(&self, composer: &Composer) -> bool {
1007 #[cfg(feature = "inspection")]
1008 let _source_context = source_trace::restore_source_trace(&self.inner.source_trace.borrow());
1009 let Some(mut callback) = self.inner.recompose.take() else {
1010 return false;
1011 };
1012 (callback.body)(composer);
1013 update_cell(&self.inner.recompose, |slot| {
1015 if slot.is_none() {
1016 *slot = Some(callback);
1017 }
1018 });
1019 true
1020 }
1021
1022 fn has_recompose_callback(&self) -> bool {
1023 read_cell(&self.inner.recompose, Option::is_some)
1024 }
1025
1026 fn snapshot_locals(&self, stack: &LocalStackSnapshot) {
1027 update_cell(&self.inner.local_stack, |locals| {
1028 let unchanged = match (&*locals, stack) {
1029 (Some(current), Some(stack)) => Rc::ptr_eq(current, stack),
1030 (current, stack) => current.is_none() && stack.is_none(),
1031 };
1032 if !unchanged {
1033 locals.clone_from(stack);
1034 }
1035 });
1036 }
1037
1038 fn local_stack(&self) -> LocalStackSnapshot {
1039 read_cell(&self.inner.local_stack, Clone::clone)
1040 }
1041
1042 fn set_parent_hint(&self, parent: Option<NodeId>) {
1043 self.inner.parent_hint.set(parent.unwrap_or(NO_PARENT_HINT));
1044 }
1045
1046 fn set_parent_scope(&self, parent: Option<&RecomposeScope>) {
1047 link_scope(&self.inner.parent_scope, parent);
1048 }
1049
1050 fn parent_scope(&self) -> Option<RecomposeScope> {
1051 read_cell(&self.inner.parent_scope, |parent| {
1052 parent.as_ref().and_then(RecomposeScope::upgrade)
1053 })
1054 }
1055
1056 fn set_lifetime_owner_scope(&self, owner: Option<&RecomposeScope>) {
1057 link_scope(&self.inner.lifetime_owner_scope, owner);
1058 }
1059
1060 #[cfg(test)]
1061 fn lifetime_owner_scope(&self) -> Option<RecomposeScope> {
1062 read_cell(&self.inner.lifetime_owner_scope, |owner| {
1063 owner.as_ref().and_then(RecomposeScope::upgrade)
1064 })
1065 }
1066
1067 fn callback_promotion_target(&self) -> Option<RecomposeScope> {
1068 let mut current = self.parent_scope();
1069 while let Some(scope) = current {
1070 if scope.has_recompose_callback() {
1071 return Some(scope);
1072 }
1073 current = scope.parent_scope();
1074 }
1075 None
1076 }
1077
1078 fn parent_hint(&self) -> Option<NodeId> {
1079 Some(self.inner.parent_hint.get()).filter(|&hint| hint != NO_PARENT_HINT)
1080 }
1081
1082 pub(crate) fn group_anchor(&self) -> AnchorId {
1083 self.inner.group_anchor.get()
1084 }
1085
1086 pub(crate) fn set_group_anchor(&self, anchor: AnchorId) {
1087 self.inner.group_anchor.set(anchor);
1088 }
1089
1090 fn set_slots_host(&self, host: &SlotsHost) {
1091 let storage_key = host.storage_key();
1092 update_cell(&self.inner.slots_runtime_state, |runtime_state| {
1093 if self.inner.slots_storage_key.get() == storage_key
1094 && runtime_state.as_ref().map(std::rc::Weak::as_ptr) == host.runtime_state_ptr()
1095 {
1096 return;
1097 }
1098 self.inner.slots_storage_key.set(storage_key);
1099 *runtime_state = host.runtime_state().map(|state| Rc::downgrade(&state));
1100 });
1101 }
1102
1103 pub(crate) fn slots_storage_key(&self) -> Option<usize> {
1104 let key = self.inner.slots_storage_key.get();
1105 (key != 0).then_some(key)
1106 }
1107
1108 pub(crate) fn slots_host_identity(
1111 &self,
1112 ) -> (usize, *const crate::composer::ComposerRuntimeState) {
1113 let state = read_cell(&self.inner.slots_runtime_state, |state| {
1114 state
1115 .as_ref()
1116 .map_or(std::ptr::null(), std::rc::Weak::as_ptr)
1117 });
1118 (self.inner.slots_storage_key.get(), state)
1119 }
1120
1121 pub(crate) fn slots_runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
1122 read_cell(&self.inner.slots_runtime_state, |state| {
1123 state.as_ref().and_then(std::rc::Weak::upgrade)
1124 })
1125 }
1126
1127 pub fn deactivate(&self) {
1128 if !self.inner.active.replace(false) {
1129 return;
1130 }
1131 note_scope_activity_change();
1132 self.inner
1133 .deactivations
1134 .set(self.inner.deactivations.get() + 1);
1135 if self.inner.enqueued.replace(false) {
1136 self.inner.runtime.mark_scope_recomposed();
1137 }
1138 }
1139
1140 pub(crate) fn defer_until_reactivated(&self) {
1141 if self.inner.enqueued.replace(false) {
1142 self.inner.runtime.mark_scope_recomposed();
1143 }
1144 }
1145
1146 pub fn reactivate(&self) {
1147 self.inner.active.set(true);
1148 if self.inner.invalid.get()
1149 && self.is_effectively_active()
1150 && !self.inner.enqueued.replace(true)
1151 {
1152 self.inner.runtime.register_invalid_scope(self.downgrade());
1153 }
1154 }
1155
1156 pub fn force_reuse(&self) {
1157 self.inner.force_reuse.set(true);
1158 self.inner.force_recompose.set(false);
1159 self.inner.pending_recompose.set(true);
1160 }
1161
1162 pub(crate) fn request_pending_recompose(&self) {
1163 self.inner.pending_recompose.set(true);
1164 }
1165
1166 pub fn force_recompose(&self) {
1167 self.inner.force_recompose.set(true);
1168 self.inner.force_reuse.set(false);
1169 self.inner.pending_recompose.set(false);
1170 }
1171
1172 pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
1173 self.inner.retention_mode.set(mode);
1174 }
1175
1176 pub(crate) fn retention_mode(&self) -> RetentionMode {
1177 self.inner.retention_mode.get()
1178 }
1179
1180 pub fn should_recompose(&self) -> bool {
1181 if self.inner.force_recompose.replace(false) {
1182 self.inner.force_reuse.set(false);
1183 return true;
1184 }
1185 if self.inner.force_reuse.replace(false) {
1186 return false;
1187 }
1188 self.is_invalid()
1189 }
1190
1191 pub fn has_composed_once(&self) -> bool {
1192 self.inner.composed_once.get()
1193 }
1194
1195 fn mark_composed_once(&self) {
1196 self.inner.composed_once.set(true);
1197 }
1198
1199 fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
1200 if self.inner.unknown_invalidation_source.get() {
1201 return None;
1202 }
1203 read_cell(&self.inner.invalidation_sources, |sources| {
1204 (!sources.is_empty()).then(|| {
1205 sources
1206 .iter()
1207 .all(|source| allowed_sources.contains(source))
1208 })
1209 })
1210 }
1211
1212 fn has_unknown_invalidation_source(&self) -> bool {
1213 self.inner.unknown_invalidation_source.get()
1214 }
1215}
1216
1217#[cfg(test)]
1218impl RecomposeScope {
1219 pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
1220 Self::new(runtime)
1221 }
1222}
1223
1224#[derive(Debug, Clone, Copy, Default)]
1225pub struct RecomposeOptions {
1226 pub force_reuse: bool,
1227 pub force_recompose: bool,
1228 pub retention: RetentionMode,
1229}
1230
1231#[derive(Debug, Clone, PartialEq, Eq)]
1232pub enum NodeError {
1233 Missing {
1234 id: NodeId,
1235 },
1236 TypeMismatch {
1237 id: NodeId,
1238 expected: &'static str,
1239 },
1240 MissingContext {
1241 id: NodeId,
1242 reason: &'static str,
1243 },
1244 AlreadyExists {
1245 id: NodeId,
1246 },
1247 MalformedCommandPayload {
1248 tag: &'static str,
1249 },
1250 SlotHostUnavailable {
1251 operation: &'static str,
1252 reason: &'static str,
1253 },
1254 RecompositionLimitExceeded {
1255 operation: &'static str,
1256 limit: usize,
1257 },
1258}
1259
1260impl std::fmt::Display for NodeError {
1261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1262 match self {
1263 NodeError::Missing { id } => write!(f, "node {id} missing"),
1264 NodeError::TypeMismatch { id, expected } => {
1265 write!(f, "node {id} type mismatch; expected {expected}")
1266 }
1267 NodeError::MissingContext { id, reason } => {
1268 write!(f, "missing context for node {id}: {reason}")
1269 }
1270 NodeError::AlreadyExists { id } => {
1271 write!(f, "node {id} already exists")
1272 }
1273 NodeError::MalformedCommandPayload { tag } => {
1274 write!(f, "command queue missing or invalid {tag} payload")
1275 }
1276 NodeError::SlotHostUnavailable { operation, reason } => {
1277 write!(f, "{operation} cannot access slot host: {reason}")
1278 }
1279 NodeError::RecompositionLimitExceeded { operation, limit } => {
1280 write!(
1281 f,
1282 "{operation} exceeded {limit} iterations while reconciling composition"
1283 )
1284 }
1285 }
1286 }
1287}
1288
1289impl std::error::Error for NodeError {}
1290
1291pub use subcompose::{
1292 ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
1293};
1294
1295#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1296pub enum Phase {
1297 Compose,
1298 Measure,
1299 Layout,
1300}
1301
1302pub use composer_context::{
1303 __current_composer, note_nested_slots_host, with_composer as with_current_composer,
1304};
1305
1306#[expect(non_snake_case)]
1307pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
1308 composer_context::with_composer(f)
1309}
1310
1311fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
1312 composer_context::try_with_composer(f)
1313}
1314
1315#[doc(hidden)]
1316pub fn current_recompose_scope_invalidated_only_by(
1317 allowed_sources: impl IntoIterator<Item = StateId>,
1318) -> Option<bool> {
1319 with_current_composer_opt(|composer| {
1320 let allowed_sources = allowed_sources.into_iter().collect();
1321 let mut scope = composer.current_recompose_scope();
1322 let mut saw_unknown_source = false;
1323 while let Some(current) = scope {
1324 if current.has_unknown_invalidation_source() {
1325 saw_unknown_source = true;
1326 scope = current.parent_scope();
1327 continue;
1328 }
1329 if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
1330 return Some(matches);
1331 }
1332 scope = current.parent_scope();
1333 }
1334 saw_unknown_source.then_some(false)
1335 })
1336 .flatten()
1337}
1338
1339fn key_scoped<K: Hash, R>(
1340 key: &K,
1341 caller: &'static std::panic::Location<'static>,
1342 content: impl FnOnce() -> R,
1343) -> R {
1344 let seed = explicit_group_key_seed(key, caller);
1345 with_current_composer(|composer| composer.with_group_seed(seed, |_| content()))
1346}
1347
1348#[track_caller]
1349pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
1350 key_scoped(key, std::panic::Location::caller(), content);
1351}
1352
1353#[track_caller]
1364pub fn key<K: Hash, R>(keys: K, content: impl FnOnce() -> R) -> R {
1365 key_scoped(&keys, std::panic::Location::caller(), content)
1366}
1367
1368#[track_caller]
1387pub fn movable<K: Hash>(key: K, content: impl FnOnce()) {
1388 let id = hash_key(&key);
1389 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1390}
1391
1392#[expect(non_snake_case)]
1426#[track_caller]
1427pub fn rememberMovableContentOf(content: impl Fn() + 'static) -> MovableContent {
1428 let runtime = with_current_composer(composer::Composer::runtime_handle);
1429 let id = remember(|| runtime.next_movable_content_id()).with(|id| *id);
1430 MovableContent {
1431 id,
1432 content: Rc::new(content),
1433 }
1434}
1435
1436#[expect(non_snake_case)]
1444pub fn movableContentOf<K: Hash>(key: K, content: impl Fn() + 'static) -> MovableContent {
1445 MovableContent {
1446 id: hash_key(&key),
1447 content: Rc::new(content),
1448 }
1449}
1450
1451#[derive(Clone)]
1453pub struct MovableContent {
1454 id: Key,
1455 content: Rc<dyn Fn()>,
1456}
1457
1458impl MovableContent {
1459 pub fn show(&self) {
1463 let content = Rc::clone(&self.content);
1464 let id = self.id;
1465 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1466 }
1467
1468 pub fn forget(&self) {
1471 forget_movable_id(self.id);
1472 }
1473}
1474
1475impl std::fmt::Debug for MovableContent {
1476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1477 f.debug_struct("MovableContent")
1478 .field("id", &self.id)
1479 .finish()
1480 }
1481}
1482
1483impl PartialEq for MovableContent {
1484 fn eq(&self, other: &Self) -> bool {
1485 self.id == other.id && Rc::ptr_eq(&self.content, &other.content)
1486 }
1487}
1488
1489pub fn forget_movable<K: Hash>(key: K) {
1495 let id = hash_key(&key);
1496 forget_movable_id(id);
1497}
1498
1499fn forget_movable_id(id: Key) {
1500 let runtime = composer_context::try_with_composer(composer::Composer::runtime_handle)
1501 .or_else(runtime::current_runtime_handle);
1502 match runtime {
1503 Some(runtime) => runtime.forget_movable(id),
1504 None => log::error!("forget_movable called without an active runtime"),
1505 }
1506}
1507
1508#[derive(Default)]
1509struct DisposableEffectState {
1510 key: Option<effect_key::EffectKey>,
1511 cleanup: Option<Box<dyn FnOnce()>>,
1512}
1513
1514impl DisposableEffectState {
1515 fn should_run(&self, key: &effect_key::EffectKey) -> bool {
1516 match &self.key {
1517 Some(current) => key.differs_from(current),
1518 None => true,
1519 }
1520 }
1521
1522 fn set_key(&mut self, key: effect_key::EffectKey) {
1523 self.key = Some(key);
1524 }
1525
1526 fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
1527 self.cleanup = cleanup;
1528 }
1529
1530 fn run_cleanup(&mut self) {
1531 if let Some(cleanup) = self.cleanup.take() {
1532 cleanup();
1533 }
1534 }
1535}
1536
1537impl Drop for DisposableEffectState {
1538 fn drop(&mut self) {
1539 self.run_cleanup();
1540 }
1541}
1542
1543#[derive(Clone, Copy, Debug, Default)]
1544pub struct DisposableEffectScope;
1545
1546#[derive(Default)]
1547pub struct DisposableEffectResult {
1548 cleanup: Option<Box<dyn FnOnce()>>,
1549}
1550
1551impl DisposableEffectScope {
1552 pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
1553 DisposableEffectResult::new(cleanup)
1554 }
1555}
1556
1557impl DisposableEffectResult {
1558 pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
1559 Self {
1560 cleanup: Some(Box::new(cleanup)),
1561 }
1562 }
1563
1564 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
1565 self.cleanup
1566 }
1567}
1568
1569#[expect(non_snake_case)]
1570pub fn SideEffect(effect: impl FnOnce() + 'static) {
1571 with_current_composer(|composer| composer.register_side_effect(effect));
1572}
1573
1574pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
1575where
1576 K: PartialEq + 'static,
1577 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1578{
1579 with_current_composer(|composer| {
1580 composer.with_group(group_key, |composer| {
1581 let key = effect_key::EffectKey::new(keys);
1582 let state = composer.remember_effect::<DisposableEffectState>();
1583 if state.with(|state| state.should_run(&key)) {
1584 state.update(|state| {
1585 state.run_cleanup();
1586 state.set_key(key);
1587 });
1588 let mut effect_opt = Some(effect);
1589 composer.register_side_effect(move || {
1590 if let Some(effect) = effect_opt.take() {
1591 let result = effect(DisposableEffectScope);
1592 state.update(|state| state.set_cleanup(result.into_cleanup()));
1593 }
1594 });
1595 }
1596 });
1597 });
1598}
1599
1600#[expect(non_snake_case)]
1608#[track_caller]
1609pub fn DisposableEffect<K, F>(keys: K, effect: F)
1610where
1611 K: PartialEq + 'static,
1612 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1613{
1614 __disposable_effect_impl(crate::caller_location_key(), keys, effect);
1615}
1616
1617#[macro_export]
1618macro_rules! clone_captures {
1619 ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1620 $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1621 $body
1622 }};
1623 (@clone $alias:ident = $value:expr) => {
1624 ($value).clone()
1625 };
1626 (@clone $alias:ident) => {
1627 $alias.clone()
1628 };
1629}
1630
1631pub fn with_node_mut<N: Node + 'static, R>(
1632 id: NodeId,
1633 f: impl FnOnce(&mut N) -> R,
1634) -> Result<R, NodeError> {
1635 with_current_composer(|composer| composer.with_node_mut(id, f))
1636}
1637
1638pub fn push_parent(id: NodeId) {
1639 with_current_composer(|composer| composer.push_parent(id));
1640}
1641
1642pub fn pop_parent() {
1643 with_current_composer(composer::Composer::pop_parent);
1644}
1645
1646pub trait Node: Any {
1647 fn mount(&mut self) {}
1648 fn unmount(&mut self) {}
1649 fn insert_child(&mut self, _child: NodeId) -> bool {
1655 false
1656 }
1657 fn remove_child(&mut self, _child: NodeId) -> bool {
1662 false
1663 }
1664 fn move_child(&mut self, _from: usize, _to: usize) {}
1665 fn update_children(&mut self, _children: &[NodeId]) {}
1666 fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1670 out.clear();
1671 }
1672 fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1673 self.collect_children_into(out);
1674 }
1675 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1687 let mut children = SmallVec::<[NodeId; 8]>::new();
1688 self.collect_owned_children_into(&mut children);
1689 children.iter().position(|&id| id == child)
1690 }
1691 fn set_node_id(&mut self, _id: NodeId) {}
1694 fn on_attached_to_parent(&mut self, _parent: NodeId) {}
1697 fn on_removed_from_parent(&mut self) {}
1700 fn parent(&self) -> Option<NodeId> {
1703 None
1704 }
1705 fn mark_needs_layout(&self) {}
1708 fn needs_layout(&self) -> bool {
1710 false
1711 }
1712 fn mark_needs_measure(&self) {}
1715 fn needs_measure(&self) -> bool {
1717 false
1718 }
1719 fn mark_descendant_needs_layout(&self, measure: bool) {
1724 if measure {
1725 self.mark_needs_measure();
1726 } else {
1727 self.mark_needs_layout();
1728 }
1729 }
1730 fn descendant_needs_layout(&self) -> bool {
1732 false
1733 }
1734 fn descendant_needs_measure(&self) -> bool {
1736 false
1737 }
1738 fn layout_dirty(&self) -> bool {
1741 self.needs_measure() || self.needs_layout() || self.descendant_needs_layout()
1742 }
1743 fn is_virtual(&self) -> bool {
1746 false
1747 }
1748 fn mark_needs_semantics(&self) {}
1750 fn mark_descendant_needs_semantics(&self) {
1754 self.mark_needs_semantics();
1755 }
1756 fn needs_semantics(&self) -> bool {
1758 false
1759 }
1760 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1769 if self.parent().is_none() {
1770 self.on_attached_to_parent(parent);
1771 }
1772 }
1773
1774 fn recycle_key(&self) -> Option<TypeId> {
1776 None
1777 }
1778
1779 fn recycle_pool_limit(&self) -> Option<usize> {
1781 None
1782 }
1783
1784 fn prepare_for_recycle(&mut self) {}
1786
1787 fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
1792 None
1793 }
1794
1795 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
1801 None
1802 }
1803
1804 fn debug_heap_bytes(&self) -> usize {
1806 0
1807 }
1808}
1809
1810pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1828 DirtyBubble::LAYOUT.apply(applier, node_id);
1829}
1830
1831pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1841 DirtyBubble::MEASURE.apply(applier, node_id);
1842}
1843
1844pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1850 DirtyBubble::SEMANTICS.apply(applier, node_id);
1851}
1852
1853pub fn queue_semantics_invalidation(node_id: NodeId) {
1858 let _ = composer_context::try_with_composer(|composer| {
1859 composer.enqueue_semantics_invalidation(node_id);
1860 });
1861}
1862
1863pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1885 bubble_layout_dirty_composer::<N>(node_id);
1886}
1887
1888pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
1894 with_current_composer(|composer| {
1895 composer.commands_mut().push(Command::BubbleDirty {
1896 node_id,
1897 bubble: DirtyBubble {
1898 layout: false,
1899 measure: true,
1900 semantics: false,
1901 },
1902 });
1903 });
1904}
1905
1906pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1913 bubble_semantics_dirty_composer::<N>(node_id);
1914}
1915
1916fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1917 let _ = with_node_mut(node_id, |node: &mut N| {
1918 node.mark_needs_layout();
1919 });
1920
1921 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1922 let parent_id = pid;
1923
1924 let advanced = with_node_mut(parent_id, |node: &mut N| {
1925 node.mark_descendant_needs_layout(false);
1926 true
1927 })
1928 .unwrap_or(false);
1929
1930 if advanced {
1931 node_id = parent_id;
1932 } else {
1933 break;
1934 }
1935 }
1936}
1937
1938fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1939 let _ = with_node_mut(node_id, |node: &mut N| {
1940 node.mark_needs_semantics();
1941 });
1942
1943 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1944 let parent_id = pid;
1945
1946 let advanced = with_node_mut(parent_id, |node: &mut N| {
1947 if !node.needs_semantics() {
1948 node.mark_descendant_needs_semantics();
1949 }
1950 true
1951 })
1952 .unwrap_or(false);
1953
1954 if advanced {
1955 node_id = parent_id;
1956 } else {
1957 break;
1958 }
1959 }
1960}
1961
1962impl dyn Node {
1963 pub fn as_any_mut(&mut self) -> &mut dyn Any {
1964 self
1965 }
1966}
1967
1968pub struct RecycledNode {
1969 stable_id: NodeId,
1970 node: Box<dyn Node>,
1971 warm_origin: bool,
1972}
1973
1974impl RecycledNode {
1975 fn new(stable_id: NodeId, mut node: Box<dyn Node>, warm_origin: bool) -> Self {
1979 let node = node.rehouse_for_recycle().unwrap_or_else(|| {
1980 node.prepare_for_recycle();
1981 node
1982 });
1983 Self {
1984 stable_id,
1985 node,
1986 warm_origin,
1987 }
1988 }
1989
1990 fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
1991 Self {
1992 stable_id,
1993 node,
1994 warm_origin,
1995 }
1996 }
1997
1998 pub fn stable_id(&self) -> NodeId {
1999 self.stable_id
2000 }
2001
2002 fn warm_origin(&self) -> bool {
2003 self.warm_origin
2004 }
2005
2006 fn set_warm_origin(&mut self, warm_origin: bool) {
2007 self.warm_origin = warm_origin;
2008 }
2009
2010 pub fn node_mut(&mut self) -> &mut dyn Node {
2011 self.node.as_mut()
2012 }
2013
2014 pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
2015 (self.stable_id, self.node, self.warm_origin)
2016 }
2017}
2018
2019#[derive(Debug, Clone, PartialEq, Eq)]
2020pub struct RecycledNodeInsertion {
2021 pub id: NodeId,
2022 pub stable_id_reused: bool,
2023 pub fallback_error: Option<NodeError>,
2024}
2025
2026impl RecycledNodeInsertion {
2027 fn reused(stable_id: NodeId) -> Self {
2028 Self {
2029 id: stable_id,
2030 stable_id_reused: true,
2031 fallback_error: None,
2032 }
2033 }
2034
2035 fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
2036 Self {
2037 id,
2038 stable_id_reused: false,
2039 fallback_error,
2040 }
2041 }
2042}
2043
2044pub trait Applier: Any {
2045 fn create(&mut self, node: Box<dyn Node>) -> NodeId;
2046 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
2047 fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
2048
2049 fn begin_bubble_batch(&mut self) {}
2053
2054 fn end_bubble_batch(&mut self) {}
2056
2057 fn forget_bubble_passes(&mut self) {}
2060
2061 fn note_bubble_pass(&mut self, _id: NodeId, _flags: u8) -> bool {
2066 false
2067 }
2068
2069 fn record_structural_change(&mut self, _parent_id: NodeId) {}
2075
2076 fn node_generation(&self, id: NodeId) -> u32;
2080
2081 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
2089
2090 fn insert_recycled_node_or_create(
2093 &mut self,
2094 stable_id: NodeId,
2095 node: Box<dyn Node>,
2096 ) -> RecycledNodeInsertion {
2097 let id = self.create(node);
2098 RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
2099 }
2100
2101 fn as_any(&self) -> &dyn Any
2102 where
2103 Self: Sized,
2104 {
2105 self
2106 }
2107
2108 fn as_any_mut(&mut self) -> &mut dyn Any
2109 where
2110 Self: Sized,
2111 {
2112 self
2113 }
2114
2115 fn compact(&mut self) {}
2117
2118 fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
2120 None
2121 }
2122
2123 fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
2125
2126 fn seed_recycled_node_shell(
2128 &mut self,
2129 _key: TypeId,
2130 _recycle_pool_limit: Option<usize>,
2131 _shell: Box<dyn Node>,
2132 ) {
2133 }
2134
2135 fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
2137
2138 fn clear_recycled_nodes(&mut self) {}
2140}
2141
2142type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
2143
2144#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2145pub(crate) struct DirtyBubble {
2146 layout: bool,
2147 measure: bool,
2148 semantics: bool,
2149}
2150
2151impl DirtyBubble {
2152 const LAYOUT: Self = Self {
2153 layout: true,
2154 measure: false,
2155 semantics: false,
2156 };
2157
2158 const MEASURE: Self = Self {
2159 layout: false,
2160 measure: true,
2161 semantics: false,
2162 };
2163
2164 pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
2165 layout: true,
2166 measure: true,
2167 semantics: false,
2168 };
2169
2170 pub(crate) const SEMANTICS: Self = Self {
2171 layout: false,
2172 measure: false,
2173 semantics: true,
2174 };
2175
2176 fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
2179 self.walk(applier, Some(node_id), true);
2180 }
2181
2182 fn apply_to_ancestors(self, applier: &mut dyn Applier, node_id: NodeId) {
2185 let parent = applier.get_mut(node_id).ok().and_then(|node| node.parent());
2186 self.walk(applier, parent, false);
2187 }
2188
2189 fn walk(self, applier: &mut dyn Applier, first: Option<NodeId>, mut start: bool) {
2196 let flags =
2197 u8::from(self.layout) | u8::from(self.measure) << 1 | u8::from(self.semantics) << 2;
2198 let mut next = first;
2199 while let Some(id) = next {
2200 if !start && applier.note_bubble_pass(id, flags) {
2201 break;
2202 }
2203 let Ok(node) = applier.get_mut(id) else {
2204 break;
2205 };
2206 self.mark(node, start);
2207 next = node.parent();
2208 start = start && node.is_virtual();
2209 }
2210 }
2211
2212 fn mark(self, node: &mut dyn Node, start: bool) {
2215 if start {
2216 if self.layout {
2217 node.mark_needs_layout();
2218 }
2219 if self.measure {
2220 node.mark_needs_measure();
2221 }
2222 if self.semantics {
2223 node.mark_needs_semantics();
2224 }
2225 return;
2226 }
2227 let marked = if self.measure {
2228 node.descendant_needs_measure()
2229 } else {
2230 node.descendant_needs_layout()
2231 };
2232 if (self.layout || self.measure) && !marked {
2233 node.mark_descendant_needs_layout(self.measure);
2234 }
2235 if self.semantics && !node.needs_semantics() {
2236 node.mark_descendant_needs_semantics();
2237 }
2238 }
2239}
2240
2241pub(crate) enum Command {
2242 BubbleDirty {
2243 node_id: NodeId,
2244 bubble: DirtyBubble,
2245 },
2246 RemoveNode {
2247 id: NodeId,
2248 },
2249 MountNode {
2250 id: NodeId,
2251 },
2252 AttachChild {
2253 parent_id: NodeId,
2254 child_id: NodeId,
2255 insert_index: Option<usize>,
2256 bubble: DirtyBubble,
2257 },
2258 InsertChild {
2259 parent_id: NodeId,
2260 child_id: NodeId,
2261 appended_index: usize,
2262 insert_index: usize,
2263 bubble: DirtyBubble,
2264 },
2265 MoveChild {
2266 parent_id: NodeId,
2267 from_index: usize,
2268 to_index: usize,
2269 bubble: DirtyBubble,
2270 },
2271 RemoveChild {
2272 parent_id: NodeId,
2273 child_id: NodeId,
2274 },
2275 DetachChild {
2276 parent_id: NodeId,
2277 child_id: NodeId,
2278 },
2279 SyncChildren {
2280 parent_id: NodeId,
2281 expected_children: ChildList,
2282 },
2283 Callback(CommandCallback),
2284}
2285
2286#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2287struct DeferredChildCleanup {
2288 child_id: NodeId,
2289 generation: u32,
2290 removed_from_parent: bool,
2291}
2292
2293#[derive(Default)]
2294struct DeferredChildCleanupQueue {
2295 pending: Vec<DeferredChildCleanup>,
2296 preserved: Vec<(NodeId, u32)>,
2297}
2298
2299impl DeferredChildCleanupQueue {
2300 fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
2301 if self
2302 .preserved
2303 .iter()
2304 .any(|&(preserved_id, preserved_generation)| {
2305 preserved_id == child_id && preserved_generation == generation
2306 })
2307 {
2308 return;
2309 }
2310 self.pending.push(DeferredChildCleanup {
2311 child_id,
2312 generation,
2313 removed_from_parent,
2314 });
2315 }
2316
2317 fn preserve(&mut self, child_id: NodeId, generation: u32) {
2318 if !self
2319 .preserved
2320 .iter()
2321 .any(|&(preserved_id, preserved_generation)| {
2322 preserved_id == child_id && preserved_generation == generation
2323 })
2324 {
2325 self.preserved.push((child_id, generation));
2326 }
2327 self.pending
2328 .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
2329 }
2330
2331 fn flush(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2332 for cleanup in self.pending.drain(..) {
2333 cleanup_detached_child(applier, cleanup)?;
2334 }
2335 Ok(())
2336 }
2337
2338 fn clear(&mut self) {
2339 self.pending.clear();
2340 self.preserved.clear();
2341 }
2342}
2343
2344impl Command {
2345 pub(crate) fn callback(
2346 callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
2347 ) -> Self {
2348 Self::Callback(Box::new(callback))
2349 }
2350
2351 pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2352 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2353 self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
2354 deferred_cleanup.flush(applier)
2355 }
2356
2357 fn apply_with_cleanup(
2358 self,
2359 applier: &mut dyn Applier,
2360 deferred_cleanup: &mut DeferredChildCleanupQueue,
2361 ) -> Result<(), NodeError> {
2362 match self {
2363 Self::BubbleDirty { node_id, bubble } => {
2364 bubble.apply(applier, node_id);
2365 Ok(())
2366 }
2367 Self::RemoveNode { id } => {
2368 if let Ok(node) = applier.get_mut(id) {
2369 node.unmount();
2370 }
2371 match applier.remove(id) {
2372 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2373 Err(err) => Err(err),
2374 }
2375 }
2376 Self::MountNode { id } => {
2377 let node = match applier.get_mut(id) {
2378 Ok(node) => node,
2379 Err(NodeError::Missing { .. }) => return Ok(()),
2380 Err(err) => return Err(err),
2381 };
2382 node.set_node_id(id);
2383 node.mount();
2384 Ok(())
2385 }
2386 Self::AttachChild {
2387 parent_id,
2388 child_id,
2389 insert_index,
2390 bubble,
2391 } => {
2392 attach_child_at(applier, parent_id, child_id, insert_index, bubble);
2393 Ok(())
2394 }
2395 Self::InsertChild {
2396 parent_id,
2397 child_id,
2398 appended_index,
2399 insert_index,
2400 bubble,
2401 } => {
2402 insert_child_with_reparenting(applier, parent_id, child_id);
2403 bubble.apply(applier, parent_id);
2404 if insert_index != appended_index
2405 && let Ok(parent_node) = applier.get_mut(parent_id)
2406 {
2407 parent_node.move_child(appended_index, insert_index);
2408 }
2409 Ok(())
2410 }
2411 Self::MoveChild {
2412 parent_id,
2413 from_index,
2414 to_index,
2415 bubble,
2416 } => {
2417 if let Ok(parent_node) = applier.get_mut(parent_id) {
2418 parent_node.move_child(from_index, to_index);
2419 }
2420 bubble.apply(applier, parent_id);
2421 note_structural_move(parent_id, from_index, to_index);
2422 applier.record_structural_change(parent_id);
2423 Ok(())
2424 }
2425 Self::RemoveChild {
2426 parent_id,
2427 child_id,
2428 } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
2429 Self::DetachChild {
2430 parent_id,
2431 child_id,
2432 } => {
2433 let generation = applier.node_generation(child_id);
2434 detach_child_from_parent(applier, parent_id, child_id)?;
2435 deferred_cleanup.preserve(child_id, generation);
2436 Ok(())
2437 }
2438 Self::SyncChildren {
2439 parent_id,
2440 expected_children,
2441 } => sync_children(applier, parent_id, &expected_children, deferred_cleanup),
2442 Self::Callback(callback) => callback(applier),
2443 }
2444 }
2445}
2446
2447const COMMAND_FLUSH_THRESHOLD: usize = 4096;
2448type ChildList = SmallVec<[NodeId; 4]>;
2449const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
2450
2451#[derive(Copy, Clone)]
2452enum CommandTag {
2453 BubbleDirty,
2454 RemoveNode,
2455 MountNode,
2456 AttachChild,
2457 InsertChild,
2458 MoveChild,
2459 RemoveChild,
2460 DetachChild,
2461 SyncChildren,
2462 Callback,
2463}
2464
2465impl CommandTag {
2466 fn label(self) -> &'static str {
2467 match self {
2468 Self::BubbleDirty => "BubbleDirty",
2469 Self::RemoveNode => "RemoveNode",
2470 Self::MountNode => "MountNode",
2471 Self::AttachChild => "AttachChild",
2472 Self::InsertChild => "InsertChild",
2473 Self::MoveChild => "MoveChild",
2474 Self::RemoveChild => "RemoveChild",
2475 Self::DetachChild => "DetachChild",
2476 Self::SyncChildren => "SyncChildren",
2477 Self::Callback => "Callback",
2478 }
2479 }
2480}
2481
2482#[derive(Copy, Clone)]
2483struct BubbleDirtyCommand {
2484 node_id: NodeId,
2485 bubble: DirtyBubble,
2486}
2487
2488#[derive(Copy, Clone)]
2489struct AttachChildCommand {
2490 parent_id: NodeId,
2491 child_id: NodeId,
2492 insert_index: Option<usize>,
2493 bubble: DirtyBubble,
2494}
2495
2496#[derive(Copy, Clone)]
2497struct InsertChildCommand {
2498 parent_id: NodeId,
2499 child_id: NodeId,
2500 appended_index: usize,
2501 insert_index: usize,
2502 bubble: DirtyBubble,
2503}
2504
2505#[derive(Copy, Clone)]
2506struct MoveChildCommand {
2507 parent_id: NodeId,
2508 from_index: usize,
2509 to_index: usize,
2510 bubble: DirtyBubble,
2511}
2512
2513#[derive(Copy, Clone)]
2514struct RemoveChildCommand {
2515 parent_id: NodeId,
2516 child_id: NodeId,
2517}
2518
2519#[derive(Copy, Clone)]
2520struct DetachChildCommand {
2521 parent_id: NodeId,
2522 child_id: NodeId,
2523}
2524
2525struct SyncChildrenCommand {
2526 parent_id: NodeId,
2527 child_start: usize,
2528 child_len: usize,
2529}
2530
2531#[derive(Default)]
2535pub(crate) struct CommandQueue {
2536 tags: Vec<CommandTag>,
2537 bubble_dirty: Vec<BubbleDirtyCommand>,
2538 remove_nodes: Vec<NodeId>,
2539 mount_nodes: Vec<NodeId>,
2540 attach_children: Vec<AttachChildCommand>,
2541 insert_children: Vec<InsertChildCommand>,
2542 move_children: Vec<MoveChildCommand>,
2543 remove_children: Vec<RemoveChildCommand>,
2544 detach_children: Vec<DetachChildCommand>,
2545 sync_children: Vec<SyncChildrenCommand>,
2546 sync_child_ids: Vec<NodeId>,
2547 callbacks: Vec<CommandCallback>,
2548 cleanup: DeferredChildCleanupQueue,
2549}
2550
2551impl CommandQueue {
2552 fn push_tag(&mut self, tag: CommandTag) {
2553 self.tags.push(tag);
2554 }
2555
2556 fn push(&mut self, command: Command) {
2557 match command {
2558 Command::BubbleDirty { node_id, bubble } => {
2559 self.bubble_dirty
2560 .push(BubbleDirtyCommand { node_id, bubble });
2561 self.push_tag(CommandTag::BubbleDirty);
2562 }
2563 Command::RemoveNode { id } => {
2564 self.remove_nodes.push(id);
2565 self.push_tag(CommandTag::RemoveNode);
2566 }
2567 Command::MountNode { id } => {
2568 self.mount_nodes.push(id);
2569 self.push_tag(CommandTag::MountNode);
2570 }
2571 Command::AttachChild {
2572 parent_id,
2573 child_id,
2574 insert_index,
2575 bubble,
2576 } => {
2577 self.attach_children.push(AttachChildCommand {
2578 parent_id,
2579 child_id,
2580 insert_index,
2581 bubble,
2582 });
2583 self.push_tag(CommandTag::AttachChild);
2584 }
2585 Command::InsertChild {
2586 parent_id,
2587 child_id,
2588 appended_index,
2589 insert_index,
2590 bubble,
2591 } => {
2592 self.insert_children.push(InsertChildCommand {
2593 parent_id,
2594 child_id,
2595 appended_index,
2596 insert_index,
2597 bubble,
2598 });
2599 self.push_tag(CommandTag::InsertChild);
2600 }
2601 Command::MoveChild {
2602 parent_id,
2603 from_index,
2604 to_index,
2605 bubble,
2606 } => {
2607 self.move_children.push(MoveChildCommand {
2608 parent_id,
2609 from_index,
2610 to_index,
2611 bubble,
2612 });
2613 self.push_tag(CommandTag::MoveChild);
2614 }
2615 Command::RemoveChild {
2616 parent_id,
2617 child_id,
2618 } => {
2619 self.remove_children.push(RemoveChildCommand {
2620 parent_id,
2621 child_id,
2622 });
2623 self.push_tag(CommandTag::RemoveChild);
2624 }
2625 Command::DetachChild {
2626 parent_id,
2627 child_id,
2628 } => {
2629 self.detach_children.push(DetachChildCommand {
2630 parent_id,
2631 child_id,
2632 });
2633 self.push_tag(CommandTag::DetachChild);
2634 }
2635 Command::SyncChildren {
2636 parent_id,
2637 expected_children,
2638 } => {
2639 let child_start = self.sync_child_ids.len();
2640 let child_len = expected_children.len();
2641 self.sync_child_ids.extend(expected_children);
2642 self.sync_children.push(SyncChildrenCommand {
2643 parent_id,
2644 child_start,
2645 child_len,
2646 });
2647 self.push_tag(CommandTag::SyncChildren);
2648 }
2649 Command::Callback(callback) => {
2650 self.callbacks.push(callback);
2651 self.push_tag(CommandTag::Callback);
2652 }
2653 }
2654 }
2655
2656 fn len(&self) -> usize {
2657 self.tags.len()
2658 }
2659
2660 fn capacity(&self) -> usize {
2661 self.tags.capacity()
2662 }
2663
2664 fn payload_len_bytes(&self) -> usize {
2665 self.bubble_dirty
2666 .len()
2667 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2668 .saturating_add(
2669 self.remove_nodes
2670 .len()
2671 .saturating_mul(std::mem::size_of::<NodeId>()),
2672 )
2673 .saturating_add(
2674 self.mount_nodes
2675 .len()
2676 .saturating_mul(std::mem::size_of::<NodeId>()),
2677 )
2678 .saturating_add(
2679 self.attach_children
2680 .len()
2681 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2682 )
2683 .saturating_add(
2684 self.insert_children
2685 .len()
2686 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2687 )
2688 .saturating_add(
2689 self.move_children
2690 .len()
2691 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2692 )
2693 .saturating_add(
2694 self.remove_children
2695 .len()
2696 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2697 )
2698 .saturating_add(
2699 self.detach_children
2700 .len()
2701 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2702 )
2703 .saturating_add(
2704 self.sync_children
2705 .len()
2706 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2707 )
2708 .saturating_add(
2709 self.sync_child_ids
2710 .len()
2711 .saturating_mul(std::mem::size_of::<NodeId>()),
2712 )
2713 .saturating_add(
2714 self.callbacks
2715 .len()
2716 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2717 )
2718 }
2719
2720 fn payload_capacity_bytes(&self) -> usize {
2721 self.bubble_dirty
2722 .capacity()
2723 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2724 .saturating_add(
2725 self.remove_nodes
2726 .capacity()
2727 .saturating_mul(std::mem::size_of::<NodeId>()),
2728 )
2729 .saturating_add(
2730 self.mount_nodes
2731 .capacity()
2732 .saturating_mul(std::mem::size_of::<NodeId>()),
2733 )
2734 .saturating_add(
2735 self.attach_children
2736 .capacity()
2737 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2738 )
2739 .saturating_add(
2740 self.insert_children
2741 .capacity()
2742 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2743 )
2744 .saturating_add(
2745 self.move_children
2746 .capacity()
2747 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2748 )
2749 .saturating_add(
2750 self.remove_children
2751 .capacity()
2752 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2753 )
2754 .saturating_add(
2755 self.detach_children
2756 .capacity()
2757 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2758 )
2759 .saturating_add(
2760 self.sync_children
2761 .capacity()
2762 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2763 )
2764 .saturating_add(
2765 self.sync_child_ids
2766 .capacity()
2767 .saturating_mul(std::mem::size_of::<NodeId>()),
2768 )
2769 .saturating_add(
2770 self.callbacks
2771 .capacity()
2772 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2773 )
2774 }
2775
2776 pub(crate) fn apply(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2779 applier.begin_bubble_batch();
2780 let result = self.apply_in_order(applier);
2781 applier.end_bubble_batch();
2782 self.clear();
2783 result
2784 }
2785
2786 fn clear(&mut self) {
2787 let Self {
2788 tags,
2789 bubble_dirty,
2790 remove_nodes,
2791 mount_nodes,
2792 attach_children,
2793 insert_children,
2794 move_children,
2795 remove_children,
2796 detach_children,
2797 sync_children,
2798 sync_child_ids,
2799 callbacks,
2800 cleanup,
2801 } = self;
2802 tags.clear();
2803 bubble_dirty.clear();
2804 remove_nodes.clear();
2805 mount_nodes.clear();
2806 attach_children.clear();
2807 insert_children.clear();
2808 move_children.clear();
2809 remove_children.clear();
2810 detach_children.clear();
2811 sync_children.clear();
2812 sync_child_ids.clear();
2813 callbacks.clear();
2814 cleanup.clear();
2815 }
2816
2817 fn apply_in_order(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2818 let mut payloads = CommandPayloads {
2819 bubble_dirty: self.bubble_dirty.drain(..),
2820 remove_nodes: self.remove_nodes.drain(..),
2821 mount_nodes: self.mount_nodes.drain(..),
2822 attach_children: self.attach_children.drain(..),
2823 insert_children: self.insert_children.drain(..),
2824 move_children: self.move_children.drain(..),
2825 remove_children: self.remove_children.drain(..),
2826 detach_children: self.detach_children.drain(..),
2827 sync_children: self.sync_children.drain(..),
2828 sync_child_ids: &self.sync_child_ids,
2829 callbacks: self.callbacks.drain(..),
2830 };
2831 for &tag in &self.tags {
2832 payloads.apply(tag, applier, &mut self.cleanup)?;
2833 }
2834 payloads.assert_all_taken();
2835 self.cleanup.flush(applier)
2836 }
2837}
2838
2839struct CommandPayloads<'a> {
2842 bubble_dirty: std::vec::Drain<'a, BubbleDirtyCommand>,
2843 remove_nodes: std::vec::Drain<'a, NodeId>,
2844 mount_nodes: std::vec::Drain<'a, NodeId>,
2845 attach_children: std::vec::Drain<'a, AttachChildCommand>,
2846 insert_children: std::vec::Drain<'a, InsertChildCommand>,
2847 move_children: std::vec::Drain<'a, MoveChildCommand>,
2848 remove_children: std::vec::Drain<'a, RemoveChildCommand>,
2849 detach_children: std::vec::Drain<'a, DetachChildCommand>,
2850 sync_children: std::vec::Drain<'a, SyncChildrenCommand>,
2851 sync_child_ids: &'a [NodeId],
2852 callbacks: std::vec::Drain<'a, CommandCallback>,
2853}
2854
2855impl CommandPayloads<'_> {
2856 fn apply(
2857 &mut self,
2858 tag: CommandTag,
2859 applier: &mut dyn Applier,
2860 cleanup: &mut DeferredChildCleanupQueue,
2861 ) -> Result<(), NodeError> {
2862 match tag {
2863 CommandTag::BubbleDirty
2864 | CommandTag::RemoveNode
2865 | CommandTag::MountNode
2866 | CommandTag::Callback => self.apply_node_command(tag, applier, cleanup),
2867 CommandTag::AttachChild
2868 | CommandTag::InsertChild
2869 | CommandTag::MoveChild
2870 | CommandTag::RemoveChild
2871 | CommandTag::DetachChild
2872 | CommandTag::SyncChildren => self.apply_child_command(tag, applier, cleanup),
2873 }
2874 }
2875
2876 fn apply_node_command(
2877 &mut self,
2878 tag: CommandTag,
2879 applier: &mut dyn Applier,
2880 cleanup: &mut DeferredChildCleanupQueue,
2881 ) -> Result<(), NodeError> {
2882 match tag {
2883 CommandTag::BubbleDirty => {
2884 let BubbleDirtyCommand { node_id, bubble } =
2885 next_command_payload(&mut self.bubble_dirty, tag)?;
2886 Command::BubbleDirty { node_id, bubble }.apply_with_cleanup(applier, cleanup)?;
2887 }
2888 CommandTag::RemoveNode => {
2889 let id = next_command_payload(&mut self.remove_nodes, tag)?;
2890 Command::RemoveNode { id }.apply_with_cleanup(applier, cleanup)?;
2891 }
2892 CommandTag::MountNode => {
2893 let id = next_command_payload(&mut self.mount_nodes, tag)?;
2894 Command::MountNode { id }.apply_with_cleanup(applier, cleanup)?;
2895 }
2896 CommandTag::Callback => {
2897 let callback = next_command_payload(&mut self.callbacks, tag)?;
2898 Command::Callback(callback).apply_with_cleanup(applier, cleanup)?;
2899 applier.forget_bubble_passes();
2900 }
2901 CommandTag::AttachChild
2902 | CommandTag::InsertChild
2903 | CommandTag::MoveChild
2904 | CommandTag::RemoveChild
2905 | CommandTag::DetachChild
2906 | CommandTag::SyncChildren => return Err(command_payload_error(tag)),
2907 }
2908 Ok(())
2909 }
2910
2911 fn apply_child_command(
2912 &mut self,
2913 tag: CommandTag,
2914 applier: &mut dyn Applier,
2915 cleanup: &mut DeferredChildCleanupQueue,
2916 ) -> Result<(), NodeError> {
2917 match tag {
2918 CommandTag::AttachChild => {
2919 let AttachChildCommand {
2920 parent_id,
2921 child_id,
2922 insert_index,
2923 bubble,
2924 } = next_command_payload(&mut self.attach_children, tag)?;
2925 Command::AttachChild {
2926 parent_id,
2927 child_id,
2928 insert_index,
2929 bubble,
2930 }
2931 .apply_with_cleanup(applier, cleanup)?;
2932 }
2933 CommandTag::InsertChild => {
2934 let InsertChildCommand {
2935 parent_id,
2936 child_id,
2937 appended_index,
2938 insert_index,
2939 bubble,
2940 } = next_command_payload(&mut self.insert_children, tag)?;
2941 Command::InsertChild {
2942 parent_id,
2943 child_id,
2944 appended_index,
2945 insert_index,
2946 bubble,
2947 }
2948 .apply_with_cleanup(applier, cleanup)?;
2949 }
2950 CommandTag::MoveChild => {
2951 let MoveChildCommand {
2952 parent_id,
2953 from_index,
2954 to_index,
2955 bubble,
2956 } = next_command_payload(&mut self.move_children, tag)?;
2957 Command::MoveChild {
2958 parent_id,
2959 from_index,
2960 to_index,
2961 bubble,
2962 }
2963 .apply_with_cleanup(applier, cleanup)?;
2964 }
2965 CommandTag::RemoveChild => {
2966 let RemoveChildCommand {
2967 parent_id,
2968 child_id,
2969 } = next_command_payload(&mut self.remove_children, tag)?;
2970 Command::RemoveChild {
2971 parent_id,
2972 child_id,
2973 }
2974 .apply_with_cleanup(applier, cleanup)?;
2975 }
2976 CommandTag::DetachChild => {
2977 let DetachChildCommand {
2978 parent_id,
2979 child_id,
2980 } = next_command_payload(&mut self.detach_children, tag)?;
2981 Command::DetachChild {
2982 parent_id,
2983 child_id,
2984 }
2985 .apply_with_cleanup(applier, cleanup)?;
2986 }
2987 CommandTag::SyncChildren => self.sync_children(applier, cleanup)?,
2988 CommandTag::BubbleDirty
2989 | CommandTag::RemoveNode
2990 | CommandTag::MountNode
2991 | CommandTag::Callback => return Err(command_payload_error(tag)),
2992 }
2993 Ok(())
2994 }
2995
2996 fn sync_children(
2997 &mut self,
2998 applier: &mut dyn Applier,
2999 cleanup: &mut DeferredChildCleanupQueue,
3000 ) -> Result<(), NodeError> {
3001 let tag = CommandTag::SyncChildren;
3002 let SyncChildrenCommand {
3003 parent_id,
3004 child_start,
3005 child_len,
3006 } = next_command_payload(&mut self.sync_children, tag)?;
3007 let expected_children = child_start
3008 .checked_add(child_len)
3009 .and_then(|child_end| self.sync_child_ids.get(child_start..child_end))
3010 .ok_or_else(|| command_payload_error(tag))?;
3011 sync_children(applier, parent_id, expected_children, cleanup)
3012 }
3013
3014 fn assert_all_taken(&mut self) {
3015 debug_assert!(self.bubble_dirty.next().is_none());
3016 debug_assert!(self.remove_nodes.next().is_none());
3017 debug_assert!(self.mount_nodes.next().is_none());
3018 debug_assert!(self.attach_children.next().is_none());
3019 debug_assert!(self.insert_children.next().is_none());
3020 debug_assert!(self.move_children.next().is_none());
3021 debug_assert!(self.remove_children.next().is_none());
3022 debug_assert!(self.detach_children.next().is_none());
3023 debug_assert!(self.sync_children.next().is_none());
3024 debug_assert!(self.callbacks.next().is_none());
3025 }
3026}
3027
3028fn command_payload_error(tag: CommandTag) -> NodeError {
3029 NodeError::MalformedCommandPayload { tag: tag.label() }
3030}
3031
3032fn next_command_payload<T>(
3033 payloads: &mut impl Iterator<Item = T>,
3034 tag: CommandTag,
3035) -> Result<T, NodeError> {
3036 payloads.next().ok_or_else(|| command_payload_error(tag))
3037}
3038
3039fn attach_child_at(
3040 applier: &mut dyn Applier,
3041 parent_id: NodeId,
3042 child_id: NodeId,
3043 insert_index: Option<usize>,
3044 bubble: DirtyBubble,
3045) {
3046 if insert_child_with_reparenting(applier, parent_id, child_id) {
3047 if let Some(target) = insert_index {
3048 move_attached_child_left_to(applier, parent_id, child_id, target);
3049 }
3050 bubble.apply(applier, parent_id);
3051 } else if let Ok(child) = applier.get_mut(child_id) {
3052 let dirty_bubble = DirtyBubble {
3053 layout: child.layout_dirty(),
3054 measure: child.needs_measure() || child.descendant_needs_measure(),
3055 semantics: false,
3056 };
3057 dirty_bubble.apply_to_ancestors(applier, child_id);
3058 }
3059}
3060
3061fn move_attached_child_left_to(
3062 applier: &mut dyn Applier,
3063 parent_id: NodeId,
3064 child_id: NodeId,
3065 target: usize,
3066) {
3067 let Ok(parent_node) = applier.get_mut(parent_id) else {
3068 return;
3069 };
3070 let Some(current_index) = parent_node.owned_child_index(child_id) else {
3071 return;
3072 };
3073 if target < current_index {
3074 parent_node.move_child(current_index, target);
3075 note_structural_move(parent_id, current_index, target);
3076 }
3077}
3078
3079fn insert_child_with_reparenting(
3080 applier: &mut dyn Applier,
3081 parent_id: NodeId,
3082 child_id: NodeId,
3083) -> bool {
3084 if parent_id == child_id {
3085 debug_assert_ne!(
3086 parent_id, child_id,
3087 "a node cannot be attached as its own child"
3088 );
3089 return false;
3090 }
3091
3092 let old_parent = applier
3093 .get_mut(child_id)
3094 .ok()
3095 .and_then(|node| node.parent());
3096 if old_parent != Some(parent_id) {
3097 applier.forget_bubble_passes();
3098 }
3099 if let Some(old_parent_id) = old_parent
3100 && old_parent_id != parent_id
3101 {
3102 let removed = applier
3103 .get_mut(old_parent_id)
3104 .is_ok_and(|old_parent_node| old_parent_node.remove_child(child_id));
3105 if let Ok(child_node) = applier.get_mut(child_id) {
3106 child_node.on_removed_from_parent();
3107 }
3108 if removed {
3109 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, old_parent_id);
3110 note_structural("reparent-detach", old_parent_id, child_id);
3111 applier.record_structural_change(old_parent_id);
3112 }
3113 }
3114
3115 let inserted = applier
3116 .get_mut(parent_id)
3117 .is_ok_and(|parent_node| parent_node.insert_child(child_id));
3118 if inserted {
3119 note_structural("attach", parent_id, child_id);
3120 applier.record_structural_change(parent_id);
3121 }
3122 if let Ok(child_node) = applier.get_mut(child_id) {
3123 child_node.on_attached_to_parent(parent_id);
3124 }
3125 inserted
3126}
3127
3128fn apply_remove_child(
3129 applier: &mut dyn Applier,
3130 parent_id: NodeId,
3131 child_id: NodeId,
3132 deferred_cleanup: &mut DeferredChildCleanupQueue,
3133) -> Result<(), NodeError> {
3134 detach_child_from_parent(applier, parent_id, child_id)?;
3135
3136 let generation = applier.node_generation(child_id);
3137 let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
3138 node.parent().is_none()
3139 } else {
3140 return Ok(());
3141 };
3142 deferred_cleanup.push(child_id, generation, removed_from_parent);
3143 Ok(())
3144}
3145
3146fn detach_child_from_parent(
3147 applier: &mut dyn Applier,
3148 parent_id: NodeId,
3149 child_id: NodeId,
3150) -> Result<(), NodeError> {
3151 let removed = applier
3152 .get_mut(parent_id)
3153 .is_ok_and(|parent_node| parent_node.remove_child(child_id));
3154 if removed {
3155 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3156 note_structural("detach", parent_id, child_id);
3157 applier.record_structural_change(parent_id);
3158 }
3159
3160 if let Ok(node) = applier.get_mut(child_id) {
3161 match node.parent() {
3162 Some(existing_parent_id) if existing_parent_id == parent_id => {
3163 node.on_removed_from_parent();
3164 applier.forget_bubble_passes();
3165 }
3166 None => {}
3167 Some(_) => return Ok(()),
3168 }
3169 } else {
3170 return Ok(());
3171 }
3172
3173 Ok(())
3174}
3175
3176fn cleanup_detached_child(
3177 applier: &mut dyn Applier,
3178 cleanup: DeferredChildCleanup,
3179) -> Result<(), NodeError> {
3180 if applier.node_generation(cleanup.child_id) != cleanup.generation {
3181 return Ok(());
3182 }
3183
3184 let parent_id = match applier.get_mut(cleanup.child_id) {
3185 Ok(node) => node.parent(),
3186 Err(NodeError::Missing { .. }) => return Ok(()),
3187 Err(err) => return Err(err),
3188 };
3189 if parent_id.is_some() {
3190 return Ok(());
3191 }
3192
3193 if let Ok(node) = applier.get_mut(cleanup.child_id) {
3194 if !cleanup.removed_from_parent {
3195 node.on_removed_from_parent();
3196 }
3197 node.unmount();
3198 }
3199 match applier.remove(cleanup.child_id) {
3200 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
3201 Err(err) => Err(err),
3202 }
3203}
3204
3205fn remove_child_and_cleanup_now(
3206 applier: &mut dyn Applier,
3207 parent_id: NodeId,
3208 child_id: NodeId,
3209) -> Result<(), NodeError> {
3210 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
3211 apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
3212 deferred_cleanup.flush(applier)
3213}
3214
3215fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
3216 let mut scratch = SmallVec::<[NodeId; 8]>::new();
3217 if let Ok(node) = applier.get_mut(parent_id) {
3218 node.collect_children_into(&mut scratch);
3219 }
3220 let mut current = ChildList::new();
3221 current.extend(scratch);
3222 current
3223}
3224
3225fn sync_children(
3226 applier: &mut dyn Applier,
3227 parent_id: NodeId,
3228 expected_children: &[NodeId],
3229 deferred_cleanup: &mut DeferredChildCleanupQueue,
3230) -> Result<(), NodeError> {
3231 let mut current = collect_current_children(applier, parent_id);
3232 let children_changed = current.as_slice() != expected_children;
3233
3234 if children_changed {
3235 if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
3236 sync_children_small(
3237 applier,
3238 parent_id,
3239 &mut current,
3240 expected_children,
3241 deferred_cleanup,
3242 )?;
3243 } else {
3244 let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
3245 target_positions.reserve(expected_children.len());
3246 for (index, &child) in expected_children.iter().enumerate() {
3247 target_positions.insert(child, index);
3248 }
3249
3250 for index in (0..current.len()).rev() {
3251 let child = current[index];
3252 if !target_positions.contains_key(&child) {
3253 current.remove(index);
3254 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3255 }
3256 }
3257
3258 let mut current_positions = build_child_positions(¤t);
3259 for (target_index, &child) in expected_children.iter().enumerate() {
3260 if let Some(current_index) = current_positions.get(&child).copied() {
3261 if current_index != target_index {
3262 let from_index = current_index;
3263 let to_index = move_child_in_diff_state(
3264 &mut current,
3265 &mut current_positions,
3266 from_index,
3267 target_index,
3268 );
3269 Command::MoveChild {
3270 parent_id,
3271 from_index,
3272 to_index,
3273 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3274 }
3275 .apply(applier)?;
3276 }
3277 } else {
3278 let insert_index = target_index.min(current.len());
3279 let appended_index = current.len();
3280 insert_child_into_diff_state(
3281 &mut current,
3282 &mut current_positions,
3283 insert_index,
3284 child,
3285 );
3286 Command::InsertChild {
3287 parent_id,
3288 child_id: child,
3289 appended_index,
3290 insert_index,
3291 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3292 }
3293 .apply(applier)?;
3294 }
3295 }
3296 }
3297 }
3298
3299 reconcile_children(applier, parent_id, expected_children, !children_changed)
3300}
3301
3302fn sync_children_small(
3303 applier: &mut dyn Applier,
3304 parent_id: NodeId,
3305 current: &mut ChildList,
3306 expected_children: &[NodeId],
3307 deferred_cleanup: &mut DeferredChildCleanupQueue,
3308) -> Result<(), NodeError> {
3309 for index in (0..current.len()).rev() {
3310 let child = current[index];
3311 if !expected_children.contains(&child) {
3312 current.remove(index);
3313 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3314 }
3315 }
3316
3317 for (target_index, &child) in expected_children.iter().enumerate() {
3318 if let Some(current_index) = current
3319 .iter()
3320 .position(|¤t_child| current_child == child)
3321 {
3322 if current_index != target_index {
3323 let child = current.remove(current_index);
3324 let to_index = target_index.min(current.len());
3325 current.insert(to_index, child);
3326 Command::MoveChild {
3327 parent_id,
3328 from_index: current_index,
3329 to_index,
3330 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3331 }
3332 .apply(applier)?;
3333 }
3334 } else {
3335 let insert_index = target_index.min(current.len());
3336 let appended_index = current.len();
3337 current.insert(insert_index, child);
3338 Command::InsertChild {
3339 parent_id,
3340 child_id: child,
3341 appended_index,
3342 insert_index,
3343 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3344 }
3345 .apply(applier)?;
3346 }
3347 }
3348
3349 Ok(())
3350}
3351
3352fn reconcile_children(
3357 applier: &mut dyn Applier,
3358 parent_id: NodeId,
3359 expected_children: &[NodeId],
3360 children_unchanged: bool,
3361) -> Result<(), NodeError> {
3362 let mut repaired = false;
3363 if children_unchanged {
3364 debug_assert!(
3365 expected_children.iter().all(|&child_id| applier
3366 .get_mut(child_id)
3367 .map_or(true, |node| node.parent().is_none_or(|p| p == parent_id))),
3368 "the children a parent holds must name no other parent"
3369 );
3370 } else {
3371 for &child_id in expected_children {
3372 let needs_attach = applier
3373 .get_mut(child_id)
3374 .is_ok_and(|node| node.parent() != Some(parent_id));
3375 if needs_attach {
3376 insert_child_with_reparenting(applier, parent_id, child_id);
3377 repaired = true;
3378 }
3379 }
3380 }
3381
3382 let is_dirty = children_unchanged
3383 && applier
3384 .get_mut(parent_id)
3385 .is_ok_and(|node| node.layout_dirty());
3386
3387 if repaired {
3388 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3389 } else if is_dirty {
3390 DirtyBubble::LAYOUT.apply_to_ancestors(applier, parent_id);
3391 }
3392
3393 Ok(())
3394}
3395
3396#[derive(Default)]
3397pub struct MemoryApplier {
3398 nodes: Vec<Option<Box<dyn Node>>>,
3399 physical_stable_ids: Vec<u32>,
3400 physical_warm_recycled_origins: Vec<bool>,
3401 stable_index: stable_index::StableIndex,
3402 free_ids: BinaryHeap<Reverse<usize>>,
3403 high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
3404 high_id_warm_recycled_origins: HashMap<NodeId, bool>,
3405 high_id_generations: HashMap<NodeId, u32>,
3406 next_stable_id: NodeId,
3407 layout_runtime: Option<RuntimeHandle>,
3408 slots: SlotTable,
3409 recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3410 returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3411 cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3412 recycled_node_limits: HashMap<TypeId, usize>,
3413 warm_recycled_node_targets: HashMap<TypeId, usize>,
3414 fresh_recyclable_creations: HashMap<TypeId, usize>,
3415 recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
3416 structural_change_parents: Vec<NodeId>,
3417 virtual_node_ids: HashSet<NodeId>,
3418 bubble_passes: bubble_passes::BubblePasses,
3419}
3420
3421#[derive(Default)]
3426pub struct SceneNodeAttachmentScratch {
3427 attached: NodeMarks,
3428 path: Vec<NodeId>,
3429}
3430
3431const ATTACHED: u8 = 1;
3432const DETACHED: u8 = 2;
3433
3434struct RemovalFrame {
3435 node_id: NodeId,
3436 children: SmallVec<[NodeId; 8]>,
3437 next_child: usize,
3438}
3439
3440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3441pub struct MemoryApplierDebugStats {
3442 pub next_stable_id: NodeId,
3443 pub nodes_len: usize,
3444 pub nodes_cap: usize,
3445 pub physical_stable_ids_len: usize,
3446 pub physical_stable_ids_cap: usize,
3447 pub stable_to_physical_len: usize,
3448 pub stable_to_physical_cap: usize,
3449 pub stable_generations_len: usize,
3450 pub stable_generations_cap: usize,
3451 pub free_ids_len: usize,
3452 pub free_ids_cap: usize,
3453 pub high_id_nodes_len: usize,
3454 pub high_id_nodes_cap: usize,
3455 pub high_id_generations_len: usize,
3456 pub high_id_generations_cap: usize,
3457 pub recycled_type_count: usize,
3458 pub recycled_type_cap: usize,
3459 pub recycled_node_count: usize,
3460 pub recycled_node_capacity: usize,
3461 pub warm_recycled_node_id_count: usize,
3462 pub warm_recycled_node_id_capacity: usize,
3463}
3464
3465impl MemoryApplier {
3466 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
3467 const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
3468 const INVALID_STABLE_ID: u32 = u32::MAX;
3469 const INITIAL_DENSE_NODE_CAP: usize = 32;
3470 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
3471 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
3472
3473 fn pack_stable_id(stable_id: NodeId) -> u32 {
3474 u32::try_from(stable_id).expect("stable id overflow")
3475 }
3476
3477 fn unpack_stable_id(stable_id: u32) -> NodeId {
3478 stable_id as NodeId
3479 }
3480
3481 fn next_dense_node_target_len(old_len: usize) -> usize {
3482 if old_len < Self::INITIAL_DENSE_NODE_CAP {
3483 return Self::INITIAL_DENSE_NODE_CAP;
3484 }
3485 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
3486 return old_len.saturating_mul(2);
3487 }
3488
3489 let incremental_growth =
3490 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
3491 old_len.saturating_add(incremental_growth)
3492 }
3493
3494 fn ensure_dense_node_storage_capacity(&mut self) {
3495 let len = self
3496 .nodes
3497 .len()
3498 .max(self.physical_stable_ids.len())
3499 .max(self.physical_warm_recycled_origins.len());
3500 if len < self.nodes.capacity()
3501 && len < self.physical_stable_ids.capacity()
3502 && len < self.physical_warm_recycled_origins.capacity()
3503 {
3504 return;
3505 }
3506
3507 let target = Self::next_dense_node_target_len(len);
3508 if self.nodes.capacity() < target {
3509 self.nodes
3510 .reserve_exact(target.saturating_sub(self.nodes.len()));
3511 }
3512 if self.physical_stable_ids.capacity() < target {
3513 self.physical_stable_ids
3514 .reserve_exact(target.saturating_sub(self.physical_stable_ids.len()));
3515 }
3516 if self.physical_warm_recycled_origins.capacity() < target {
3517 self.physical_warm_recycled_origins
3518 .reserve_exact(target.saturating_sub(self.physical_warm_recycled_origins.len()));
3519 }
3520 }
3521
3522 pub fn new() -> Self {
3523 Self {
3524 nodes: Vec::new(),
3525 physical_stable_ids: Vec::new(),
3526 physical_warm_recycled_origins: Vec::new(),
3527 stable_index: stable_index::StableIndex::default(),
3528 free_ids: BinaryHeap::new(),
3529 high_id_nodes: HashMap::default(),
3530 high_id_warm_recycled_origins: HashMap::default(),
3531 high_id_generations: HashMap::default(),
3532 next_stable_id: 0,
3533 layout_runtime: None,
3534 slots: SlotTable::default(),
3535 recycled_nodes: HashMap::default(),
3536 returning_recycled_nodes: HashMap::default(),
3537 cold_recycled_nodes: HashMap::default(),
3538 recycled_node_limits: HashMap::default(),
3539 warm_recycled_node_targets: HashMap::default(),
3540 fresh_recyclable_creations: HashMap::default(),
3541 recycled_node_prototypes: HashMap::default(),
3542 structural_change_parents: Vec::new(),
3543 virtual_node_ids: HashSet::default(),
3544 bubble_passes: bubble_passes::BubblePasses::default(),
3545 }
3546 }
3547
3548 pub fn slots(&mut self) -> &mut SlotTable {
3549 &mut self.slots
3550 }
3551
3552 pub fn scene_node_attached_to(&mut self, node_id: NodeId, root: NodeId) -> Option<NodeId> {
3565 let resolved = self.first_non_virtual_ancestor(node_id)?;
3566 self.is_attached_to(resolved, root).then_some(resolved)
3567 }
3568
3569 pub fn scene_nodes_attached_to(
3573 &mut self,
3574 nodes: impl IntoIterator<Item = NodeId>,
3575 root: NodeId,
3576 ) -> Vec<Option<NodeId>> {
3577 let mut result = Vec::new();
3578 self.scene_nodes_attached_to_into(
3579 nodes,
3580 root,
3581 &mut result,
3582 &mut SceneNodeAttachmentScratch::default(),
3583 );
3584 result
3585 }
3586
3587 pub fn scene_nodes_attached_to_into(
3605 &mut self,
3606 nodes: impl IntoIterator<Item = NodeId>,
3607 root: NodeId,
3608 output: &mut Vec<Option<NodeId>>,
3609 scratch: &mut SceneNodeAttachmentScratch,
3610 ) {
3611 let nodes = nodes.into_iter();
3612 output.clear();
3613 let (lower_bound, upper_bound) = nodes.size_hint();
3614 if lower_bound == 0 && upper_bound == Some(0) {
3615 scratch.path.clear();
3616 return;
3617 }
3618 scratch.attached.reset(self);
3619 scratch.attached.set(self, root, ATTACHED);
3620 output.reserve(lower_bound);
3621 scratch.path.clear();
3622 for node_id in nodes {
3623 let Some(resolved) = self.first_non_virtual_ancestor(node_id) else {
3624 output.push(None);
3625 continue;
3626 };
3627 let mut current = resolved;
3628 scratch.path.clear();
3629 let answer = loop {
3630 match scratch.attached.get(self, current) {
3631 ATTACHED => break true,
3632 DETACHED => break false,
3633 _ => {}
3634 }
3635 scratch.path.push(current);
3636 match self.get_mut(current).ok().and_then(|node| node.parent()) {
3637 Some(parent) if scratch.path.len() < 100_000 => current = parent,
3638 _ => break false,
3639 }
3640 };
3641 let mark = if answer { ATTACHED } else { DETACHED };
3642 for visited in scratch.path.drain(..) {
3643 scratch.attached.set(self, visited, mark);
3644 }
3645 output.push(answer.then_some(resolved));
3646 }
3647 }
3648
3649 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
3650 let mut candidates = Vec::new();
3651 self.take_structural_change_parents_into(&mut candidates);
3652 let mut seen = HashSet::<NodeId>::default();
3653 candidates.retain_mut(|parent_id| {
3654 let Some(resolved) = self.first_non_virtual_ancestor(*parent_id) else {
3655 return false;
3656 };
3657 *parent_id = resolved;
3658 self.is_attached_to(resolved, root) && seen.insert(resolved)
3659 });
3660 candidates
3661 }
3662
3663 pub fn take_structural_change_parents_into(&mut self, output: &mut Vec<NodeId>) {
3676 output.clear();
3677 std::mem::swap(&mut self.structural_change_parents, output);
3678 }
3679
3680 fn first_non_virtual_ancestor(&mut self, node_id: NodeId) -> Option<NodeId> {
3681 if self.virtual_node_ids.is_empty() {
3682 return Some(node_id);
3683 }
3684 let mut current = node_id;
3685 for _ in 0..100_000 {
3686 if !self.virtual_node_ids.contains(¤t) {
3687 return Some(current);
3688 }
3689 match self.get_mut(current) {
3690 Ok(node) => current = node.parent()?,
3691 Err(_) => return None,
3692 }
3693 }
3694 None
3695 }
3696
3697 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
3698 let mut current = node_id;
3699 for _ in 0..100_000 {
3700 if current == root {
3701 return true;
3702 }
3703 match self.get_mut(current) {
3704 Ok(node) => match node.parent() {
3705 Some(parent) => current = parent,
3706 None => return false,
3707 },
3708 Err(_) => return false,
3709 }
3710 }
3711 false
3712 }
3713
3714 pub fn with_node<N: Node + 'static, R>(
3715 &mut self,
3716 id: NodeId,
3717 f: impl FnOnce(&mut N) -> R,
3718 ) -> Result<R, NodeError> {
3719 let typed = self
3720 .get_mut(id)?
3721 .as_any_mut()
3722 .downcast_mut::<N>()
3723 .ok_or_else(|| NodeError::TypeMismatch {
3724 id,
3725 expected: std::any::type_name::<N>(),
3726 })?;
3727 Ok(f(typed))
3728 }
3729
3730 pub fn len(&self) -> usize {
3731 self.nodes.iter().filter(|n| n.is_some()).count()
3732 }
3733
3734 pub fn capacity(&self) -> usize {
3735 self.nodes.len()
3736 }
3737
3738 pub fn tombstone_count(&self) -> usize {
3739 self.nodes.iter().filter(|n| n.is_none()).count()
3740 }
3741
3742 pub fn freelist_len(&self) -> usize {
3743 self.free_ids.len()
3744 }
3745
3746 pub fn debug_recycled_node_count(&self) -> usize {
3747 self.total_recycled_node_count()
3748 }
3749
3750 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
3751 let key = TypeId::of::<N>();
3752 self.recycled_nodes.get(&key).map_or(0, Vec::len)
3753 + self.returning_recycled_nodes.get(&key).map_or(0, Vec::len)
3754 + self.cold_recycled_nodes.get(&key).map_or(0, Vec::len)
3755 }
3756
3757 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
3758 let mut recycled_keys: HashSet<TypeId> = HashSet::default();
3759 recycled_keys.extend(self.recycled_nodes.keys().copied());
3760 recycled_keys.extend(self.returning_recycled_nodes.keys().copied());
3761 recycled_keys.extend(self.cold_recycled_nodes.keys().copied());
3762
3763 MemoryApplierDebugStats {
3764 next_stable_id: self.next_stable_id,
3765 nodes_len: self.len(),
3766 nodes_cap: self.nodes.len(),
3767 physical_stable_ids_len: self.physical_stable_ids.len(),
3768 physical_stable_ids_cap: self.physical_stable_ids.capacity(),
3769 stable_to_physical_len: self.stable_index.live(),
3770 stable_to_physical_cap: self.stable_index.capacity(),
3771 stable_generations_len: self.stable_index.occupied(),
3772 stable_generations_cap: self.stable_index.capacity(),
3773 free_ids_len: self.free_ids.len(),
3774 free_ids_cap: self.free_ids.capacity(),
3775 high_id_nodes_len: self.high_id_nodes.len(),
3776 high_id_nodes_cap: self.high_id_nodes.capacity(),
3777 high_id_generations_len: self.high_id_generations.len(),
3778 high_id_generations_cap: self.high_id_generations.capacity(),
3779 recycled_type_count: recycled_keys.len(),
3780 recycled_type_cap: self.recycled_nodes.capacity()
3781 + self.returning_recycled_nodes.capacity()
3782 + self.cold_recycled_nodes.capacity(),
3783 recycled_node_count: self.total_recycled_node_count(),
3784 recycled_node_capacity: self.total_recycled_node_capacity(),
3785 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
3786 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
3787 }
3788 }
3789
3790 pub fn is_empty(&self) -> bool {
3791 self.len() == 0
3792 }
3793
3794 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut dyn Node)) {
3796 for node in self.nodes.iter_mut().flatten() {
3797 visit(node.as_mut());
3798 }
3799 for node in self.high_id_nodes.values_mut() {
3800 visit(node.as_mut());
3801 }
3802 }
3803
3804 pub fn debug_live_node_heap_bytes(&self) -> usize {
3805 let dense_nodes = self
3806 .nodes
3807 .iter()
3808 .flatten()
3809 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3810 .sum::<usize>();
3811 let high_id_nodes = self
3812 .high_id_nodes
3813 .values()
3814 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3815 .sum::<usize>();
3816 dense_nodes + high_id_nodes
3817 }
3818
3819 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
3820 let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
3821 pools
3822 .values()
3823 .flat_map(|nodes| nodes.iter())
3824 .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
3825 .sum::<usize>()
3826 };
3827
3828 pool_bytes(&self.recycled_nodes)
3829 + pool_bytes(&self.returning_recycled_nodes)
3830 + pool_bytes(&self.cold_recycled_nodes)
3831 }
3832
3833 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
3834 self.layout_runtime = Some(handle);
3835 }
3836
3837 pub fn clear_runtime_handle(&mut self) {
3838 self.layout_runtime = None;
3839 }
3840
3841 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
3842 self.layout_runtime.clone()
3843 }
3844
3845 fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3846 pools.values().map(Vec::len).sum()
3847 }
3848
3849 fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3850 pools.values().map(Vec::capacity).sum()
3851 }
3852
3853 fn total_recycled_node_count(&self) -> usize {
3854 Self::pool_node_count(&self.recycled_nodes)
3855 + Self::pool_node_count(&self.returning_recycled_nodes)
3856 + Self::pool_node_count(&self.cold_recycled_nodes)
3857 }
3858
3859 fn total_recycled_node_capacity(&self) -> usize {
3860 Self::pool_node_capacity(&self.recycled_nodes)
3861 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3862 + Self::pool_node_capacity(&self.cold_recycled_nodes)
3863 }
3864
3865 fn total_warm_recycled_node_id_count(&self) -> usize {
3866 self.live_warm_recycled_origin_count()
3867 + Self::pool_node_count(&self.recycled_nodes)
3868 + Self::pool_node_count(&self.returning_recycled_nodes)
3869 }
3870
3871 fn total_warm_recycled_node_id_capacity(&self) -> usize {
3872 self.live_warm_recycled_origin_capacity()
3873 + Self::pool_node_capacity(&self.recycled_nodes)
3874 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3875 }
3876
3877 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
3878 if let Some(limit) = recycle_pool_limit {
3879 self.recycled_node_limits.insert(key, limit);
3880 } else {
3881 self.recycled_node_limits.remove(&key);
3882 }
3883 }
3884
3885 fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
3886 self.recycled_node_limits.get(&key).copied()
3887 }
3888
3889 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
3890 self.recycled_nodes.get(&key).map_or(0, Vec::len)
3891 }
3892
3893 fn warm_recycled_node_target(&self, key: TypeId) -> usize {
3894 self.warm_recycled_node_targets
3895 .get(&key)
3896 .copied()
3897 .unwrap_or(0)
3898 }
3899
3900 fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
3901 let Some(limit) = self.recycle_pool_limit_for(key) else {
3902 return usize::MAX;
3903 };
3904 if limit <= 8 { limit } else { limit / 4 }
3905 }
3906
3907 fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
3908 let target_limit = self.warm_recycled_node_target_limit(key);
3909 let existing = self.warm_recycled_node_target(key).min(target_limit);
3910 if observed_demand == 0 {
3911 return existing;
3912 }
3913
3914 let target = match self.recycle_pool_limit_for(key) {
3915 Some(limit) if limit > 8 => target_limit,
3916 Some(_) => observed_demand.min(target_limit),
3917 None => observed_demand,
3918 };
3919 self.warm_recycled_node_targets.insert(key, target);
3920 target
3921 }
3922
3923 fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
3924 if self.recycled_node_prototypes.contains_key(&key) {
3925 return;
3926 }
3927 if let Some(prototype) = shell.rehouse_for_recycle() {
3928 self.recycled_node_prototypes.insert(key, prototype);
3929 }
3930 }
3931
3932 fn live_warm_recycled_origin_count(&self) -> usize {
3933 self.physical_warm_recycled_origins
3934 .iter()
3935 .zip(self.nodes.iter())
3936 .filter(|(warm_origin, node)| **warm_origin && node.is_some())
3937 .count()
3938 + self
3939 .high_id_warm_recycled_origins
3940 .values()
3941 .filter(|warm_origin| **warm_origin)
3942 .count()
3943 }
3944
3945 fn live_warm_recycled_origin_capacity(&self) -> usize {
3946 self.physical_warm_recycled_origins.capacity()
3947 + self.high_id_warm_recycled_origins.capacity()
3948 }
3949
3950 fn push_recycled_node(
3951 &mut self,
3952 key: TypeId,
3953 recycle_pool_limit: Option<usize>,
3954 recycled: RecycledNode,
3955 ) {
3956 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3957 self.remember_recycled_node_prototype(key, recycled.node.as_ref());
3958
3959 let warm_origin = recycled.warm_origin();
3960 let pool = if warm_origin {
3961 self.returning_recycled_nodes.entry(key).or_default()
3962 } else {
3963 self.cold_recycled_nodes.entry(key).or_default()
3964 };
3965 pool.push(recycled);
3966 if let Some(limit) = recycle_pool_limit
3967 && pool.len() > limit
3968 {
3969 let excess = pool.len() - limit;
3970 let dropped: Vec<_> = pool.drain(0..excess).collect();
3971 drop(dropped);
3972 }
3973 }
3974
3975 fn push_warm_recycled_node(
3976 &mut self,
3977 key: TypeId,
3978 recycle_pool_limit: Option<usize>,
3979 mut recycled: RecycledNode,
3980 ) {
3981 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3982
3983 recycled.set_warm_origin(true);
3984 let mut dropped = Vec::new();
3985 let mut remove_pool_entry = false;
3986 {
3987 let pool = self.recycled_nodes.entry(key).or_default();
3988 pool.push(recycled);
3989 if let Some(limit) = recycle_pool_limit
3990 && pool.len() > limit
3991 {
3992 let excess = pool.len() - limit;
3993 dropped = pool.drain(0..excess).collect();
3994 remove_pool_entry = pool.is_empty();
3995 }
3996 }
3997 if remove_pool_entry {
3998 self.recycled_nodes.remove(&key);
3999 }
4000 drop(dropped);
4001 }
4002
4003 fn seed_recycled_node_shell_impl(
4004 &mut self,
4005 key: TypeId,
4006 recycle_pool_limit: Option<usize>,
4007 shell: Box<dyn Node>,
4008 ) {
4009 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
4010 if self.warm_recycled_pool_len(key) >= limit {
4011 return;
4012 }
4013
4014 self.remember_recycled_node_prototype(key, shell.as_ref());
4015 let stable_id = self.next_stable_id;
4016 self.next_stable_id = self.next_stable_id.saturating_add(1);
4017 self.push_warm_recycled_node(
4018 key,
4019 recycle_pool_limit,
4020 RecycledNode::from_shell(stable_id, shell, true),
4021 );
4022 }
4023
4024 fn take_recycled_node_from_pool(
4025 pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
4026 key: TypeId,
4027 ) -> Option<RecycledNode> {
4028 let pool = pools.get_mut(&key)?;
4029 let node = pool.pop();
4030 if pool.is_empty() {
4031 pools.remove(&key);
4032 }
4033 node
4034 }
4035
4036 fn compact_idle_warm_pool(&mut self, key: TypeId) {
4037 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
4038 return;
4039 };
4040 if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
4041 return;
4042 }
4043
4044 let retained = pool.len();
4045 let mut compacted = Vec::with_capacity(retained);
4046 compacted.append(pool);
4047 let remove_pool_entry = compacted.is_empty();
4048 *pool = compacted;
4049 let _ = pool;
4050
4051 if remove_pool_entry {
4052 self.recycled_nodes.remove(&key);
4053 }
4054 }
4055
4056 fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4057 let pool_len = self.warm_recycled_pool_len(key);
4058 if pool_len <= target {
4059 return;
4060 }
4061
4062 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
4063 return;
4064 };
4065 let removable = (pool_len - target).min(pool.len());
4066 let dropped: Vec<_> = pool.drain(0..removable).collect();
4067 let remove_pool_entry = pool.is_empty();
4068 let _ = pool;
4069
4070 if remove_pool_entry {
4071 self.recycled_nodes.remove(&key);
4072 }
4073 drop(dropped);
4074 }
4075
4076 fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4077 let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
4078 if missing == 0 {
4079 return;
4080 }
4081
4082 let recycle_pool_limit = self.recycle_pool_limit_for(key);
4083 let mut shells = Vec::with_capacity(missing);
4084 if let Some(prototype) = self.recycled_node_prototypes.get(&key) {
4085 for _ in 0..missing {
4086 let Some(shell) = prototype.rehouse_for_recycle() else {
4087 break;
4088 };
4089 shells.push(shell);
4090 }
4091 }
4092
4093 for shell in shells {
4094 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4095 }
4096 }
4097
4098 fn prune_stable_generations(&mut self) {
4099 let retained_len = self.stable_index.live() + self.total_recycled_node_count();
4100 if retained_len == self.stable_index.occupied() {
4101 return;
4102 }
4103
4104 let recycled: HashSet<NodeId> = [
4105 &self.recycled_nodes,
4106 &self.returning_recycled_nodes,
4107 &self.cold_recycled_nodes,
4108 ]
4109 .into_iter()
4110 .flat_map(|pools| pools.values().flatten().map(RecycledNode::stable_id))
4111 .collect();
4112 self.stable_index
4113 .retain_retired(|stable_id| recycled.contains(&stable_id));
4114 }
4115
4116 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
4117 let mut output = String::new();
4118 if let Some(root_id) = root {
4119 self.dump_node(&mut output, root_id, 0);
4120 } else {
4121 output.push_str("(no root)\n");
4122 }
4123 output
4124 }
4125
4126 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
4127 let indent = " ".repeat(depth);
4128 let Ok(node) = self.get_ref(id) else {
4129 if let Some(physical_id) = self.resolve_node_index(id) {
4130 output.push_str(&format!(
4131 "{indent}[{id}] (missing physical node {physical_id})\n"
4132 ));
4133 } else {
4134 output.push_str(&format!("{indent}[{id}] (missing)\n"));
4135 }
4136 return;
4137 };
4138 let type_name = std::any::type_name_of_val(node);
4139 output.push_str(&format!("{indent}[{id}] {type_name}\n"));
4140
4141 let mut children = SmallVec::<[NodeId; 8]>::new();
4142 node.collect_children_into(&mut children);
4143 for child_id in children {
4144 self.dump_node(output, child_id, depth + 1);
4145 }
4146 }
4147
4148 fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
4149 self.stable_index.slot(id)
4150 }
4151
4152 fn contains_node_id(&self, id: NodeId) -> bool {
4153 self.resolve_node_index(id).is_some() || self.high_id_nodes.contains_key(&id)
4154 }
4155
4156 fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
4157 self.high_id_nodes.insert(stable_id, node);
4158 self.high_id_warm_recycled_origins
4159 .insert(stable_id, warm_origin);
4160 self.high_id_generations.entry(stable_id).or_insert(0);
4161 }
4162
4163 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
4164 if stable_id >= Self::HIGH_ID_THRESHOLD {
4165 self.insert_high_id_node(stable_id, node, false);
4166 return;
4167 }
4168
4169 let physical_id = if let Some(Reverse(free_physical_id)) = self.free_ids.pop() {
4170 self.nodes[free_physical_id] = Some(node);
4171 self.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
4172 self.physical_warm_recycled_origins[free_physical_id] = false;
4173 free_physical_id
4174 } else {
4175 self.ensure_dense_node_storage_capacity();
4176 let physical_id = self.nodes.len();
4177 self.nodes.push(Some(node));
4178 self.physical_stable_ids
4179 .push(Self::pack_stable_id(stable_id));
4180 self.physical_warm_recycled_origins.push(false);
4181 physical_id
4182 };
4183
4184 self.next_stable_id = self.next_stable_id.max(stable_id.saturating_add(1));
4185 self.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
4186 self.stable_index.set_slot(stable_id, physical_id);
4187 }
4188
4189 pub fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4192 if let Some(physical_id) = self.resolve_node_index(id) {
4193 let slot = self
4194 .nodes
4195 .get(physical_id)
4196 .ok_or(NodeError::Missing { id })?
4197 .as_deref()
4198 .ok_or(NodeError::Missing { id })?;
4199 return Ok(slot);
4200 }
4201
4202 self.high_id_nodes
4203 .get(&id)
4204 .map(AsRef::as_ref)
4205 .ok_or(NodeError::Missing { id })
4206 }
4207
4208 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
4209 Ok(self.get_ref(id)?.parent())
4210 }
4211
4212 fn collect_owned_children(
4213 &self,
4214 node_id: NodeId,
4215 out: &mut SmallVec<[NodeId; 8]>,
4216 ) -> Result<(), NodeError> {
4217 self.get_ref(node_id)?.collect_owned_children_into(out);
4218 out.retain(|child_id| {
4219 self.node_parent(*child_id)
4220 .is_ok_and(|parent| parent == Some(node_id))
4221 });
4222 Ok(())
4223 }
4224
4225 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
4226 self.virtual_node_ids.remove(&node_id);
4227 if self.high_id_nodes.contains_key(&node_id) {
4228 if let Some(node) = self.high_id_nodes.remove(&node_id)
4229 && let Some(key) = node.recycle_key()
4230 {
4231 let recycle_pool_limit = node.recycle_pool_limit();
4232 let warm_origin = self
4233 .high_id_warm_recycled_origins
4234 .remove(&node_id)
4235 .unwrap_or(false);
4236 self.push_recycled_node(
4237 key,
4238 recycle_pool_limit,
4239 RecycledNode::new(node_id, node, warm_origin),
4240 );
4241 }
4242 let generation = self.high_id_generations.entry(node_id).or_insert(0);
4243 *generation = generation.wrapping_add(1);
4244 return Ok(());
4245 }
4246
4247 let physical_id = self
4248 .resolve_node_index(node_id)
4249 .ok_or(NodeError::Missing { id: node_id })?;
4250 if let Some(node) = self.nodes[physical_id].take()
4251 && let Some(key) = node.recycle_key()
4252 {
4253 let recycle_pool_limit = node.recycle_pool_limit();
4254 let warm_origin = self
4255 .physical_warm_recycled_origins
4256 .get_mut(physical_id)
4257 .is_some_and(std::mem::take);
4258 self.push_recycled_node(
4259 key,
4260 recycle_pool_limit,
4261 RecycledNode::new(node_id, node, warm_origin),
4262 );
4263 }
4264 self.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
4265 self.stable_index.release(node_id);
4266 self.free_ids.push(Reverse(physical_id));
4267 Ok(())
4268 }
4269
4270 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
4271 self.get_ref(id)?;
4272
4273 let mut root_children = SmallVec::<[NodeId; 8]>::new();
4274 self.collect_owned_children(id, &mut root_children)?;
4275
4276 let mut stack = Vec::new();
4277 stack.push(RemovalFrame {
4278 node_id: id,
4279 children: root_children,
4280 next_child: 0,
4281 });
4282 let mut max_depth = stack.len();
4283
4284 while let Some(frame) = stack.last_mut() {
4285 if frame.next_child < frame.children.len() {
4286 let child_id = frame.children[frame.next_child];
4287 frame.next_child += 1;
4288
4289 if let Ok(child) = self.get_mut(child_id) {
4290 child.on_removed_from_parent();
4291 child.unmount();
4292 }
4293
4294 let mut child_children = SmallVec::<[NodeId; 8]>::new();
4295 self.collect_owned_children(child_id, &mut child_children)?;
4296 stack.push(RemovalFrame {
4297 node_id: child_id,
4298 children: child_children,
4299 next_child: 0,
4300 });
4301 max_depth = max_depth.max(stack.len());
4302 continue;
4303 }
4304
4305 let node_id = frame.node_id;
4306 stack.pop();
4307 self.remove_node_storage(node_id)?;
4308 }
4309
4310 Ok(max_depth)
4311 }
4312
4313 #[cfg(test)]
4314 fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
4315 self.remove_subtree_postorder(id)
4316 }
4317}
4318
4319impl Applier for MemoryApplier {
4320 fn record_structural_change(&mut self, parent_id: NodeId) {
4321 if self.structural_change_parents.last() != Some(&parent_id) {
4322 self.structural_change_parents.push(parent_id);
4323 }
4324 }
4325
4326 fn create(&mut self, node: Box<dyn Node>) -> NodeId {
4327 let stable_id = self.next_stable_id;
4328 self.next_stable_id = self.next_stable_id.saturating_add(1);
4329 if stable_id >= Self::HIGH_ID_THRESHOLD {
4330 self.insert_high_id_node(stable_id, node, false);
4331 return stable_id;
4332 }
4333
4334 let physical_id = if let Some(Reverse(id)) = self.free_ids.pop() {
4335 debug_assert!(self.nodes[id].is_none(), "freelist entry {id} is not None");
4336 self.nodes[id] = Some(node);
4337 self.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
4338 self.physical_warm_recycled_origins[id] = false;
4339 id
4340 } else {
4341 self.ensure_dense_node_storage_capacity();
4342 let id = self.nodes.len();
4343 self.nodes.push(Some(node));
4344 self.physical_stable_ids
4345 .push(Self::pack_stable_id(stable_id));
4346 self.physical_warm_recycled_origins.push(false);
4347 id
4348 };
4349 self.stable_index.insert_fresh(stable_id, physical_id);
4350 stable_id
4351 }
4352
4353 fn node_generation(&self, id: NodeId) -> u32 {
4354 if id >= Self::HIGH_ID_THRESHOLD {
4355 return self.high_id_generations.get(&id).copied().unwrap_or(0);
4356 }
4357 self.stable_index.generation(id)
4358 }
4359
4360 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
4361 if let Some(physical_id) = self.resolve_node_index(id) {
4362 let slot = self
4363 .nodes
4364 .get_mut(physical_id)
4365 .ok_or(NodeError::Missing { id })?
4366 .as_deref_mut()
4367 .ok_or(NodeError::Missing { id })?;
4368 return Ok(slot);
4369 }
4370 self.high_id_nodes
4371 .get_mut(&id)
4372 .map(std::convert::AsMut::as_mut)
4373 .ok_or(NodeError::Missing { id })
4374 }
4375
4376 fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
4377 self.bubble_passes.forget();
4378 self.remove_subtree_postorder(id).map(|_| ())
4379 }
4380
4381 fn begin_bubble_batch(&mut self) {
4382 self.bubble_passes.begin_batch();
4383 }
4384
4385 fn end_bubble_batch(&mut self) {
4386 self.bubble_passes.end_batch();
4387 }
4388
4389 fn forget_bubble_passes(&mut self) {
4390 self.bubble_passes.forget();
4391 }
4392
4393 fn note_bubble_pass(&mut self, id: NodeId, flags: u8) -> bool {
4394 self.resolve_node_index(id)
4395 .is_some_and(|index| self.bubble_passes.note(index, flags))
4396 }
4397
4398 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
4399 if self.contains_node_id(id) {
4400 return Err(NodeError::AlreadyExists { id });
4401 }
4402 self.insert_available_with_id(id, node);
4403 self.virtual_node_ids.insert(id);
4404 Ok(())
4405 }
4406
4407 fn insert_recycled_node_or_create(
4408 &mut self,
4409 stable_id: NodeId,
4410 node: Box<dyn Node>,
4411 ) -> RecycledNodeInsertion {
4412 if self.contains_node_id(stable_id) {
4413 let id = self.create(node);
4414 return RecycledNodeInsertion::fresh(
4415 id,
4416 Some(NodeError::AlreadyExists { id: stable_id }),
4417 );
4418 }
4419
4420 self.insert_available_with_id(stable_id, node);
4421 RecycledNodeInsertion::reused(stable_id)
4422 }
4423
4424 fn compact(&mut self) {
4425 let live_count = self.nodes.iter().filter(|slot| slot.is_some()).count();
4426 let tombstone_count = self.nodes.len().saturating_sub(live_count);
4427 if tombstone_count == 0 {
4428 return;
4429 }
4430 if self.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
4431 return;
4432 }
4433 let rehouse_live_nodes = tombstone_count >= live_count;
4434 let mut packed_nodes = Vec::with_capacity(live_count);
4435 let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
4436 let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
4437
4438 for physical_id in 0..self.nodes.len() {
4439 let Some(mut node) = self.nodes[physical_id].take() else {
4440 continue;
4441 };
4442 if rehouse_live_nodes && let Some(rehoused) = node.rehouse_for_live_compaction() {
4443 node = rehoused;
4444 }
4445 let stable_id = std::mem::replace(
4446 &mut self.physical_stable_ids[physical_id],
4447 Self::INVALID_STABLE_ID,
4448 );
4449 debug_assert_ne!(
4450 stable_id,
4451 Self::INVALID_STABLE_ID,
4452 "live physical slot must have a stable id",
4453 );
4454 let stable_id = Self::unpack_stable_id(stable_id);
4455 packed_nodes.push(Some(node));
4456 packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
4457 packed_warm_recycled_origins.push(self.physical_warm_recycled_origins[physical_id]);
4458 self.stable_index
4459 .set_slot(stable_id, packed_nodes.len() - 1);
4460 }
4461
4462 self.nodes = packed_nodes;
4463 self.physical_stable_ids = packed_physical_stable_ids;
4464 self.physical_warm_recycled_origins = packed_warm_recycled_origins;
4465 self.free_ids = BinaryHeap::new();
4466 debug_assert_eq!(
4467 self.stable_index.live(),
4468 live_count,
4469 "every live node keeps one indexed slot through compaction",
4470 );
4471 self.prune_stable_generations();
4472 }
4473
4474 fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
4475 Self::take_recycled_node_from_pool(&mut self.returning_recycled_nodes, key)
4476 .or_else(|| Self::take_recycled_node_from_pool(&mut self.recycled_nodes, key))
4477 }
4478
4479 fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
4480 if let Some(physical_id) = self.resolve_node_index(id) {
4481 self.physical_warm_recycled_origins[physical_id] = warm_origin;
4482 } else if self.high_id_nodes.contains_key(&id) {
4483 self.high_id_warm_recycled_origins.insert(id, warm_origin);
4484 }
4485 }
4486
4487 fn seed_recycled_node_shell(
4488 &mut self,
4489 key: TypeId,
4490 recycle_pool_limit: Option<usize>,
4491 shell: Box<dyn Node>,
4492 ) {
4493 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4494 }
4495
4496 fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
4497 *self.fresh_recyclable_creations.entry(key).or_insert(0) += 1;
4498 }
4499
4500 fn clear_recycled_nodes(&mut self) {
4501 let returning = std::mem::take(&mut self.returning_recycled_nodes);
4502 for (key, mut nodes) in returning {
4503 let pool = self.recycled_nodes.entry(key).or_default();
4504 pool.append(&mut nodes);
4505 }
4506
4507 let fresh_recyclable_creations = std::mem::take(&mut self.fresh_recyclable_creations);
4508 let cold = std::mem::take(&mut self.cold_recycled_nodes);
4509 for (key, mut nodes) in cold {
4510 let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4511 if needed > 0 {
4512 let remaining_limit = self
4513 .recycle_pool_limit_for(key)
4514 .unwrap_or(usize::MAX)
4515 .saturating_sub(self.warm_recycled_pool_len(key));
4516 let promote = nodes.len().min(needed).min(remaining_limit);
4517 let split_at = nodes.len().saturating_sub(promote);
4518 let promoted = nodes.split_off(split_at);
4519 for mut recycled in promoted {
4520 recycled.set_warm_origin(true);
4521 self.recycled_nodes.entry(key).or_default().push(recycled);
4522 }
4523 }
4524 }
4525
4526 let mut keys: HashSet<TypeId> = HashSet::default();
4527 keys.extend(self.recycled_nodes.keys().copied());
4528 keys.extend(self.recycled_node_limits.keys().copied());
4529 keys.extend(self.warm_recycled_node_targets.keys().copied());
4530 keys.extend(self.recycled_node_prototypes.keys().copied());
4531 for key in keys {
4532 let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4533 let target = self.update_warm_recycled_node_target(key, observed_demand);
4534 self.replenish_warm_pool_to_target(key, target);
4535 self.trim_idle_warm_pool_to_target(key, target);
4536 self.compact_idle_warm_pool(key);
4537 }
4538 self.prune_stable_generations();
4539 self.compact();
4540 }
4541}
4542
4543pub struct SlotsHost {
4544 storage_key: Cell<usize>,
4545 inner: RefCell<SlotsHostInner>,
4546}
4547
4548#[derive(Debug, Default)]
4549pub(crate) struct SlotPassOutcome {
4550 pub(crate) compacted: bool,
4551 pub(crate) compact_anchor_registry_storage: bool,
4552 pub(crate) compact_payload_storage: bool,
4553 pub(crate) trim_growth_slack: bool,
4554}
4555
4556#[derive(Default)]
4557pub(crate) struct FinishedSlotPass {
4558 pub(crate) outcome: SlotPassOutcome,
4559 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
4560}
4561
4562struct ActivePassState {
4563 state: slot::SlotWriteSessionState,
4564 storage_capacity: usize,
4565}
4566
4567struct SlotsHostInner {
4568 table: SlotTable,
4569 nested_hosts: Vec<std::rc::Weak<SlotsHost>>,
4570 lifecycle: slot::SlotLifecycleCoordinator,
4571 runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
4572 active_pass: Option<ActivePassState>,
4573}
4574
4575impl Drop for SlotsHost {
4576 fn drop(&mut self) {
4577 let storage_key = self.storage_key.get();
4578 let inner = self.inner.get_mut();
4579 if let Some(state) = inner.runtime_state.clone() {
4580 if let Err(err) = state.dispose_retained_subtrees_for_host(
4581 storage_key,
4582 &mut inner.table,
4583 &mut inner.lifecycle,
4584 ) {
4585 log::error!(
4586 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
4587 );
4588 state.abandon_retained_subtrees_for_host(
4589 storage_key,
4590 &mut inner.table,
4591 &mut inner.lifecycle,
4592 );
4593 } else {
4594 state.clear_host_storage_key(storage_key, &mut inner.table);
4595 }
4596 }
4597 inner.lifecycle.dispose_slot_table(&mut inner.table);
4598 }
4599}
4600
4601impl SlotsHost {
4602 pub fn storage_key(&self) -> usize {
4603 self.storage_key.get()
4604 }
4605
4606 pub fn new(storage: SlotTable) -> Self {
4607 let storage_key = storage.storage_id();
4608 Self {
4609 storage_key: Cell::new(storage_key),
4610 inner: RefCell::new(SlotsHostInner {
4611 table: storage,
4612 nested_hosts: Vec::new(),
4613 lifecycle: slot::SlotLifecycleCoordinator::default(),
4614 runtime_state: None,
4615 active_pass: None,
4616 }),
4617 }
4618 }
4619
4620 pub fn note_nested_host(&self, nested: &Rc<SlotsHost>) {
4621 let Ok(mut inner) = self.inner.try_borrow_mut() else {
4622 return;
4623 };
4624 inner.nested_hosts.retain(|held| held.upgrade().is_some());
4625 if inner
4626 .nested_hosts
4627 .iter()
4628 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
4629 {
4630 return;
4631 }
4632 inner.nested_hosts.push(Rc::downgrade(nested));
4633 }
4634
4635 pub(crate) fn forget_effects(&self) -> bool {
4636 let (forgotten, nested, runtime_state) = {
4637 let Ok(mut inner) = self.inner.try_borrow_mut() else {
4638 return false;
4639 };
4640 if inner.active_pass.is_some() {
4641 return false;
4642 }
4643 let drops = inner.table.take_effect_drops();
4644 inner.nested_hosts.retain(|held| held.upgrade().is_some());
4645 let nested: Vec<Rc<SlotsHost>> = inner
4646 .nested_hosts
4647 .iter()
4648 .filter_map(std::rc::Weak::upgrade)
4649 .collect();
4650 (drops, nested, inner.runtime_state.clone())
4651 };
4652 let mut any = !forgotten.is_empty();
4653 drop(forgotten);
4654 for host in nested {
4655 any |= host.forget_effects();
4656 }
4657 if any {
4658 self.borrow()
4659 .scopes()
4660 .for_each(RecomposeScope::force_recompose);
4661 if let Some(runtime_state) = runtime_state {
4662 runtime_state.force_recompose_retained_scopes(self.storage_key());
4663 }
4664 }
4665 any
4666 }
4667
4668 pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
4669 let mut inner = self.inner.borrow_mut();
4670 inner.runtime_state = Some(Rc::clone(state));
4671 }
4672
4673 pub(crate) fn rebind_orphaned_runtime_state(
4674 &self,
4675 state: &Rc<crate::composer::ComposerRuntimeState>,
4676 ) -> bool {
4677 let inner = self.inner.borrow();
4678 if inner.active_pass.is_some() {
4679 log::error!("cannot rebind SlotsHost during an active pass");
4680 return false;
4681 }
4682 let Some(bound_state) = inner.runtime_state.as_ref() else {
4683 drop(inner);
4684 self.bind_runtime_state(state);
4685 return true;
4686 };
4687 if Rc::ptr_eq(bound_state, state) {
4688 return true;
4689 }
4690 if bound_state.has_live_applier_host() {
4691 return false;
4692 }
4693 drop(inner);
4694
4695 let mut inner = self.inner.borrow_mut();
4696 let Some(bound_state) = inner.runtime_state.as_ref() else {
4697 inner.runtime_state = Some(Rc::clone(state));
4698 return true;
4699 };
4700 if Rc::ptr_eq(bound_state, state) {
4701 return true;
4702 }
4703 if bound_state.has_live_applier_host() {
4704 return false;
4705 }
4706
4707 let previous_state = Rc::clone(bound_state);
4708 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4709 lifecycle.flush_pending_drops();
4710 let host_key = self.storage_key();
4711 if previous_state
4712 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4713 .is_err()
4714 {
4715 inner.lifecycle = lifecycle;
4716 return false;
4717 }
4718 previous_state.clear_host(self, &mut inner.table);
4719 lifecycle.flush_pending_drops();
4720 inner.runtime_state = Some(Rc::clone(state));
4721 inner.lifecycle = lifecycle;
4722 true
4723 }
4724
4725 pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
4726 self.inner.borrow().runtime_state.clone()
4727 }
4728
4729 fn runtime_state_ptr(&self) -> Option<*const crate::composer::ComposerRuntimeState> {
4730 self.inner.borrow().runtime_state.as_ref().map(Rc::as_ptr)
4731 }
4732
4733 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
4734 Ref::map(self.inner.borrow(), |inner| &inner.table)
4735 }
4736
4737 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
4738 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
4739 }
4740
4741 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
4742 if Rc::strong_count(&self) != 1 {
4743 return Err(NodeError::SlotHostUnavailable {
4744 operation: "SlotsHost::into_table",
4745 reason: "other host references are alive",
4746 });
4747 }
4748 self.take_table_for_transfer()
4749 }
4750
4751 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
4752 let inner = self.inner.borrow();
4753 if inner.active_pass.is_some() {
4754 return Err(NodeError::SlotHostUnavailable {
4755 operation: "SlotsHost::into_table",
4756 reason: "slot pass is active",
4757 });
4758 }
4759 drop(inner);
4760 let mut inner = self.inner.borrow_mut();
4761 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4762 lifecycle.flush_pending_drops();
4763 if let Some(state) = inner.runtime_state.clone() {
4764 let host_key = self.storage_key();
4765 if let Err(error) =
4766 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4767 {
4768 inner.lifecycle = lifecycle;
4769 return Err(error);
4770 }
4771 state.clear_host(self, &mut inner.table);
4772 lifecycle.flush_pending_drops();
4773 }
4774 let taken = std::mem::take(&mut inner.table);
4775 self.storage_key.set(inner.table.storage_id());
4776 inner.runtime_state = None;
4777 inner.lifecycle = lifecycle;
4778 Ok(taken)
4779 }
4780
4781 pub fn reset(&self) -> Result<(), NodeError> {
4782 let inner = self.inner.borrow();
4783 if inner.active_pass.is_some() {
4784 return Err(NodeError::SlotHostUnavailable {
4785 operation: "SlotsHost::reset",
4786 reason: "slot pass is active",
4787 });
4788 }
4789 let runtime_state = inner.runtime_state.clone();
4790 drop(inner);
4791 let mut inner = self.inner.borrow_mut();
4792 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4793 if let Some(state) = runtime_state {
4794 let host_key = self.storage_key();
4795 if let Err(error) =
4796 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4797 {
4798 inner.lifecycle = lifecycle;
4799 return Err(error);
4800 }
4801 state.clear_host(self, &mut inner.table);
4802 }
4803 lifecycle.dispose_slot_table(&mut inner.table);
4804 inner.table = SlotTable::default();
4805 self.storage_key.set(inner.table.storage_id());
4806 inner.runtime_state = None;
4807 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4808 Ok(())
4809 }
4810
4811 pub(crate) fn abandon_after_apply_failure(&self) {
4812 let inner = self.inner.borrow();
4813 if inner.active_pass.is_some() {
4814 log::error!("cannot abandon SlotsHost during an active pass");
4815 return;
4816 }
4817 let runtime_state = inner.runtime_state.clone();
4818 drop(inner);
4819 let mut inner = self.inner.borrow_mut();
4820 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4821 if let Some(state) = runtime_state {
4822 let host_key = self.storage_key();
4823 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
4824 }
4825 lifecycle.dispose_slot_table(&mut inner.table);
4826 inner.table = SlotTable::default();
4827 self.storage_key.set(inner.table.storage_id());
4828 inner.runtime_state = None;
4829 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4830 }
4831
4832 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
4833 let inner = self.inner.borrow();
4834 let local = inner.table.debug_stats();
4835 let lifecycle = inner.lifecycle.debug_stats();
4836 let retention = inner
4837 .runtime_state
4838 .clone()
4839 .map(|state| state.slot_retention_debug_stats(self))
4840 .unwrap_or_default();
4841 SlotTableDebugStats::from_parts(local, lifecycle, retention)
4842 }
4843
4844 #[cold]
4845 #[inline(never)]
4846 pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
4847 let inner = self.inner.borrow();
4848 let mut snapshot = inner.table.debug_snapshot();
4849 if let Some(state) = inner.runtime_state.clone() {
4850 state.fill_slot_debug_snapshot(self, &mut snapshot);
4851 }
4852 snapshot
4853 }
4854
4855 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode, folds: &Rc<slot::BranchFolds>) {
4856 let mut inner = self.inner.borrow_mut();
4857 if inner.active_pass.is_some() {
4858 log::error!("slot pass already active for host");
4859 return;
4860 }
4861 let mut state = slot::SlotWriteSessionState::new(Rc::clone(folds));
4862 state.reset_for_pass(mode);
4863 let storage_capacity = inner.table.storage_capacity();
4864 inner.active_pass = Some(ActivePassState {
4865 state,
4866 storage_capacity,
4867 });
4868 }
4869
4870 pub(crate) fn has_active_pass(&self) -> bool {
4871 self.inner.borrow().active_pass.is_some()
4872 }
4873
4874 pub(crate) fn abandon_active_pass(&self) {
4875 self.inner.borrow_mut().active_pass = None;
4876 }
4877
4878 pub(crate) fn with_write_session<R>(
4879 &self,
4880 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
4881 ) -> R {
4882 let mut inner = self.inner.borrow_mut();
4883 let SlotsHostInner {
4884 table,
4885 lifecycle,
4886 active_pass,
4887 ..
4888 } = &mut *inner;
4889 let active_pass = active_pass
4890 .as_mut()
4891 .expect("slot write session requires an active pass");
4892 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4893 f(&mut session)
4894 }
4895
4896 pub(crate) fn with_table_and_lifecycle_mut<R>(
4897 &self,
4898 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
4899 ) -> R {
4900 let mut inner = self.inner.borrow_mut();
4901 let SlotsHostInner {
4902 table, lifecycle, ..
4903 } = &mut *inner;
4904 f(table, lifecycle)
4905 }
4906
4907 pub(crate) fn finish_pass(
4908 &self,
4909 applier: &mut dyn Applier,
4910 ) -> Result<FinishedSlotPass, NodeError> {
4911 let mut inner = self.inner.borrow_mut();
4912 let SlotsHostInner {
4913 table,
4914 lifecycle,
4915 active_pass: active_pass_slot,
4916 ..
4917 } = &mut *inner;
4918 let Some(mut active_pass) = active_pass_slot.take() else {
4919 return Ok(FinishedSlotPass::default());
4920 };
4921
4922 active_pass.state.flush_payload_location_refreshes(table);
4923
4924 #[cfg(any(test, debug_assertions))]
4925 if let Err(err) = active_pass.state.validate(table) {
4926 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
4927 return Err(NodeError::SlotHostUnavailable {
4928 operation: "SlotsHost::finish_pass",
4929 reason: "slot writer invariant violation",
4930 });
4931 }
4932
4933 let detached_root_children = {
4934 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4935 session.finalize_pass(applier)?
4936 };
4937
4938 Ok(FinishedSlotPass {
4939 outcome: SlotPassOutcome {
4940 compacted: active_pass.state.request_compaction,
4941 compact_anchor_registry_storage: active_pass
4942 .state
4943 .request_anchor_storage_compaction,
4944 compact_payload_storage: active_pass.state.request_payload_storage_compaction,
4945 trim_growth_slack: active_pass.state.removed_nothing()
4946 && table.storage_capacity() > active_pass.storage_capacity,
4947 },
4948 detached_root_children,
4949 })
4950 }
4951
4952 pub(crate) fn flush_pending_drops(&self) {
4953 self.inner.borrow_mut().lifecycle.flush_pending_drops();
4954 }
4955
4956 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
4957 let mut inner = self.inner.borrow_mut();
4958 let SlotsHostInner {
4959 table,
4960 lifecycle,
4961 runtime_state,
4962 ..
4963 } = &mut *inner;
4964 lifecycle.flush_pending_drops();
4965 if outcome.compacted {
4966 table.compact_storage();
4967 lifecycle.compact_storage();
4968 } else if outcome.trim_growth_slack {
4969 table.trim_growth_slack();
4970 }
4971 if let Some(state) = runtime_state.clone() {
4972 state.compact_table_identity_storage_for_host(
4973 self,
4974 table,
4975 outcome.compact_anchor_registry_storage,
4976 outcome.compact_payload_storage,
4977 );
4978 } else {
4979 if outcome.compact_anchor_registry_storage {
4980 table.compact_anchor_registry_storage(None);
4981 }
4982 if outcome.compact_payload_storage {
4983 table.compact_payload_anchor_registry_storage(None);
4984 }
4985 }
4986 table.assert_fast_integrity("slot pass cleanup");
4987 #[cfg(any(test, debug_assertions))]
4988 {
4989 table.debug_verify();
4990 if let Some(state) = runtime_state.clone() {
4991 state.debug_verify_host(self, table);
4992 }
4993 }
4994 }
4995}
4996
4997fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
4998 let mut positions = HashMap::default();
4999 positions.reserve(children.len());
5000 for (index, &child) in children.iter().enumerate() {
5001 positions.insert(child, index);
5002 }
5003 positions
5004}
5005
5006fn refresh_child_positions(
5007 current: &[NodeId],
5008 positions: &mut HashMap<NodeId, usize>,
5009 start: usize,
5010 end: usize,
5011) {
5012 if current.is_empty() || start >= current.len() {
5013 return;
5014 }
5015 let end = end.min(current.len() - 1);
5016 for (offset, &child) in current[start..=end].iter().enumerate() {
5017 positions.insert(child, start + offset);
5018 }
5019}
5020
5021fn insert_child_into_diff_state(
5022 current: &mut ChildList,
5023 positions: &mut HashMap<NodeId, usize>,
5024 index: usize,
5025 child: NodeId,
5026) {
5027 let index = index.min(current.len());
5028 current.insert(index, child);
5029 refresh_child_positions(current, positions, index, current.len() - 1);
5030}
5031
5032fn move_child_in_diff_state(
5033 current: &mut ChildList,
5034 positions: &mut HashMap<NodeId, usize>,
5035 from_index: usize,
5036 target_index: usize,
5037) -> usize {
5038 let child = current.remove(from_index);
5039 let to_index = target_index.min(current.len());
5040 current.insert(to_index, child);
5041 refresh_child_positions(
5042 current,
5043 positions,
5044 from_index.min(to_index),
5045 from_index.max(to_index),
5046 );
5047 to_index
5048}
5049
5050pub(crate) use state::MutableStateInner;
5051pub use state::{
5052 MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
5053 StateSubscriptionHold,
5054};
5055
5056fn hash_key<K: Hash>(key: &K) -> Key {
5057 let mut hasher = hash::default::new();
5058 key.hash(&mut hasher);
5059 hasher.finish()
5060}
5061
5062pub(crate) fn explicit_group_key_seed<K: Hash>(
5063 key: &K,
5064 caller: &'static std::panic::Location<'static>,
5065) -> slot::GroupKeySeed {
5066 let source_key = location_key(caller.file(), caller.line(), caller.column());
5067 let explicit_key = hash_key(key);
5068 slot::GroupKeySeed::keyed(source_key, explicit_key)
5069}
5070
5071#[cfg(test)]
5072#[path = "tests/mod.rs"]
5073mod tests;
5074
5075#[cfg(test)]
5076#[path = "tests/recursive_decrease_increase_test.rs"]
5077mod recursive_decrease_increase_test;
5078
5079pub mod collections;
5080pub mod hash;
5081
5082#[cfg(any(test, feature = "test-helpers"))]
5085pub mod test_scratch;
5086#[cfg(any(test, feature = "test-helpers"))]
5087pub use test_scratch::test_scratch_dir;
5088
5089pub(crate) fn note_structural(reason: &str, parent_id: NodeId, child_id: NodeId) {
5090 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5091 eprintln!("[structural] {reason} parent={parent_id} child={child_id}");
5092 }
5093}
5094
5095pub(crate) fn note_structural_move(parent_id: NodeId, from_index: usize, to_index: usize) {
5096 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5097 eprintln!("[structural] move parent={parent_id} from={from_index} to={to_index}");
5098 }
5099}