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