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