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