1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3#![deny(unsafe_code)]
4
5pub extern crate self as cranpose_core;
6
7mod applier_host;
8mod blocking;
9mod bubble_passes;
10mod callbacks;
11mod composer;
12pub mod composer_context;
15mod composition;
16mod composition_locals;
17pub mod concurrency;
18mod debug_trace;
19mod effect_key;
20mod emit;
21pub mod env_flags;
22#[cfg(any(feature = "internal", test))]
23mod frame_clock;
24mod hooks;
25mod launched_effect;
26mod node_marks;
27pub mod owned;
29pub mod platform;
30mod recompose;
31mod retention;
32pub mod runtime;
35mod slot;
36pub mod snapshot_double_index_heap;
38pub mod snapshot_id_set;
40pub mod snapshot_pinning;
42pub mod snapshot_state_observer;
44pub mod snapshot_v2;
45mod snapshot_weak_set;
46pub mod source_trace;
47mod stable_index;
48mod state;
49#[doc(hidden)]
50pub use source_trace::__source_scope;
51#[doc(hidden)]
52pub use thread_state::with_or_fresh;
53mod stage_times;
54pub mod subcompose;
55mod thread_state;
56#[doc(hidden)]
57pub use stage_times::{StageTimes, stage_ms};
58
59#[cfg(feature = "internal")]
60#[doc(hidden)]
61pub mod internal {
62 pub use crate::frame_clock::{FrameCallbackRegistration, FrameClock};
63}
64pub use applier_host::{ApplierGuard, ApplierHost, ConcreteApplierHost, NodeDisposal};
65pub use blocking::{
66 BlockingError, BlockingExecutor, BlockingExecutorConfig, BlockingTask, launchBlocking,
67 withBlocking,
68};
69pub use callbacks::{
70 CallbackHolder, CallbackHolder1, ParamReturnState, ParamSlot, ParamState, ReturnSlot,
71 SharedParam, refresh_param, refresh_shared_param, store_param,
72};
73pub use composer::{
74 BranchGroupGuard, CapturedCompositionContext, ComposableGroup, Composer,
75 ScopedBranchGroupGuard, ValueSlotHandle,
76};
77pub(crate) use composer::{ComposerCore, EmittedNode, ParentAttachMode, ParentFrame};
78pub use composition::{Composition, ROOT_RENDER_REPLAY_LIMIT};
79pub use composition_locals::{
80 CompositionLocal, CompositionLocalAccessorScope, CompositionLocalProvider, ProvidedValue,
81 ReadableCompositionLocal, StaticCompositionLocal, compositionLocalOf,
82 compositionLocalOfWithPolicy, compositionLocalWithComputedDefaultOf, staticCompositionLocalOf,
83 staticCompositionLocalOfWithPolicy, staticCompositionLocalWithComputedDefaultOf,
84};
85pub(crate) use composition_locals::{
86 ComputedLocalEntry, LocalStateEntry, StaticComputedLocalEntry, StaticLocalEntry,
87};
88pub use concurrency::{
89 CollectEvents, CoroutineScope, Delay, EventChannel, EventSender, EventStream, EventStreamNext,
90 ProduceScope, collectAsState, delay, interval, produceState, rememberCoroutineScope,
91 rememberEventStream, spawn_ui_task,
92};
93#[doc(hidden)]
94pub use debug_trace::{
95 debug_label_current_scope, debug_live_recompose_scope_count,
96 debug_recompose_scope_registry_stats, debug_scope_invalidation_sources, debug_scope_label,
97};
98pub use hooks::{
99 derivedStateOf, mutableStateList, mutableStateListOf, mutableStateMap, mutableStateMapOf,
100 mutableStateOf, mutableStateOfNeverEqual, ownedMutableStateOf, ownedMutableStateOfNeverEqual,
101 remember, rememberKeyed, rememberMutableStateOf, rememberMutableStateOfNeverEqual,
102 rememberUpdatedState, try_mutableStateOf,
103};
104#[cfg(feature = "internal")]
105#[doc(hidden)]
106pub use hooks::{withFrameMillis, withFrameNanos};
107pub use launched_effect::{
108 __launched_effect_async_impl, __launched_effect_impl, CancelToken, LaunchedEffect,
109 LaunchedEffectAsync, LaunchedEffectScope, TaskSite,
110};
111pub use node_marks::NodeMarks;
112pub use owned::Owned;
113pub use platform::{Clock, RuntimeScheduler, SchedulerRef, scheduler_ref};
114pub use retention::{RetentionBudget, RetentionEvictionPolicy, RetentionMode, RetentionPolicy};
115#[doc(hidden)]
116pub use runtime::{
117 DefaultScheduler, Runtime, RuntimeHandle, StateId, TaskHandle, UiDispatcher,
118 current_runtime_handle, label_next_ui_task, schedule_frame, schedule_node_update,
119};
120pub use slot::{
121 SlotDebugAnchor, SlotDebugEntry, SlotDebugEntryKind, SlotDebugGroup, SlotDebugScope,
122 SlotDebugSnapshot, SlotRetentionDebugStats, SlotTable, SlotTableDebugStats,
123 SlotTableLocalDebugStats, SlotTableMutationDebugStats,
124};
125#[doc(hidden)]
126pub use snapshot_state_observer::SnapshotStateObserver;
127
128pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
153 let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
154
155 let _applied_guard = AppliedSnapshotFlagGuard::enter();
156 let value = snapshot.enter(block);
157
158 match snapshot.apply() {
159 snapshot_v2::SnapshotApplyResult::Success => Ok(value),
160 snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
161 }
162}
163
164struct AppliedSnapshotFlagGuard {
165 previous: bool,
166}
167
168impl AppliedSnapshotFlagGuard {
169 fn enter() -> Self {
170 let previous = thread_state::cells(|cells| cells.in_applied_snapshot.replace(true));
171 Self { previous }
172 }
173}
174
175impl Drop for AppliedSnapshotFlagGuard {
176 fn drop(&mut self) {
177 thread_state::cells(|cells| cells.in_applied_snapshot.set(self.previous));
178 }
179}
180
181pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
196 run_in_mutable_snapshot(block).ok()
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
202pub struct CompositionPassDebugStats {
203 pub commands_len: usize,
205 pub commands_cap: usize,
207 pub command_payload_len_bytes: usize,
209 pub command_payload_cap_bytes: usize,
211 pub sync_children_len: usize,
213 pub sync_children_cap: usize,
215 pub sync_child_ids_len: usize,
217 pub sync_child_ids_cap: usize,
219 pub side_effects_len: usize,
221 pub side_effects_cap: usize,
223}
224
225#[must_use]
227pub struct EventHandlerScopeGuard {
228 previous: bool,
229}
230
231impl Drop for EventHandlerScopeGuard {
232 fn drop(&mut self) {
233 thread_state::cells(|cells| cells.in_event_handler.set(self.previous));
234 }
235}
236
237pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
240 let previous = thread_state::cells(|cells| cells.in_event_handler.replace(true));
241 EventHandlerScopeGuard { previous }
242}
243
244pub fn in_event_handler() -> bool {
246 thread_state::cells(|cells| cells.in_event_handler.get())
247}
248
249pub fn in_applied_snapshot() -> bool {
251 thread_state::cells(|cells| cells.in_applied_snapshot.get())
252}
253
254use std::{
255 any::{Any, TypeId},
256 cell::{Cell, Ref, RefCell, RefMut},
257 cmp::Reverse,
258 collections::BinaryHeap,
259 hash::{Hash, Hasher},
260 rc::{Rc, Weak},
261 sync::OnceLock,
262};
263
264#[cfg(test)]
265pub(crate) use runtime::{TestRuntime, TestScheduler};
266use smallvec::SmallVec;
267
268use crate::collections::map::{HashMap, HashSet};
269
270pub type Key = u64;
273pub type NodeId = usize;
275
276#[cfg(any(test, debug_assertions))]
277#[derive(Clone, Debug, PartialEq, Eq)]
278pub(crate) struct LocationKeyDebugInfo {
279 file: String,
280 line: u32,
281 column: u32,
282}
283
284#[cfg(any(test, debug_assertions))]
285fn register_location_key_debug_info(key: Key, file: &str, line: u32, column: u32) {
286 let info = || LocationKeyDebugInfo {
287 file: file.to_owned(),
288 line,
289 column,
290 };
291 let collision = thread_state::runtimes(|state| {
292 let mut registry = state.location_keys.borrow_mut();
293 match registry.entry(key) {
294 std::collections::hash_map::Entry::Vacant(entry) => {
295 thread_state::watch_thread_exit();
296 entry.insert(info());
297 None
298 }
299 std::collections::hash_map::Entry::Occupied(entry) => {
300 let existing = entry.get();
301 (existing.file != file || existing.line != line || existing.column != column)
302 .then(|| (existing.clone(), info()))
303 }
304 }
305 });
306 if let Some((existing, incoming)) = collision {
307 thread_state::cells(|cells| {
308 let count = &cells.location_key_collisions;
309 count.set(count.get().saturating_add(1));
310 });
311 log::error!("location key collision: key={key} first={existing:?} second={incoming:?}");
312 }
313}
314
315#[cfg(all(debug_assertions, not(test)))]
316fn location_key_diagnostics_enabled() -> bool {
317 crate::env_flag!("CRANPOSE_LOCATION_KEY_DIAGNOSTICS")
318}
319
320#[cfg(test)]
321pub(crate) fn register_location_key_debug_info_for_test(
322 key: Key,
323 file: &str,
324 line: u32,
325 column: u32,
326) {
327 register_location_key_debug_info(key, file, line, column);
328}
329
330#[cfg(test)]
331pub(crate) fn location_key_debug_collision_count_for_test() -> usize {
332 thread_state::cells(|cells| cells.location_key_collisions.get())
333}
334
335#[cfg(test)]
336pub(crate) fn location_key_debug_info_for_test(key: Key) -> Option<LocationKeyDebugInfo> {
337 thread_state::runtimes(|state| state.location_keys.borrow().get(&key).cloned())
338}
339
340#[cfg(test)]
341pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
342 true
343}
344
345#[cfg(all(debug_assertions, not(test)))]
346pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
347 crate::env_flag!("CRANPOSE_VALIDATE_SLOTS")
348}
349
350const fn source_location_hash(file: &str, line: u32, column: u32) -> u64 {
351 position_location_hash(file_location_hash(file), line, column)
352}
353
354const fn file_location_hash(file: &str) -> u64 {
355 fnv1a_location_key_bytes(0xcbf2_9ce4_8422_2325u64, file.as_bytes())
356}
357
358fn static_file_location_hash(cells: &thread_state::ThreadCells, file: &'static str) -> u64 {
363 let address = file.as_ptr() as usize;
364 let cached = &cells.file_hashes[(address >> 4) % thread_state::FILE_HASH_SLOTS];
365 let (cached_address, cached_len, hash) = cached.get();
366 if cached_address == address && cached_len == file.len() {
367 return hash;
368 }
369 let hash = file_location_hash(file);
370 cached.set((address, file.len(), hash));
371 hash
372}
373
374const fn position_location_hash(mut hash: u64, line: u32, column: u32) -> u64 {
375 hash = fnv1a_location_key_bytes(hash, &[0xff]);
376 hash = fnv1a_location_key_bytes(hash, &line.to_le_bytes());
377 hash = fnv1a_location_key_bytes(hash, &[0xfe]);
378 hash = fnv1a_location_key_bytes(hash, &column.to_le_bytes());
379 hash
380}
381
382const fn fnv1a_location_key_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
383 let mut index = 0;
384 while index < bytes.len() {
385 hash = (hash ^ bytes[index] as u64).wrapping_mul(0x0000_0100_0000_01b3);
386 index += 1;
387 }
388 hash
389}
390
391const fn avalanche_location_key(mut value: u64) -> u64 {
392 value ^= value >> 33;
393 value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
394 value ^= value >> 33;
395 value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
396 value ^ (value >> 33)
397}
398
399#[doc(hidden)]
400#[track_caller]
401pub fn caller_location_key() -> Key {
402 let caller = std::panic::Location::caller();
403 let file = caller.file();
404 if cfg!(feature = "hot-reload")
405 && let Some(key) = hot_call_site_key(file, caller.line(), caller.column())
406 {
407 return key;
408 }
409 call_site_key(caller)
410}
411
412fn call_site_key(caller: &'static std::panic::Location<'static>) -> Key {
418 let address = std::ptr::from_ref(caller).addr();
419 thread_state::cells(|cells| {
420 let cached = &cells.call_site_keys[(address >> 3) % thread_state::CALL_SITE_KEY_SLOTS];
421 let (cached_address, key) = cached.get();
422 if cached_address == address {
423 return key;
424 }
425 let file = caller.file();
426 let key = registered_location_key(
427 static_file_location_hash(cells, file),
428 file,
429 caller.line(),
430 caller.column(),
431 );
432 cached.set((address, key));
433 key
434 })
435}
436
437#[derive(Clone, Copy)]
438pub(crate) struct HotOrigin {
439 file: &'static str,
440 line: u32,
441 end: u32,
442 identity: Key,
443}
444
445impl HotOrigin {
446 pub(crate) const NONE: Self = Self {
447 file: "",
448 line: 0,
449 end: 0,
450 identity: 0,
451 };
452}
453
454#[doc(hidden)]
456pub struct HotOriginGuard {
457 previous: HotOrigin,
458}
459
460impl Drop for HotOriginGuard {
461 fn drop(&mut self) {
462 let previous = self.previous;
463 thread_state::cells(|cells| cells.hot_origin.set(previous));
464 }
465}
466
467#[doc(hidden)]
474pub fn hot_origin(file: &'static str, line: u32, end: u32, identity: Key) -> HotOriginGuard {
475 let next = HotOrigin {
476 file,
477 line,
478 end,
479 identity,
480 };
481 HotOriginGuard {
482 previous: thread_state::cells(|cells| cells.hot_origin.replace(next)),
483 }
484}
485
486fn hot_call_site_key(file: &str, line: u32, column: u32) -> Option<Key> {
487 let origin = thread_state::cells(|cells| cells.hot_origin.get());
488 if origin.file.is_empty() || !(origin.line..=origin.end).contains(&line) || origin.file != file
489 {
490 return None;
491 }
492 let relative = line.wrapping_sub(origin.line);
493 let hash = fnv1a_location_key_bytes(origin.identity, &relative.to_le_bytes());
494 Some(avalanche_location_key(fnv1a_location_key_bytes(
495 hash ^ 0xfc,
496 &column.to_le_bytes(),
497 )))
498}
499
500#[doc(hidden)]
503pub const fn hot_branch_key(file: &str, path_hash: u64) -> Key {
504 let hash = file_location_hash(file);
505 avalanche_location_key(fnv1a_location_key_bytes(
506 hash ^ 0xfd,
507 &path_hash.to_le_bytes(),
508 ))
509}
510
511#[doc(hidden)]
514pub const fn hot_definition_key(file: &str, module: &str, name: &str) -> Key {
515 let mut hash = file_location_hash(file);
516 hash = fnv1a_location_key_bytes(hash ^ 0xfe, module.as_bytes());
517 avalanche_location_key(fnv1a_location_key_bytes(hash ^ 0xff, name.as_bytes()))
518}
519
520#[doc(hidden)]
521#[track_caller]
522pub fn composable_identity_key(definition: Key) -> Key {
523 (definition.wrapping_mul(0x0000_0100_0000_01b3) ^ caller_location_key())
524 .wrapping_mul(0x0000_0100_0000_01b3)
525}
526
527#[doc(hidden)]
528pub fn composable_definition_key(
529 file: &str,
530 line: u32,
531 column: u32,
532 marker: std::any::TypeId,
533) -> Key {
534 let mut hasher = std::collections::hash_map::DefaultHasher::new();
535 std::hash::Hash::hash(&marker, &mut hasher);
536 location_key(file, line, column) ^ avalanche_location_key(std::hash::Hasher::finish(&hasher))
537}
538
539#[doc(hidden)]
542pub const fn const_location_key(file: &str, line: u32, column: u32) -> Key {
543 avalanche_location_key(source_location_hash(file, line, column))
544}
545
546#[macro_export]
549macro_rules! location_key {
550 () => {{
551 const KEY: $crate::Key = $crate::const_location_key(file!(), line!(), column!());
552 $crate::noted_location_key(KEY, file!(), line!(), column!())
553 }};
554}
555
556#[doc(hidden)]
559#[inline(always)]
560pub fn noted_location_key(key: Key, file: &str, line: u32, column: u32) -> Key {
561 note_location_key(key, file, line, column);
562 key
563}
564
565#[doc(hidden)]
566pub fn cached_composable_definition_key(
567 cell: &OnceLock<Key>,
568 file: &str,
569 line: u32,
570 column: u32,
571 marker: TypeId,
572) -> Key {
573 *cell.get_or_init(|| composable_definition_key(file, line, column, marker))
574}
575
576pub fn location_key(file: &str, line: u32, column: u32) -> Key {
579 registered_location_key(file_location_hash(file), file, line, column)
580}
581
582fn registered_location_key(file_hash: u64, file: &str, line: u32, column: u32) -> Key {
583 let key = avalanche_location_key(position_location_hash(file_hash, line, column));
584 note_location_key(key, file, line, column);
585 key
586}
587
588#[cfg(any(test, debug_assertions))]
590fn note_location_key(key: Key, file: &str, line: u32, column: u32) {
591 #[cfg(test)]
592 register_location_key_debug_info(key, file, line, column);
593 #[cfg(all(debug_assertions, not(test)))]
594 if location_key_diagnostics_enabled() {
595 register_location_key_debug_info(key, file, line, column);
596 }
597}
598
599#[cfg(not(any(test, debug_assertions)))]
600fn note_location_key(_key: Key, _file: &str, _line: u32, _column: u32) {}
601
602#[doc(hidden)]
603#[inline]
604pub fn __branch_group_scope_deferred(key: Key) -> Option<ScopedBranchGroupGuard> {
605 composer_context::with_current_core(|core| {
606 ScopedBranchGroupGuard::open(&core.branch_folds, key)
607 })
608}
609
610#[doc(hidden)]
613pub const fn branch_location_key(file: &str, line: u32, column: u32, branch: u32) -> Key {
614 let mut hash = source_location_hash(file, line, column);
615 hash = fnv1a_location_key_bytes(hash, &[0xfd]);
616 hash = fnv1a_location_key_bytes(hash, &branch.to_le_bytes());
617 avalanche_location_key(hash)
618}
619
620#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
626pub struct AnchorId {
627 id: u32,
628 generation: u32,
629}
630
631impl AnchorId {
632 pub(crate) const INVALID: AnchorId = AnchorId {
633 id: 0,
634 generation: 0,
635 };
636
637 pub(crate) fn new(id: usize) -> Self {
638 Self {
639 id: crate::slot::checked_usize_to_u32(id, "anchor id"),
640 generation: 1,
641 }
642 }
643
644 pub fn is_valid(&self) -> bool {
646 self.id != 0
647 }
648}
649
650pub(crate) type ScopeId = usize;
651pub(crate) type FrameCallbackId = u64;
652type LocalStackSnapshot = Option<Rc<composer::LocalFrame>>;
657
658#[derive(Clone)]
659pub(crate) struct LocalKey(Rc<()>);
660
661impl LocalKey {
662 fn new() -> Self {
663 Self(Rc::new(()))
664 }
665
666 pub(crate) fn entry_source(&self) -> Key {
667 avalanche_location_key(Rc::as_ptr(&self.0) as usize as u64)
668 }
669}
670
671impl std::fmt::Debug for LocalKey {
672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673 f.debug_tuple("LocalKey")
674 .field(&(Rc::as_ptr(&self.0) as usize))
675 .finish()
676 }
677}
678
679impl PartialEq for LocalKey {
680 fn eq(&self, other: &Self) -> bool {
681 Rc::ptr_eq(&self.0, &other.0)
682 }
683}
684
685impl Eq for LocalKey {}
686
687impl Hash for LocalKey {
688 fn hash<H: Hasher>(&self, state: &mut H) {
689 Rc::as_ptr(&self.0).hash(state);
690 }
691}
692
693struct RecomposeCallback {
697 body: Box<dyn FnMut(&Composer) + 'static>,
698 stateless_body: Option<fn() -> TypeId>,
699}
700
701fn scope_activity_epoch() -> u64 {
702 thread_state::cells(|cells| cells.scope_activity_epoch.get())
703}
704
705fn note_scope_activity_change() {
706 thread_state::cells(|cells| {
707 let epoch = &cells.scope_activity_epoch;
708 epoch.set(epoch.get() + 1);
709 });
710}
711
712fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
717 let value = cell.take();
718 let result = read(&value);
719 cell.set(value);
720 result
721}
722
723fn update_cell<T: Default, R>(cell: &Cell<T>, update: impl FnOnce(&mut T) -> R) -> R {
725 let mut value = cell.take();
726 let result = update(&mut value);
727 cell.set(value);
728 result
729}
730
731#[derive(Clone, Copy, PartialEq, Eq)]
733enum OwnerKind {
734 Parent,
736 Lifetime,
739}
740
741type OwnerLink = Cell<Option<(OwnerKind, Weak<RecomposeScopeInner>)>>;
743
744const NO_PARENT_HINT: NodeId = NodeId::MAX;
747
748pub(crate) struct RecomposeScopeInner {
749 runtime: runtime::ScopeRuntime,
750 invalid: Cell<bool>,
751 enqueued: Cell<bool>,
752 active: Cell<bool>,
753 active_epoch: Cell<u64>,
754 deactivations: Cell<u64>,
755 composed_once: Cell<bool>,
756 pending_recompose: Cell<bool>,
757 force_reuse: Cell<bool>,
758 force_recompose: Cell<bool>,
759 derivation: Cell<bool>,
760 retention_mode: Cell<RetentionMode>,
761 parent_hint: Cell<NodeId>,
763 group_anchor: Cell<AnchorId>,
764 recompose: Cell<Option<RecomposeCallback>>,
765 owner: OwnerLink,
766 local_stack: Cell<LocalStackSnapshot>,
767 #[cfg(feature = "inspection")]
768 source_trace: RefCell<Rc<[source_trace::SourceLocation]>>,
769 #[cfg(all(feature = "inspection", debug_assertions))]
770 recompositions: std::cell::OnceCell<source_trace::RecompositionCounter>,
771 slots_storage_key: Cell<usize>,
772 slots_runtime_state: Cell<Option<std::rc::Weak<crate::composer::ComposerRuntimeState>>>,
773 state_subscriptions: RefCell<StateIds>,
776 run_reads: RefCell<StateIds>,
779 read_cursor: Cell<u32>,
782 invalidation_sources: Cell<StateIds>,
783 unknown_invalidation_source: Cell<bool>,
784}
785
786type StateIds = SmallVec<[StateId; 2]>;
787
788pub(crate) enum ScopeRead {
790 Watched,
792 Collected,
795 NotReading,
797}
798
799const NOT_READING: u32 = u32::MAX;
801const DIVERGED: u32 = u32::MAX - 1;
804
805enum ScopeOwner {
806 Root,
807 Live(Rc<RecomposeScopeInner>),
808 Dropped,
809}
810
811impl RecomposeScopeInner {
812 fn owner(&self) -> ScopeOwner {
813 read_cell(&self.owner, |owner| {
814 owner.as_ref().map_or(ScopeOwner::Root, |(_, owner)| {
815 owner
816 .upgrade()
817 .map_or(ScopeOwner::Dropped, ScopeOwner::Live)
818 })
819 })
820 }
821
822 fn is_effectively_active(&self, epoch: u64) -> bool {
823 if self.active_epoch.get() == epoch {
824 return true;
825 }
826 if !self.active.get() {
827 return false;
828 }
829 let active = match self.owner() {
830 ScopeOwner::Root => true,
831 ScopeOwner::Dropped => false,
832 ScopeOwner::Live(owner) => owner.is_effectively_active(epoch),
833 };
834 if active {
835 self.active_epoch.set(epoch);
836 }
837 active
838 }
839
840 fn new(runtime: runtime::ScopeRuntime) -> Self {
841 runtime.increment_live_recompose_scope_count();
842 Self {
843 runtime,
844 invalid: Cell::new(false),
845 enqueued: Cell::new(false),
846 active: Cell::new(true),
847 active_epoch: Cell::new(0),
848 deactivations: Cell::new(0),
849 composed_once: Cell::new(false),
850 pending_recompose: Cell::new(false),
851 force_reuse: Cell::new(false),
852 force_recompose: Cell::new(false),
853 derivation: Cell::new(false),
854 retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
855 parent_hint: Cell::new(NO_PARENT_HINT),
856 group_anchor: Cell::new(AnchorId::INVALID),
857 recompose: Cell::new(None),
858 owner: Cell::new(None),
859 local_stack: Cell::new(None),
860 #[cfg(feature = "inspection")]
861 source_trace: RefCell::new(Rc::from([])),
862 #[cfg(all(feature = "inspection", debug_assertions))]
863 recompositions: std::cell::OnceCell::new(),
864 slots_storage_key: Cell::new(0),
865 slots_runtime_state: Cell::new(None),
866 state_subscriptions: RefCell::new(StateIds::new()),
867 run_reads: RefCell::new(StateIds::new()),
868 read_cursor: Cell::new(NOT_READING),
869 invalidation_sources: Cell::new(StateIds::new()),
870 unknown_invalidation_source: Cell::new(false),
871 }
872 }
873
874 fn id(&self) -> ScopeId {
875 std::ptr::from_ref(self).addr()
876 }
877}
878
879fn push_unique_state_id(ids: &mut StateIds, state_id: StateId) {
880 if !ids.contains(&state_id) {
881 ids.push(state_id);
882 }
883}
884
885impl Drop for RecomposeScopeInner {
886 fn drop(&mut self) {
887 note_scope_activity_change();
888 let id = self.id();
889 self.runtime.decrement_live_recompose_scope_count();
890 let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
891 let run_reads = std::mem::take(self.run_reads.get_mut());
892 for state_id in subscriptions.into_iter().chain(run_reads) {
893 self.runtime.unregister_state_scope(state_id, id);
894 }
895 #[cfg(debug_assertions)]
896 {
897 thread_state::try_runtimes(|state| {
898 state.scope_labels.borrow_mut().remove(&id);
899 state.scope_invalidation_sources.borrow_mut().remove(&id);
900 });
901 }
902 if self.enqueued.replace(false) {
903 self.runtime.mark_scope_recomposed();
904 }
905 }
906}
907
908#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
910pub struct RecomposeScopeRegistryDebugStats {
911 pub len: usize,
913 pub capacity: usize,
915}
916
917#[derive(Clone)]
920pub struct RecomposeScope {
921 inner: Rc<RecomposeScopeInner>,
922}
923
924impl PartialEq for RecomposeScope {
925 fn eq(&self, other: &Self) -> bool {
926 Rc::ptr_eq(&self.inner, &other.inner)
927 }
928}
929
930impl Eq for RecomposeScope {}
931
932impl Hash for RecomposeScope {
933 fn hash<H: Hasher>(&self, state: &mut H) {
934 self.id().hash(state);
935 }
936}
937
938impl RecomposeScope {
939 pub(crate) fn mark_derivation(&self) {
943 self.inner.derivation.set(true);
944 }
945
946 pub(crate) fn is_derivation(&self) -> bool {
947 self.inner.derivation.get()
948 }
949
950 fn new(runtime: RuntimeHandle) -> Self {
951 Self {
952 inner: Rc::new(RecomposeScopeInner::new(runtime.scope_runtime())),
953 }
954 }
955
956 pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
957 Rc::downgrade(&self.inner)
958 }
959
960 pub(crate) fn upgrade(weak: &Weak<RecomposeScopeInner>) -> Option<Self> {
961 weak.upgrade().map(|inner| Self { inner })
962 }
963
964 pub fn id(&self) -> ScopeId {
966 self.inner.id()
967 }
968
969 pub fn is_invalid(&self) -> bool {
971 self.inner.invalid.get()
972 }
973
974 pub(crate) fn is_enqueued(&self) -> bool {
975 self.inner.enqueued.get()
976 }
977
978 pub fn is_active(&self) -> bool {
980 self.inner.active.get()
981 }
982
983 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
990 let mut total = self.inner.deactivations.get();
991 let mut owner = self.inner.owner();
992 while let ScopeOwner::Live(scope) = owner {
993 total = total.wrapping_add(scope.deactivations.get());
994 owner = scope.owner();
995 }
996 total
997 }
998
999 pub(crate) fn is_effectively_active(&self) -> bool {
1000 self.inner.is_effectively_active(scope_activity_epoch())
1001 }
1002
1003 fn record_state_subscription(&self, state_id: StateId) {
1004 push_unique_state_id(&mut self.inner.state_subscriptions.borrow_mut(), state_id);
1005 }
1006
1007 pub(crate) fn begin_reads(&self) {
1009 self.inner.read_cursor.set(0);
1010 }
1011
1012 #[inline]
1018 fn note_read(&self, state_id: StateId) -> ScopeRead {
1019 let cursor = self.inner.read_cursor.get();
1020 if cursor == NOT_READING {
1021 return ScopeRead::NotReading;
1022 }
1023 if cursor == DIVERGED {
1024 let mut reads = self.inner.run_reads.borrow_mut();
1025 if reads.last() != Some(&state_id) {
1026 reads.push(state_id);
1027 }
1028 return ScopeRead::Collected;
1029 }
1030 let at = cursor as usize;
1031 let subscribed = self.inner.state_subscriptions.borrow();
1032 if subscribed.get(at) == Some(&state_id) {
1033 self.inner.read_cursor.set(cursor + 1);
1034 return ScopeRead::Watched;
1035 }
1036 if at > 0 && subscribed[at - 1] == state_id {
1037 return ScopeRead::Watched;
1038 }
1039 let mut reads = self.inner.run_reads.borrow_mut();
1040 reads.clear();
1041 reads.extend_from_slice(&subscribed[..at]);
1042 reads.push(state_id);
1043 self.inner.read_cursor.set(DIVERGED);
1044 ScopeRead::Collected
1045 }
1046
1047 #[inline]
1051 pub(crate) fn end_reads(&self, ran: bool) {
1052 let cursor = self.inner.read_cursor.replace(NOT_READING);
1053 if cursor == NOT_READING || (!ran && cursor != DIVERGED) {
1056 return;
1057 }
1058 self.settle_reads(cursor, ran);
1059 }
1060
1061 #[inline(never)]
1064 fn settle_reads(&self, cursor: u32, ran: bool) {
1065 let id = self.id();
1066 let mut subscribed = self.inner.state_subscriptions.borrow_mut();
1067 if cursor != DIVERGED {
1068 let read = cursor as usize;
1070 if ran && read < subscribed.len() {
1071 for &state_id in &subscribed[read..] {
1072 if !subscribed[..read].contains(&state_id) {
1073 self.inner.runtime.forget_state_scope(state_id, id);
1074 }
1075 }
1076 subscribed.truncate(read);
1077 }
1078 return;
1079 }
1080 let mut reads = self.inner.run_reads.borrow_mut();
1081 if ran {
1082 for &state_id in subscribed.iter() {
1083 if !reads.contains(&state_id) {
1084 self.inner.runtime.forget_state_scope(state_id, id);
1085 }
1086 }
1087 std::mem::swap(&mut *reads, &mut *subscribed);
1088 reads.clear();
1089 } else {
1090 for state_id in reads.drain(..) {
1091 push_unique_state_id(&mut subscribed, state_id);
1092 }
1093 }
1094 }
1095
1096 fn record_unknown_invalidation_source(&self) {
1097 self.inner.unknown_invalidation_source.set(true);
1098 update_cell(&self.inner.invalidation_sources, StateIds::clear);
1099 }
1100
1101 fn record_state_invalidation_source(&self, state_id: StateId) {
1102 if !self.inner.unknown_invalidation_source.get() {
1103 update_cell(&self.inner.invalidation_sources, |ids| {
1104 push_unique_state_id(ids, state_id);
1105 });
1106 }
1107 }
1108
1109 fn enqueue_invalidation(&self) {
1110 self.inner.invalid.set(true);
1111 if !self.is_effectively_active() {
1112 return;
1113 }
1114 if !self.inner.enqueued.replace(true) {
1115 self.inner.runtime.register_invalid_scope(self.downgrade());
1116 }
1117 }
1118
1119 fn invalidate(&self) {
1120 self.record_unknown_invalidation_source();
1121 self.enqueue_invalidation();
1122 }
1123
1124 pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
1125 self.record_state_invalidation_source(state_id);
1126 self.enqueue_invalidation();
1127 }
1128
1129 fn mark_recomposed(&self) {
1130 if self.inner.invalid.replace(false) {
1133 self.inner.unknown_invalidation_source.set(false);
1134 update_cell(&self.inner.invalidation_sources, StateIds::clear);
1135 }
1136 self.inner.force_reuse.set(false);
1137 self.inner.force_recompose.set(false);
1138 if self.inner.enqueued.replace(false) {
1139 self.inner.runtime.mark_scope_recomposed();
1140 }
1141 let pending = self.inner.pending_recompose.replace(false);
1142 if pending {
1143 if self.inner.active.get() {
1144 self.invalidate();
1145 } else {
1146 self.inner.invalid.set(true);
1147 }
1148 }
1149 }
1150
1151 pub(crate) fn set_recompose_callback<F>(&self, callback: F)
1155 where
1156 F: FnMut(&Composer) + 'static,
1157 {
1158 let body: fn() -> TypeId = TypeId::of::<F>;
1159 let stateless = std::mem::size_of::<F>() == 0;
1160 if stateless && self.reruns_stateless(body) {
1161 return;
1162 }
1163 self.set_boxed_recompose(Box::new(callback), stateless.then_some(body));
1164 }
1165
1166 #[inline(never)]
1167 fn set_boxed_recompose(
1168 &self,
1169 body: Box<dyn FnMut(&Composer) + 'static>,
1170 stateless_body: Option<fn() -> TypeId>,
1171 ) {
1172 #[cfg(feature = "inspection")]
1173 self.inner
1174 .source_trace
1175 .replace(source_trace::current_source_trace());
1176 self.inner.recompose.set(Some(RecomposeCallback {
1177 body,
1178 stateless_body,
1179 }));
1180 }
1181
1182 fn reruns_stateless(&self, body: fn() -> TypeId) -> bool {
1186 read_cell(&self.inner.recompose, |callback| {
1187 callback
1188 .as_ref()
1189 .and_then(|callback| callback.stateless_body)
1190 .is_some_and(|current| std::ptr::fn_addr_eq(current, body) || current() == body())
1191 })
1192 }
1193
1194 fn run_recompose(&self, composer: &Composer) -> bool {
1195 #[cfg(feature = "inspection")]
1196 let _source_context = source_trace::restore_source_trace(&self.inner.source_trace.borrow());
1197 let Some(mut callback) = self.inner.recompose.take() else {
1198 return false;
1199 };
1200 self.begin_reads();
1201 (callback.body)(composer);
1202 self.end_reads(true);
1203 update_cell(&self.inner.recompose, |slot| {
1205 if slot.is_none() {
1206 *slot = Some(callback);
1207 }
1208 });
1209 true
1210 }
1211
1212 fn has_recompose_callback(&self) -> bool {
1213 read_cell(&self.inner.recompose, Option::is_some)
1214 }
1215
1216 fn snapshot_locals(&self, stack: &LocalStackSnapshot) {
1217 update_cell(&self.inner.local_stack, |locals| {
1218 let unchanged = match (&*locals, stack) {
1219 (Some(current), Some(stack)) => Rc::ptr_eq(current, stack),
1220 (current, stack) => current.is_none() && stack.is_none(),
1221 };
1222 if !unchanged {
1223 locals.clone_from(stack);
1224 }
1225 });
1226 }
1227
1228 fn local_stack(&self) -> LocalStackSnapshot {
1229 read_cell(&self.inner.local_stack, Clone::clone)
1230 }
1231
1232 fn set_parent_hint(&self, parent: Option<NodeId>) {
1233 self.inner.parent_hint.set(parent.unwrap_or(NO_PARENT_HINT));
1234 }
1235
1236 fn set_owner(&self, kind: OwnerKind, owner: Option<&RecomposeScope>) {
1238 update_cell(&self.inner.owner, |link| {
1239 let current = link
1240 .as_ref()
1241 .map(|(kind, owner)| (*kind, Weak::as_ptr(owner)));
1242 if current != owner.map(|owner| (kind, Rc::as_ptr(&owner.inner))) {
1243 *link = owner.map(|owner| (kind, owner.downgrade()));
1244 note_scope_activity_change();
1245 }
1246 });
1247 }
1248
1249 fn set_parent_scope(&self, parent: Option<&RecomposeScope>) {
1251 self.set_owner(OwnerKind::Parent, parent);
1252 }
1253
1254 fn owner_of_kind(&self, kind: OwnerKind) -> Option<RecomposeScope> {
1255 read_cell(&self.inner.owner, |owner| {
1256 owner
1257 .as_ref()
1258 .filter(|(owner_kind, _)| *owner_kind == kind)
1259 .and_then(|(_, owner)| RecomposeScope::upgrade(owner))
1260 })
1261 }
1262
1263 fn parent_scope(&self) -> Option<RecomposeScope> {
1264 self.owner_of_kind(OwnerKind::Parent)
1265 }
1266
1267 fn set_lifetime_owner_scope(&self, owner: Option<&RecomposeScope>) {
1270 self.set_owner(OwnerKind::Lifetime, owner);
1271 }
1272
1273 #[cfg(test)]
1274 fn lifetime_owner_scope(&self) -> Option<RecomposeScope> {
1275 self.owner_of_kind(OwnerKind::Lifetime)
1276 }
1277
1278 fn callback_promotion_target(&self) -> Option<RecomposeScope> {
1279 let mut current = self.parent_scope();
1280 while let Some(scope) = current {
1281 if scope.has_recompose_callback() {
1282 return Some(scope);
1283 }
1284 current = scope.parent_scope();
1285 }
1286 None
1287 }
1288
1289 fn parent_hint(&self) -> Option<NodeId> {
1290 Some(self.inner.parent_hint.get()).filter(|&hint| hint != NO_PARENT_HINT)
1291 }
1292
1293 pub(crate) fn group_anchor(&self) -> AnchorId {
1294 self.inner.group_anchor.get()
1295 }
1296
1297 pub(crate) fn set_group_anchor(&self, anchor: AnchorId) {
1298 self.inner.group_anchor.set(anchor);
1299 }
1300
1301 fn set_slots_host(&self, host: &SlotsHost) {
1302 let storage_key = host.storage_key();
1303 update_cell(&self.inner.slots_runtime_state, |runtime_state| {
1304 if self.inner.slots_storage_key.get() == storage_key
1305 && runtime_state.as_ref().map(std::rc::Weak::as_ptr) == host.runtime_state_ptr()
1306 {
1307 return;
1308 }
1309 self.inner.slots_storage_key.set(storage_key);
1310 *runtime_state = host.runtime_state().map(|state| Rc::downgrade(&state));
1311 });
1312 }
1313
1314 pub(crate) fn slots_storage_key(&self) -> Option<usize> {
1315 let key = self.inner.slots_storage_key.get();
1316 (key != 0).then_some(key)
1317 }
1318
1319 pub(crate) fn slots_host_identity(
1322 &self,
1323 ) -> (usize, *const crate::composer::ComposerRuntimeState) {
1324 let state = read_cell(&self.inner.slots_runtime_state, |state| {
1325 state
1326 .as_ref()
1327 .map_or(std::ptr::null(), std::rc::Weak::as_ptr)
1328 });
1329 (self.inner.slots_storage_key.get(), state)
1330 }
1331
1332 pub(crate) fn slots_runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
1333 read_cell(&self.inner.slots_runtime_state, |state| {
1334 state.as_ref().and_then(std::rc::Weak::upgrade)
1335 })
1336 }
1337
1338 pub fn deactivate(&self) {
1341 if !self.inner.active.replace(false) {
1342 return;
1343 }
1344 note_scope_activity_change();
1345 self.inner
1346 .deactivations
1347 .set(self.inner.deactivations.get() + 1);
1348 if self.inner.enqueued.replace(false) {
1349 self.inner.runtime.mark_scope_recomposed();
1350 }
1351 }
1352
1353 pub(crate) fn defer_until_reactivated(&self) {
1354 if self.inner.enqueued.replace(false) {
1355 self.inner.runtime.mark_scope_recomposed();
1356 }
1357 }
1358
1359 pub fn reactivate(&self) {
1362 self.inner.active.set(true);
1363 if self.inner.invalid.get()
1364 && self.is_effectively_active()
1365 && !self.inner.enqueued.replace(true)
1366 {
1367 self.inner.runtime.register_invalid_scope(self.downgrade());
1368 }
1369 }
1370
1371 pub fn force_reuse(&self) {
1374 self.inner.force_reuse.set(true);
1375 self.inner.force_recompose.set(false);
1376 self.inner.pending_recompose.set(true);
1377 }
1378
1379 pub(crate) fn request_pending_recompose(&self) {
1380 self.inner.pending_recompose.set(true);
1381 }
1382
1383 pub fn force_recompose(&self) {
1386 self.inner.force_recompose.set(true);
1387 self.inner.force_reuse.set(false);
1388 self.inner.pending_recompose.set(false);
1389 }
1390
1391 pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
1392 self.inner.retention_mode.set(mode);
1393 }
1394
1395 pub(crate) fn retention_mode(&self) -> RetentionMode {
1396 self.inner.retention_mode.get()
1397 }
1398
1399 pub fn should_recompose(&self) -> bool {
1403 if self.inner.force_recompose.replace(false) {
1404 self.inner.force_reuse.set(false);
1405 return true;
1406 }
1407 if self.inner.force_reuse.replace(false) {
1408 return false;
1409 }
1410 self.is_invalid()
1411 }
1412
1413 pub fn has_composed_once(&self) -> bool {
1415 self.inner.composed_once.get()
1416 }
1417
1418 fn mark_composed_once(&self) {
1419 self.inner.composed_once.set(true);
1420 }
1421
1422 fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
1423 if self.inner.unknown_invalidation_source.get() {
1424 return None;
1425 }
1426 read_cell(&self.inner.invalidation_sources, |sources| {
1427 (!sources.is_empty()).then(|| {
1428 sources
1429 .iter()
1430 .all(|source| allowed_sources.contains(source))
1431 })
1432 })
1433 }
1434
1435 fn has_unknown_invalidation_source(&self) -> bool {
1436 self.inner.unknown_invalidation_source.get()
1437 }
1438}
1439
1440#[cfg(test)]
1441impl RecomposeScope {
1442 pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
1443 Self::new(runtime)
1444 }
1445}
1446
1447#[derive(Debug, Clone, Copy, Default)]
1449pub struct RecomposeOptions {
1450 pub force_reuse: bool,
1452 pub force_recompose: bool,
1454 pub retention: RetentionMode,
1456}
1457
1458#[derive(Debug, Clone, PartialEq, Eq)]
1460pub enum NodeError {
1461 Missing {
1463 id: NodeId,
1465 },
1466 TypeMismatch {
1468 id: NodeId,
1470 expected: &'static str,
1472 },
1473 MissingContext {
1475 id: NodeId,
1477 reason: &'static str,
1479 },
1480 AlreadyExists {
1482 id: NodeId,
1484 },
1485 MalformedCommandPayload {
1487 tag: &'static str,
1489 },
1490 SlotHostUnavailable {
1492 operation: &'static str,
1494 reason: &'static str,
1496 },
1497 RecompositionLimitExceeded {
1499 operation: &'static str,
1501 limit: usize,
1503 },
1504}
1505
1506impl std::fmt::Display for NodeError {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 match self {
1509 NodeError::Missing { id } => write!(f, "node {id} missing"),
1510 NodeError::TypeMismatch { id, expected } => {
1511 write!(f, "node {id} type mismatch; expected {expected}")
1512 }
1513 NodeError::MissingContext { id, reason } => {
1514 write!(f, "missing context for node {id}: {reason}")
1515 }
1516 NodeError::AlreadyExists { id } => {
1517 write!(f, "node {id} already exists")
1518 }
1519 NodeError::MalformedCommandPayload { tag } => {
1520 write!(f, "command queue missing or invalid {tag} payload")
1521 }
1522 NodeError::SlotHostUnavailable { operation, reason } => {
1523 write!(f, "{operation} cannot access slot host: {reason}")
1524 }
1525 NodeError::RecompositionLimitExceeded { operation, limit } => {
1526 write!(
1527 f,
1528 "{operation} exceeded {limit} iterations while reconciling composition"
1529 )
1530 }
1531 }
1532 }
1533}
1534
1535impl std::error::Error for NodeError {}
1536
1537pub use subcompose::{
1538 ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
1539};
1540
1541#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1543pub enum Phase {
1544 Compose,
1546 Measure,
1548 Layout,
1550}
1551
1552pub use composer_context::{
1553 __current_composer, note_nested_slots_host, with_composer as with_current_composer,
1554};
1555
1556#[expect(non_snake_case)]
1559pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
1560 composer_context::with_composer(f)
1561}
1562
1563fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
1564 composer_context::try_with_composer(f)
1565}
1566
1567#[doc(hidden)]
1568pub fn current_recompose_scope_invalidated_only_by(
1569 allowed_sources: impl IntoIterator<Item = StateId>,
1570) -> Option<bool> {
1571 with_current_composer_opt(|composer| {
1572 let allowed_sources = allowed_sources.into_iter().collect();
1573 let mut scope = composer.current_recompose_scope();
1574 let mut saw_unknown_source = false;
1575 while let Some(current) = scope {
1576 if current.has_unknown_invalidation_source() {
1577 saw_unknown_source = true;
1578 scope = current.parent_scope();
1579 continue;
1580 }
1581 if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
1582 return Some(matches);
1583 }
1584 scope = current.parent_scope();
1585 }
1586 saw_unknown_source.then_some(false)
1587 })
1588 .flatten()
1589}
1590
1591fn key_scoped<K: Hash, R>(
1592 key: &K,
1593 caller: &'static std::panic::Location<'static>,
1594 content: impl FnOnce() -> R,
1595) -> R {
1596 let seed = explicit_group_key_seed(key, caller);
1597 with_current_composer(|composer| composer.with_group_seed(seed, |_| content()))
1598}
1599
1600#[track_caller]
1603pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
1604 key_scoped(key, std::panic::Location::caller(), content);
1605}
1606
1607#[track_caller]
1618pub fn key<K: Hash, R>(keys: K, content: impl FnOnce() -> R) -> R {
1619 key_scoped(&keys, std::panic::Location::caller(), content)
1620}
1621
1622#[track_caller]
1641pub fn movable<K: Hash>(key: K, content: impl FnOnce()) {
1642 let id = hash_key(&key);
1643 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1644}
1645
1646#[expect(non_snake_case)]
1680#[track_caller]
1681pub fn rememberMovableContentOf(content: impl Fn() + 'static) -> MovableContent {
1682 let runtime = with_current_composer(composer::Composer::runtime_handle);
1683 let id = remember(|| runtime.next_movable_content_id()).with(|id| *id);
1684 MovableContent {
1685 id,
1686 content: Rc::new(content),
1687 }
1688}
1689
1690#[expect(non_snake_case)]
1698pub fn movableContentOf<K: Hash>(key: K, content: impl Fn() + 'static) -> MovableContent {
1699 MovableContent {
1700 id: hash_key(&key),
1701 content: Rc::new(content),
1702 }
1703}
1704
1705#[derive(Clone)]
1707pub struct MovableContent {
1708 id: Key,
1709 content: Rc<dyn Fn()>,
1710}
1711
1712impl MovableContent {
1713 pub fn show(&self) {
1717 let content = Rc::clone(&self.content);
1718 let id = self.id;
1719 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1720 }
1721
1722 pub fn forget(&self) {
1725 forget_movable_id(self.id);
1726 }
1727}
1728
1729impl std::fmt::Debug for MovableContent {
1730 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1731 f.debug_struct("MovableContent")
1732 .field("id", &self.id)
1733 .finish()
1734 }
1735}
1736
1737impl PartialEq for MovableContent {
1738 fn eq(&self, other: &Self) -> bool {
1739 self.id == other.id && Rc::ptr_eq(&self.content, &other.content)
1740 }
1741}
1742
1743pub fn forget_movable<K: Hash>(key: K) {
1749 let id = hash_key(&key);
1750 forget_movable_id(id);
1751}
1752
1753fn forget_movable_id(id: Key) {
1754 let runtime = composer_context::try_with_composer(composer::Composer::runtime_handle)
1755 .or_else(runtime::current_runtime_handle);
1756 match runtime {
1757 Some(runtime) => runtime.forget_movable(id),
1758 None => log::error!("forget_movable called without an active runtime"),
1759 }
1760}
1761
1762#[derive(Default)]
1763struct DisposableEffectState {
1764 key: Option<effect_key::EffectKey>,
1765 cleanup: Option<Box<dyn FnOnce()>>,
1766}
1767
1768impl DisposableEffectState {
1769 fn should_run<K: PartialEq + 'static>(&self, keys: &K) -> bool {
1770 self.key.as_ref().is_none_or(|current| !current.holds(keys))
1771 }
1772
1773 fn set_key(&mut self, key: effect_key::EffectKey) {
1774 self.key = Some(key);
1775 }
1776
1777 fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
1778 self.cleanup = cleanup;
1779 }
1780
1781 fn run_cleanup(&mut self) {
1782 if let Some(cleanup) = self.cleanup.take() {
1783 cleanup();
1784 }
1785 }
1786}
1787
1788impl Drop for DisposableEffectState {
1789 fn drop(&mut self) {
1790 self.run_cleanup();
1791 }
1792}
1793
1794#[derive(Clone, Copy, Debug, Default)]
1797pub struct DisposableEffectScope;
1798
1799#[derive(Default)]
1802pub struct DisposableEffectResult {
1803 cleanup: Option<Box<dyn FnOnce()>>,
1804}
1805
1806impl DisposableEffectScope {
1807 pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
1810 DisposableEffectResult::new(cleanup)
1811 }
1812}
1813
1814impl DisposableEffectResult {
1815 pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
1817 Self {
1818 cleanup: Some(Box::new(cleanup)),
1819 }
1820 }
1821
1822 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
1823 self.cleanup
1824 }
1825}
1826
1827#[expect(non_snake_case)]
1830pub fn SideEffect(effect: impl FnOnce() + 'static) {
1831 with_current_composer(|composer| composer.register_side_effect(effect));
1832}
1833
1834#[doc(hidden)]
1837pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
1838where
1839 K: PartialEq + 'static,
1840 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1841{
1842 with_current_composer(|composer| {
1843 composer.with_group(group_key, |composer| {
1844 let state = composer.remember_effect::<DisposableEffectState>();
1845 if state.with(|state| state.should_run(&keys)) {
1846 let key = effect_key::EffectKey::new(keys);
1847 state.update(|state| {
1848 state.run_cleanup();
1849 state.set_key(key);
1850 });
1851 let mut effect_opt = Some(effect);
1852 composer.register_side_effect(move || {
1853 if let Some(effect) = effect_opt.take() {
1854 let result = effect(DisposableEffectScope);
1855 state.update(|state| state.set_cleanup(result.into_cleanup()));
1856 }
1857 });
1858 }
1859 });
1860 });
1861}
1862
1863#[expect(non_snake_case)]
1871#[track_caller]
1872pub fn DisposableEffect<K, F>(keys: K, effect: F)
1873where
1874 K: PartialEq + 'static,
1875 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1876{
1877 __disposable_effect_impl(crate::caller_location_key(), keys, effect);
1878}
1879
1880#[macro_export]
1885macro_rules! clone_captures {
1886 ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1887 $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1888 $body
1889 }};
1890 (@clone $alias:ident = $value:expr) => {
1891 ($value).clone()
1892 };
1893 (@clone $alias:ident) => {
1894 $alias.clone()
1895 };
1896}
1897
1898pub fn with_node_mut<N: Node + 'static, R>(
1901 id: NodeId,
1902 f: impl FnOnce(&mut N) -> R,
1903) -> Result<R, NodeError> {
1904 with_current_composer(|composer| composer.with_node_mut(id, f))
1905}
1906
1907pub fn push_parent(id: NodeId) {
1910 with_current_composer(|composer| composer.push_parent(id));
1911}
1912
1913pub fn pop_parent() {
1916 with_current_composer(composer::Composer::pop_parent);
1917}
1918
1919pub trait Node: Any {
1922 fn mount(&mut self) {}
1924 fn unmount(&mut self) {}
1926 fn insert_child(&mut self, _child: NodeId) -> bool {
1932 false
1933 }
1934 fn remove_child(&mut self, _child: NodeId) -> bool {
1939 false
1940 }
1941 fn move_child(&mut self, _from: usize, _to: usize) {}
1943 fn update_children(&mut self, _children: &[NodeId]) {}
1945 fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1949 out.clear();
1950 }
1951 fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1955 self.collect_children_into(out);
1956 }
1957 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1969 let mut children = SmallVec::<[NodeId; 8]>::new();
1970 self.collect_owned_children_into(&mut children);
1971 children.iter().position(|&id| id == child)
1972 }
1973 fn set_node_id(&mut self, _id: NodeId) {}
1976 fn on_attached_to_parent(&mut self, _parent: NodeId) {}
1979 fn on_removed_from_parent(&mut self) {}
1982 fn parent(&self) -> Option<NodeId> {
1985 None
1986 }
1987 fn mark_needs_layout(&self) {}
1990 fn needs_layout(&self) -> bool {
1992 false
1993 }
1994 fn mark_needs_measure(&self) {}
1997 fn needs_measure(&self) -> bool {
1999 false
2000 }
2001 fn mark_descendant_needs_layout(&self, measure: bool) {
2006 if measure {
2007 self.mark_needs_measure();
2008 } else {
2009 self.mark_needs_layout();
2010 }
2011 }
2012 fn descendant_needs_layout(&self) -> bool {
2014 false
2015 }
2016 fn descendant_needs_measure(&self) -> bool {
2018 false
2019 }
2020 fn layout_dirty(&self) -> bool {
2023 self.needs_measure() || self.needs_layout() || self.descendant_needs_layout()
2024 }
2025 fn is_virtual(&self) -> bool {
2028 false
2029 }
2030 fn mark_needs_semantics(&self) {}
2032 fn mark_descendant_needs_semantics(&self) {
2036 self.mark_needs_semantics();
2037 }
2038 fn needs_semantics(&self) -> bool {
2040 false
2041 }
2042 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
2051 if self.parent().is_none() {
2052 self.on_attached_to_parent(parent);
2053 }
2054 }
2055
2056 fn recycle_key(&self) -> Option<TypeId> {
2058 None
2059 }
2060
2061 fn recycle_pool_limit(&self) -> Option<usize> {
2063 None
2064 }
2065
2066 fn prepare_for_recycle(&mut self) {}
2068
2069 fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
2074 None
2075 }
2076
2077 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
2083 None
2084 }
2085
2086 fn debug_heap_bytes(&self) -> usize {
2088 0
2089 }
2090}
2091
2092pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2110 DirtyBubble::LAYOUT.apply(applier, node_id);
2111}
2112
2113pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2123 DirtyBubble::MEASURE.apply(applier, node_id);
2124}
2125
2126pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2132 DirtyBubble::SEMANTICS.apply(applier, node_id);
2133}
2134
2135pub fn queue_semantics_invalidation(node_id: NodeId) {
2140 let _ = composer_context::try_with_composer(|composer| {
2141 composer.enqueue_semantics_invalidation(node_id);
2142 });
2143}
2144
2145pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
2167 bubble_layout_dirty_composer::<N>(node_id);
2168}
2169
2170pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
2176 with_current_composer(|composer| {
2177 composer.commands_mut().push(Command::BubbleDirty {
2178 node_id,
2179 bubble: DirtyBubble {
2180 layout: false,
2181 measure: true,
2182 semantics: false,
2183 },
2184 });
2185 });
2186}
2187
2188pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
2195 bubble_semantics_dirty_composer::<N>(node_id);
2196}
2197
2198fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
2199 let _ = with_node_mut(node_id, |node: &mut N| {
2200 node.mark_needs_layout();
2201 });
2202
2203 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
2204 let parent_id = pid;
2205
2206 let advanced = with_node_mut(parent_id, |node: &mut N| {
2207 node.mark_descendant_needs_layout(false);
2208 true
2209 })
2210 .unwrap_or(false);
2211
2212 if advanced {
2213 node_id = parent_id;
2214 } else {
2215 break;
2216 }
2217 }
2218}
2219
2220fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
2221 let _ = with_node_mut(node_id, |node: &mut N| {
2222 node.mark_needs_semantics();
2223 });
2224
2225 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
2226 let parent_id = pid;
2227
2228 let advanced = with_node_mut(parent_id, |node: &mut N| {
2229 if !node.needs_semantics() {
2230 node.mark_descendant_needs_semantics();
2231 }
2232 true
2233 })
2234 .unwrap_or(false);
2235
2236 if advanced {
2237 node_id = parent_id;
2238 } else {
2239 break;
2240 }
2241 }
2242}
2243
2244impl dyn Node {
2245 pub fn as_any_mut(&mut self) -> &mut dyn Any {
2247 self
2248 }
2249}
2250
2251pub struct RecycledNode {
2254 stable_id: NodeId,
2255 node: Box<dyn Node>,
2256 warm_origin: bool,
2257}
2258
2259impl RecycledNode {
2260 fn new(stable_id: NodeId, mut node: Box<dyn Node>, warm_origin: bool) -> Self {
2264 let node = node.rehouse_for_recycle().unwrap_or_else(|| {
2265 node.prepare_for_recycle();
2266 node
2267 });
2268 Self {
2269 stable_id,
2270 node,
2271 warm_origin,
2272 }
2273 }
2274
2275 fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
2276 Self {
2277 stable_id,
2278 node,
2279 warm_origin,
2280 }
2281 }
2282
2283 pub fn stable_id(&self) -> NodeId {
2285 self.stable_id
2286 }
2287
2288 fn warm_origin(&self) -> bool {
2289 self.warm_origin
2290 }
2291
2292 fn set_warm_origin(&mut self, warm_origin: bool) {
2293 self.warm_origin = warm_origin;
2294 }
2295
2296 pub fn node_mut(&mut self) -> &mut dyn Node {
2298 self.node.as_mut()
2299 }
2300
2301 pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
2304 (self.stable_id, self.node, self.warm_origin)
2305 }
2306}
2307
2308#[derive(Debug, Clone, PartialEq, Eq)]
2310pub struct RecycledNodeInsertion {
2311 pub id: NodeId,
2313 pub stable_id_reused: bool,
2315 pub fallback_error: Option<NodeError>,
2317}
2318
2319impl RecycledNodeInsertion {
2320 fn reused(stable_id: NodeId) -> Self {
2321 Self {
2322 id: stable_id,
2323 stable_id_reused: true,
2324 fallback_error: None,
2325 }
2326 }
2327
2328 fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
2329 Self {
2330 id,
2331 stable_id_reused: false,
2332 fallback_error,
2333 }
2334 }
2335}
2336
2337pub trait Applier: Any {
2340 fn create(&mut self, node: Box<dyn Node>) -> NodeId;
2342 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
2344 fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
2346
2347 fn begin_bubble_batch(&mut self) {}
2351
2352 fn end_bubble_batch(&mut self) {}
2354
2355 fn forget_bubble_passes(&mut self) {}
2358
2359 fn note_bubble_pass(&mut self, _id: NodeId, _flags: u8) -> bool {
2364 false
2365 }
2366
2367 fn record_structural_change(&mut self, _parent_id: NodeId) {}
2373
2374 fn node_generation(&self, id: NodeId) -> u32;
2378
2379 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
2387
2388 fn insert_recycled_node_or_create(
2391 &mut self,
2392 stable_id: NodeId,
2393 node: Box<dyn Node>,
2394 ) -> RecycledNodeInsertion {
2395 let id = self.create(node);
2396 RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
2397 }
2398
2399 fn as_any(&self) -> &dyn Any
2401 where
2402 Self: Sized,
2403 {
2404 self
2405 }
2406
2407 fn as_any_mut(&mut self) -> &mut dyn Any
2409 where
2410 Self: Sized,
2411 {
2412 self
2413 }
2414
2415 fn compact(&mut self) {}
2417
2418 fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
2420 None
2421 }
2422
2423 fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
2425
2426 fn seed_recycled_node_shell(
2428 &mut self,
2429 _key: TypeId,
2430 _recycle_pool_limit: Option<usize>,
2431 _shell: Box<dyn Node>,
2432 ) {
2433 }
2434
2435 fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
2437
2438 fn clear_recycled_nodes(&mut self) {}
2440}
2441
2442type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
2443
2444#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2445pub(crate) struct DirtyBubble {
2446 layout: bool,
2447 measure: bool,
2448 semantics: bool,
2449}
2450
2451impl DirtyBubble {
2452 const LAYOUT: Self = Self {
2453 layout: true,
2454 measure: false,
2455 semantics: false,
2456 };
2457
2458 const MEASURE: Self = Self {
2459 layout: false,
2460 measure: true,
2461 semantics: false,
2462 };
2463
2464 pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
2465 layout: true,
2466 measure: true,
2467 semantics: false,
2468 };
2469
2470 pub(crate) const SEMANTICS: Self = Self {
2471 layout: false,
2472 measure: false,
2473 semantics: true,
2474 };
2475
2476 fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
2479 self.walk(applier, Some(node_id), true);
2480 }
2481
2482 fn apply_to_ancestors(self, applier: &mut dyn Applier, node_id: NodeId) {
2485 let parent = applier.get_mut(node_id).ok().and_then(|node| node.parent());
2486 self.walk(applier, parent, false);
2487 }
2488
2489 fn walk(self, applier: &mut dyn Applier, first: Option<NodeId>, mut start: bool) {
2496 let flags =
2497 u8::from(self.layout) | u8::from(self.measure) << 1 | u8::from(self.semantics) << 2;
2498 let mut next = first;
2499 while let Some(id) = next {
2500 if !start && applier.note_bubble_pass(id, flags) {
2501 break;
2502 }
2503 let Ok(node) = applier.get_mut(id) else {
2504 break;
2505 };
2506 self.mark(node, start);
2507 next = node.parent();
2508 start = start && node.is_virtual();
2509 }
2510 }
2511
2512 fn mark(self, node: &mut dyn Node, start: bool) {
2515 if start {
2516 if self.layout {
2517 node.mark_needs_layout();
2518 }
2519 if self.measure {
2520 node.mark_needs_measure();
2521 }
2522 if self.semantics {
2523 node.mark_needs_semantics();
2524 }
2525 return;
2526 }
2527 let marked = if self.measure {
2528 node.descendant_needs_measure()
2529 } else {
2530 node.descendant_needs_layout()
2531 };
2532 if (self.layout || self.measure) && !marked {
2533 node.mark_descendant_needs_layout(self.measure);
2534 }
2535 if self.semantics && !node.needs_semantics() {
2536 node.mark_descendant_needs_semantics();
2537 }
2538 }
2539}
2540
2541pub(crate) enum Command {
2542 BubbleDirty {
2543 node_id: NodeId,
2544 bubble: DirtyBubble,
2545 },
2546 RemoveNode {
2547 id: NodeId,
2548 },
2549 MountNode {
2550 id: NodeId,
2551 },
2552 AttachChild {
2553 parent_id: NodeId,
2554 child_id: NodeId,
2555 insert_index: Option<usize>,
2556 bubble: DirtyBubble,
2557 },
2558 InsertChild {
2559 parent_id: NodeId,
2560 child_id: NodeId,
2561 appended_index: usize,
2562 insert_index: usize,
2563 bubble: DirtyBubble,
2564 },
2565 MoveChild {
2566 parent_id: NodeId,
2567 from_index: usize,
2568 to_index: usize,
2569 bubble: DirtyBubble,
2570 },
2571 RemoveChild {
2572 parent_id: NodeId,
2573 child_id: NodeId,
2574 },
2575 DetachChild {
2576 parent_id: NodeId,
2577 child_id: NodeId,
2578 },
2579 SyncChildren {
2580 parent_id: NodeId,
2581 expected_children: ChildList,
2582 },
2583 Callback(CommandCallback),
2584}
2585
2586#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2587struct DeferredChildCleanup {
2588 child_id: NodeId,
2589 generation: u32,
2590 removed_from_parent: bool,
2591}
2592
2593#[derive(Default)]
2594struct DeferredChildCleanupQueue {
2595 pending: Vec<DeferredChildCleanup>,
2596 preserved: Vec<(NodeId, u32)>,
2597}
2598
2599impl DeferredChildCleanupQueue {
2600 fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
2601 if self
2602 .preserved
2603 .iter()
2604 .any(|&(preserved_id, preserved_generation)| {
2605 preserved_id == child_id && preserved_generation == generation
2606 })
2607 {
2608 return;
2609 }
2610 self.pending.push(DeferredChildCleanup {
2611 child_id,
2612 generation,
2613 removed_from_parent,
2614 });
2615 }
2616
2617 fn preserve(&mut self, child_id: NodeId, generation: u32) {
2618 if !self
2619 .preserved
2620 .iter()
2621 .any(|&(preserved_id, preserved_generation)| {
2622 preserved_id == child_id && preserved_generation == generation
2623 })
2624 {
2625 self.preserved.push((child_id, generation));
2626 }
2627 self.pending
2628 .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
2629 }
2630
2631 fn flush(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2632 for cleanup in self.pending.drain(..) {
2633 cleanup_detached_child(applier, cleanup)?;
2634 }
2635 Ok(())
2636 }
2637
2638 fn clear(&mut self) {
2639 self.pending.clear();
2640 self.preserved.clear();
2641 }
2642}
2643
2644impl Command {
2645 pub(crate) fn callback(
2646 callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
2647 ) -> Self {
2648 Self::Callback(Box::new(callback))
2649 }
2650
2651 pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2652 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2653 self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
2654 deferred_cleanup.flush(applier)
2655 }
2656
2657 fn apply_with_cleanup(
2658 self,
2659 applier: &mut dyn Applier,
2660 deferred_cleanup: &mut DeferredChildCleanupQueue,
2661 ) -> Result<(), NodeError> {
2662 match self {
2663 Self::BubbleDirty { node_id, bubble } => {
2664 bubble.apply(applier, node_id);
2665 Ok(())
2666 }
2667 Self::RemoveNode { id } => {
2668 if let Ok(node) = applier.get_mut(id) {
2669 node.unmount();
2670 }
2671 match applier.remove(id) {
2672 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2673 Err(err) => Err(err),
2674 }
2675 }
2676 Self::MountNode { id } => {
2677 let node = match applier.get_mut(id) {
2678 Ok(node) => node,
2679 Err(NodeError::Missing { .. }) => return Ok(()),
2680 Err(err) => return Err(err),
2681 };
2682 node.set_node_id(id);
2683 node.mount();
2684 Ok(())
2685 }
2686 Self::AttachChild {
2687 parent_id,
2688 child_id,
2689 insert_index,
2690 bubble,
2691 } => {
2692 attach_child_at(applier, parent_id, child_id, insert_index, bubble);
2693 Ok(())
2694 }
2695 Self::InsertChild {
2696 parent_id,
2697 child_id,
2698 appended_index,
2699 insert_index,
2700 bubble,
2701 } => {
2702 insert_child_with_reparenting(applier, parent_id, child_id);
2703 bubble.apply(applier, parent_id);
2704 if insert_index != appended_index
2705 && let Ok(parent_node) = applier.get_mut(parent_id)
2706 {
2707 parent_node.move_child(appended_index, insert_index);
2708 }
2709 Ok(())
2710 }
2711 Self::MoveChild {
2712 parent_id,
2713 from_index,
2714 to_index,
2715 bubble,
2716 } => {
2717 if let Ok(parent_node) = applier.get_mut(parent_id) {
2718 parent_node.move_child(from_index, to_index);
2719 }
2720 bubble.apply(applier, parent_id);
2721 note_structural_move(parent_id, from_index, to_index);
2722 applier.record_structural_change(parent_id);
2723 Ok(())
2724 }
2725 Self::RemoveChild {
2726 parent_id,
2727 child_id,
2728 } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
2729 Self::DetachChild {
2730 parent_id,
2731 child_id,
2732 } => {
2733 let generation = applier.node_generation(child_id);
2734 detach_child_from_parent(applier, parent_id, child_id)?;
2735 deferred_cleanup.preserve(child_id, generation);
2736 Ok(())
2737 }
2738 Self::SyncChildren {
2739 parent_id,
2740 expected_children,
2741 } => sync_children(applier, parent_id, &expected_children, deferred_cleanup),
2742 Self::Callback(callback) => callback(applier),
2743 }
2744 }
2745}
2746
2747const COMMAND_FLUSH_THRESHOLD: usize = 4096;
2748type ChildList = SmallVec<[NodeId; 4]>;
2749const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
2750
2751#[derive(Copy, Clone)]
2752enum CommandTag {
2753 BubbleDirty,
2754 RemoveNode,
2755 MountNode,
2756 AttachChild,
2757 InsertChild,
2758 MoveChild,
2759 RemoveChild,
2760 DetachChild,
2761 SyncChildren,
2762 Callback,
2763}
2764
2765impl CommandTag {
2766 fn label(self) -> &'static str {
2767 match self {
2768 Self::BubbleDirty => "BubbleDirty",
2769 Self::RemoveNode => "RemoveNode",
2770 Self::MountNode => "MountNode",
2771 Self::AttachChild => "AttachChild",
2772 Self::InsertChild => "InsertChild",
2773 Self::MoveChild => "MoveChild",
2774 Self::RemoveChild => "RemoveChild",
2775 Self::DetachChild => "DetachChild",
2776 Self::SyncChildren => "SyncChildren",
2777 Self::Callback => "Callback",
2778 }
2779 }
2780}
2781
2782#[derive(Copy, Clone)]
2783struct BubbleDirtyCommand {
2784 node_id: NodeId,
2785 bubble: DirtyBubble,
2786}
2787
2788#[derive(Copy, Clone)]
2789struct AttachChildCommand {
2790 parent_id: NodeId,
2791 child_id: NodeId,
2792 insert_index: Option<usize>,
2793 bubble: DirtyBubble,
2794}
2795
2796#[derive(Copy, Clone)]
2797struct InsertChildCommand {
2798 parent_id: NodeId,
2799 child_id: NodeId,
2800 appended_index: usize,
2801 insert_index: usize,
2802 bubble: DirtyBubble,
2803}
2804
2805#[derive(Copy, Clone)]
2806struct MoveChildCommand {
2807 parent_id: NodeId,
2808 from_index: usize,
2809 to_index: usize,
2810 bubble: DirtyBubble,
2811}
2812
2813#[derive(Copy, Clone)]
2814struct RemoveChildCommand {
2815 parent_id: NodeId,
2816 child_id: NodeId,
2817}
2818
2819#[derive(Copy, Clone)]
2820struct DetachChildCommand {
2821 parent_id: NodeId,
2822 child_id: NodeId,
2823}
2824
2825struct SyncChildrenCommand {
2826 parent_id: NodeId,
2827 child_start: usize,
2828 child_len: usize,
2829}
2830
2831#[derive(Default)]
2835pub(crate) struct CommandQueue {
2836 tags: Vec<CommandTag>,
2837 bubble_dirty: Vec<BubbleDirtyCommand>,
2838 remove_nodes: Vec<NodeId>,
2839 mount_nodes: Vec<NodeId>,
2840 attach_children: Vec<AttachChildCommand>,
2841 insert_children: Vec<InsertChildCommand>,
2842 move_children: Vec<MoveChildCommand>,
2843 remove_children: Vec<RemoveChildCommand>,
2844 detach_children: Vec<DetachChildCommand>,
2845 sync_children: Vec<SyncChildrenCommand>,
2846 sync_child_ids: Vec<NodeId>,
2847 callbacks: Vec<CommandCallback>,
2848 cleanup: DeferredChildCleanupQueue,
2849}
2850
2851impl CommandQueue {
2852 fn push_tag(&mut self, tag: CommandTag) {
2853 self.tags.push(tag);
2854 }
2855
2856 fn push(&mut self, command: Command) {
2857 match command {
2858 Command::BubbleDirty { node_id, bubble } => {
2859 self.bubble_dirty
2860 .push(BubbleDirtyCommand { node_id, bubble });
2861 self.push_tag(CommandTag::BubbleDirty);
2862 }
2863 Command::RemoveNode { id } => {
2864 self.remove_nodes.push(id);
2865 self.push_tag(CommandTag::RemoveNode);
2866 }
2867 Command::MountNode { id } => {
2868 self.mount_nodes.push(id);
2869 self.push_tag(CommandTag::MountNode);
2870 }
2871 Command::AttachChild {
2872 parent_id,
2873 child_id,
2874 insert_index,
2875 bubble,
2876 } => {
2877 self.attach_children.push(AttachChildCommand {
2878 parent_id,
2879 child_id,
2880 insert_index,
2881 bubble,
2882 });
2883 self.push_tag(CommandTag::AttachChild);
2884 }
2885 Command::InsertChild {
2886 parent_id,
2887 child_id,
2888 appended_index,
2889 insert_index,
2890 bubble,
2891 } => {
2892 self.insert_children.push(InsertChildCommand {
2893 parent_id,
2894 child_id,
2895 appended_index,
2896 insert_index,
2897 bubble,
2898 });
2899 self.push_tag(CommandTag::InsertChild);
2900 }
2901 Command::MoveChild {
2902 parent_id,
2903 from_index,
2904 to_index,
2905 bubble,
2906 } => {
2907 self.move_children.push(MoveChildCommand {
2908 parent_id,
2909 from_index,
2910 to_index,
2911 bubble,
2912 });
2913 self.push_tag(CommandTag::MoveChild);
2914 }
2915 Command::RemoveChild {
2916 parent_id,
2917 child_id,
2918 } => {
2919 self.remove_children.push(RemoveChildCommand {
2920 parent_id,
2921 child_id,
2922 });
2923 self.push_tag(CommandTag::RemoveChild);
2924 }
2925 Command::DetachChild {
2926 parent_id,
2927 child_id,
2928 } => {
2929 self.detach_children.push(DetachChildCommand {
2930 parent_id,
2931 child_id,
2932 });
2933 self.push_tag(CommandTag::DetachChild);
2934 }
2935 Command::SyncChildren {
2936 parent_id,
2937 expected_children,
2938 } => {
2939 let child_start = self.sync_child_ids.len();
2940 let child_len = expected_children.len();
2941 self.sync_child_ids.extend(expected_children);
2942 self.sync_children.push(SyncChildrenCommand {
2943 parent_id,
2944 child_start,
2945 child_len,
2946 });
2947 self.push_tag(CommandTag::SyncChildren);
2948 }
2949 Command::Callback(callback) => {
2950 self.callbacks.push(callback);
2951 self.push_tag(CommandTag::Callback);
2952 }
2953 }
2954 }
2955
2956 fn len(&self) -> usize {
2957 self.tags.len()
2958 }
2959
2960 fn capacity(&self) -> usize {
2961 self.tags.capacity()
2962 }
2963
2964 fn payload_len_bytes(&self) -> usize {
2965 self.bubble_dirty
2966 .len()
2967 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2968 .saturating_add(
2969 self.remove_nodes
2970 .len()
2971 .saturating_mul(std::mem::size_of::<NodeId>()),
2972 )
2973 .saturating_add(
2974 self.mount_nodes
2975 .len()
2976 .saturating_mul(std::mem::size_of::<NodeId>()),
2977 )
2978 .saturating_add(
2979 self.attach_children
2980 .len()
2981 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2982 )
2983 .saturating_add(
2984 self.insert_children
2985 .len()
2986 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2987 )
2988 .saturating_add(
2989 self.move_children
2990 .len()
2991 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2992 )
2993 .saturating_add(
2994 self.remove_children
2995 .len()
2996 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2997 )
2998 .saturating_add(
2999 self.detach_children
3000 .len()
3001 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
3002 )
3003 .saturating_add(
3004 self.sync_children
3005 .len()
3006 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
3007 )
3008 .saturating_add(
3009 self.sync_child_ids
3010 .len()
3011 .saturating_mul(std::mem::size_of::<NodeId>()),
3012 )
3013 .saturating_add(
3014 self.callbacks
3015 .len()
3016 .saturating_mul(std::mem::size_of::<CommandCallback>()),
3017 )
3018 }
3019
3020 fn payload_capacity_bytes(&self) -> usize {
3021 self.bubble_dirty
3022 .capacity()
3023 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
3024 .saturating_add(
3025 self.remove_nodes
3026 .capacity()
3027 .saturating_mul(std::mem::size_of::<NodeId>()),
3028 )
3029 .saturating_add(
3030 self.mount_nodes
3031 .capacity()
3032 .saturating_mul(std::mem::size_of::<NodeId>()),
3033 )
3034 .saturating_add(
3035 self.attach_children
3036 .capacity()
3037 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
3038 )
3039 .saturating_add(
3040 self.insert_children
3041 .capacity()
3042 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
3043 )
3044 .saturating_add(
3045 self.move_children
3046 .capacity()
3047 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
3048 )
3049 .saturating_add(
3050 self.remove_children
3051 .capacity()
3052 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
3053 )
3054 .saturating_add(
3055 self.detach_children
3056 .capacity()
3057 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
3058 )
3059 .saturating_add(
3060 self.sync_children
3061 .capacity()
3062 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
3063 )
3064 .saturating_add(
3065 self.sync_child_ids
3066 .capacity()
3067 .saturating_mul(std::mem::size_of::<NodeId>()),
3068 )
3069 .saturating_add(
3070 self.callbacks
3071 .capacity()
3072 .saturating_mul(std::mem::size_of::<CommandCallback>()),
3073 )
3074 }
3075
3076 pub(crate) fn apply(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
3079 applier.begin_bubble_batch();
3080 let result = self.apply_in_order(applier);
3081 applier.end_bubble_batch();
3082 self.clear();
3083 result
3084 }
3085
3086 fn clear(&mut self) {
3087 let Self {
3088 tags,
3089 bubble_dirty,
3090 remove_nodes,
3091 mount_nodes,
3092 attach_children,
3093 insert_children,
3094 move_children,
3095 remove_children,
3096 detach_children,
3097 sync_children,
3098 sync_child_ids,
3099 callbacks,
3100 cleanup,
3101 } = self;
3102 tags.clear();
3103 bubble_dirty.clear();
3104 remove_nodes.clear();
3105 mount_nodes.clear();
3106 attach_children.clear();
3107 insert_children.clear();
3108 move_children.clear();
3109 remove_children.clear();
3110 detach_children.clear();
3111 sync_children.clear();
3112 sync_child_ids.clear();
3113 callbacks.clear();
3114 cleanup.clear();
3115 }
3116
3117 fn apply_in_order(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
3118 let mut payloads = CommandPayloads {
3119 bubble_dirty: self.bubble_dirty.drain(..),
3120 remove_nodes: self.remove_nodes.drain(..),
3121 mount_nodes: self.mount_nodes.drain(..),
3122 attach_children: self.attach_children.drain(..),
3123 insert_children: self.insert_children.drain(..),
3124 move_children: self.move_children.drain(..),
3125 remove_children: self.remove_children.drain(..),
3126 detach_children: self.detach_children.drain(..),
3127 sync_children: self.sync_children.drain(..),
3128 sync_child_ids: &self.sync_child_ids,
3129 callbacks: self.callbacks.drain(..),
3130 };
3131 for &tag in &self.tags {
3132 payloads.apply(tag, applier, &mut self.cleanup)?;
3133 }
3134 payloads.assert_all_taken();
3135 self.cleanup.flush(applier)
3136 }
3137}
3138
3139struct CommandPayloads<'a> {
3142 bubble_dirty: std::vec::Drain<'a, BubbleDirtyCommand>,
3143 remove_nodes: std::vec::Drain<'a, NodeId>,
3144 mount_nodes: std::vec::Drain<'a, NodeId>,
3145 attach_children: std::vec::Drain<'a, AttachChildCommand>,
3146 insert_children: std::vec::Drain<'a, InsertChildCommand>,
3147 move_children: std::vec::Drain<'a, MoveChildCommand>,
3148 remove_children: std::vec::Drain<'a, RemoveChildCommand>,
3149 detach_children: std::vec::Drain<'a, DetachChildCommand>,
3150 sync_children: std::vec::Drain<'a, SyncChildrenCommand>,
3151 sync_child_ids: &'a [NodeId],
3152 callbacks: std::vec::Drain<'a, CommandCallback>,
3153}
3154
3155impl CommandPayloads<'_> {
3156 fn apply(
3157 &mut self,
3158 tag: CommandTag,
3159 applier: &mut dyn Applier,
3160 cleanup: &mut DeferredChildCleanupQueue,
3161 ) -> Result<(), NodeError> {
3162 match tag {
3163 CommandTag::BubbleDirty
3164 | CommandTag::RemoveNode
3165 | CommandTag::MountNode
3166 | CommandTag::Callback => self.apply_node_command(tag, applier, cleanup),
3167 CommandTag::AttachChild
3168 | CommandTag::InsertChild
3169 | CommandTag::MoveChild
3170 | CommandTag::RemoveChild
3171 | CommandTag::DetachChild
3172 | CommandTag::SyncChildren => self.apply_child_command(tag, applier, cleanup),
3173 }
3174 }
3175
3176 fn apply_node_command(
3177 &mut self,
3178 tag: CommandTag,
3179 applier: &mut dyn Applier,
3180 cleanup: &mut DeferredChildCleanupQueue,
3181 ) -> Result<(), NodeError> {
3182 match tag {
3183 CommandTag::BubbleDirty => {
3184 let BubbleDirtyCommand { node_id, bubble } =
3185 next_command_payload(&mut self.bubble_dirty, tag)?;
3186 Command::BubbleDirty { node_id, bubble }.apply_with_cleanup(applier, cleanup)?;
3187 }
3188 CommandTag::RemoveNode => {
3189 let id = next_command_payload(&mut self.remove_nodes, tag)?;
3190 Command::RemoveNode { id }.apply_with_cleanup(applier, cleanup)?;
3191 }
3192 CommandTag::MountNode => {
3193 let id = next_command_payload(&mut self.mount_nodes, tag)?;
3194 Command::MountNode { id }.apply_with_cleanup(applier, cleanup)?;
3195 }
3196 CommandTag::Callback => {
3197 let callback = next_command_payload(&mut self.callbacks, tag)?;
3198 Command::Callback(callback).apply_with_cleanup(applier, cleanup)?;
3199 applier.forget_bubble_passes();
3200 }
3201 CommandTag::AttachChild
3202 | CommandTag::InsertChild
3203 | CommandTag::MoveChild
3204 | CommandTag::RemoveChild
3205 | CommandTag::DetachChild
3206 | CommandTag::SyncChildren => return Err(command_payload_error(tag)),
3207 }
3208 Ok(())
3209 }
3210
3211 fn apply_child_command(
3212 &mut self,
3213 tag: CommandTag,
3214 applier: &mut dyn Applier,
3215 cleanup: &mut DeferredChildCleanupQueue,
3216 ) -> Result<(), NodeError> {
3217 match tag {
3218 CommandTag::AttachChild => {
3219 let AttachChildCommand {
3220 parent_id,
3221 child_id,
3222 insert_index,
3223 bubble,
3224 } = next_command_payload(&mut self.attach_children, tag)?;
3225 Command::AttachChild {
3226 parent_id,
3227 child_id,
3228 insert_index,
3229 bubble,
3230 }
3231 .apply_with_cleanup(applier, cleanup)?;
3232 }
3233 CommandTag::InsertChild => {
3234 let InsertChildCommand {
3235 parent_id,
3236 child_id,
3237 appended_index,
3238 insert_index,
3239 bubble,
3240 } = next_command_payload(&mut self.insert_children, tag)?;
3241 Command::InsertChild {
3242 parent_id,
3243 child_id,
3244 appended_index,
3245 insert_index,
3246 bubble,
3247 }
3248 .apply_with_cleanup(applier, cleanup)?;
3249 }
3250 CommandTag::MoveChild => {
3251 let MoveChildCommand {
3252 parent_id,
3253 from_index,
3254 to_index,
3255 bubble,
3256 } = next_command_payload(&mut self.move_children, tag)?;
3257 Command::MoveChild {
3258 parent_id,
3259 from_index,
3260 to_index,
3261 bubble,
3262 }
3263 .apply_with_cleanup(applier, cleanup)?;
3264 }
3265 CommandTag::RemoveChild => {
3266 let RemoveChildCommand {
3267 parent_id,
3268 child_id,
3269 } = next_command_payload(&mut self.remove_children, tag)?;
3270 Command::RemoveChild {
3271 parent_id,
3272 child_id,
3273 }
3274 .apply_with_cleanup(applier, cleanup)?;
3275 }
3276 CommandTag::DetachChild => {
3277 let DetachChildCommand {
3278 parent_id,
3279 child_id,
3280 } = next_command_payload(&mut self.detach_children, tag)?;
3281 Command::DetachChild {
3282 parent_id,
3283 child_id,
3284 }
3285 .apply_with_cleanup(applier, cleanup)?;
3286 }
3287 CommandTag::SyncChildren => self.sync_children(applier, cleanup)?,
3288 CommandTag::BubbleDirty
3289 | CommandTag::RemoveNode
3290 | CommandTag::MountNode
3291 | CommandTag::Callback => return Err(command_payload_error(tag)),
3292 }
3293 Ok(())
3294 }
3295
3296 fn sync_children(
3297 &mut self,
3298 applier: &mut dyn Applier,
3299 cleanup: &mut DeferredChildCleanupQueue,
3300 ) -> Result<(), NodeError> {
3301 let tag = CommandTag::SyncChildren;
3302 let SyncChildrenCommand {
3303 parent_id,
3304 child_start,
3305 child_len,
3306 } = next_command_payload(&mut self.sync_children, tag)?;
3307 let expected_children = child_start
3308 .checked_add(child_len)
3309 .and_then(|child_end| self.sync_child_ids.get(child_start..child_end))
3310 .ok_or_else(|| command_payload_error(tag))?;
3311 sync_children(applier, parent_id, expected_children, cleanup)
3312 }
3313
3314 fn assert_all_taken(&mut self) {
3315 debug_assert!(self.bubble_dirty.next().is_none());
3316 debug_assert!(self.remove_nodes.next().is_none());
3317 debug_assert!(self.mount_nodes.next().is_none());
3318 debug_assert!(self.attach_children.next().is_none());
3319 debug_assert!(self.insert_children.next().is_none());
3320 debug_assert!(self.move_children.next().is_none());
3321 debug_assert!(self.remove_children.next().is_none());
3322 debug_assert!(self.detach_children.next().is_none());
3323 debug_assert!(self.sync_children.next().is_none());
3324 debug_assert!(self.callbacks.next().is_none());
3325 }
3326}
3327
3328fn command_payload_error(tag: CommandTag) -> NodeError {
3329 NodeError::MalformedCommandPayload { tag: tag.label() }
3330}
3331
3332fn next_command_payload<T>(
3333 payloads: &mut impl Iterator<Item = T>,
3334 tag: CommandTag,
3335) -> Result<T, NodeError> {
3336 payloads.next().ok_or_else(|| command_payload_error(tag))
3337}
3338
3339fn attach_child_at(
3340 applier: &mut dyn Applier,
3341 parent_id: NodeId,
3342 child_id: NodeId,
3343 insert_index: Option<usize>,
3344 bubble: DirtyBubble,
3345) {
3346 if insert_child_with_reparenting(applier, parent_id, child_id) {
3347 if let Some(target) = insert_index {
3348 move_attached_child_left_to(applier, parent_id, child_id, target);
3349 }
3350 bubble.apply(applier, parent_id);
3351 } else if let Ok(child) = applier.get_mut(child_id) {
3352 let dirty_bubble = DirtyBubble {
3353 layout: child.layout_dirty(),
3354 measure: child.needs_measure() || child.descendant_needs_measure(),
3355 semantics: false,
3356 };
3357 dirty_bubble.apply_to_ancestors(applier, child_id);
3358 }
3359}
3360
3361fn move_attached_child_left_to(
3362 applier: &mut dyn Applier,
3363 parent_id: NodeId,
3364 child_id: NodeId,
3365 target: usize,
3366) {
3367 let Ok(parent_node) = applier.get_mut(parent_id) else {
3368 return;
3369 };
3370 let Some(current_index) = parent_node.owned_child_index(child_id) else {
3371 return;
3372 };
3373 if target < current_index {
3374 parent_node.move_child(current_index, target);
3375 note_structural_move(parent_id, current_index, target);
3376 }
3377}
3378
3379fn insert_child_with_reparenting(
3380 applier: &mut dyn Applier,
3381 parent_id: NodeId,
3382 child_id: NodeId,
3383) -> bool {
3384 if parent_id == child_id {
3385 debug_assert_ne!(
3386 parent_id, child_id,
3387 "a node cannot be attached as its own child"
3388 );
3389 return false;
3390 }
3391
3392 let old_parent = applier
3393 .get_mut(child_id)
3394 .ok()
3395 .and_then(|node| node.parent());
3396 if old_parent != Some(parent_id) {
3397 applier.forget_bubble_passes();
3398 }
3399 if let Some(old_parent_id) = old_parent
3400 && old_parent_id != parent_id
3401 {
3402 let removed = applier
3403 .get_mut(old_parent_id)
3404 .is_ok_and(|old_parent_node| old_parent_node.remove_child(child_id));
3405 if let Ok(child_node) = applier.get_mut(child_id) {
3406 child_node.on_removed_from_parent();
3407 }
3408 if removed {
3409 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, old_parent_id);
3410 note_structural("reparent-detach", old_parent_id, child_id);
3411 applier.record_structural_change(old_parent_id);
3412 }
3413 }
3414
3415 let inserted = applier
3416 .get_mut(parent_id)
3417 .is_ok_and(|parent_node| parent_node.insert_child(child_id));
3418 if inserted {
3419 note_structural("attach", parent_id, child_id);
3420 applier.record_structural_change(parent_id);
3421 }
3422 if let Ok(child_node) = applier.get_mut(child_id) {
3423 child_node.on_attached_to_parent(parent_id);
3424 }
3425 inserted
3426}
3427
3428fn apply_remove_child(
3429 applier: &mut dyn Applier,
3430 parent_id: NodeId,
3431 child_id: NodeId,
3432 deferred_cleanup: &mut DeferredChildCleanupQueue,
3433) -> Result<(), NodeError> {
3434 detach_child_from_parent(applier, parent_id, child_id)?;
3435
3436 let generation = applier.node_generation(child_id);
3437 let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
3438 node.parent().is_none()
3439 } else {
3440 return Ok(());
3441 };
3442 deferred_cleanup.push(child_id, generation, removed_from_parent);
3443 Ok(())
3444}
3445
3446fn detach_child_from_parent(
3447 applier: &mut dyn Applier,
3448 parent_id: NodeId,
3449 child_id: NodeId,
3450) -> Result<(), NodeError> {
3451 let removed = applier
3452 .get_mut(parent_id)
3453 .is_ok_and(|parent_node| parent_node.remove_child(child_id));
3454 if removed {
3455 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3456 note_structural("detach", parent_id, child_id);
3457 applier.record_structural_change(parent_id);
3458 }
3459
3460 if let Ok(node) = applier.get_mut(child_id) {
3461 match node.parent() {
3462 Some(existing_parent_id) if existing_parent_id == parent_id => {
3463 node.on_removed_from_parent();
3464 applier.forget_bubble_passes();
3465 }
3466 None => {}
3467 Some(_) => return Ok(()),
3468 }
3469 } else {
3470 return Ok(());
3471 }
3472
3473 Ok(())
3474}
3475
3476fn cleanup_detached_child(
3477 applier: &mut dyn Applier,
3478 cleanup: DeferredChildCleanup,
3479) -> Result<(), NodeError> {
3480 if applier.node_generation(cleanup.child_id) != cleanup.generation {
3481 return Ok(());
3482 }
3483
3484 let parent_id = match applier.get_mut(cleanup.child_id) {
3485 Ok(node) => node.parent(),
3486 Err(NodeError::Missing { .. }) => return Ok(()),
3487 Err(err) => return Err(err),
3488 };
3489 if parent_id.is_some() {
3490 return Ok(());
3491 }
3492
3493 if let Ok(node) = applier.get_mut(cleanup.child_id) {
3494 if !cleanup.removed_from_parent {
3495 node.on_removed_from_parent();
3496 }
3497 node.unmount();
3498 }
3499 match applier.remove(cleanup.child_id) {
3500 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
3501 Err(err) => Err(err),
3502 }
3503}
3504
3505fn remove_child_and_cleanup_now(
3506 applier: &mut dyn Applier,
3507 parent_id: NodeId,
3508 child_id: NodeId,
3509) -> Result<(), NodeError> {
3510 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
3511 apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
3512 deferred_cleanup.flush(applier)
3513}
3514
3515fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
3516 let mut scratch = SmallVec::<[NodeId; 8]>::new();
3517 if let Ok(node) = applier.get_mut(parent_id) {
3518 node.collect_children_into(&mut scratch);
3519 }
3520 let mut current = ChildList::new();
3521 current.extend(scratch);
3522 current
3523}
3524
3525fn sync_children(
3526 applier: &mut dyn Applier,
3527 parent_id: NodeId,
3528 expected_children: &[NodeId],
3529 deferred_cleanup: &mut DeferredChildCleanupQueue,
3530) -> Result<(), NodeError> {
3531 let mut current = collect_current_children(applier, parent_id);
3532 let children_changed = current.as_slice() != expected_children;
3533
3534 if children_changed {
3535 if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
3536 sync_children_small(
3537 applier,
3538 parent_id,
3539 &mut current,
3540 expected_children,
3541 deferred_cleanup,
3542 )?;
3543 } else {
3544 let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
3545 target_positions.reserve(expected_children.len());
3546 for (index, &child) in expected_children.iter().enumerate() {
3547 target_positions.insert(child, index);
3548 }
3549
3550 for index in (0..current.len()).rev() {
3551 let child = current[index];
3552 if !target_positions.contains_key(&child) {
3553 current.remove(index);
3554 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3555 }
3556 }
3557
3558 let mut current_positions = build_child_positions(¤t);
3559 for (target_index, &child) in expected_children.iter().enumerate() {
3560 if let Some(current_index) = current_positions.get(&child).copied() {
3561 if current_index != target_index {
3562 let from_index = current_index;
3563 let to_index = move_child_in_diff_state(
3564 &mut current,
3565 &mut current_positions,
3566 from_index,
3567 target_index,
3568 );
3569 Command::MoveChild {
3570 parent_id,
3571 from_index,
3572 to_index,
3573 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3574 }
3575 .apply(applier)?;
3576 }
3577 } else {
3578 let insert_index = target_index.min(current.len());
3579 let appended_index = current.len();
3580 insert_child_into_diff_state(
3581 &mut current,
3582 &mut current_positions,
3583 insert_index,
3584 child,
3585 );
3586 Command::InsertChild {
3587 parent_id,
3588 child_id: child,
3589 appended_index,
3590 insert_index,
3591 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3592 }
3593 .apply(applier)?;
3594 }
3595 }
3596 }
3597 }
3598
3599 reconcile_children(applier, parent_id, expected_children, !children_changed)
3600}
3601
3602fn sync_children_small(
3603 applier: &mut dyn Applier,
3604 parent_id: NodeId,
3605 current: &mut ChildList,
3606 expected_children: &[NodeId],
3607 deferred_cleanup: &mut DeferredChildCleanupQueue,
3608) -> Result<(), NodeError> {
3609 for index in (0..current.len()).rev() {
3610 let child = current[index];
3611 if !expected_children.contains(&child) {
3612 current.remove(index);
3613 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3614 }
3615 }
3616
3617 for (target_index, &child) in expected_children.iter().enumerate() {
3618 if let Some(current_index) = current
3619 .iter()
3620 .position(|¤t_child| current_child == child)
3621 {
3622 if current_index != target_index {
3623 let child = current.remove(current_index);
3624 let to_index = target_index.min(current.len());
3625 current.insert(to_index, child);
3626 Command::MoveChild {
3627 parent_id,
3628 from_index: current_index,
3629 to_index,
3630 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3631 }
3632 .apply(applier)?;
3633 }
3634 } else {
3635 let insert_index = target_index.min(current.len());
3636 let appended_index = current.len();
3637 current.insert(insert_index, child);
3638 Command::InsertChild {
3639 parent_id,
3640 child_id: child,
3641 appended_index,
3642 insert_index,
3643 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3644 }
3645 .apply(applier)?;
3646 }
3647 }
3648
3649 Ok(())
3650}
3651
3652fn reconcile_children(
3657 applier: &mut dyn Applier,
3658 parent_id: NodeId,
3659 expected_children: &[NodeId],
3660 children_unchanged: bool,
3661) -> Result<(), NodeError> {
3662 let mut repaired = false;
3663 if children_unchanged {
3664 debug_assert!(
3665 expected_children.iter().all(|&child_id| applier
3666 .get_mut(child_id)
3667 .map_or(true, |node| node.parent().is_none_or(|p| p == parent_id))),
3668 "the children a parent holds must name no other parent"
3669 );
3670 } else {
3671 for &child_id in expected_children {
3672 let needs_attach = applier
3673 .get_mut(child_id)
3674 .is_ok_and(|node| node.parent() != Some(parent_id));
3675 if needs_attach {
3676 insert_child_with_reparenting(applier, parent_id, child_id);
3677 repaired = true;
3678 }
3679 }
3680 }
3681
3682 let is_dirty = children_unchanged
3683 && applier
3684 .get_mut(parent_id)
3685 .is_ok_and(|node| node.layout_dirty());
3686
3687 if repaired {
3688 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3689 } else if is_dirty {
3690 DirtyBubble::LAYOUT.apply_to_ancestors(applier, parent_id);
3691 }
3692
3693 Ok(())
3694}
3695
3696#[derive(Default)]
3699pub struct MemoryApplier {
3700 state: Box<MemoryApplierState>,
3703}
3704
3705#[derive(Default)]
3706struct MemoryApplierState {
3707 nodes: Vec<Option<Box<dyn Node>>>,
3708 physical_stable_ids: Vec<u32>,
3709 physical_warm_recycled_origins: Vec<bool>,
3710 stable_index: stable_index::StableIndex,
3711 free_ids: BinaryHeap<Reverse<usize>>,
3712 high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
3713 high_id_warm_recycled_origins: HashMap<NodeId, bool>,
3714 high_id_generations: HashMap<NodeId, u32>,
3715 next_stable_id: NodeId,
3716 layout_runtime: Option<RuntimeHandle>,
3717 slots: Rc<RefCell<SlotTable>>,
3718 recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3719 returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3720 cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3721 recycled_node_limits: HashMap<TypeId, usize>,
3722 warm_recycled_node_targets: HashMap<TypeId, usize>,
3723 fresh_recyclable_creations: HashMap<TypeId, usize>,
3724 recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
3725 structural_change_parents: Vec<NodeId>,
3726 virtual_node_ids: HashSet<NodeId>,
3727 bubble_passes: bubble_passes::BubblePasses,
3728}
3729
3730#[derive(Default)]
3735pub struct SceneNodeAttachmentScratch {
3736 attached: NodeMarks,
3737 path: Vec<NodeId>,
3738}
3739
3740const ATTACHED: u8 = 1;
3741const DETACHED: u8 = 2;
3742
3743struct RemovalFrame {
3744 node_id: NodeId,
3745 children: SmallVec<[NodeId; 8]>,
3746 next_child: usize,
3747}
3748
3749#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3752pub struct MemoryApplierDebugStats {
3753 pub next_stable_id: NodeId,
3755 pub nodes_len: usize,
3757 pub nodes_cap: usize,
3759 pub physical_stable_ids_len: usize,
3761 pub physical_stable_ids_cap: usize,
3763 pub stable_to_physical_len: usize,
3765 pub stable_to_physical_cap: usize,
3767 pub stable_generations_len: usize,
3769 pub stable_generations_cap: usize,
3771 pub free_ids_len: usize,
3773 pub free_ids_cap: usize,
3775 pub high_id_nodes_len: usize,
3777 pub high_id_nodes_cap: usize,
3779 pub high_id_generations_len: usize,
3781 pub high_id_generations_cap: usize,
3783 pub recycled_type_count: usize,
3785 pub recycled_type_cap: usize,
3787 pub recycled_node_count: usize,
3789 pub recycled_node_capacity: usize,
3791 pub warm_recycled_node_id_count: usize,
3793 pub warm_recycled_node_id_capacity: usize,
3795}
3796
3797impl MemoryApplier {
3798 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
3799 const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
3800 const INVALID_STABLE_ID: u32 = u32::MAX;
3801 const INITIAL_DENSE_NODE_CAP: usize = 32;
3802 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
3803 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
3804
3805 fn pack_stable_id(stable_id: NodeId) -> u32 {
3806 u32::try_from(stable_id).expect("stable id overflow")
3807 }
3808
3809 fn unpack_stable_id(stable_id: u32) -> NodeId {
3810 stable_id as NodeId
3811 }
3812
3813 fn next_dense_node_target_len(old_len: usize) -> usize {
3814 if old_len < Self::INITIAL_DENSE_NODE_CAP {
3815 return Self::INITIAL_DENSE_NODE_CAP;
3816 }
3817 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
3818 return old_len.saturating_mul(2);
3819 }
3820
3821 let incremental_growth =
3822 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
3823 old_len.saturating_add(incremental_growth)
3824 }
3825
3826 fn ensure_dense_node_storage_capacity(&mut self) {
3827 let len = self
3828 .state
3829 .nodes
3830 .len()
3831 .max(self.state.physical_stable_ids.len())
3832 .max(self.state.physical_warm_recycled_origins.len());
3833 if len < self.state.nodes.capacity()
3834 && len < self.state.physical_stable_ids.capacity()
3835 && len < self.state.physical_warm_recycled_origins.capacity()
3836 {
3837 return;
3838 }
3839
3840 let target = Self::next_dense_node_target_len(len);
3841 if self.state.nodes.capacity() < target {
3842 self.state
3843 .nodes
3844 .reserve_exact(target.saturating_sub(self.state.nodes.len()));
3845 }
3846 if self.state.physical_stable_ids.capacity() < target {
3847 self.state
3848 .physical_stable_ids
3849 .reserve_exact(target.saturating_sub(self.state.physical_stable_ids.len()));
3850 }
3851 if self.state.physical_warm_recycled_origins.capacity() < target {
3852 self.state.physical_warm_recycled_origins.reserve_exact(
3853 target.saturating_sub(self.state.physical_warm_recycled_origins.len()),
3854 );
3855 }
3856 }
3857
3858 pub fn new() -> Self {
3860 Self::default()
3861 }
3862
3863 pub fn slots_handle(&self) -> Rc<RefCell<SlotTable>> {
3866 Rc::clone(&self.state.slots)
3867 }
3868
3869 pub fn scene_node_attached_to(&mut self, node_id: NodeId, root: NodeId) -> Option<NodeId> {
3875 let resolved = self.first_non_virtual_ancestor(node_id)?;
3876 self.is_attached_to(resolved, root).then_some(resolved)
3877 }
3878
3879 pub fn scene_nodes_attached_to(
3883 &mut self,
3884 nodes: impl IntoIterator<Item = NodeId>,
3885 root: NodeId,
3886 ) -> Vec<Option<NodeId>> {
3887 let mut result = Vec::new();
3888 self.scene_nodes_attached_to_into(
3889 nodes,
3890 root,
3891 &mut result,
3892 &mut SceneNodeAttachmentScratch::default(),
3893 );
3894 result
3895 }
3896
3897 pub fn scene_nodes_attached_to_into(
3915 &mut self,
3916 nodes: impl IntoIterator<Item = NodeId>,
3917 root: NodeId,
3918 output: &mut Vec<Option<NodeId>>,
3919 scratch: &mut SceneNodeAttachmentScratch,
3920 ) {
3921 let nodes = nodes.into_iter();
3922 output.clear();
3923 let (lower_bound, upper_bound) = nodes.size_hint();
3924 if lower_bound == 0 && upper_bound == Some(0) {
3925 scratch.path.clear();
3926 return;
3927 }
3928 scratch.attached.reset(self);
3929 scratch.attached.set(self, root, ATTACHED);
3930 output.reserve(lower_bound);
3931 scratch.path.clear();
3932 for node_id in nodes {
3933 let Some(resolved) = self.first_non_virtual_ancestor(node_id) else {
3934 output.push(None);
3935 continue;
3936 };
3937 let mut current = resolved;
3938 scratch.path.clear();
3939 let answer = loop {
3940 match scratch.attached.get(self, current) {
3941 ATTACHED => break true,
3942 DETACHED => break false,
3943 _ => {}
3944 }
3945 scratch.path.push(current);
3946 match self.get_mut(current).ok().and_then(|node| node.parent()) {
3947 Some(parent) if scratch.path.len() < 100_000 => current = parent,
3948 _ => break false,
3949 }
3950 };
3951 let mark = if answer { ATTACHED } else { DETACHED };
3952 for visited in scratch.path.drain(..) {
3953 scratch.attached.set(self, visited, mark);
3954 }
3955 output.push(answer.then_some(resolved));
3956 }
3957 }
3958
3959 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
3967 let mut candidates = Vec::new();
3968 self.take_structural_change_parents_into(&mut candidates);
3969 let mut seen = HashSet::<NodeId>::default();
3970 candidates.retain_mut(|parent_id| {
3971 let Some(resolved) = self.first_non_virtual_ancestor(*parent_id) else {
3972 return false;
3973 };
3974 *parent_id = resolved;
3975 self.is_attached_to(resolved, root) && seen.insert(resolved)
3976 });
3977 candidates
3978 }
3979
3980 pub fn take_structural_change_parents_into(&mut self, output: &mut Vec<NodeId>) {
3993 output.clear();
3994 std::mem::swap(&mut self.state.structural_change_parents, output);
3995 }
3996
3997 fn first_non_virtual_ancestor(&mut self, node_id: NodeId) -> Option<NodeId> {
3998 if self.state.virtual_node_ids.is_empty() {
3999 return Some(node_id);
4000 }
4001 let mut current = node_id;
4002 for _ in 0..100_000 {
4003 if !self.state.virtual_node_ids.contains(¤t) {
4004 return Some(current);
4005 }
4006 match self.get_mut(current) {
4007 Ok(node) => current = node.parent()?,
4008 Err(_) => return None,
4009 }
4010 }
4011 None
4012 }
4013
4014 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
4015 let mut current = node_id;
4016 for _ in 0..100_000 {
4017 if current == root {
4018 return true;
4019 }
4020 match self.get_mut(current) {
4021 Ok(node) => match node.parent() {
4022 Some(parent) => current = parent,
4023 None => return false,
4024 },
4025 Err(_) => return false,
4026 }
4027 }
4028 false
4029 }
4030
4031 pub fn with_node<N: Node + 'static, R>(
4034 &mut self,
4035 id: NodeId,
4036 f: impl FnOnce(&mut N) -> R,
4037 ) -> Result<R, NodeError> {
4038 let typed = self
4039 .get_mut(id)?
4040 .as_any_mut()
4041 .downcast_mut::<N>()
4042 .ok_or_else(|| NodeError::TypeMismatch {
4043 id,
4044 expected: std::any::type_name::<N>(),
4045 })?;
4046 Ok(f(typed))
4047 }
4048
4049 pub fn len(&self) -> usize {
4051 self.state.nodes.iter().filter(|n| n.is_some()).count()
4052 }
4053
4054 pub fn capacity(&self) -> usize {
4056 self.state.nodes.len()
4057 }
4058
4059 pub fn tombstone_count(&self) -> usize {
4061 self.state.nodes.iter().filter(|n| n.is_none()).count()
4062 }
4063
4064 pub fn freelist_len(&self) -> usize {
4066 self.state.free_ids.len()
4067 }
4068
4069 pub fn debug_recycled_node_count(&self) -> usize {
4071 self.total_recycled_node_count()
4072 }
4073
4074 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
4076 let key = TypeId::of::<N>();
4077 self.state.recycled_nodes.get(&key).map_or(0, Vec::len)
4078 + self
4079 .state
4080 .returning_recycled_nodes
4081 .get(&key)
4082 .map_or(0, Vec::len)
4083 + self.state.cold_recycled_nodes.get(&key).map_or(0, Vec::len)
4084 }
4085
4086 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
4088 let mut recycled_keys: HashSet<TypeId> = HashSet::default();
4089 recycled_keys.extend(self.state.recycled_nodes.keys().copied());
4090 recycled_keys.extend(self.state.returning_recycled_nodes.keys().copied());
4091 recycled_keys.extend(self.state.cold_recycled_nodes.keys().copied());
4092
4093 MemoryApplierDebugStats {
4094 next_stable_id: self.state.next_stable_id,
4095 nodes_len: self.len(),
4096 nodes_cap: self.state.nodes.len(),
4097 physical_stable_ids_len: self.state.physical_stable_ids.len(),
4098 physical_stable_ids_cap: self.state.physical_stable_ids.capacity(),
4099 stable_to_physical_len: self.state.stable_index.live(),
4100 stable_to_physical_cap: self.state.stable_index.capacity(),
4101 stable_generations_len: self.state.stable_index.occupied(),
4102 stable_generations_cap: self.state.stable_index.capacity(),
4103 free_ids_len: self.state.free_ids.len(),
4104 free_ids_cap: self.state.free_ids.capacity(),
4105 high_id_nodes_len: self.state.high_id_nodes.len(),
4106 high_id_nodes_cap: self.state.high_id_nodes.capacity(),
4107 high_id_generations_len: self.state.high_id_generations.len(),
4108 high_id_generations_cap: self.state.high_id_generations.capacity(),
4109 recycled_type_count: recycled_keys.len(),
4110 recycled_type_cap: self.state.recycled_nodes.capacity()
4111 + self.state.returning_recycled_nodes.capacity()
4112 + self.state.cold_recycled_nodes.capacity(),
4113 recycled_node_count: self.total_recycled_node_count(),
4114 recycled_node_capacity: self.total_recycled_node_capacity(),
4115 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
4116 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
4117 }
4118 }
4119
4120 pub fn is_empty(&self) -> bool {
4122 self.len() == 0
4123 }
4124
4125 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut dyn Node)) {
4127 for node in self.state.nodes.iter_mut().flatten() {
4128 visit(node.as_mut());
4129 }
4130 for node in self.state.high_id_nodes.values_mut() {
4131 visit(node.as_mut());
4132 }
4133 }
4134
4135 pub fn debug_live_node_heap_bytes(&self) -> usize {
4137 let dense_nodes = self
4138 .state
4139 .nodes
4140 .iter()
4141 .flatten()
4142 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
4143 .sum::<usize>();
4144 let high_id_nodes = self
4145 .state
4146 .high_id_nodes
4147 .values()
4148 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
4149 .sum::<usize>();
4150 dense_nodes + high_id_nodes
4151 }
4152
4153 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
4155 let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
4156 pools
4157 .values()
4158 .flat_map(|nodes| nodes.iter())
4159 .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
4160 .sum::<usize>()
4161 };
4162
4163 pool_bytes(&self.state.recycled_nodes)
4164 + pool_bytes(&self.state.returning_recycled_nodes)
4165 + pool_bytes(&self.state.cold_recycled_nodes)
4166 }
4167
4168 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
4171 self.state.layout_runtime = Some(handle);
4172 }
4173
4174 pub fn clear_runtime_handle(&mut self) {
4176 self.state.layout_runtime = None;
4177 }
4178
4179 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
4181 self.state.layout_runtime.clone()
4182 }
4183
4184 fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
4185 pools.values().map(Vec::len).sum()
4186 }
4187
4188 fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
4189 pools.values().map(Vec::capacity).sum()
4190 }
4191
4192 fn total_recycled_node_count(&self) -> usize {
4193 Self::pool_node_count(&self.state.recycled_nodes)
4194 + Self::pool_node_count(&self.state.returning_recycled_nodes)
4195 + Self::pool_node_count(&self.state.cold_recycled_nodes)
4196 }
4197
4198 fn total_recycled_node_capacity(&self) -> usize {
4199 Self::pool_node_capacity(&self.state.recycled_nodes)
4200 + Self::pool_node_capacity(&self.state.returning_recycled_nodes)
4201 + Self::pool_node_capacity(&self.state.cold_recycled_nodes)
4202 }
4203
4204 fn total_warm_recycled_node_id_count(&self) -> usize {
4205 self.live_warm_recycled_origin_count()
4206 + Self::pool_node_count(&self.state.recycled_nodes)
4207 + Self::pool_node_count(&self.state.returning_recycled_nodes)
4208 }
4209
4210 fn total_warm_recycled_node_id_capacity(&self) -> usize {
4211 self.live_warm_recycled_origin_capacity()
4212 + Self::pool_node_capacity(&self.state.recycled_nodes)
4213 + Self::pool_node_capacity(&self.state.returning_recycled_nodes)
4214 }
4215
4216 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
4217 if let Some(limit) = recycle_pool_limit {
4218 self.state.recycled_node_limits.insert(key, limit);
4219 } else {
4220 self.state.recycled_node_limits.remove(&key);
4221 }
4222 }
4223
4224 fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
4225 self.state.recycled_node_limits.get(&key).copied()
4226 }
4227
4228 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
4229 self.state.recycled_nodes.get(&key).map_or(0, Vec::len)
4230 }
4231
4232 fn warm_recycled_node_target(&self, key: TypeId) -> usize {
4233 self.state
4234 .warm_recycled_node_targets
4235 .get(&key)
4236 .copied()
4237 .unwrap_or(0)
4238 }
4239
4240 fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
4241 let Some(limit) = self.recycle_pool_limit_for(key) else {
4242 return usize::MAX;
4243 };
4244 if limit <= 8 { limit } else { limit / 4 }
4245 }
4246
4247 fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
4248 let target_limit = self.warm_recycled_node_target_limit(key);
4249 let existing = self.warm_recycled_node_target(key).min(target_limit);
4250 if observed_demand == 0 {
4251 return existing;
4252 }
4253
4254 let target = match self.recycle_pool_limit_for(key) {
4255 Some(limit) if limit > 8 => target_limit,
4256 Some(_) => observed_demand.min(target_limit),
4257 None => observed_demand,
4258 };
4259 self.state.warm_recycled_node_targets.insert(key, target);
4260 target
4261 }
4262
4263 fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
4264 if self.state.recycled_node_prototypes.contains_key(&key) {
4265 return;
4266 }
4267 if let Some(prototype) = shell.rehouse_for_recycle() {
4268 self.state.recycled_node_prototypes.insert(key, prototype);
4269 }
4270 }
4271
4272 fn live_warm_recycled_origin_count(&self) -> usize {
4273 self.state
4274 .physical_warm_recycled_origins
4275 .iter()
4276 .zip(self.state.nodes.iter())
4277 .filter(|(warm_origin, node)| **warm_origin && node.is_some())
4278 .count()
4279 + self
4280 .state
4281 .high_id_warm_recycled_origins
4282 .values()
4283 .filter(|warm_origin| **warm_origin)
4284 .count()
4285 }
4286
4287 fn live_warm_recycled_origin_capacity(&self) -> usize {
4288 self.state.physical_warm_recycled_origins.capacity()
4289 + self.state.high_id_warm_recycled_origins.capacity()
4290 }
4291
4292 fn push_recycled_node(
4293 &mut self,
4294 key: TypeId,
4295 recycle_pool_limit: Option<usize>,
4296 recycled: RecycledNode,
4297 ) {
4298 self.remember_recycle_pool_limit(key, recycle_pool_limit);
4299 self.remember_recycled_node_prototype(key, recycled.node.as_ref());
4300
4301 let warm_origin = recycled.warm_origin();
4302 let pool = if warm_origin {
4303 self.state.returning_recycled_nodes.entry(key).or_default()
4304 } else {
4305 self.state.cold_recycled_nodes.entry(key).or_default()
4306 };
4307 pool.push(recycled);
4308 if let Some(limit) = recycle_pool_limit
4309 && pool.len() > limit
4310 {
4311 let excess = pool.len() - limit;
4312 let dropped: Vec<_> = pool.drain(0..excess).collect();
4313 drop(dropped);
4314 }
4315 }
4316
4317 fn push_warm_recycled_node(
4318 &mut self,
4319 key: TypeId,
4320 recycle_pool_limit: Option<usize>,
4321 mut recycled: RecycledNode,
4322 ) {
4323 self.remember_recycle_pool_limit(key, recycle_pool_limit);
4324
4325 recycled.set_warm_origin(true);
4326 let mut dropped = Vec::new();
4327 let mut remove_pool_entry = false;
4328 {
4329 let pool = self.state.recycled_nodes.entry(key).or_default();
4330 pool.push(recycled);
4331 if let Some(limit) = recycle_pool_limit
4332 && pool.len() > limit
4333 {
4334 let excess = pool.len() - limit;
4335 dropped = pool.drain(0..excess).collect();
4336 remove_pool_entry = pool.is_empty();
4337 }
4338 }
4339 if remove_pool_entry {
4340 self.state.recycled_nodes.remove(&key);
4341 }
4342 drop(dropped);
4343 }
4344
4345 fn seed_recycled_node_shell_impl(
4346 &mut self,
4347 key: TypeId,
4348 recycle_pool_limit: Option<usize>,
4349 shell: Box<dyn Node>,
4350 ) {
4351 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
4352 if self.warm_recycled_pool_len(key) >= limit {
4353 return;
4354 }
4355
4356 self.remember_recycled_node_prototype(key, shell.as_ref());
4357 let stable_id = self.state.next_stable_id;
4358 self.state.next_stable_id = self.state.next_stable_id.saturating_add(1);
4359 self.push_warm_recycled_node(
4360 key,
4361 recycle_pool_limit,
4362 RecycledNode::from_shell(stable_id, shell, true),
4363 );
4364 }
4365
4366 fn take_recycled_node_from_pool(
4367 pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
4368 key: TypeId,
4369 ) -> Option<RecycledNode> {
4370 let pool = pools.get_mut(&key)?;
4371 let node = pool.pop();
4372 if pool.is_empty() {
4373 pools.remove(&key);
4374 }
4375 node
4376 }
4377
4378 fn compact_idle_warm_pool(&mut self, key: TypeId) {
4379 let Some(pool) = self.state.recycled_nodes.get_mut(&key) else {
4380 return;
4381 };
4382 if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
4383 return;
4384 }
4385
4386 let retained = pool.len();
4387 let mut compacted = Vec::with_capacity(retained);
4388 compacted.append(pool);
4389 let remove_pool_entry = compacted.is_empty();
4390 *pool = compacted;
4391 let _ = pool;
4392
4393 if remove_pool_entry {
4394 self.state.recycled_nodes.remove(&key);
4395 }
4396 }
4397
4398 fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4399 let pool_len = self.warm_recycled_pool_len(key);
4400 if pool_len <= target {
4401 return;
4402 }
4403
4404 let Some(pool) = self.state.recycled_nodes.get_mut(&key) else {
4405 return;
4406 };
4407 let removable = (pool_len - target).min(pool.len());
4408 let dropped: Vec<_> = pool.drain(0..removable).collect();
4409 let remove_pool_entry = pool.is_empty();
4410 let _ = pool;
4411
4412 if remove_pool_entry {
4413 self.state.recycled_nodes.remove(&key);
4414 }
4415 drop(dropped);
4416 }
4417
4418 fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4419 let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
4420 if missing == 0 {
4421 return;
4422 }
4423
4424 let recycle_pool_limit = self.recycle_pool_limit_for(key);
4425 let mut shells = Vec::with_capacity(missing);
4426 if let Some(prototype) = self.state.recycled_node_prototypes.get(&key) {
4427 for _ in 0..missing {
4428 let Some(shell) = prototype.rehouse_for_recycle() else {
4429 break;
4430 };
4431 shells.push(shell);
4432 }
4433 }
4434
4435 for shell in shells {
4436 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4437 }
4438 }
4439
4440 fn prune_stable_generations(&mut self) {
4441 let retained_len = self.state.stable_index.live() + self.total_recycled_node_count();
4442 if retained_len == self.state.stable_index.occupied() {
4443 return;
4444 }
4445
4446 let recycled: HashSet<NodeId> = [
4447 &self.state.recycled_nodes,
4448 &self.state.returning_recycled_nodes,
4449 &self.state.cold_recycled_nodes,
4450 ]
4451 .into_iter()
4452 .flat_map(|pools| pools.values().flatten().map(RecycledNode::stable_id))
4453 .collect();
4454 self.state
4455 .stable_index
4456 .retain_retired(|stable_id| recycled.contains(&stable_id));
4457 }
4458
4459 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
4462 let mut output = String::new();
4463 if let Some(root_id) = root {
4464 self.dump_node(&mut output, root_id, 0);
4465 } else {
4466 output.push_str("(no root)\n");
4467 }
4468 output
4469 }
4470
4471 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
4472 let indent = " ".repeat(depth);
4473 let Ok(node) = self.get_ref(id) else {
4474 if let Some(physical_id) = self.resolve_node_index(id) {
4475 output.push_str(&format!(
4476 "{indent}[{id}] (missing physical node {physical_id})\n"
4477 ));
4478 } else {
4479 output.push_str(&format!("{indent}[{id}] (missing)\n"));
4480 }
4481 return;
4482 };
4483 let type_name = std::any::type_name_of_val(node);
4484 output.push_str(&format!("{indent}[{id}] {type_name}\n"));
4485
4486 let mut children = SmallVec::<[NodeId; 8]>::new();
4487 node.collect_children_into(&mut children);
4488 for child_id in children {
4489 self.dump_node(output, child_id, depth + 1);
4490 }
4491 }
4492
4493 fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
4494 self.state.stable_index.slot(id)
4495 }
4496
4497 fn contains_node_id(&self, id: NodeId) -> bool {
4498 self.resolve_node_index(id).is_some() || self.state.high_id_nodes.contains_key(&id)
4499 }
4500
4501 fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
4502 self.state.high_id_nodes.insert(stable_id, node);
4503 self.state
4504 .high_id_warm_recycled_origins
4505 .insert(stable_id, warm_origin);
4506 self.state.high_id_generations.entry(stable_id).or_insert(0);
4507 }
4508
4509 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
4510 if stable_id >= Self::HIGH_ID_THRESHOLD {
4511 self.insert_high_id_node(stable_id, node, false);
4512 return;
4513 }
4514
4515 let physical_id = if let Some(Reverse(free_physical_id)) = self.state.free_ids.pop() {
4516 self.state.nodes[free_physical_id] = Some(node);
4517 self.state.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
4518 self.state.physical_warm_recycled_origins[free_physical_id] = false;
4519 free_physical_id
4520 } else {
4521 self.ensure_dense_node_storage_capacity();
4522 let physical_id = self.state.nodes.len();
4523 self.state.nodes.push(Some(node));
4524 self.state
4525 .physical_stable_ids
4526 .push(Self::pack_stable_id(stable_id));
4527 self.state.physical_warm_recycled_origins.push(false);
4528 physical_id
4529 };
4530
4531 self.state.next_stable_id = self.state.next_stable_id.max(stable_id.saturating_add(1));
4532 self.state.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
4533 self.state.stable_index.set_slot(stable_id, physical_id);
4534 }
4535
4536 pub fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4539 if let Some(physical_id) = self.resolve_node_index(id) {
4540 let slot = self
4541 .state
4542 .nodes
4543 .get(physical_id)
4544 .ok_or(NodeError::Missing { id })?
4545 .as_deref()
4546 .ok_or(NodeError::Missing { id })?;
4547 return Ok(slot);
4548 }
4549
4550 self.state
4551 .high_id_nodes
4552 .get(&id)
4553 .map(AsRef::as_ref)
4554 .ok_or(NodeError::Missing { id })
4555 }
4556
4557 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
4558 Ok(self.get_ref(id)?.parent())
4559 }
4560
4561 fn collect_owned_children(
4562 &self,
4563 node_id: NodeId,
4564 out: &mut SmallVec<[NodeId; 8]>,
4565 ) -> Result<(), NodeError> {
4566 self.get_ref(node_id)?.collect_owned_children_into(out);
4567 out.retain(|child_id| {
4568 self.node_parent(*child_id)
4569 .is_ok_and(|parent| parent == Some(node_id))
4570 });
4571 Ok(())
4572 }
4573
4574 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
4575 self.state.virtual_node_ids.remove(&node_id);
4576 if self.state.high_id_nodes.contains_key(&node_id) {
4577 if let Some(node) = self.state.high_id_nodes.remove(&node_id)
4578 && let Some(key) = node.recycle_key()
4579 {
4580 let recycle_pool_limit = node.recycle_pool_limit();
4581 let warm_origin = self
4582 .state
4583 .high_id_warm_recycled_origins
4584 .remove(&node_id)
4585 .unwrap_or(false);
4586 self.push_recycled_node(
4587 key,
4588 recycle_pool_limit,
4589 RecycledNode::new(node_id, node, warm_origin),
4590 );
4591 }
4592 let generation = self.state.high_id_generations.entry(node_id).or_insert(0);
4593 *generation = generation.wrapping_add(1);
4594 return Ok(());
4595 }
4596
4597 let physical_id = self
4598 .resolve_node_index(node_id)
4599 .ok_or(NodeError::Missing { id: node_id })?;
4600 if let Some(node) = self.state.nodes[physical_id].take()
4601 && let Some(key) = node.recycle_key()
4602 {
4603 let recycle_pool_limit = node.recycle_pool_limit();
4604 let warm_origin = self
4605 .state
4606 .physical_warm_recycled_origins
4607 .get_mut(physical_id)
4608 .is_some_and(std::mem::take);
4609 self.push_recycled_node(
4610 key,
4611 recycle_pool_limit,
4612 RecycledNode::new(node_id, node, warm_origin),
4613 );
4614 }
4615 self.state.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
4616 self.state.stable_index.release(node_id);
4617 self.state.free_ids.push(Reverse(physical_id));
4618 Ok(())
4619 }
4620
4621 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
4622 self.get_ref(id)?;
4623
4624 let mut root_children = SmallVec::<[NodeId; 8]>::new();
4625 self.collect_owned_children(id, &mut root_children)?;
4626
4627 let mut stack = Vec::new();
4628 stack.push(RemovalFrame {
4629 node_id: id,
4630 children: root_children,
4631 next_child: 0,
4632 });
4633 let mut max_depth = stack.len();
4634
4635 while let Some(frame) = stack.last_mut() {
4636 if frame.next_child < frame.children.len() {
4637 let child_id = frame.children[frame.next_child];
4638 frame.next_child += 1;
4639
4640 if let Ok(child) = self.get_mut(child_id) {
4641 child.on_removed_from_parent();
4642 child.unmount();
4643 }
4644
4645 let mut child_children = SmallVec::<[NodeId; 8]>::new();
4646 self.collect_owned_children(child_id, &mut child_children)?;
4647 stack.push(RemovalFrame {
4648 node_id: child_id,
4649 children: child_children,
4650 next_child: 0,
4651 });
4652 max_depth = max_depth.max(stack.len());
4653 continue;
4654 }
4655
4656 let node_id = frame.node_id;
4657 stack.pop();
4658 self.remove_node_storage(node_id)?;
4659 }
4660
4661 Ok(max_depth)
4662 }
4663
4664 #[cfg(test)]
4665 fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
4666 self.remove_subtree_postorder(id)
4667 }
4668}
4669
4670impl Applier for MemoryApplier {
4671 fn record_structural_change(&mut self, parent_id: NodeId) {
4672 if self.state.structural_change_parents.last() != Some(&parent_id) {
4673 self.state.structural_change_parents.push(parent_id);
4674 }
4675 }
4676
4677 fn create(&mut self, node: Box<dyn Node>) -> NodeId {
4678 let stable_id = self.state.next_stable_id;
4679 self.state.next_stable_id = self.state.next_stable_id.saturating_add(1);
4680 if stable_id >= Self::HIGH_ID_THRESHOLD {
4681 self.insert_high_id_node(stable_id, node, false);
4682 return stable_id;
4683 }
4684
4685 let physical_id = if let Some(Reverse(id)) = self.state.free_ids.pop() {
4686 debug_assert!(
4687 self.state.nodes[id].is_none(),
4688 "freelist entry {id} is not None"
4689 );
4690 self.state.nodes[id] = Some(node);
4691 self.state.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
4692 self.state.physical_warm_recycled_origins[id] = false;
4693 id
4694 } else {
4695 self.ensure_dense_node_storage_capacity();
4696 let id = self.state.nodes.len();
4697 self.state.nodes.push(Some(node));
4698 self.state
4699 .physical_stable_ids
4700 .push(Self::pack_stable_id(stable_id));
4701 self.state.physical_warm_recycled_origins.push(false);
4702 id
4703 };
4704 self.state.stable_index.insert_fresh(stable_id, physical_id);
4705 stable_id
4706 }
4707
4708 fn node_generation(&self, id: NodeId) -> u32 {
4709 if id >= Self::HIGH_ID_THRESHOLD {
4710 return self
4711 .state
4712 .high_id_generations
4713 .get(&id)
4714 .copied()
4715 .unwrap_or(0);
4716 }
4717 self.state.stable_index.generation(id)
4718 }
4719
4720 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
4721 if let Some(physical_id) = self.resolve_node_index(id) {
4722 let slot = self
4723 .state
4724 .nodes
4725 .get_mut(physical_id)
4726 .ok_or(NodeError::Missing { id })?
4727 .as_deref_mut()
4728 .ok_or(NodeError::Missing { id })?;
4729 return Ok(slot);
4730 }
4731 self.state
4732 .high_id_nodes
4733 .get_mut(&id)
4734 .map(std::convert::AsMut::as_mut)
4735 .ok_or(NodeError::Missing { id })
4736 }
4737
4738 fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
4739 self.state.bubble_passes.forget();
4740 self.remove_subtree_postorder(id).map(|_| ())
4741 }
4742
4743 fn begin_bubble_batch(&mut self) {
4744 self.state.bubble_passes.begin_batch();
4745 }
4746
4747 fn end_bubble_batch(&mut self) {
4748 self.state.bubble_passes.end_batch();
4749 }
4750
4751 fn forget_bubble_passes(&mut self) {
4752 self.state.bubble_passes.forget();
4753 }
4754
4755 fn note_bubble_pass(&mut self, id: NodeId, flags: u8) -> bool {
4756 self.resolve_node_index(id)
4757 .is_some_and(|index| self.state.bubble_passes.note(index, flags))
4758 }
4759
4760 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
4761 if self.contains_node_id(id) {
4762 return Err(NodeError::AlreadyExists { id });
4763 }
4764 self.insert_available_with_id(id, node);
4765 self.state.virtual_node_ids.insert(id);
4766 Ok(())
4767 }
4768
4769 fn insert_recycled_node_or_create(
4770 &mut self,
4771 stable_id: NodeId,
4772 node: Box<dyn Node>,
4773 ) -> RecycledNodeInsertion {
4774 if self.contains_node_id(stable_id) {
4775 let id = self.create(node);
4776 return RecycledNodeInsertion::fresh(
4777 id,
4778 Some(NodeError::AlreadyExists { id: stable_id }),
4779 );
4780 }
4781
4782 self.insert_available_with_id(stable_id, node);
4783 RecycledNodeInsertion::reused(stable_id)
4784 }
4785
4786 fn compact(&mut self) {
4787 let live_count = self
4788 .state
4789 .nodes
4790 .iter()
4791 .filter(|slot| slot.is_some())
4792 .count();
4793 let tombstone_count = self.state.nodes.len().saturating_sub(live_count);
4794 if tombstone_count == 0 {
4795 return;
4796 }
4797 if self.state.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
4798 return;
4799 }
4800 let rehouse_live_nodes = tombstone_count >= live_count;
4801 let mut packed_nodes = Vec::with_capacity(live_count);
4802 let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
4803 let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
4804
4805 for physical_id in 0..self.state.nodes.len() {
4806 let Some(mut node) = self.state.nodes[physical_id].take() else {
4807 continue;
4808 };
4809 if rehouse_live_nodes && let Some(rehoused) = node.rehouse_for_live_compaction() {
4810 node = rehoused;
4811 }
4812 let stable_id = std::mem::replace(
4813 &mut self.state.physical_stable_ids[physical_id],
4814 Self::INVALID_STABLE_ID,
4815 );
4816 debug_assert_ne!(
4817 stable_id,
4818 Self::INVALID_STABLE_ID,
4819 "live physical slot must have a stable id",
4820 );
4821 let stable_id = Self::unpack_stable_id(stable_id);
4822 packed_nodes.push(Some(node));
4823 packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
4824 packed_warm_recycled_origins
4825 .push(self.state.physical_warm_recycled_origins[physical_id]);
4826 self.state
4827 .stable_index
4828 .set_slot(stable_id, packed_nodes.len() - 1);
4829 }
4830
4831 self.state.nodes = packed_nodes;
4832 self.state.physical_stable_ids = packed_physical_stable_ids;
4833 self.state.physical_warm_recycled_origins = packed_warm_recycled_origins;
4834 self.state.free_ids = BinaryHeap::new();
4835 debug_assert_eq!(
4836 self.state.stable_index.live(),
4837 live_count,
4838 "every live node keeps one indexed slot through compaction",
4839 );
4840 self.prune_stable_generations();
4841 }
4842
4843 fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
4844 Self::take_recycled_node_from_pool(&mut self.state.returning_recycled_nodes, key)
4845 .or_else(|| Self::take_recycled_node_from_pool(&mut self.state.recycled_nodes, key))
4846 }
4847
4848 fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
4849 if let Some(physical_id) = self.resolve_node_index(id) {
4850 self.state.physical_warm_recycled_origins[physical_id] = warm_origin;
4851 } else if self.state.high_id_nodes.contains_key(&id) {
4852 self.state
4853 .high_id_warm_recycled_origins
4854 .insert(id, warm_origin);
4855 }
4856 }
4857
4858 fn seed_recycled_node_shell(
4859 &mut self,
4860 key: TypeId,
4861 recycle_pool_limit: Option<usize>,
4862 shell: Box<dyn Node>,
4863 ) {
4864 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4865 }
4866
4867 fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
4868 *self
4869 .state
4870 .fresh_recyclable_creations
4871 .entry(key)
4872 .or_insert(0) += 1;
4873 }
4874
4875 fn clear_recycled_nodes(&mut self) {
4876 let returning = std::mem::take(&mut self.state.returning_recycled_nodes);
4877 for (key, mut nodes) in returning {
4878 let pool = self.state.recycled_nodes.entry(key).or_default();
4879 pool.append(&mut nodes);
4880 }
4881
4882 let fresh_recyclable_creations = std::mem::take(&mut self.state.fresh_recyclable_creations);
4883 let cold = std::mem::take(&mut self.state.cold_recycled_nodes);
4884 for (key, mut nodes) in cold {
4885 let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4886 if needed > 0 {
4887 let remaining_limit = self
4888 .recycle_pool_limit_for(key)
4889 .unwrap_or(usize::MAX)
4890 .saturating_sub(self.warm_recycled_pool_len(key));
4891 let promote = nodes.len().min(needed).min(remaining_limit);
4892 let split_at = nodes.len().saturating_sub(promote);
4893 let promoted = nodes.split_off(split_at);
4894 for mut recycled in promoted {
4895 recycled.set_warm_origin(true);
4896 self.state
4897 .recycled_nodes
4898 .entry(key)
4899 .or_default()
4900 .push(recycled);
4901 }
4902 }
4903 }
4904
4905 let mut keys: HashSet<TypeId> = HashSet::default();
4906 keys.extend(self.state.recycled_nodes.keys().copied());
4907 keys.extend(self.state.recycled_node_limits.keys().copied());
4908 keys.extend(self.state.warm_recycled_node_targets.keys().copied());
4909 keys.extend(self.state.recycled_node_prototypes.keys().copied());
4910 for key in keys {
4911 let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4912 let target = self.update_warm_recycled_node_target(key, observed_demand);
4913 self.replenish_warm_pool_to_target(key, target);
4914 self.trim_idle_warm_pool_to_target(key, target);
4915 self.compact_idle_warm_pool(key);
4916 }
4917 self.prune_stable_generations();
4918 self.compact();
4919 }
4920}
4921
4922pub struct SlotsHost {
4925 storage_key: Cell<usize>,
4926 inner: RefCell<SlotsHostInner>,
4927}
4928
4929#[derive(Debug, Default)]
4930pub(crate) struct SlotPassOutcome {
4931 pub(crate) compacted: bool,
4932 pub(crate) compact_anchor_registry_storage: bool,
4933 pub(crate) compact_payload_storage: bool,
4934 pub(crate) trim_growth_slack: bool,
4935}
4936
4937#[derive(Default)]
4938pub(crate) struct FinishedSlotPass {
4939 pub(crate) outcome: SlotPassOutcome,
4940 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
4941}
4942
4943struct SlotsHostInner {
4944 table: SlotTable,
4945 nested_hosts: Vec<std::rc::Weak<SlotsHost>>,
4946 lifecycle: slot::SlotLifecycleCoordinator,
4947 runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
4948 active_pass: Option<usize>,
4951 pass_state: Option<slot::SlotWriteSessionState>,
4955}
4956
4957impl Drop for SlotsHost {
4958 fn drop(&mut self) {
4959 let storage_key = self.storage_key.get();
4960 let inner = self.inner.get_mut();
4961 if let Some(state) = inner.runtime_state.clone() {
4962 if let Err(err) = state.dispose_retained_subtrees_for_host(
4963 storage_key,
4964 &mut inner.table,
4965 &mut inner.lifecycle,
4966 ) {
4967 log::error!(
4968 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
4969 );
4970 state.abandon_retained_subtrees_for_host(
4971 storage_key,
4972 &mut inner.table,
4973 &mut inner.lifecycle,
4974 );
4975 } else {
4976 state.clear_host_storage_key(storage_key, &mut inner.table);
4977 }
4978 }
4979 inner.lifecycle.dispose_slot_table(&mut inner.table);
4980 }
4981}
4982
4983impl SlotsHost {
4984 pub fn storage_key(&self) -> usize {
4987 self.storage_key.get()
4988 }
4989
4990 pub fn new(storage: SlotTable) -> Self {
4992 let storage_key = storage.storage_id();
4993 Self {
4994 storage_key: Cell::new(storage_key),
4995 inner: RefCell::new(SlotsHostInner {
4996 table: storage,
4997 nested_hosts: Vec::new(),
4998 lifecycle: slot::SlotLifecycleCoordinator::default(),
4999 runtime_state: None,
5000 active_pass: None,
5001 pass_state: None,
5002 }),
5003 }
5004 }
5005
5006 pub fn note_nested_host(&self, nested: &Rc<SlotsHost>) {
5009 let Ok(mut inner) = self.inner.try_borrow_mut() else {
5010 return;
5011 };
5012 inner.nested_hosts.retain(|held| held.upgrade().is_some());
5013 if inner
5014 .nested_hosts
5015 .iter()
5016 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
5017 {
5018 return;
5019 }
5020 inner.nested_hosts.push(Rc::downgrade(nested));
5021 }
5022
5023 pub(crate) fn forget_effects(&self) -> bool {
5024 let (forgotten, nested, runtime_state) = {
5025 let Ok(mut inner) = self.inner.try_borrow_mut() else {
5026 return false;
5027 };
5028 if inner.active_pass.is_some() {
5029 return false;
5030 }
5031 let drops = inner.table.take_effect_drops();
5032 inner.nested_hosts.retain(|held| held.upgrade().is_some());
5033 let nested: Vec<Rc<SlotsHost>> = inner
5034 .nested_hosts
5035 .iter()
5036 .filter_map(std::rc::Weak::upgrade)
5037 .collect();
5038 (drops, nested, inner.runtime_state.clone())
5039 };
5040 let mut any = !forgotten.is_empty();
5041 drop(forgotten);
5042 for host in nested {
5043 any |= host.forget_effects();
5044 }
5045 if any {
5046 self.borrow()
5047 .scopes()
5048 .for_each(RecomposeScope::force_recompose);
5049 if let Some(runtime_state) = runtime_state {
5050 runtime_state.force_recompose_retained_scopes(self.storage_key());
5051 }
5052 }
5053 any
5054 }
5055
5056 pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
5057 let mut inner = self.inner.borrow_mut();
5058 inner.runtime_state = Some(Rc::clone(state));
5059 }
5060
5061 pub(crate) fn rebind_orphaned_runtime_state(
5062 &self,
5063 state: &Rc<crate::composer::ComposerRuntimeState>,
5064 ) -> bool {
5065 let inner = self.inner.borrow();
5066 if inner.active_pass.is_some() {
5067 log::error!("cannot rebind SlotsHost during an active pass");
5068 return false;
5069 }
5070 let Some(bound_state) = inner.runtime_state.as_ref() else {
5071 drop(inner);
5072 self.bind_runtime_state(state);
5073 return true;
5074 };
5075 if Rc::ptr_eq(bound_state, state) {
5076 return true;
5077 }
5078 if bound_state.has_live_applier_host() {
5079 return false;
5080 }
5081 drop(inner);
5082
5083 let mut inner = self.inner.borrow_mut();
5084 let Some(bound_state) = inner.runtime_state.as_ref() else {
5085 inner.runtime_state = Some(Rc::clone(state));
5086 return true;
5087 };
5088 if Rc::ptr_eq(bound_state, state) {
5089 return true;
5090 }
5091 if bound_state.has_live_applier_host() {
5092 return false;
5093 }
5094
5095 let previous_state = Rc::clone(bound_state);
5096 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5097 lifecycle.flush_pending_drops();
5098 let host_key = self.storage_key();
5099 if previous_state
5100 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5101 .is_err()
5102 {
5103 inner.lifecycle = lifecycle;
5104 return false;
5105 }
5106 previous_state.clear_host(self, &mut inner.table);
5107 lifecycle.flush_pending_drops();
5108 inner.runtime_state = Some(Rc::clone(state));
5109 inner.lifecycle = lifecycle;
5110 true
5111 }
5112
5113 pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
5114 self.inner.borrow().runtime_state.clone()
5115 }
5116
5117 fn runtime_state_ptr(&self) -> Option<*const crate::composer::ComposerRuntimeState> {
5118 self.inner.borrow().runtime_state.as_ref().map(Rc::as_ptr)
5119 }
5120
5121 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
5122 Ref::map(self.inner.borrow(), |inner| &inner.table)
5123 }
5124
5125 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
5126 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
5127 }
5128
5129 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
5132 if Rc::strong_count(&self) != 1 {
5133 return Err(NodeError::SlotHostUnavailable {
5134 operation: "SlotsHost::into_table",
5135 reason: "other host references are alive",
5136 });
5137 }
5138 self.take_table_for_transfer()
5139 }
5140
5141 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
5142 let inner = self.inner.borrow();
5143 if inner.active_pass.is_some() {
5144 return Err(NodeError::SlotHostUnavailable {
5145 operation: "SlotsHost::into_table",
5146 reason: "slot pass is active",
5147 });
5148 }
5149 drop(inner);
5150 let mut inner = self.inner.borrow_mut();
5151 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5152 lifecycle.flush_pending_drops();
5153 if let Some(state) = inner.runtime_state.clone() {
5154 let host_key = self.storage_key();
5155 if let Err(error) =
5156 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5157 {
5158 inner.lifecycle = lifecycle;
5159 return Err(error);
5160 }
5161 state.clear_host(self, &mut inner.table);
5162 lifecycle.flush_pending_drops();
5163 }
5164 let taken = std::mem::take(&mut inner.table);
5165 self.storage_key.set(inner.table.storage_id());
5166 inner.runtime_state = None;
5167 inner.lifecycle = lifecycle;
5168 Ok(taken)
5169 }
5170
5171 pub fn reset(&self) -> Result<(), NodeError> {
5174 let inner = self.inner.borrow();
5175 if inner.active_pass.is_some() {
5176 return Err(NodeError::SlotHostUnavailable {
5177 operation: "SlotsHost::reset",
5178 reason: "slot pass is active",
5179 });
5180 }
5181 let runtime_state = inner.runtime_state.clone();
5182 drop(inner);
5183 let mut inner = self.inner.borrow_mut();
5184 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5185 if let Some(state) = runtime_state {
5186 let host_key = self.storage_key();
5187 if let Err(error) =
5188 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5189 {
5190 inner.lifecycle = lifecycle;
5191 return Err(error);
5192 }
5193 state.clear_host(self, &mut inner.table);
5194 }
5195 lifecycle.dispose_slot_table(&mut inner.table);
5196 inner.table = SlotTable::default();
5197 self.storage_key.set(inner.table.storage_id());
5198 inner.runtime_state = None;
5199 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
5200 Ok(())
5201 }
5202
5203 pub(crate) fn abandon_after_apply_failure(&self) {
5204 let inner = self.inner.borrow();
5205 if inner.active_pass.is_some() {
5206 log::error!("cannot abandon SlotsHost during an active pass");
5207 return;
5208 }
5209 let runtime_state = inner.runtime_state.clone();
5210 drop(inner);
5211 let mut inner = self.inner.borrow_mut();
5212 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5213 if let Some(state) = runtime_state {
5214 let host_key = self.storage_key();
5215 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
5216 }
5217 lifecycle.dispose_slot_table(&mut inner.table);
5218 inner.table = SlotTable::default();
5219 self.storage_key.set(inner.table.storage_id());
5220 inner.runtime_state = None;
5221 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
5222 }
5223
5224 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
5225 let inner = self.inner.borrow();
5226 let local = inner.table.debug_stats();
5227 let lifecycle = inner.lifecycle.debug_stats();
5228 let retention = inner
5229 .runtime_state
5230 .clone()
5231 .map(|state| state.slot_retention_debug_stats(self))
5232 .unwrap_or_default();
5233 SlotTableDebugStats::from_parts(local, lifecycle, retention)
5234 }
5235
5236 #[cold]
5237 #[inline(never)]
5238 pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
5239 let inner = self.inner.borrow();
5240 let mut snapshot = inner.table.debug_snapshot();
5241 if let Some(state) = inner.runtime_state.clone() {
5242 state.fill_slot_debug_snapshot(self, &mut snapshot);
5243 }
5244 snapshot
5245 }
5246
5247 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode, folds: &Rc<slot::BranchFolds>) {
5248 let mut inner = self.inner.borrow_mut();
5249 let SlotsHostInner {
5250 table,
5251 active_pass,
5252 pass_state,
5253 ..
5254 } = &mut *inner;
5255 if active_pass.is_some() {
5256 log::error!("slot pass already active for host");
5257 return;
5258 }
5259 let state = match pass_state {
5260 Some(state) => {
5261 state.set_folds(Rc::clone(folds));
5262 state
5263 }
5264 None => pass_state.insert(slot::SlotWriteSessionState::new(Rc::clone(folds))),
5265 };
5266 state.reset_for_pass(mode);
5267 *active_pass = Some(table.storage_capacity());
5268 }
5269
5270 pub(crate) fn has_active_pass(&self) -> bool {
5271 self.inner.borrow().active_pass.is_some()
5272 }
5273
5274 pub(crate) fn abandon_active_pass(&self) {
5275 self.inner.borrow_mut().active_pass = None;
5276 }
5277
5278 pub(crate) fn with_write_session<R>(
5279 &self,
5280 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
5281 ) -> R {
5282 let mut inner = self.inner.borrow_mut();
5283 let SlotsHostInner {
5284 table,
5285 lifecycle,
5286 active_pass,
5287 pass_state,
5288 ..
5289 } = &mut *inner;
5290 let state = pass_state
5291 .as_mut()
5292 .filter(|_| active_pass.is_some())
5293 .expect("slot write session requires an active pass");
5294 let mut session = table.write_session(lifecycle, state);
5295 f(&mut session)
5296 }
5297
5298 pub(crate) fn with_table_and_lifecycle_mut<R>(
5299 &self,
5300 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
5301 ) -> R {
5302 let mut inner = self.inner.borrow_mut();
5303 let SlotsHostInner {
5304 table, lifecycle, ..
5305 } = &mut *inner;
5306 f(table, lifecycle)
5307 }
5308
5309 pub(crate) fn finish_pass(
5310 &self,
5311 applier: &mut dyn Applier,
5312 ) -> Result<FinishedSlotPass, NodeError> {
5313 let mut inner = self.inner.borrow_mut();
5314 let SlotsHostInner {
5315 table,
5316 lifecycle,
5317 active_pass,
5318 pass_state,
5319 ..
5320 } = &mut *inner;
5321 let (Some(storage_capacity), Some(state)) = (active_pass.take(), pass_state.as_mut())
5322 else {
5323 return Ok(FinishedSlotPass::default());
5324 };
5325
5326 state.flush_payload_location_refreshes(table);
5327
5328 #[cfg(any(test, debug_assertions))]
5329 if let Err(err) = state.validate(table) {
5330 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
5331 return Err(NodeError::SlotHostUnavailable {
5332 operation: "SlotsHost::finish_pass",
5333 reason: "slot writer invariant violation",
5334 });
5335 }
5336
5337 let detached_root_children = {
5338 let mut session = table.write_session(lifecycle, state);
5339 session.finalize_pass(applier)?
5340 };
5341
5342 let outcome = SlotPassOutcome {
5343 compacted: state.request_compaction,
5344 compact_anchor_registry_storage: state.request_anchor_storage_compaction,
5345 compact_payload_storage: state.request_payload_storage_compaction,
5346 trim_growth_slack: state.removed_nothing()
5347 && table.storage_capacity() > storage_capacity,
5348 };
5349 Ok(FinishedSlotPass {
5350 outcome,
5351 detached_root_children,
5352 })
5353 }
5354
5355 pub(crate) fn flush_pending_drops(&self) {
5356 self.inner.borrow_mut().lifecycle.flush_pending_drops();
5357 }
5358
5359 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
5360 let mut inner = self.inner.borrow_mut();
5361 let SlotsHostInner {
5362 table,
5363 lifecycle,
5364 runtime_state,
5365 ..
5366 } = &mut *inner;
5367 lifecycle.flush_pending_drops();
5368 if outcome.compacted {
5369 table.compact_storage();
5370 lifecycle.compact_storage();
5371 } else if outcome.trim_growth_slack {
5372 table.trim_growth_slack();
5373 }
5374 if let Some(state) = runtime_state.clone() {
5375 state.compact_table_identity_storage_for_host(
5376 self,
5377 table,
5378 outcome.compact_anchor_registry_storage,
5379 outcome.compact_payload_storage,
5380 );
5381 } else {
5382 if outcome.compact_anchor_registry_storage {
5383 table.compact_anchor_registry_storage(None);
5384 }
5385 if outcome.compact_payload_storage {
5386 table.compact_payload_anchor_registry_storage(None);
5387 }
5388 }
5389 table.assert_fast_integrity("slot pass cleanup");
5390 #[cfg(any(test, debug_assertions))]
5391 {
5392 table.debug_verify();
5393 if let Some(state) = runtime_state.clone() {
5394 state.debug_verify_host(self, table);
5395 }
5396 }
5397 }
5398}
5399
5400fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
5401 let mut positions = HashMap::default();
5402 positions.reserve(children.len());
5403 for (index, &child) in children.iter().enumerate() {
5404 positions.insert(child, index);
5405 }
5406 positions
5407}
5408
5409fn refresh_child_positions(
5410 current: &[NodeId],
5411 positions: &mut HashMap<NodeId, usize>,
5412 start: usize,
5413 end: usize,
5414) {
5415 if current.is_empty() || start >= current.len() {
5416 return;
5417 }
5418 let end = end.min(current.len() - 1);
5419 for (offset, &child) in current[start..=end].iter().enumerate() {
5420 positions.insert(child, start + offset);
5421 }
5422}
5423
5424fn insert_child_into_diff_state(
5425 current: &mut ChildList,
5426 positions: &mut HashMap<NodeId, usize>,
5427 index: usize,
5428 child: NodeId,
5429) {
5430 let index = index.min(current.len());
5431 current.insert(index, child);
5432 refresh_child_positions(current, positions, index, current.len() - 1);
5433}
5434
5435fn move_child_in_diff_state(
5436 current: &mut ChildList,
5437 positions: &mut HashMap<NodeId, usize>,
5438 from_index: usize,
5439 target_index: usize,
5440) -> usize {
5441 let child = current.remove(from_index);
5442 let to_index = target_index.min(current.len());
5443 current.insert(to_index, child);
5444 refresh_child_positions(
5445 current,
5446 positions,
5447 from_index.min(to_index),
5448 from_index.max(to_index),
5449 );
5450 to_index
5451}
5452
5453pub(crate) use state::MutableStateInner;
5454pub use state::{
5455 MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
5456 StateSubscriptionHold,
5457};
5458
5459fn hash_key<K: Hash>(key: &K) -> Key {
5460 let mut hasher = hash::default::new();
5461 key.hash(&mut hasher);
5462 hasher.finish()
5463}
5464
5465pub(crate) fn explicit_group_key_seed<K: Hash>(
5466 key: &K,
5467 caller: &'static std::panic::Location<'static>,
5468) -> slot::GroupKeySeed {
5469 let source_key = location_key(caller.file(), caller.line(), caller.column());
5470 let explicit_key = hash_key(key);
5471 slot::GroupKeySeed::keyed(source_key, explicit_key)
5472}
5473
5474#[cfg(test)]
5475#[path = "tests/mod.rs"]
5476mod tests;
5477
5478#[cfg(test)]
5479#[path = "tests/recursive_decrease_increase_test.rs"]
5480mod recursive_decrease_increase_test;
5481
5482pub mod collections;
5483pub mod hash;
5485
5486#[cfg(any(test, feature = "test-helpers"))]
5489pub mod test_scratch;
5490#[cfg(any(test, feature = "test-helpers"))]
5491pub use test_scratch::test_scratch_dir;
5492
5493pub(crate) fn note_structural(reason: &str, parent_id: NodeId, child_id: NodeId) {
5494 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5495 eprintln!("[structural] {reason} parent={parent_id} child={child_id}");
5496 }
5497}
5498
5499pub(crate) fn note_structural_move(parent_id: NodeId, from_index: usize, to_index: usize) {
5500 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5501 eprintln!("[structural] move parent={parent_id} from={from_index} to={to_index}");
5502 }
5503}