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