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