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