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