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