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::note_nested_slots_host;
928pub use composer_context::with_composer as with_current_composer;
929
930#[allow(non_snake_case)]
931pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
932 composer_context::with_composer(f)
933}
934
935fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
936 composer_context::try_with_composer(f)
937}
938
939#[doc(hidden)]
940pub fn current_recompose_scope_invalidated_only_by(
941 allowed_sources: impl IntoIterator<Item = StateId>,
942) -> Option<bool> {
943 with_current_composer_opt(|composer| {
944 let allowed_sources = allowed_sources.into_iter().collect();
945 let mut scope = composer.current_recranpose_scope();
946 let mut saw_unknown_source = false;
947 while let Some(current) = scope {
948 if current.has_unknown_invalidation_source() {
949 saw_unknown_source = true;
950 scope = current.parent_scope();
951 continue;
952 }
953 if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
954 return Some(matches);
955 }
956 scope = current.parent_scope();
957 }
958 saw_unknown_source.then_some(false)
959 })
960 .flatten()
961}
962
963#[track_caller]
964pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
965 let seed = explicit_group_key_seed(key, std::panic::Location::caller());
966 with_current_composer(|composer| composer.with_group_seed(seed, |_| content()));
967}
968
969#[allow(non_snake_case)]
970pub fn withKey<K: Hash>(key: &K, content: impl FnOnce()) {
971 with_key(key, content)
972}
973
974#[derive(Default)]
975struct DisposableEffectState {
976 key: Option<Key>,
977 cleanup: Option<Box<dyn FnOnce()>>,
978}
979
980impl DisposableEffectState {
981 fn should_run(&self, key: Key) -> bool {
982 match self.key {
983 Some(current) => current != key,
984 None => true,
985 }
986 }
987
988 fn set_key(&mut self, key: Key) {
989 self.key = Some(key);
990 }
991
992 fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
993 self.cleanup = cleanup;
994 }
995
996 fn run_cleanup(&mut self) {
997 if let Some(cleanup) = self.cleanup.take() {
998 cleanup();
999 }
1000 }
1001}
1002
1003impl Drop for DisposableEffectState {
1004 fn drop(&mut self) {
1005 self.run_cleanup();
1006 }
1007}
1008
1009#[derive(Clone, Copy, Debug, Default)]
1010pub struct DisposableEffectScope;
1011
1012#[derive(Default)]
1013pub struct DisposableEffectResult {
1014 cleanup: Option<Box<dyn FnOnce()>>,
1015}
1016
1017impl DisposableEffectScope {
1018 pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
1019 DisposableEffectResult::new(cleanup)
1020 }
1021}
1022
1023impl DisposableEffectResult {
1024 pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
1025 Self {
1026 cleanup: Some(Box::new(cleanup)),
1027 }
1028 }
1029
1030 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
1031 self.cleanup
1032 }
1033}
1034
1035#[allow(non_snake_case)]
1036pub fn SideEffect(effect: impl FnOnce() + 'static) {
1037 with_current_composer(|composer| composer.register_side_effect(effect));
1038}
1039
1040pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
1041where
1042 K: Hash,
1043 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1044{
1045 with_current_composer(|composer| {
1048 composer.with_group(group_key, |composer| {
1049 let key_hash = hash_key(&keys);
1050 let state = composer.remember_effect::<DisposableEffectState>();
1051 if state.with(|state| state.should_run(key_hash)) {
1052 state.update(|state| {
1053 state.run_cleanup();
1054 state.set_key(key_hash);
1055 });
1056 let state_for_effect = state.clone();
1057 let mut effect_opt = Some(effect);
1058 composer.register_side_effect(move || {
1059 if let Some(effect) = effect_opt.take() {
1060 let result = effect(DisposableEffectScope);
1061 state_for_effect.update(|state| state.set_cleanup(result.into_cleanup()));
1062 }
1063 });
1064 }
1065 });
1066 });
1067}
1068
1069#[macro_export]
1070macro_rules! DisposableEffect {
1071 ($keys:expr, $effect:expr) => {
1072 $crate::__disposable_effect_impl(
1073 $crate::location_key(file!(), line!(), column!()),
1074 $keys,
1075 $effect,
1076 )
1077 };
1078}
1079
1080#[macro_export]
1081macro_rules! clone_captures {
1082 ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1083 $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1084 $body
1085 }};
1086 (@clone $alias:ident = $value:expr) => {
1087 ($value).clone()
1088 };
1089 (@clone $alias:ident) => {
1090 $alias.clone()
1091 };
1092}
1093
1094pub fn with_node_mut<N: Node + 'static, R>(
1095 id: NodeId,
1096 f: impl FnOnce(&mut N) -> R,
1097) -> Result<R, NodeError> {
1098 with_current_composer(|composer| composer.with_node_mut(id, f))
1099}
1100
1101pub fn push_parent(id: NodeId) {
1102 with_current_composer(|composer| composer.push_parent(id));
1103}
1104
1105pub fn pop_parent() {
1106 with_current_composer(|composer| composer.pop_parent());
1107}
1108
1109pub trait Node: Any {
1114 fn mount(&mut self) {}
1115 fn update(&mut self) {}
1116 fn unmount(&mut self) {}
1117 fn insert_child(&mut self, _child: NodeId) {}
1118 fn remove_child(&mut self, _child: NodeId) {}
1119 fn move_child(&mut self, _from: usize, _to: usize) {}
1120 fn update_children(&mut self, _children: &[NodeId]) {}
1121 fn children(&self) -> Vec<NodeId> {
1122 Vec::new()
1123 }
1124 fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1127 out.clear();
1128 out.extend(self.children());
1129 }
1130 fn set_node_id(&mut self, _id: NodeId) {}
1133 fn on_attached_to_parent(&mut self, _parent: NodeId) {}
1136 fn on_removed_from_parent(&mut self) {}
1139 fn parent(&self) -> Option<NodeId> {
1142 None
1143 }
1144 fn mark_needs_layout(&self) {}
1147 fn needs_layout(&self) -> bool {
1149 false
1150 }
1151 fn mark_needs_measure(&self) {}
1154 fn needs_measure(&self) -> bool {
1156 false
1157 }
1158 fn mark_needs_semantics(&self) {}
1160 fn needs_semantics(&self) -> bool {
1162 false
1163 }
1164 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1172 self.on_attached_to_parent(parent);
1173 }
1174
1175 fn recycle_key(&self) -> Option<TypeId> {
1177 None
1178 }
1179
1180 fn recycle_pool_limit(&self) -> Option<usize> {
1182 None
1183 }
1184
1185 fn prepare_for_recycle(&mut self) {}
1187
1188 fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
1193 None
1194 }
1195
1196 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
1202 None
1203 }
1204
1205 fn debug_heap_bytes(&self) -> usize {
1207 0
1208 }
1209}
1210
1211pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1230 bubble_layout_dirty_applier(applier, node_id);
1231}
1232
1233pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1244 bubble_measure_dirty_applier(applier, node_id);
1245}
1246
1247pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1253 bubble_semantics_dirty_applier(applier, node_id);
1254}
1255
1256pub fn queue_semantics_invalidation(node_id: NodeId) {
1261 let _ = composer_context::try_with_composer(|composer| {
1262 composer.enqueue_semantics_invalidation(node_id);
1263 });
1264}
1265
1266pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1289 bubble_layout_dirty_composer::<N>(node_id);
1290}
1291
1292pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
1298 with_current_composer(|composer| {
1299 composer.commands_mut().push(Command::BubbleDirty {
1300 node_id,
1301 bubble: DirtyBubble {
1302 layout: false,
1303 measure: true,
1304 semantics: false,
1305 },
1306 });
1307 });
1308}
1309
1310pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1317 bubble_semantics_dirty_composer::<N>(node_id);
1318}
1319
1320fn bubble_layout_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1322 if let Ok(node) = applier.get_mut(node_id) {
1325 node.mark_needs_layout();
1326 }
1327
1328 loop {
1330 let parent_id = match applier.get_mut(node_id) {
1332 Ok(node) => node.parent(),
1333 Err(_) => None,
1334 };
1335
1336 match parent_id {
1337 Some(pid) => {
1338 if let Ok(parent) = applier.get_mut(pid) {
1340 let parent_already_dirty = parent.needs_layout();
1341 if !parent_already_dirty {
1342 parent.mark_needs_layout();
1343 }
1344 node_id = pid;
1345 } else {
1346 break;
1347 }
1348 }
1349 None => break, }
1351 }
1352}
1353
1354fn bubble_measure_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1356 if let Ok(node) = applier.get_mut(node_id) {
1358 node.mark_needs_measure();
1359 }
1360
1361 loop {
1363 let parent_id = match applier.get_mut(node_id) {
1365 Ok(node) => node.parent(),
1366 Err(_) => None,
1367 };
1368
1369 match parent_id {
1370 Some(pid) => {
1371 if let Ok(parent) = applier.get_mut(pid) {
1373 if !parent.needs_measure() {
1374 parent.mark_needs_measure();
1375 }
1376 node_id = pid;
1377 } else {
1378 break;
1379 }
1380 }
1381 None => {
1382 break; }
1384 }
1385 }
1386}
1387
1388fn bubble_semantics_dirty_applier(applier: &mut dyn Applier, mut node_id: NodeId) {
1390 if let Ok(node) = applier.get_mut(node_id) {
1391 node.mark_needs_semantics();
1392 }
1393
1394 loop {
1395 let parent_id = match applier.get_mut(node_id) {
1396 Ok(node) => node.parent(),
1397 Err(_) => None,
1398 };
1399
1400 match parent_id {
1401 Some(pid) => {
1402 if let Ok(parent) = applier.get_mut(pid) {
1403 if !parent.needs_semantics() {
1404 parent.mark_needs_semantics();
1405 }
1406 node_id = pid;
1407 } else {
1408 break;
1409 }
1410 }
1411 None => break,
1412 }
1413 }
1414}
1415
1416fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1420 let _ = with_node_mut(node_id, |node: &mut N| {
1422 node.mark_needs_layout();
1423 });
1424
1425 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1427 let parent_id = pid;
1428
1429 let advanced = with_node_mut(parent_id, |node: &mut N| {
1431 if !node.needs_layout() {
1432 node.mark_needs_layout();
1433 }
1434 true
1435 })
1436 .unwrap_or(false);
1437
1438 if advanced {
1439 node_id = parent_id;
1440 } else {
1441 break;
1442 }
1443 }
1444}
1445
1446fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1448 let _ = with_node_mut(node_id, |node: &mut N| {
1450 node.mark_needs_semantics();
1451 });
1452
1453 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1454 let parent_id = pid;
1455
1456 let advanced = with_node_mut(parent_id, |node: &mut N| {
1457 if !node.needs_semantics() {
1458 node.mark_needs_semantics();
1459 }
1460 true
1461 })
1462 .unwrap_or(false);
1463
1464 if advanced {
1465 node_id = parent_id;
1466 } else {
1467 break;
1468 }
1469 }
1470}
1471
1472impl dyn Node {
1473 pub fn as_any_mut(&mut self) -> &mut dyn Any {
1474 self
1475 }
1476}
1477
1478pub struct RecycledNode {
1479 stable_id: NodeId,
1480 node: Box<dyn Node>,
1481 warm_origin: bool,
1482}
1483
1484impl RecycledNode {
1485 fn new(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
1486 let node = node.rehouse_for_recycle().unwrap_or(node);
1487 Self {
1488 stable_id,
1489 node,
1490 warm_origin,
1491 }
1492 }
1493
1494 fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
1495 Self {
1496 stable_id,
1497 node,
1498 warm_origin,
1499 }
1500 }
1501
1502 pub fn stable_id(&self) -> NodeId {
1503 self.stable_id
1504 }
1505
1506 fn warm_origin(&self) -> bool {
1507 self.warm_origin
1508 }
1509
1510 fn set_warm_origin(&mut self, warm_origin: bool) {
1511 self.warm_origin = warm_origin;
1512 }
1513
1514 pub fn node_mut(&mut self) -> &mut dyn Node {
1515 self.node.as_mut()
1516 }
1517
1518 pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
1519 (self.stable_id, self.node, self.warm_origin)
1520 }
1521}
1522
1523#[derive(Debug, Clone, PartialEq, Eq)]
1524pub struct RecycledNodeInsertion {
1525 pub id: NodeId,
1526 pub stable_id_reused: bool,
1527 pub fallback_error: Option<NodeError>,
1528}
1529
1530impl RecycledNodeInsertion {
1531 fn reused(stable_id: NodeId) -> Self {
1532 Self {
1533 id: stable_id,
1534 stable_id_reused: true,
1535 fallback_error: None,
1536 }
1537 }
1538
1539 fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
1540 Self {
1541 id,
1542 stable_id_reused: false,
1543 fallback_error,
1544 }
1545 }
1546}
1547
1548pub trait Applier: Any {
1549 fn create(&mut self, node: Box<dyn Node>) -> NodeId;
1550 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
1551 fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
1552
1553 fn record_structural_change(&mut self, _parent_id: NodeId) {}
1559
1560 fn node_generation(&self, id: NodeId) -> u32;
1564
1565 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
1573
1574 fn insert_recycled_node_or_create(
1577 &mut self,
1578 stable_id: NodeId,
1579 node: Box<dyn Node>,
1580 ) -> RecycledNodeInsertion {
1581 let id = self.create(node);
1582 RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
1583 }
1584
1585 fn as_any(&self) -> &dyn Any
1586 where
1587 Self: Sized,
1588 {
1589 self
1590 }
1591
1592 fn as_any_mut(&mut self) -> &mut dyn Any
1593 where
1594 Self: Sized,
1595 {
1596 self
1597 }
1598
1599 fn compact(&mut self) {}
1601
1602 fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
1604 None
1605 }
1606
1607 fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
1609
1610 fn seed_recycled_node_shell(
1612 &mut self,
1613 _key: TypeId,
1614 _recycle_pool_limit: Option<usize>,
1615 _shell: Box<dyn Node>,
1616 ) {
1617 }
1618
1619 fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
1621
1622 fn clear_recycled_nodes(&mut self) {}
1624}
1625
1626type TypedNodeUpdate = fn(&mut dyn Node, NodeId) -> Result<(), NodeError>;
1627type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
1628
1629#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1630pub(crate) struct DirtyBubble {
1631 layout: bool,
1632 measure: bool,
1633 semantics: bool,
1634}
1635
1636impl DirtyBubble {
1637 pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
1638 layout: true,
1639 measure: true,
1640 semantics: false,
1641 };
1642
1643 pub(crate) const SEMANTICS: Self = Self {
1644 layout: false,
1645 measure: false,
1646 semantics: true,
1647 };
1648
1649 fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
1650 if self.layout {
1651 bubble_layout_dirty(applier, node_id);
1652 }
1653 if self.measure {
1654 bubble_measure_dirty(applier, node_id);
1655 }
1656 if self.semantics {
1657 bubble_semantics_dirty(applier, node_id);
1658 }
1659 }
1660}
1661
1662pub(crate) enum Command {
1663 BubbleDirty {
1664 node_id: NodeId,
1665 bubble: DirtyBubble,
1666 },
1667 UpdateTypedNode {
1668 id: NodeId,
1669 updater: TypedNodeUpdate,
1670 },
1671 RemoveNode {
1672 id: NodeId,
1673 },
1674 MountNode {
1675 id: NodeId,
1676 },
1677 AttachChild {
1678 parent_id: NodeId,
1679 child_id: NodeId,
1680 bubble: DirtyBubble,
1681 },
1682 InsertChild {
1683 parent_id: NodeId,
1684 child_id: NodeId,
1685 appended_index: usize,
1686 insert_index: usize,
1687 bubble: DirtyBubble,
1688 },
1689 MoveChild {
1690 parent_id: NodeId,
1691 from_index: usize,
1692 to_index: usize,
1693 bubble: DirtyBubble,
1694 },
1695 RemoveChild {
1696 parent_id: NodeId,
1697 child_id: NodeId,
1698 },
1699 DetachChild {
1700 parent_id: NodeId,
1701 child_id: NodeId,
1702 },
1703 SyncChildren {
1704 parent_id: NodeId,
1705 expected_children: ChildList,
1706 },
1707 Callback(CommandCallback),
1708}
1709
1710#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1711struct DeferredChildCleanup {
1712 child_id: NodeId,
1713 generation: u32,
1714 removed_from_parent: bool,
1715}
1716
1717#[derive(Default)]
1718struct DeferredChildCleanupQueue {
1719 pending: Vec<DeferredChildCleanup>,
1720 preserved: Vec<(NodeId, u32)>,
1721}
1722
1723impl DeferredChildCleanupQueue {
1724 fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
1725 if self
1726 .preserved
1727 .iter()
1728 .any(|&(preserved_id, preserved_generation)| {
1729 preserved_id == child_id && preserved_generation == generation
1730 })
1731 {
1732 return;
1733 }
1734 self.pending.push(DeferredChildCleanup {
1735 child_id,
1736 generation,
1737 removed_from_parent,
1738 });
1739 }
1740
1741 fn preserve(&mut self, child_id: NodeId, generation: u32) {
1742 if !self
1743 .preserved
1744 .iter()
1745 .any(|&(preserved_id, preserved_generation)| {
1746 preserved_id == child_id && preserved_generation == generation
1747 })
1748 {
1749 self.preserved.push((child_id, generation));
1750 }
1751 self.pending
1752 .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
1753 }
1754
1755 fn flush(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
1756 for cleanup in self.pending {
1757 cleanup_detached_child(applier, cleanup)?;
1758 }
1759 Ok(())
1760 }
1761}
1762
1763impl Command {
1764 pub(crate) fn update_node<N: Node + 'static>(id: NodeId) -> Self {
1765 Self::UpdateTypedNode {
1766 id,
1767 updater: update_typed_node::<N>,
1768 }
1769 }
1770
1771 pub(crate) fn callback(
1772 callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1773 ) -> Self {
1774 Self::Callback(Box::new(callback))
1775 }
1776
1777 pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
1778 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
1779 self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
1780 deferred_cleanup.flush(applier)
1781 }
1782
1783 fn apply_with_cleanup(
1784 self,
1785 applier: &mut dyn Applier,
1786 deferred_cleanup: &mut DeferredChildCleanupQueue,
1787 ) -> Result<(), NodeError> {
1788 match self {
1789 Self::BubbleDirty { node_id, bubble } => {
1790 bubble.apply(applier, node_id);
1791 Ok(())
1792 }
1793 Self::UpdateTypedNode { id, updater } => {
1794 let node = match applier.get_mut(id) {
1795 Ok(node) => node,
1796 Err(NodeError::Missing { .. }) => return Ok(()),
1797 Err(err) => return Err(err),
1798 };
1799 updater(node, id)
1800 }
1801 Self::RemoveNode { id } => {
1802 if let Ok(node) = applier.get_mut(id) {
1803 node.unmount();
1804 }
1805 match applier.remove(id) {
1806 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
1807 Err(err) => Err(err),
1808 }
1809 }
1810 Self::MountNode { id } => {
1811 let node = match applier.get_mut(id) {
1812 Ok(node) => node,
1813 Err(NodeError::Missing { .. }) => return Ok(()),
1814 Err(err) => return Err(err),
1815 };
1816 node.set_node_id(id);
1817 node.mount();
1818 Ok(())
1819 }
1820 Self::AttachChild {
1821 parent_id,
1822 child_id,
1823 bubble,
1824 } => {
1825 insert_child_with_reparenting(applier, parent_id, child_id);
1826 bubble.apply(applier, parent_id);
1827 Ok(())
1828 }
1829 Self::InsertChild {
1830 parent_id,
1831 child_id,
1832 appended_index,
1833 insert_index,
1834 bubble,
1835 } => {
1836 insert_child_with_reparenting(applier, parent_id, child_id);
1837 bubble.apply(applier, parent_id);
1838 if insert_index != appended_index {
1839 if let Ok(parent_node) = applier.get_mut(parent_id) {
1840 parent_node.move_child(appended_index, insert_index);
1841 }
1842 }
1843 Ok(())
1844 }
1845 Self::MoveChild {
1846 parent_id,
1847 from_index,
1848 to_index,
1849 bubble,
1850 } => {
1851 if let Ok(parent_node) = applier.get_mut(parent_id) {
1852 parent_node.move_child(from_index, to_index);
1853 }
1854 bubble.apply(applier, parent_id);
1855 applier.record_structural_change(parent_id);
1856 Ok(())
1857 }
1858 Self::RemoveChild {
1859 parent_id,
1860 child_id,
1861 } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
1862 Self::DetachChild {
1863 parent_id,
1864 child_id,
1865 } => {
1866 let generation = applier.node_generation(child_id);
1867 detach_child_from_parent(applier, parent_id, child_id)?;
1868 deferred_cleanup.preserve(child_id, generation);
1869 Ok(())
1870 }
1871 Self::SyncChildren {
1872 parent_id,
1873 expected_children,
1874 } => sync_children(applier, parent_id, &expected_children, deferred_cleanup),
1875 Self::Callback(callback) => callback(applier),
1876 }
1877 }
1878}
1879
1880const COMMAND_CHUNK_CAPACITY: usize = 1024;
1881const COMMAND_FLUSH_THRESHOLD: usize = COMMAND_CHUNK_CAPACITY * 4;
1882type ChildList = SmallVec<[NodeId; 4]>;
1883const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
1884
1885#[derive(Copy, Clone)]
1886enum CommandTag {
1887 BubbleDirty,
1888 UpdateTypedNode,
1889 RemoveNode,
1890 MountNode,
1891 AttachChild,
1892 InsertChild,
1893 MoveChild,
1894 RemoveChild,
1895 DetachChild,
1896 SyncChildren,
1897 Callback,
1898}
1899
1900impl CommandTag {
1901 fn label(self) -> &'static str {
1902 match self {
1903 Self::BubbleDirty => "BubbleDirty",
1904 Self::UpdateTypedNode => "UpdateTypedNode",
1905 Self::RemoveNode => "RemoveNode",
1906 Self::MountNode => "MountNode",
1907 Self::AttachChild => "AttachChild",
1908 Self::InsertChild => "InsertChild",
1909 Self::MoveChild => "MoveChild",
1910 Self::RemoveChild => "RemoveChild",
1911 Self::DetachChild => "DetachChild",
1912 Self::SyncChildren => "SyncChildren",
1913 Self::Callback => "Callback",
1914 }
1915 }
1916}
1917
1918#[derive(Copy, Clone)]
1919struct BubbleDirtyCommand {
1920 node_id: NodeId,
1921 bubble: DirtyBubble,
1922}
1923
1924#[derive(Copy, Clone)]
1925struct UpdateTypedNodeCommand {
1926 id: NodeId,
1927 updater: TypedNodeUpdate,
1928}
1929
1930#[derive(Copy, Clone)]
1931struct AttachChildCommand {
1932 parent_id: NodeId,
1933 child_id: NodeId,
1934 bubble: DirtyBubble,
1935}
1936
1937#[derive(Copy, Clone)]
1938struct InsertChildCommand {
1939 parent_id: NodeId,
1940 child_id: NodeId,
1941 appended_index: usize,
1942 insert_index: usize,
1943 bubble: DirtyBubble,
1944}
1945
1946#[derive(Copy, Clone)]
1947struct MoveChildCommand {
1948 parent_id: NodeId,
1949 from_index: usize,
1950 to_index: usize,
1951 bubble: DirtyBubble,
1952}
1953
1954#[derive(Copy, Clone)]
1955struct RemoveChildCommand {
1956 parent_id: NodeId,
1957 child_id: NodeId,
1958}
1959
1960#[derive(Copy, Clone)]
1961struct DetachChildCommand {
1962 parent_id: NodeId,
1963 child_id: NodeId,
1964}
1965
1966struct SyncChildrenCommand {
1967 parent_id: NodeId,
1968 child_start: usize,
1969 child_len: usize,
1970}
1971
1972#[derive(Default)]
1973struct CommandQueue {
1974 chunks: Vec<Vec<CommandTag>>,
1975 len: usize,
1976 bubble_dirty: Vec<BubbleDirtyCommand>,
1977 update_typed_nodes: Vec<UpdateTypedNodeCommand>,
1978 remove_nodes: Vec<NodeId>,
1979 mount_nodes: Vec<NodeId>,
1980 attach_children: Vec<AttachChildCommand>,
1981 insert_children: Vec<InsertChildCommand>,
1982 move_children: Vec<MoveChildCommand>,
1983 remove_children: Vec<RemoveChildCommand>,
1984 detach_children: Vec<DetachChildCommand>,
1985 sync_children: Vec<SyncChildrenCommand>,
1986 sync_child_ids: Vec<NodeId>,
1987 callbacks: Vec<CommandCallback>,
1988}
1989
1990impl CommandQueue {
1991 fn push_tag(&mut self, tag: CommandTag) {
1992 let needs_chunk = self
1993 .chunks
1994 .last()
1995 .map(|chunk| chunk.len() == chunk.capacity())
1996 .unwrap_or(true);
1997 if needs_chunk {
1998 self.chunks.push(Vec::with_capacity(COMMAND_CHUNK_CAPACITY));
1999 }
2000 if let Some(chunk) = self.chunks.last_mut() {
2001 chunk.push(tag);
2002 self.len += 1;
2003 }
2004 }
2005
2006 fn push(&mut self, command: Command) {
2007 match command {
2008 Command::BubbleDirty { node_id, bubble } => {
2009 self.bubble_dirty
2010 .push(BubbleDirtyCommand { node_id, bubble });
2011 self.push_tag(CommandTag::BubbleDirty);
2012 }
2013 Command::UpdateTypedNode { id, updater } => {
2014 self.update_typed_nodes
2015 .push(UpdateTypedNodeCommand { id, updater });
2016 self.push_tag(CommandTag::UpdateTypedNode);
2017 }
2018 Command::RemoveNode { id } => {
2019 self.remove_nodes.push(id);
2020 self.push_tag(CommandTag::RemoveNode);
2021 }
2022 Command::MountNode { id } => {
2023 self.mount_nodes.push(id);
2024 self.push_tag(CommandTag::MountNode);
2025 }
2026 Command::AttachChild {
2027 parent_id,
2028 child_id,
2029 bubble,
2030 } => {
2031 self.attach_children.push(AttachChildCommand {
2032 parent_id,
2033 child_id,
2034 bubble,
2035 });
2036 self.push_tag(CommandTag::AttachChild);
2037 }
2038 Command::InsertChild {
2039 parent_id,
2040 child_id,
2041 appended_index,
2042 insert_index,
2043 bubble,
2044 } => {
2045 self.insert_children.push(InsertChildCommand {
2046 parent_id,
2047 child_id,
2048 appended_index,
2049 insert_index,
2050 bubble,
2051 });
2052 self.push_tag(CommandTag::InsertChild);
2053 }
2054 Command::MoveChild {
2055 parent_id,
2056 from_index,
2057 to_index,
2058 bubble,
2059 } => {
2060 self.move_children.push(MoveChildCommand {
2061 parent_id,
2062 from_index,
2063 to_index,
2064 bubble,
2065 });
2066 self.push_tag(CommandTag::MoveChild);
2067 }
2068 Command::RemoveChild {
2069 parent_id,
2070 child_id,
2071 } => {
2072 self.remove_children.push(RemoveChildCommand {
2073 parent_id,
2074 child_id,
2075 });
2076 self.push_tag(CommandTag::RemoveChild);
2077 }
2078 Command::DetachChild {
2079 parent_id,
2080 child_id,
2081 } => {
2082 self.detach_children.push(DetachChildCommand {
2083 parent_id,
2084 child_id,
2085 });
2086 self.push_tag(CommandTag::DetachChild);
2087 }
2088 Command::SyncChildren {
2089 parent_id,
2090 expected_children,
2091 } => {
2092 let child_start = self.sync_child_ids.len();
2093 let child_len = expected_children.len();
2094 self.sync_child_ids.extend(expected_children);
2095 self.sync_children.push(SyncChildrenCommand {
2096 parent_id,
2097 child_start,
2098 child_len,
2099 });
2100 self.push_tag(CommandTag::SyncChildren);
2101 }
2102 Command::Callback(callback) => {
2103 self.callbacks.push(callback);
2104 self.push_tag(CommandTag::Callback);
2105 }
2106 }
2107 }
2108
2109 fn len(&self) -> usize {
2110 self.len
2111 }
2112
2113 fn capacity(&self) -> usize {
2114 self.chunks.iter().map(Vec::capacity).sum()
2115 }
2116
2117 fn payload_len_bytes(&self) -> usize {
2118 self.bubble_dirty
2119 .len()
2120 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2121 .saturating_add(
2122 self.update_typed_nodes
2123 .len()
2124 .saturating_mul(std::mem::size_of::<UpdateTypedNodeCommand>()),
2125 )
2126 .saturating_add(
2127 self.remove_nodes
2128 .len()
2129 .saturating_mul(std::mem::size_of::<NodeId>()),
2130 )
2131 .saturating_add(
2132 self.mount_nodes
2133 .len()
2134 .saturating_mul(std::mem::size_of::<NodeId>()),
2135 )
2136 .saturating_add(
2137 self.attach_children
2138 .len()
2139 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2140 )
2141 .saturating_add(
2142 self.insert_children
2143 .len()
2144 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2145 )
2146 .saturating_add(
2147 self.move_children
2148 .len()
2149 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2150 )
2151 .saturating_add(
2152 self.remove_children
2153 .len()
2154 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2155 )
2156 .saturating_add(
2157 self.detach_children
2158 .len()
2159 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2160 )
2161 .saturating_add(
2162 self.sync_children
2163 .len()
2164 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2165 )
2166 .saturating_add(
2167 self.sync_child_ids
2168 .len()
2169 .saturating_mul(std::mem::size_of::<NodeId>()),
2170 )
2171 .saturating_add(
2172 self.callbacks
2173 .len()
2174 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2175 )
2176 }
2177
2178 fn payload_capacity_bytes(&self) -> usize {
2179 self.bubble_dirty
2180 .capacity()
2181 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2182 .saturating_add(
2183 self.update_typed_nodes
2184 .capacity()
2185 .saturating_mul(std::mem::size_of::<UpdateTypedNodeCommand>()),
2186 )
2187 .saturating_add(
2188 self.remove_nodes
2189 .capacity()
2190 .saturating_mul(std::mem::size_of::<NodeId>()),
2191 )
2192 .saturating_add(
2193 self.mount_nodes
2194 .capacity()
2195 .saturating_mul(std::mem::size_of::<NodeId>()),
2196 )
2197 .saturating_add(
2198 self.attach_children
2199 .capacity()
2200 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2201 )
2202 .saturating_add(
2203 self.insert_children
2204 .capacity()
2205 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2206 )
2207 .saturating_add(
2208 self.move_children
2209 .capacity()
2210 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2211 )
2212 .saturating_add(
2213 self.remove_children
2214 .capacity()
2215 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2216 )
2217 .saturating_add(
2218 self.detach_children
2219 .capacity()
2220 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2221 )
2222 .saturating_add(
2223 self.sync_children
2224 .capacity()
2225 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2226 )
2227 .saturating_add(
2228 self.sync_child_ids
2229 .capacity()
2230 .saturating_mul(std::mem::size_of::<NodeId>()),
2231 )
2232 .saturating_add(
2233 self.callbacks
2234 .capacity()
2235 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2236 )
2237 }
2238
2239 fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2240 let mut bubble_dirty = self.bubble_dirty.into_iter();
2241 let mut update_typed_nodes = self.update_typed_nodes.into_iter();
2242 let mut remove_nodes = self.remove_nodes.into_iter();
2243 let mut mount_nodes = self.mount_nodes.into_iter();
2244 let mut attach_children = self.attach_children.into_iter();
2245 let mut insert_children = self.insert_children.into_iter();
2246 let mut move_children = self.move_children.into_iter();
2247 let mut remove_children = self.remove_children.into_iter();
2248 let mut detach_children = self.detach_children.into_iter();
2249 let mut sync_children_commands = self.sync_children.into_iter();
2250 let sync_child_ids = self.sync_child_ids;
2251 let mut callbacks = self.callbacks.into_iter();
2252 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2253
2254 for chunk in self.chunks {
2255 for tag in chunk {
2256 match tag {
2257 CommandTag::BubbleDirty => {
2258 let BubbleDirtyCommand { node_id, bubble } =
2259 next_command_payload(&mut bubble_dirty, tag)?;
2260 Command::BubbleDirty { node_id, bubble }
2261 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2262 }
2263 CommandTag::UpdateTypedNode => {
2264 let UpdateTypedNodeCommand { id, updater } =
2265 next_command_payload(&mut update_typed_nodes, tag)?;
2266 Command::UpdateTypedNode { id, updater }
2267 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2268 }
2269 CommandTag::RemoveNode => {
2270 let id = next_command_payload(&mut remove_nodes, tag)?;
2271 Command::RemoveNode { id }
2272 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2273 }
2274 CommandTag::MountNode => {
2275 let id = next_command_payload(&mut mount_nodes, tag)?;
2276 Command::MountNode { id }
2277 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2278 }
2279 CommandTag::AttachChild => {
2280 let AttachChildCommand {
2281 parent_id,
2282 child_id,
2283 bubble,
2284 } = next_command_payload(&mut attach_children, tag)?;
2285 Command::AttachChild {
2286 parent_id,
2287 child_id,
2288 bubble,
2289 }
2290 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2291 }
2292 CommandTag::InsertChild => {
2293 let InsertChildCommand {
2294 parent_id,
2295 child_id,
2296 appended_index,
2297 insert_index,
2298 bubble,
2299 } = next_command_payload(&mut insert_children, tag)?;
2300 Command::InsertChild {
2301 parent_id,
2302 child_id,
2303 appended_index,
2304 insert_index,
2305 bubble,
2306 }
2307 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2308 }
2309 CommandTag::MoveChild => {
2310 let MoveChildCommand {
2311 parent_id,
2312 from_index,
2313 to_index,
2314 bubble,
2315 } = next_command_payload(&mut move_children, tag)?;
2316 Command::MoveChild {
2317 parent_id,
2318 from_index,
2319 to_index,
2320 bubble,
2321 }
2322 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2323 }
2324 CommandTag::RemoveChild => {
2325 let RemoveChildCommand {
2326 parent_id,
2327 child_id,
2328 } = next_command_payload(&mut remove_children, tag)?;
2329 Command::RemoveChild {
2330 parent_id,
2331 child_id,
2332 }
2333 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2334 }
2335 CommandTag::DetachChild => {
2336 let DetachChildCommand {
2337 parent_id,
2338 child_id,
2339 } = next_command_payload(&mut detach_children, tag)?;
2340 Command::DetachChild {
2341 parent_id,
2342 child_id,
2343 }
2344 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2345 }
2346 CommandTag::SyncChildren => {
2347 let SyncChildrenCommand {
2348 parent_id,
2349 child_start,
2350 child_len,
2351 } = next_command_payload(&mut sync_children_commands, tag)?;
2352 let child_end = child_start
2353 .checked_add(child_len)
2354 .ok_or_else(|| command_payload_error(tag))?;
2355 let expected_children = sync_child_ids
2356 .get(child_start..child_end)
2357 .ok_or_else(|| command_payload_error(tag))?;
2358 sync_children(
2359 applier,
2360 parent_id,
2361 expected_children,
2362 &mut deferred_cleanup,
2363 )?;
2364 }
2365 CommandTag::Callback => {
2366 let callback = next_command_payload(&mut callbacks, tag)?;
2367 Command::Callback(callback)
2368 .apply_with_cleanup(applier, &mut deferred_cleanup)?;
2369 }
2370 }
2371 }
2372 }
2373
2374 debug_assert!(bubble_dirty.next().is_none());
2375 debug_assert!(update_typed_nodes.next().is_none());
2376 debug_assert!(remove_nodes.next().is_none());
2377 debug_assert!(mount_nodes.next().is_none());
2378 debug_assert!(attach_children.next().is_none());
2379 debug_assert!(insert_children.next().is_none());
2380 debug_assert!(move_children.next().is_none());
2381 debug_assert!(remove_children.next().is_none());
2382 debug_assert!(detach_children.next().is_none());
2383 debug_assert!(sync_children_commands.next().is_none());
2384 debug_assert!(callbacks.next().is_none());
2385 deferred_cleanup.flush(applier)
2386 }
2387}
2388
2389fn command_payload_error(tag: CommandTag) -> NodeError {
2390 NodeError::MalformedCommandPayload { tag: tag.label() }
2391}
2392
2393fn next_command_payload<T>(
2394 payloads: &mut impl Iterator<Item = T>,
2395 tag: CommandTag,
2396) -> Result<T, NodeError> {
2397 payloads.next().ok_or_else(|| command_payload_error(tag))
2398}
2399
2400fn update_typed_node<N: Node + 'static>(node: &mut dyn Node, id: NodeId) -> Result<(), NodeError> {
2401 let typed = node
2402 .as_any_mut()
2403 .downcast_mut::<N>()
2404 .ok_or(NodeError::TypeMismatch {
2405 id,
2406 expected: std::any::type_name::<N>(),
2407 })?;
2408 typed.update();
2409 Ok(())
2410}
2411
2412fn insert_child_with_reparenting(applier: &mut dyn Applier, parent_id: NodeId, child_id: NodeId) {
2413 if parent_id == child_id {
2414 debug_assert_ne!(
2415 parent_id, child_id,
2416 "a node cannot be attached as its own child"
2417 );
2418 return;
2419 }
2420
2421 let old_parent = applier
2422 .get_mut(child_id)
2423 .ok()
2424 .and_then(|node| node.parent());
2425 if let Some(old_parent_id) = old_parent {
2426 if old_parent_id != parent_id {
2427 if let Ok(old_parent_node) = applier.get_mut(old_parent_id) {
2428 old_parent_node.remove_child(child_id);
2429 }
2430 if let Ok(child_node) = applier.get_mut(child_id) {
2431 child_node.on_removed_from_parent();
2432 }
2433 bubble_layout_dirty(applier, old_parent_id);
2434 bubble_measure_dirty(applier, old_parent_id);
2435 applier.record_structural_change(old_parent_id);
2436 }
2437 }
2438
2439 if let Ok(parent_node) = applier.get_mut(parent_id) {
2440 parent_node.insert_child(child_id);
2441 }
2442 applier.record_structural_change(parent_id);
2443 if let Ok(child_node) = applier.get_mut(child_id) {
2444 child_node.on_attached_to_parent(parent_id);
2445 }
2446}
2447
2448fn apply_remove_child(
2449 applier: &mut dyn Applier,
2450 parent_id: NodeId,
2451 child_id: NodeId,
2452 deferred_cleanup: &mut DeferredChildCleanupQueue,
2453) -> Result<(), NodeError> {
2454 detach_child_from_parent(applier, parent_id, child_id)?;
2455
2456 let generation = applier.node_generation(child_id);
2457 let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
2458 node.parent().is_none()
2459 } else {
2460 return Ok(());
2461 };
2462 deferred_cleanup.push(child_id, generation, removed_from_parent);
2463 Ok(())
2464}
2465
2466fn detach_child_from_parent(
2467 applier: &mut dyn Applier,
2468 parent_id: NodeId,
2469 child_id: NodeId,
2470) -> Result<(), NodeError> {
2471 if let Ok(parent_node) = applier.get_mut(parent_id) {
2472 parent_node.remove_child(child_id);
2473 }
2474 bubble_layout_dirty(applier, parent_id);
2475 bubble_measure_dirty(applier, parent_id);
2476 applier.record_structural_change(parent_id);
2477
2478 if let Ok(node) = applier.get_mut(child_id) {
2479 match node.parent() {
2480 Some(existing_parent_id) if existing_parent_id == parent_id => {
2481 node.on_removed_from_parent();
2482 }
2483 None => {}
2484 Some(_) => return Ok(()),
2485 }
2486 } else {
2487 return Ok(());
2488 }
2489
2490 Ok(())
2491}
2492
2493fn cleanup_detached_child(
2494 applier: &mut dyn Applier,
2495 cleanup: DeferredChildCleanup,
2496) -> Result<(), NodeError> {
2497 if applier.node_generation(cleanup.child_id) != cleanup.generation {
2498 return Ok(());
2499 }
2500
2501 let parent_id = match applier.get_mut(cleanup.child_id) {
2502 Ok(node) => node.parent(),
2503 Err(NodeError::Missing { .. }) => return Ok(()),
2504 Err(err) => return Err(err),
2505 };
2506 if parent_id.is_some() {
2507 return Ok(());
2508 }
2509
2510 if let Ok(node) = applier.get_mut(cleanup.child_id) {
2511 if !cleanup.removed_from_parent {
2512 node.on_removed_from_parent();
2513 }
2514 node.unmount();
2515 }
2516 match applier.remove(cleanup.child_id) {
2517 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2518 Err(err) => Err(err),
2519 }
2520}
2521
2522fn remove_child_and_cleanup_now(
2523 applier: &mut dyn Applier,
2524 parent_id: NodeId,
2525 child_id: NodeId,
2526) -> Result<(), NodeError> {
2527 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2528 apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
2529 deferred_cleanup.flush(applier)
2530}
2531
2532fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
2533 let mut scratch = SmallVec::<[NodeId; 8]>::new();
2534 if let Ok(node) = applier.get_mut(parent_id) {
2535 node.collect_children_into(&mut scratch);
2536 }
2537 let mut current = ChildList::new();
2538 current.extend(scratch);
2539 current
2540}
2541
2542fn sync_children(
2543 applier: &mut dyn Applier,
2544 parent_id: NodeId,
2545 expected_children: &[NodeId],
2546 deferred_cleanup: &mut DeferredChildCleanupQueue,
2547) -> Result<(), NodeError> {
2548 let mut current = collect_current_children(applier, parent_id);
2549 let children_changed = current.as_slice() != expected_children;
2550
2551 if children_changed {
2552 if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
2553 sync_children_small(
2554 applier,
2555 parent_id,
2556 &mut current,
2557 expected_children,
2558 deferred_cleanup,
2559 )?;
2560 } else {
2561 let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
2562 target_positions.reserve(expected_children.len());
2563 for (index, &child) in expected_children.iter().enumerate() {
2564 target_positions.insert(child, index);
2565 }
2566
2567 for index in (0..current.len()).rev() {
2568 let child = current[index];
2569 if !target_positions.contains_key(&child) {
2570 current.remove(index);
2571 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
2572 }
2573 }
2574
2575 let mut current_positions = build_child_positions(¤t);
2576 for (target_index, &child) in expected_children.iter().enumerate() {
2577 if let Some(current_index) = current_positions.get(&child).copied() {
2578 if current_index != target_index {
2579 let from_index = current_index;
2580 let to_index = move_child_in_diff_state(
2581 &mut current,
2582 &mut current_positions,
2583 from_index,
2584 target_index,
2585 );
2586 Command::MoveChild {
2587 parent_id,
2588 from_index,
2589 to_index,
2590 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2591 }
2592 .apply(applier)?;
2593 }
2594 } else {
2595 let insert_index = target_index.min(current.len());
2596 let appended_index = current.len();
2597 insert_child_into_diff_state(
2598 &mut current,
2599 &mut current_positions,
2600 insert_index,
2601 child,
2602 );
2603 Command::InsertChild {
2604 parent_id,
2605 child_id: child,
2606 appended_index,
2607 insert_index,
2608 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2609 }
2610 .apply(applier)?;
2611 }
2612 }
2613 }
2614 }
2615
2616 reconcile_children(applier, parent_id, expected_children, !children_changed)
2617}
2618
2619fn sync_children_small(
2620 applier: &mut dyn Applier,
2621 parent_id: NodeId,
2622 current: &mut ChildList,
2623 expected_children: &[NodeId],
2624 deferred_cleanup: &mut DeferredChildCleanupQueue,
2625) -> Result<(), NodeError> {
2626 for index in (0..current.len()).rev() {
2627 let child = current[index];
2628 if !expected_children.contains(&child) {
2629 current.remove(index);
2630 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
2631 }
2632 }
2633
2634 for (target_index, &child) in expected_children.iter().enumerate() {
2635 if let Some(current_index) = current
2636 .iter()
2637 .position(|¤t_child| current_child == child)
2638 {
2639 if current_index != target_index {
2640 let child = current.remove(current_index);
2641 let to_index = target_index.min(current.len());
2642 current.insert(to_index, child);
2643 Command::MoveChild {
2644 parent_id,
2645 from_index: current_index,
2646 to_index,
2647 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2648 }
2649 .apply(applier)?;
2650 }
2651 } else {
2652 let insert_index = target_index.min(current.len());
2653 let appended_index = current.len();
2654 current.insert(insert_index, child);
2655 Command::InsertChild {
2656 parent_id,
2657 child_id: child,
2658 appended_index,
2659 insert_index,
2660 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
2661 }
2662 .apply(applier)?;
2663 }
2664 }
2665
2666 Ok(())
2667}
2668
2669fn reconcile_children(
2670 applier: &mut dyn Applier,
2671 parent_id: NodeId,
2672 expected_children: &[NodeId],
2673 needs_dirty_check: bool,
2674) -> Result<(), NodeError> {
2675 let mut repaired = false;
2676 for &child_id in expected_children {
2677 let needs_attach = if let Ok(node) = applier.get_mut(child_id) {
2678 node.parent() != Some(parent_id)
2679 } else {
2680 false
2681 };
2682
2683 if needs_attach {
2684 insert_child_with_reparenting(applier, parent_id, child_id);
2685 repaired = true;
2686 }
2687 }
2688
2689 let is_dirty = if needs_dirty_check {
2690 if let Ok(node) = applier.get_mut(parent_id) {
2691 node.needs_layout()
2692 } else {
2693 false
2694 }
2695 } else {
2696 false
2697 };
2698
2699 if repaired {
2700 bubble_layout_dirty(applier, parent_id);
2701 bubble_measure_dirty(applier, parent_id);
2702 } else if is_dirty {
2703 bubble_layout_dirty(applier, parent_id);
2704 }
2705
2706 Ok(())
2707}
2708
2709#[derive(Default)]
2710pub struct MemoryApplier {
2711 nodes: Vec<Option<Box<dyn Node>>>,
2712 physical_stable_ids: Vec<u32>,
2714 physical_warm_recycled_origins: Vec<bool>,
2715 stable_to_physical: HashMap<NodeId, usize>,
2717 stable_generations: HashMap<NodeId, u32>,
2719 free_ids: BinaryHeap<Reverse<usize>>,
2721 high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
2724 high_id_warm_recycled_origins: HashMap<NodeId, bool>,
2725 high_id_generations: HashMap<NodeId, u32>,
2726 next_stable_id: NodeId,
2727 layout_runtime: Option<RuntimeHandle>,
2728 slots: SlotTable,
2729 recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2730 returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2731 cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
2732 recycled_node_limits: HashMap<TypeId, usize>,
2733 warm_recycled_node_targets: HashMap<TypeId, usize>,
2734 fresh_recyclable_creations: HashMap<TypeId, usize>,
2735 recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
2736 structural_change_parents: Vec<NodeId>,
2739}
2740
2741struct RemovalFrame {
2742 node_id: NodeId,
2743 children: SmallVec<[NodeId; 8]>,
2744 next_child: usize,
2745}
2746
2747#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2748pub struct MemoryApplierDebugStats {
2749 pub next_stable_id: NodeId,
2750 pub nodes_len: usize,
2751 pub nodes_cap: usize,
2752 pub physical_stable_ids_len: usize,
2753 pub physical_stable_ids_cap: usize,
2754 pub stable_to_physical_len: usize,
2755 pub stable_to_physical_cap: usize,
2756 pub stable_generations_len: usize,
2757 pub stable_generations_cap: usize,
2758 pub free_ids_len: usize,
2759 pub free_ids_cap: usize,
2760 pub high_id_nodes_len: usize,
2761 pub high_id_nodes_cap: usize,
2762 pub high_id_generations_len: usize,
2763 pub high_id_generations_cap: usize,
2764 pub recycled_type_count: usize,
2765 pub recycled_type_cap: usize,
2766 pub recycled_node_count: usize,
2767 pub recycled_node_capacity: usize,
2768 pub warm_recycled_node_id_count: usize,
2769 pub warm_recycled_node_id_capacity: usize,
2770}
2771
2772impl MemoryApplier {
2773 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
2774 const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
2775 const INVALID_STABLE_ID: u32 = u32::MAX;
2776 const INITIAL_DENSE_NODE_CAP: usize = 32;
2777 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
2778 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
2779
2780 fn pack_stable_id(stable_id: NodeId) -> u32 {
2781 u32::try_from(stable_id).expect("stable id overflow")
2782 }
2783
2784 fn unpack_stable_id(stable_id: u32) -> NodeId {
2785 stable_id as NodeId
2786 }
2787
2788 fn next_dense_node_target_len(old_len: usize) -> usize {
2789 if old_len < Self::INITIAL_DENSE_NODE_CAP {
2790 return Self::INITIAL_DENSE_NODE_CAP;
2791 }
2792 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
2793 return old_len.saturating_mul(2);
2794 }
2795
2796 let incremental_growth =
2797 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
2798 old_len.saturating_add(incremental_growth)
2799 }
2800
2801 fn ensure_dense_node_storage_capacity(&mut self) {
2802 let len = self
2803 .nodes
2804 .len()
2805 .max(self.physical_stable_ids.len())
2806 .max(self.physical_warm_recycled_origins.len());
2807 if len < self.nodes.capacity()
2808 && len < self.physical_stable_ids.capacity()
2809 && len < self.physical_warm_recycled_origins.capacity()
2810 {
2811 return;
2812 }
2813
2814 let target = Self::next_dense_node_target_len(len);
2815 if self.nodes.capacity() < target {
2816 self.nodes
2817 .reserve_exact(target.saturating_sub(self.nodes.len()));
2818 }
2819 if self.physical_stable_ids.capacity() < target {
2820 self.physical_stable_ids
2821 .reserve_exact(target.saturating_sub(self.physical_stable_ids.len()));
2822 }
2823 if self.physical_warm_recycled_origins.capacity() < target {
2824 self.physical_warm_recycled_origins
2825 .reserve_exact(target.saturating_sub(self.physical_warm_recycled_origins.len()));
2826 }
2827 }
2828
2829 fn ensure_stable_index_capacity(&mut self) {
2830 let len = self
2831 .stable_to_physical
2832 .len()
2833 .max(self.stable_generations.len());
2834 if len < self.stable_to_physical.capacity() && len < self.stable_generations.capacity() {
2835 return;
2836 }
2837
2838 let target = Self::next_dense_node_target_len(len);
2839 let additional = target.saturating_sub(len);
2840 if self.stable_to_physical.capacity() < target {
2841 self.stable_to_physical.reserve(additional);
2842 }
2843 if self.stable_generations.capacity() < target {
2844 self.stable_generations.reserve(additional);
2845 }
2846 }
2847
2848 pub fn new() -> Self {
2849 Self {
2850 nodes: Vec::new(),
2851 physical_stable_ids: Vec::new(),
2852 physical_warm_recycled_origins: Vec::new(),
2853 stable_to_physical: HashMap::default(),
2854 stable_generations: HashMap::default(),
2855 free_ids: BinaryHeap::new(),
2856 high_id_nodes: HashMap::default(),
2857 high_id_warm_recycled_origins: HashMap::default(),
2858 high_id_generations: HashMap::default(),
2859 next_stable_id: 0,
2860 layout_runtime: None,
2861 slots: SlotTable::default(),
2862 recycled_nodes: HashMap::default(),
2863 returning_recycled_nodes: HashMap::default(),
2864 cold_recycled_nodes: HashMap::default(),
2865 recycled_node_limits: HashMap::default(),
2866 warm_recycled_node_targets: HashMap::default(),
2867 fresh_recyclable_creations: HashMap::default(),
2868 recycled_node_prototypes: HashMap::default(),
2869 structural_change_parents: Vec::new(),
2870 }
2871 }
2872
2873 pub fn slots(&mut self) -> &mut SlotTable {
2874 &mut self.slots
2875 }
2876
2877 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
2881 let recorded = std::mem::take(&mut self.structural_change_parents);
2882 let mut attached = Vec::with_capacity(recorded.len());
2883 for parent_id in recorded {
2884 if self.is_attached_to(parent_id, root) && !attached.contains(&parent_id) {
2885 attached.push(parent_id);
2886 }
2887 }
2888 attached
2889 }
2890
2891 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
2892 let mut current = node_id;
2893 for _ in 0..100_000 {
2896 if current == root {
2897 return true;
2898 }
2899 match self.get_mut(current) {
2900 Ok(node) => match node.parent() {
2901 Some(parent) => current = parent,
2902 None => return false,
2903 },
2904 Err(_) => return false,
2905 }
2906 }
2907 false
2908 }
2909
2910 pub fn with_node<N: Node + 'static, R>(
2911 &mut self,
2912 id: NodeId,
2913 f: impl FnOnce(&mut N) -> R,
2914 ) -> Result<R, NodeError> {
2915 let physical_id = self
2916 .resolve_node_index(id)
2917 .ok_or(NodeError::Missing { id })?;
2918 let slot = self
2919 .nodes
2920 .get_mut(physical_id)
2921 .ok_or(NodeError::Missing { id })?
2922 .as_deref_mut()
2923 .ok_or(NodeError::Missing { id })?;
2924 let typed = slot
2925 .as_any_mut()
2926 .downcast_mut::<N>()
2927 .ok_or(NodeError::TypeMismatch {
2928 id,
2929 expected: std::any::type_name::<N>(),
2930 })?;
2931 Ok(f(typed))
2932 }
2933
2934 pub fn len(&self) -> usize {
2935 self.nodes.iter().filter(|n| n.is_some()).count()
2936 }
2937
2938 pub fn capacity(&self) -> usize {
2939 self.nodes.len()
2940 }
2941
2942 pub fn tombstone_count(&self) -> usize {
2943 self.nodes.iter().filter(|n| n.is_none()).count()
2944 }
2945
2946 pub fn freelist_len(&self) -> usize {
2947 self.free_ids.len()
2948 }
2949
2950 pub fn debug_recycled_node_count(&self) -> usize {
2951 self.total_recycled_node_count()
2952 }
2953
2954 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
2955 let key = TypeId::of::<N>();
2956 self.recycled_nodes.get(&key).map(Vec::len).unwrap_or(0)
2957 + self
2958 .returning_recycled_nodes
2959 .get(&key)
2960 .map(Vec::len)
2961 .unwrap_or(0)
2962 + self
2963 .cold_recycled_nodes
2964 .get(&key)
2965 .map(Vec::len)
2966 .unwrap_or(0)
2967 }
2968
2969 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
2970 let mut recycled_keys: HashSet<TypeId> = HashSet::default();
2971 recycled_keys.extend(self.recycled_nodes.keys().copied());
2972 recycled_keys.extend(self.returning_recycled_nodes.keys().copied());
2973 recycled_keys.extend(self.cold_recycled_nodes.keys().copied());
2974
2975 MemoryApplierDebugStats {
2976 next_stable_id: self.next_stable_id,
2977 nodes_len: self.len(),
2978 nodes_cap: self.nodes.len(),
2979 physical_stable_ids_len: self.physical_stable_ids.len(),
2980 physical_stable_ids_cap: self.physical_stable_ids.capacity(),
2981 stable_to_physical_len: self.stable_to_physical.len(),
2982 stable_to_physical_cap: self.stable_to_physical.capacity(),
2983 stable_generations_len: self.stable_generations.len(),
2984 stable_generations_cap: self.stable_generations.capacity(),
2985 free_ids_len: self.free_ids.len(),
2986 free_ids_cap: self.free_ids.capacity(),
2987 high_id_nodes_len: self.high_id_nodes.len(),
2988 high_id_nodes_cap: self.high_id_nodes.capacity(),
2989 high_id_generations_len: self.high_id_generations.len(),
2990 high_id_generations_cap: self.high_id_generations.capacity(),
2991 recycled_type_count: recycled_keys.len(),
2992 recycled_type_cap: self.recycled_nodes.capacity()
2993 + self.returning_recycled_nodes.capacity()
2994 + self.cold_recycled_nodes.capacity(),
2995 recycled_node_count: self.total_recycled_node_count(),
2996 recycled_node_capacity: self.total_recycled_node_capacity(),
2997 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
2998 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
2999 }
3000 }
3001
3002 pub fn is_empty(&self) -> bool {
3003 self.len() == 0
3004 }
3005
3006 pub fn debug_live_node_heap_bytes(&self) -> usize {
3007 let dense_nodes = self
3008 .nodes
3009 .iter()
3010 .flatten()
3011 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3012 .sum::<usize>();
3013 let high_id_nodes = self
3014 .high_id_nodes
3015 .values()
3016 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3017 .sum::<usize>();
3018 dense_nodes + high_id_nodes
3019 }
3020
3021 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
3022 let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
3023 pools
3024 .values()
3025 .flat_map(|nodes| nodes.iter())
3026 .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
3027 .sum::<usize>()
3028 };
3029
3030 pool_bytes(&self.recycled_nodes)
3031 + pool_bytes(&self.returning_recycled_nodes)
3032 + pool_bytes(&self.cold_recycled_nodes)
3033 }
3034
3035 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
3036 self.layout_runtime = Some(handle);
3037 }
3038
3039 pub fn clear_runtime_handle(&mut self) {
3040 self.layout_runtime = None;
3041 }
3042
3043 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
3044 self.layout_runtime.clone()
3045 }
3046
3047 fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3048 pools.values().map(Vec::len).sum()
3049 }
3050
3051 fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3052 pools.values().map(Vec::capacity).sum()
3053 }
3054
3055 fn total_recycled_node_count(&self) -> usize {
3056 Self::pool_node_count(&self.recycled_nodes)
3057 + Self::pool_node_count(&self.returning_recycled_nodes)
3058 + Self::pool_node_count(&self.cold_recycled_nodes)
3059 }
3060
3061 fn total_recycled_node_capacity(&self) -> usize {
3062 Self::pool_node_capacity(&self.recycled_nodes)
3063 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3064 + Self::pool_node_capacity(&self.cold_recycled_nodes)
3065 }
3066
3067 fn total_warm_recycled_node_id_count(&self) -> usize {
3068 self.live_warm_recycled_origin_count()
3069 + Self::pool_node_count(&self.recycled_nodes)
3070 + Self::pool_node_count(&self.returning_recycled_nodes)
3071 }
3072
3073 fn total_warm_recycled_node_id_capacity(&self) -> usize {
3074 self.live_warm_recycled_origin_capacity()
3075 + Self::pool_node_capacity(&self.recycled_nodes)
3076 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3077 }
3078
3079 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
3080 if let Some(limit) = recycle_pool_limit {
3081 self.recycled_node_limits.insert(key, limit);
3082 } else {
3083 self.recycled_node_limits.remove(&key);
3084 }
3085 }
3086
3087 fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
3088 self.recycled_node_limits.get(&key).copied()
3089 }
3090
3091 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
3092 self.recycled_nodes.get(&key).map(Vec::len).unwrap_or(0)
3093 }
3094
3095 fn warm_recycled_node_target(&self, key: TypeId) -> usize {
3096 self.warm_recycled_node_targets
3097 .get(&key)
3098 .copied()
3099 .unwrap_or(0)
3100 }
3101
3102 fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
3103 let Some(limit) = self.recycle_pool_limit_for(key) else {
3104 return usize::MAX;
3105 };
3106 if limit <= 8 {
3107 limit
3108 } else {
3109 limit / 4
3110 }
3111 }
3112
3113 fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
3114 let target_limit = self.warm_recycled_node_target_limit(key);
3115 let existing = self.warm_recycled_node_target(key).min(target_limit);
3116 if observed_demand == 0 {
3117 return existing;
3118 }
3119
3120 let target = match self.recycle_pool_limit_for(key) {
3121 Some(limit) if limit > 8 => target_limit,
3122 Some(_) => observed_demand.min(target_limit),
3123 None => observed_demand,
3124 };
3125 self.warm_recycled_node_targets.insert(key, target);
3126 target
3127 }
3128
3129 fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
3130 if self.recycled_node_prototypes.contains_key(&key) {
3131 return;
3132 }
3133 if let Some(prototype) = shell.rehouse_for_recycle() {
3134 self.recycled_node_prototypes.insert(key, prototype);
3135 }
3136 }
3137
3138 fn live_warm_recycled_origin_count(&self) -> usize {
3139 self.physical_warm_recycled_origins
3140 .iter()
3141 .zip(self.nodes.iter())
3142 .filter(|(warm_origin, node)| **warm_origin && node.is_some())
3143 .count()
3144 + self
3145 .high_id_warm_recycled_origins
3146 .values()
3147 .filter(|warm_origin| **warm_origin)
3148 .count()
3149 }
3150
3151 fn live_warm_recycled_origin_capacity(&self) -> usize {
3152 self.physical_warm_recycled_origins.capacity()
3153 + self.high_id_warm_recycled_origins.capacity()
3154 }
3155
3156 fn push_recycled_node(
3157 &mut self,
3158 key: TypeId,
3159 recycle_pool_limit: Option<usize>,
3160 recycled: RecycledNode,
3161 ) {
3162 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3163 self.remember_recycled_node_prototype(key, recycled.node.as_ref());
3164
3165 let warm_origin = recycled.warm_origin();
3166 let pool = if warm_origin {
3167 self.returning_recycled_nodes.entry(key).or_default()
3168 } else {
3169 self.cold_recycled_nodes.entry(key).or_default()
3170 };
3171 pool.push(recycled);
3172 if let Some(limit) = recycle_pool_limit {
3173 if pool.len() > limit {
3174 let excess = pool.len() - limit;
3175 let dropped: Vec<_> = pool.drain(0..excess).collect();
3176 drop(dropped);
3177 }
3178 }
3179 }
3180
3181 fn push_warm_recycled_node(
3182 &mut self,
3183 key: TypeId,
3184 recycle_pool_limit: Option<usize>,
3185 mut recycled: RecycledNode,
3186 ) {
3187 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3188
3189 recycled.set_warm_origin(true);
3190 let mut dropped = Vec::new();
3191 let mut remove_pool_entry = false;
3192 {
3193 let pool = self.recycled_nodes.entry(key).or_default();
3194 pool.push(recycled);
3195 if let Some(limit) = recycle_pool_limit {
3196 if pool.len() > limit {
3197 let excess = pool.len() - limit;
3198 dropped = pool.drain(0..excess).collect();
3199 remove_pool_entry = pool.is_empty();
3200 }
3201 }
3202 }
3203 if remove_pool_entry {
3204 self.recycled_nodes.remove(&key);
3205 }
3206 drop(dropped);
3207 }
3208
3209 fn seed_recycled_node_shell_impl(
3210 &mut self,
3211 key: TypeId,
3212 recycle_pool_limit: Option<usize>,
3213 shell: Box<dyn Node>,
3214 ) {
3215 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
3216 if self.warm_recycled_pool_len(key) >= limit {
3217 return;
3218 }
3219
3220 self.remember_recycled_node_prototype(key, shell.as_ref());
3221 let stable_id = self.next_stable_id;
3222 self.next_stable_id = self.next_stable_id.saturating_add(1);
3223 self.push_warm_recycled_node(
3224 key,
3225 recycle_pool_limit,
3226 RecycledNode::from_shell(stable_id, shell, true),
3227 );
3228 }
3229
3230 fn take_recycled_node_from_pool(
3231 pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
3232 key: TypeId,
3233 ) -> Option<RecycledNode> {
3234 let pool = pools.get_mut(&key)?;
3235 let node = pool.pop();
3236 if pool.is_empty() {
3237 pools.remove(&key);
3238 }
3239 node
3240 }
3241
3242 fn compact_idle_warm_pool(&mut self, key: TypeId) {
3243 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
3244 return;
3245 };
3246 if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
3247 return;
3248 }
3249
3250 let retained = pool.len();
3251 let mut compacted = Vec::with_capacity(retained);
3252 compacted.append(pool);
3253 let remove_pool_entry = compacted.is_empty();
3254 *pool = compacted;
3255 let _ = pool;
3256
3257 if remove_pool_entry {
3258 self.recycled_nodes.remove(&key);
3259 }
3260 }
3261
3262 fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
3263 let pool_len = self.warm_recycled_pool_len(key);
3264 if pool_len <= target {
3265 return;
3266 }
3267
3268 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
3269 return;
3270 };
3271 let removable = (pool_len - target).min(pool.len());
3272 let dropped: Vec<_> = pool.drain(0..removable).collect();
3273 let remove_pool_entry = pool.is_empty();
3274 let _ = pool;
3275
3276 if remove_pool_entry {
3277 self.recycled_nodes.remove(&key);
3278 }
3279 drop(dropped);
3280 }
3281
3282 fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
3283 let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
3284 if missing == 0 {
3285 return;
3286 }
3287
3288 let recycle_pool_limit = self.recycle_pool_limit_for(key);
3289 let mut shells = Vec::with_capacity(missing);
3290 if let Some(prototype) = self.recycled_node_prototypes.get(&key) {
3291 for _ in 0..missing {
3292 let Some(shell) = prototype.rehouse_for_recycle() else {
3293 break;
3294 };
3295 shells.push(shell);
3296 }
3297 }
3298
3299 for shell in shells {
3300 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
3301 }
3302 }
3303
3304 fn prune_stable_generations(&mut self) {
3305 let retained_len = self.stable_to_physical.len() + self.total_recycled_node_count();
3306 if retained_len == self.stable_generations.len() {
3307 return;
3308 }
3309
3310 let mut retained = HashMap::default();
3311 retained.reserve(retained_len);
3312 for stable_id in self.stable_to_physical.keys().copied() {
3313 if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3314 retained.insert(stable_id, generation);
3315 }
3316 }
3317 for stable_id in self
3318 .recycled_nodes
3319 .values()
3320 .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3321 {
3322 if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3323 retained.insert(stable_id, generation);
3324 }
3325 }
3326 for stable_id in self
3327 .returning_recycled_nodes
3328 .values()
3329 .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3330 {
3331 if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3332 retained.insert(stable_id, generation);
3333 }
3334 }
3335 for stable_id in self
3336 .cold_recycled_nodes
3337 .values()
3338 .flat_map(|nodes| nodes.iter().map(RecycledNode::stable_id))
3339 {
3340 if let Some(generation) = self.stable_generations.get(&stable_id).copied() {
3341 retained.insert(stable_id, generation);
3342 }
3343 }
3344 self.stable_generations = retained;
3345 }
3346
3347 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
3348 let mut output = String::new();
3349 if let Some(root_id) = root {
3350 self.dump_node(&mut output, root_id, 0);
3351 } else {
3352 output.push_str("(no root)\n");
3353 }
3354 output
3355 }
3356
3357 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
3358 let indent = " ".repeat(depth);
3359 if let Some(physical_id) = self.resolve_node_index(id) {
3360 if let Some(node) = self.nodes.get(physical_id).and_then(Option::as_ref) {
3361 let type_name = std::any::type_name_of_val(&**node);
3362 output.push_str(&format!("{}[{}] {}\n", indent, id, type_name));
3363
3364 let children = node.children();
3365 for child_id in children {
3366 self.dump_node(output, child_id, depth + 1);
3367 }
3368 } else {
3369 output.push_str(&format!(
3370 "{}[{}] (missing physical node {})\n",
3371 indent, id, physical_id
3372 ));
3373 }
3374 } else {
3375 output.push_str(&format!("{}[{}] (missing)\n", indent, id));
3376 }
3377 }
3378
3379 fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
3380 self.stable_to_physical.get(&id).copied()
3381 }
3382
3383 fn contains_node_id(&self, id: NodeId) -> bool {
3384 self.resolve_node_index(id).is_some() || self.high_id_nodes.contains_key(&id)
3385 }
3386
3387 fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
3388 self.high_id_nodes.insert(stable_id, node);
3389 self.high_id_warm_recycled_origins
3390 .insert(stable_id, warm_origin);
3391 self.high_id_generations.entry(stable_id).or_insert(0);
3392 }
3393
3394 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
3395 if stable_id >= Self::HIGH_ID_THRESHOLD {
3396 self.insert_high_id_node(stable_id, node, false);
3397 return;
3398 }
3399
3400 let physical_id = if let Some(Reverse(free_physical_id)) = self.free_ids.pop() {
3401 self.nodes[free_physical_id] = Some(node);
3402 self.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
3403 self.physical_warm_recycled_origins[free_physical_id] = false;
3404 free_physical_id
3405 } else {
3406 self.ensure_dense_node_storage_capacity();
3407 let physical_id = self.nodes.len();
3408 self.nodes.push(Some(node));
3409 self.physical_stable_ids
3410 .push(Self::pack_stable_id(stable_id));
3411 self.physical_warm_recycled_origins.push(false);
3412 physical_id
3413 };
3414
3415 self.next_stable_id = self.next_stable_id.max(stable_id.saturating_add(1));
3416 self.ensure_stable_index_capacity();
3417 self.stable_generations.entry(stable_id).or_insert(0);
3418 self.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
3419 self.stable_to_physical.insert(stable_id, physical_id);
3420 }
3421
3422 fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
3423 if let Some(physical_id) = self.resolve_node_index(id) {
3424 let slot = self
3425 .nodes
3426 .get(physical_id)
3427 .ok_or(NodeError::Missing { id })?
3428 .as_deref()
3429 .ok_or(NodeError::Missing { id })?;
3430 return Ok(slot);
3431 }
3432
3433 self.high_id_nodes
3434 .get(&id)
3435 .map(|node| node.as_ref())
3436 .ok_or(NodeError::Missing { id })
3437 }
3438
3439 fn collect_node_children_into(
3440 &self,
3441 id: NodeId,
3442 out: &mut SmallVec<[NodeId; 8]>,
3443 ) -> Result<(), NodeError> {
3444 self.get_ref(id)?.collect_children_into(out);
3445 Ok(())
3446 }
3447
3448 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
3449 Ok(self.get_ref(id)?.parent())
3450 }
3451
3452 fn collect_owned_children(
3453 &self,
3454 node_id: NodeId,
3455 out: &mut SmallVec<[NodeId; 8]>,
3456 ) -> Result<(), NodeError> {
3457 self.collect_node_children_into(node_id, out)?;
3458 out.retain(|child_id| {
3459 self.node_parent(*child_id)
3460 .map(|parent| parent == Some(node_id))
3461 .unwrap_or(false)
3462 });
3463 Ok(())
3464 }
3465
3466 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
3467 if self.high_id_nodes.contains_key(&node_id) {
3468 if let Some(mut node) = self.high_id_nodes.remove(&node_id) {
3469 if let Some(key) = node.recycle_key() {
3470 let recycle_pool_limit = node.recycle_pool_limit();
3471 let warm_origin = self
3472 .high_id_warm_recycled_origins
3473 .remove(&node_id)
3474 .unwrap_or(false);
3475 node.prepare_for_recycle();
3476 self.push_recycled_node(
3477 key,
3478 recycle_pool_limit,
3479 RecycledNode::new(node_id, node, warm_origin),
3480 );
3481 }
3482 }
3483 let generation = self.high_id_generations.entry(node_id).or_insert(0);
3484 *generation = generation.wrapping_add(1);
3485 return Ok(());
3486 }
3487
3488 let physical_id = self
3489 .resolve_node_index(node_id)
3490 .ok_or(NodeError::Missing { id: node_id })?;
3491 if let Some(mut node) = self.nodes[physical_id].take() {
3492 if let Some(key) = node.recycle_key() {
3493 let recycle_pool_limit = node.recycle_pool_limit();
3494 let warm_origin = self
3495 .physical_warm_recycled_origins
3496 .get_mut(physical_id)
3497 .map(std::mem::take)
3498 .unwrap_or(false);
3499 node.prepare_for_recycle();
3500 self.push_recycled_node(
3501 key,
3502 recycle_pool_limit,
3503 RecycledNode::new(node_id, node, warm_origin),
3504 );
3505 }
3506 }
3507 self.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
3508 self.stable_to_physical.remove(&node_id);
3509 if let Some(generation) = self.stable_generations.get_mut(&node_id) {
3510 *generation = generation.wrapping_add(1);
3511 } else {
3512 self.stable_generations.insert(node_id, 1);
3513 }
3514 self.free_ids.push(Reverse(physical_id));
3515 Ok(())
3516 }
3517
3518 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
3519 self.get_ref(id)?;
3520
3521 let mut root_children = SmallVec::<[NodeId; 8]>::new();
3522 self.collect_owned_children(id, &mut root_children)?;
3523
3524 let mut stack = Vec::new();
3525 stack.push(RemovalFrame {
3526 node_id: id,
3527 children: root_children,
3528 next_child: 0,
3529 });
3530 let mut max_depth = stack.len();
3531
3532 while let Some(frame) = stack.last_mut() {
3533 if frame.next_child < frame.children.len() {
3534 let child_id = frame.children[frame.next_child];
3535 frame.next_child += 1;
3536
3537 if let Ok(child) = self.get_mut(child_id) {
3538 child.on_removed_from_parent();
3539 child.unmount();
3540 }
3541
3542 let mut child_children = SmallVec::<[NodeId; 8]>::new();
3543 self.collect_owned_children(child_id, &mut child_children)?;
3544 stack.push(RemovalFrame {
3545 node_id: child_id,
3546 children: child_children,
3547 next_child: 0,
3548 });
3549 max_depth = max_depth.max(stack.len());
3550 continue;
3551 }
3552
3553 let node_id = frame.node_id;
3554 stack.pop();
3555 self.remove_node_storage(node_id)?;
3556 }
3557
3558 Ok(max_depth)
3559 }
3560
3561 #[cfg(test)]
3562 fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
3563 self.remove_subtree_postorder(id)
3564 }
3565}
3566
3567impl Applier for MemoryApplier {
3568 fn record_structural_change(&mut self, parent_id: NodeId) {
3569 if self.structural_change_parents.last() != Some(&parent_id) {
3570 self.structural_change_parents.push(parent_id);
3571 }
3572 }
3573
3574 fn create(&mut self, node: Box<dyn Node>) -> NodeId {
3575 let stable_id = self.next_stable_id;
3576 self.next_stable_id = self.next_stable_id.saturating_add(1);
3577 if stable_id >= Self::HIGH_ID_THRESHOLD {
3578 self.insert_high_id_node(stable_id, node, false);
3579 return stable_id;
3580 }
3581
3582 self.ensure_stable_index_capacity();
3583 self.stable_generations.insert(stable_id, 0);
3584
3585 let physical_id = if let Some(Reverse(id)) = self.free_ids.pop() {
3586 debug_assert!(self.nodes[id].is_none(), "freelist entry {id} is not None");
3587 self.nodes[id] = Some(node);
3588 self.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
3589 self.physical_warm_recycled_origins[id] = false;
3590 id
3591 } else {
3592 self.ensure_dense_node_storage_capacity();
3593 let id = self.nodes.len();
3594 self.nodes.push(Some(node));
3595 self.physical_stable_ids
3596 .push(Self::pack_stable_id(stable_id));
3597 self.physical_warm_recycled_origins.push(false);
3598 id
3599 };
3600 self.stable_to_physical.insert(stable_id, physical_id);
3601 stable_id
3602 }
3603
3604 fn node_generation(&self, id: NodeId) -> u32 {
3605 self.high_id_generations
3606 .get(&id)
3607 .copied()
3608 .or_else(|| self.stable_generations.get(&id).copied())
3609 .unwrap_or(0)
3610 }
3611
3612 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
3613 if let Some(physical_id) = self.resolve_node_index(id) {
3615 let slot = self.nodes[physical_id]
3616 .as_deref_mut()
3617 .ok_or(NodeError::Missing { id })?;
3618 return Ok(slot);
3619 }
3620 self.high_id_nodes
3621 .get_mut(&id)
3622 .map(|n| n.as_mut())
3623 .ok_or(NodeError::Missing { id })
3624 }
3625
3626 fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
3627 self.remove_subtree_postorder(id).map(|_| ())
3628 }
3629
3630 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
3631 if self.contains_node_id(id) {
3632 return Err(NodeError::AlreadyExists { id });
3633 }
3634 self.insert_available_with_id(id, node);
3635 Ok(())
3636 }
3637
3638 fn insert_recycled_node_or_create(
3639 &mut self,
3640 stable_id: NodeId,
3641 node: Box<dyn Node>,
3642 ) -> RecycledNodeInsertion {
3643 if self.contains_node_id(stable_id) {
3644 let id = self.create(node);
3645 return RecycledNodeInsertion::fresh(
3646 id,
3647 Some(NodeError::AlreadyExists { id: stable_id }),
3648 );
3649 }
3650
3651 self.insert_available_with_id(stable_id, node);
3652 RecycledNodeInsertion::reused(stable_id)
3653 }
3654
3655 fn compact(&mut self) {
3656 let live_count = self.nodes.iter().filter(|slot| slot.is_some()).count();
3657 let tombstone_count = self.nodes.len().saturating_sub(live_count);
3658 if tombstone_count == 0 {
3659 return;
3660 }
3661 if self.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
3662 return;
3663 }
3664 let rehouse_live_nodes = tombstone_count >= live_count;
3665 let mut packed_nodes = Vec::with_capacity(live_count);
3666 let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
3667 let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
3668 let mut stable_to_physical = HashMap::default();
3669 stable_to_physical.reserve(live_count);
3670
3671 for physical_id in 0..self.nodes.len() {
3672 let Some(mut node) = self.nodes[physical_id].take() else {
3673 continue;
3674 };
3675 if rehouse_live_nodes {
3676 if let Some(rehoused) = node.rehouse_for_live_compaction() {
3677 node = rehoused;
3678 }
3679 }
3680 let stable_id = std::mem::replace(
3681 &mut self.physical_stable_ids[physical_id],
3682 Self::INVALID_STABLE_ID,
3683 );
3684 debug_assert_ne!(
3685 stable_id,
3686 Self::INVALID_STABLE_ID,
3687 "live physical slot must have a stable id",
3688 );
3689 let stable_id = Self::unpack_stable_id(stable_id);
3690 packed_nodes.push(Some(node));
3691 packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
3692 packed_warm_recycled_origins.push(self.physical_warm_recycled_origins[physical_id]);
3693 stable_to_physical.insert(stable_id, packed_nodes.len() - 1);
3694 }
3695
3696 self.nodes = packed_nodes;
3697 self.physical_stable_ids = packed_physical_stable_ids;
3698 self.physical_warm_recycled_origins = packed_warm_recycled_origins;
3699 self.free_ids = BinaryHeap::new();
3700 self.stable_to_physical = stable_to_physical;
3701 self.prune_stable_generations();
3702 }
3703
3704 fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
3705 Self::take_recycled_node_from_pool(&mut self.returning_recycled_nodes, key)
3706 .or_else(|| Self::take_recycled_node_from_pool(&mut self.recycled_nodes, key))
3707 }
3708
3709 fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
3710 if let Some(physical_id) = self.resolve_node_index(id) {
3711 self.physical_warm_recycled_origins[physical_id] = warm_origin;
3712 } else if self.high_id_nodes.contains_key(&id) {
3713 self.high_id_warm_recycled_origins.insert(id, warm_origin);
3714 }
3715 }
3716
3717 fn seed_recycled_node_shell(
3718 &mut self,
3719 key: TypeId,
3720 recycle_pool_limit: Option<usize>,
3721 shell: Box<dyn Node>,
3722 ) {
3723 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
3724 }
3725
3726 fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
3727 *self.fresh_recyclable_creations.entry(key).or_insert(0) += 1;
3728 }
3729
3730 fn clear_recycled_nodes(&mut self) {
3731 let returning = std::mem::take(&mut self.returning_recycled_nodes);
3732 for (key, mut nodes) in returning {
3733 let pool = self.recycled_nodes.entry(key).or_default();
3734 pool.append(&mut nodes);
3735 }
3736
3737 let fresh_recyclable_creations = std::mem::take(&mut self.fresh_recyclable_creations);
3738 let cold = std::mem::take(&mut self.cold_recycled_nodes);
3739 for (key, mut nodes) in cold {
3740 let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
3741 if needed > 0 {
3742 let remaining_limit = self
3743 .recycle_pool_limit_for(key)
3744 .unwrap_or(usize::MAX)
3745 .saturating_sub(self.warm_recycled_pool_len(key));
3746 let promote = nodes.len().min(needed).min(remaining_limit);
3747 let split_at = nodes.len().saturating_sub(promote);
3748 let promoted = nodes.split_off(split_at);
3749 for mut recycled in promoted {
3750 recycled.set_warm_origin(true);
3751 self.recycled_nodes.entry(key).or_default().push(recycled);
3752 }
3753 }
3754 }
3755
3756 let mut keys: HashSet<TypeId> = HashSet::default();
3757 keys.extend(self.recycled_nodes.keys().copied());
3758 keys.extend(self.recycled_node_limits.keys().copied());
3759 keys.extend(self.warm_recycled_node_targets.keys().copied());
3760 keys.extend(self.recycled_node_prototypes.keys().copied());
3761 for key in keys {
3762 let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
3763 let target = self.update_warm_recycled_node_target(key, observed_demand);
3764 self.replenish_warm_pool_to_target(key, target);
3765 self.trim_idle_warm_pool_to_target(key, target);
3766 self.compact_idle_warm_pool(key);
3767 }
3768 self.prune_stable_generations();
3769 self.compact();
3773 }
3774}
3775
3776pub trait ApplierHost {
3777 fn borrow_dyn(&self) -> RefMut<'_, dyn Applier>;
3778 fn compact(&self) {}
3780}
3781
3782pub struct ConcreteApplierHost<A: Applier + 'static> {
3783 inner: RefCell<A>,
3784}
3785
3786impl<A: Applier + 'static> ConcreteApplierHost<A> {
3787 pub fn new(applier: A) -> Self {
3788 Self {
3789 inner: RefCell::new(applier),
3790 }
3791 }
3792
3793 pub fn borrow_typed(&self) -> RefMut<'_, A> {
3794 self.inner.borrow_mut()
3795 }
3796
3797 pub fn try_borrow_typed(&self) -> Result<RefMut<'_, A>, std::cell::BorrowMutError> {
3798 self.inner.try_borrow_mut()
3799 }
3800
3801 pub fn into_inner(self) -> A {
3802 self.inner.into_inner()
3803 }
3804}
3805
3806impl<A: Applier + 'static> ApplierHost for ConcreteApplierHost<A> {
3807 fn borrow_dyn(&self) -> RefMut<'_, dyn Applier> {
3808 RefMut::map(self.inner.borrow_mut(), |applier| {
3809 applier as &mut dyn Applier
3810 })
3811 }
3812
3813 fn compact(&self) {
3814 self.inner.borrow_mut().compact();
3815 }
3816}
3817
3818pub struct ApplierGuard<'a, A: Applier + 'static> {
3819 inner: RefMut<'a, A>,
3820}
3821
3822impl<'a, A: Applier + 'static> ApplierGuard<'a, A> {
3823 fn new(inner: RefMut<'a, A>) -> Self {
3824 Self { inner }
3825 }
3826}
3827
3828impl<'a, A: Applier + 'static> Deref for ApplierGuard<'a, A> {
3829 type Target = A;
3830
3831 fn deref(&self) -> &Self::Target {
3832 &self.inner
3833 }
3834}
3835
3836impl<'a, A: Applier + 'static> DerefMut for ApplierGuard<'a, A> {
3837 fn deref_mut(&mut self) -> &mut Self::Target {
3838 &mut self.inner
3839 }
3840}
3841
3842pub struct SlotsHost {
3843 storage_key: Cell<usize>,
3844 inner: RefCell<SlotsHostInner>,
3845}
3846
3847#[derive(Debug, Default)]
3848pub(crate) struct SlotPassOutcome {
3849 pub(crate) compacted: bool,
3850 pub(crate) compact_anchor_registry_storage: bool,
3851 pub(crate) compact_payload_storage: bool,
3852}
3853
3854#[derive(Default)]
3855pub(crate) struct FinishedSlotPass {
3856 pub(crate) outcome: SlotPassOutcome,
3857 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
3858}
3859
3860struct ActivePassState {
3861 state: slot::SlotWriteSessionState,
3862}
3863
3864struct SlotsHostInner {
3865 table: SlotTable,
3866 nested_hosts: Vec<std::rc::Weak<SlotsHost>>,
3867 lifecycle: slot::SlotLifecycleCoordinator,
3868 runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
3869 active_pass: Option<ActivePassState>,
3870}
3871
3872impl Drop for SlotsHost {
3873 fn drop(&mut self) {
3874 let storage_key = self.storage_key.get();
3875 let inner = self.inner.get_mut();
3876 if let Some(state) = inner.runtime_state.clone() {
3877 if let Err(err) = state.dispose_retained_subtrees_for_host(
3878 storage_key,
3879 &mut inner.table,
3880 &mut inner.lifecycle,
3881 ) {
3882 log::error!(
3883 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
3884 );
3885 state.abandon_retained_subtrees_for_host(
3886 storage_key,
3887 &mut inner.table,
3888 &mut inner.lifecycle,
3889 );
3890 } else {
3891 state.clear_host_storage_key(storage_key);
3892 }
3893 }
3894 inner.lifecycle.dispose_slot_table(&mut inner.table);
3895 }
3896}
3897
3898impl SlotsHost {
3899 pub fn storage_key(&self) -> usize {
3900 self.storage_key.get()
3901 }
3902
3903 pub fn new(storage: SlotTable) -> Self {
3904 let storage_key = storage.storage_id();
3905 Self {
3906 storage_key: Cell::new(storage_key),
3907 inner: RefCell::new(SlotsHostInner {
3908 table: storage,
3909 nested_hosts: Vec::new(),
3910 lifecycle: slot::SlotLifecycleCoordinator::default(),
3911 runtime_state: None,
3912 active_pass: None,
3913 }),
3914 }
3915 }
3916
3917 pub fn note_nested_host(&self, nested: &Rc<SlotsHost>) {
3918 let Ok(mut inner) = self.inner.try_borrow_mut() else {
3919 return;
3920 };
3921 inner.nested_hosts.retain(|held| held.upgrade().is_some());
3922 if inner
3923 .nested_hosts
3924 .iter()
3925 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
3926 {
3927 return;
3928 }
3929 inner.nested_hosts.push(Rc::downgrade(nested));
3930 }
3931
3932 pub(crate) fn forget_effects(&self) -> bool {
3933 let (forgotten, nested, runtime_state) = {
3934 let Ok(mut inner) = self.inner.try_borrow_mut() else {
3935 return false;
3936 };
3937 if inner.active_pass.is_some() {
3938 return false;
3939 }
3940 let drops = inner.table.take_effect_drops();
3941 inner.nested_hosts.retain(|held| held.upgrade().is_some());
3942 let nested: Vec<Rc<SlotsHost>> = inner
3943 .nested_hosts
3944 .iter()
3945 .filter_map(std::rc::Weak::upgrade)
3946 .collect();
3947 (drops, nested, inner.runtime_state.clone())
3948 };
3949 let mut any = !forgotten.is_empty();
3950 drop(forgotten);
3951 for host in nested {
3952 any |= host.forget_effects();
3953 }
3954 if any {
3955 if let Some(runtime_state) = runtime_state {
3956 runtime_state.force_recompose_host_scopes(self.storage_key());
3957 }
3958 }
3959 any
3960 }
3961
3962 pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
3963 let mut inner = self.inner.borrow_mut();
3964 inner.runtime_state = Some(Rc::clone(state));
3965 }
3966
3967 pub(crate) fn rebind_orphaned_runtime_state(
3968 &self,
3969 state: &Rc<crate::composer::ComposerRuntimeState>,
3970 ) -> bool {
3971 let inner = self.inner.borrow();
3972 if inner.active_pass.is_some() {
3973 log::error!("cannot rebind SlotsHost during an active pass");
3974 return false;
3975 }
3976 let Some(bound_state) = inner.runtime_state.as_ref() else {
3977 drop(inner);
3978 self.bind_runtime_state(state);
3979 return true;
3980 };
3981 if Rc::ptr_eq(bound_state, state) {
3982 return true;
3983 }
3984 if bound_state.has_live_applier_host() {
3985 return false;
3986 }
3987 drop(inner);
3988
3989 let mut inner = self.inner.borrow_mut();
3990 let Some(bound_state) = inner.runtime_state.as_ref() else {
3991 inner.runtime_state = Some(Rc::clone(state));
3992 return true;
3993 };
3994 if Rc::ptr_eq(bound_state, state) {
3995 return true;
3996 }
3997 if bound_state.has_live_applier_host() {
3998 return false;
3999 }
4000
4001 let previous_state = Rc::clone(bound_state);
4002 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4003 lifecycle.flush_pending_drops();
4004 let host_key = self.storage_key();
4005 if previous_state
4006 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4007 .is_err()
4008 {
4009 inner.lifecycle = lifecycle;
4010 return false;
4011 }
4012 previous_state.clear_host(self);
4013 lifecycle.flush_pending_drops();
4014 inner.runtime_state = Some(Rc::clone(state));
4015 inner.lifecycle = lifecycle;
4016 true
4017 }
4018
4019 pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
4020 self.inner.borrow().runtime_state.clone()
4021 }
4022
4023 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
4024 Ref::map(self.inner.borrow(), |inner| &inner.table)
4025 }
4026
4027 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
4028 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
4029 }
4030
4031 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
4032 if Rc::strong_count(&self) != 1 {
4033 return Err(NodeError::SlotHostUnavailable {
4034 operation: "SlotsHost::into_table",
4035 reason: "other host references are alive",
4036 });
4037 }
4038 self.take_table_for_transfer()
4039 }
4040
4041 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
4042 let inner = self.inner.borrow();
4043 if inner.active_pass.is_some() {
4044 return Err(NodeError::SlotHostUnavailable {
4045 operation: "SlotsHost::into_table",
4046 reason: "slot pass is active",
4047 });
4048 }
4049 drop(inner);
4050 let mut inner = self.inner.borrow_mut();
4051 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4052 lifecycle.flush_pending_drops();
4053 if let Some(state) = inner.runtime_state.clone() {
4054 let host_key = self.storage_key();
4055 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)?;
4056 state.clear_host(self);
4057 lifecycle.flush_pending_drops();
4058 }
4059 let taken = std::mem::take(&mut inner.table);
4060 self.storage_key.set(inner.table.storage_id());
4061 inner.runtime_state = None;
4062 inner.lifecycle = lifecycle;
4063 Ok(taken)
4064 }
4065
4066 pub fn reset(&self) -> Result<(), NodeError> {
4067 let inner = self.inner.borrow();
4068 if inner.active_pass.is_some() {
4069 return Err(NodeError::SlotHostUnavailable {
4070 operation: "SlotsHost::reset",
4071 reason: "slot pass is active",
4072 });
4073 }
4074 let runtime_state = inner.runtime_state.clone();
4075 drop(inner);
4076 let mut inner = self.inner.borrow_mut();
4077 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4078 if let Some(state) = runtime_state {
4079 let host_key = self.storage_key();
4080 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)?;
4081 state.clear_host(self);
4082 }
4083 lifecycle.dispose_slot_table(&mut inner.table);
4084 inner.table = SlotTable::default();
4085 self.storage_key.set(inner.table.storage_id());
4086 inner.runtime_state = None;
4087 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4088 Ok(())
4089 }
4090
4091 pub(crate) fn abandon_after_apply_failure(&self) {
4092 let inner = self.inner.borrow();
4093 if inner.active_pass.is_some() {
4094 log::error!("cannot abandon SlotsHost during an active pass");
4095 return;
4096 }
4097 let runtime_state = inner.runtime_state.clone();
4098 drop(inner);
4099 let mut inner = self.inner.borrow_mut();
4100 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4101 if let Some(state) = runtime_state {
4102 let host_key = self.storage_key();
4103 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
4104 }
4105 lifecycle.dispose_slot_table(&mut inner.table);
4106 inner.table = SlotTable::default();
4107 self.storage_key.set(inner.table.storage_id());
4108 inner.runtime_state = None;
4109 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4110 }
4111
4112 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
4113 let inner = self.inner.borrow();
4114 let local = inner.table.debug_stats();
4115 let lifecycle = inner.lifecycle.debug_stats();
4116 let retention = inner
4117 .runtime_state
4118 .clone()
4119 .map(|state| state.slot_retention_debug_stats(self))
4120 .unwrap_or_default();
4121 SlotTableDebugStats::from_parts(local, lifecycle, retention)
4122 }
4123
4124 pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
4125 let inner = self.inner.borrow();
4126 let mut snapshot = inner.table.debug_snapshot();
4127 if let Some(state) = inner.runtime_state.clone() {
4128 state.fill_slot_debug_snapshot(self, &mut snapshot);
4129 }
4130 snapshot
4131 }
4132
4133 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode) {
4134 let mut inner = self.inner.borrow_mut();
4135 if inner.active_pass.is_some() {
4136 log::error!("slot pass already active for host");
4137 return;
4138 }
4139 let mut state = slot::SlotWriteSessionState::default();
4140 state.reset_for_pass(mode);
4141 inner.active_pass = Some(ActivePassState { state });
4142 }
4143
4144 pub(crate) fn has_active_pass(&self) -> bool {
4145 self.inner.borrow().active_pass.is_some()
4146 }
4147
4148 pub(crate) fn abandon_active_pass(&self) {
4149 self.inner.borrow_mut().active_pass = None;
4150 }
4151
4152 pub(crate) fn with_write_session<R>(
4153 &self,
4154 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
4155 ) -> R {
4156 let mut inner = self.inner.borrow_mut();
4157 let SlotsHostInner {
4158 table,
4159 lifecycle,
4160 active_pass,
4161 ..
4162 } = &mut *inner;
4163 let active_pass = active_pass
4164 .as_mut()
4165 .expect("slot write session requires an active pass");
4166 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4167 f(&mut session)
4168 }
4169
4170 pub(crate) fn with_table_and_lifecycle_mut<R>(
4171 &self,
4172 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
4173 ) -> R {
4174 let mut inner = self.inner.borrow_mut();
4175 let SlotsHostInner {
4176 table, lifecycle, ..
4177 } = &mut *inner;
4178 f(table, lifecycle)
4179 }
4180
4181 pub(crate) fn finish_pass(
4182 &self,
4183 applier: &mut dyn Applier,
4184 ) -> Result<FinishedSlotPass, NodeError> {
4185 let mut inner = self.inner.borrow_mut();
4186 let SlotsHostInner {
4187 table,
4188 lifecycle,
4189 active_pass: active_pass_slot,
4190 ..
4191 } = &mut *inner;
4192 let Some(mut active_pass) = active_pass_slot.take() else {
4193 return Ok(FinishedSlotPass::default());
4194 };
4195
4196 active_pass.state.flush_payload_location_refreshes(table);
4197
4198 #[cfg(debug_assertions)]
4199 if let Err(err) = active_pass.state.validate(table) {
4200 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
4201 return Err(NodeError::SlotHostUnavailable {
4202 operation: "SlotsHost::finish_pass",
4203 reason: "slot writer invariant violation",
4204 });
4205 }
4206
4207 let detached_root_children = {
4208 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4209 session.finalize_pass(applier)?
4210 };
4211
4212 Ok(FinishedSlotPass {
4213 outcome: SlotPassOutcome {
4214 compacted: active_pass.state.request_compaction,
4215 compact_anchor_registry_storage: active_pass
4216 .state
4217 .request_anchor_storage_compaction,
4218 compact_payload_storage: active_pass.state.request_payload_storage_compaction,
4219 },
4220 detached_root_children,
4221 })
4222 }
4223
4224 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
4225 let mut inner = self.inner.borrow_mut();
4226 let SlotsHostInner {
4227 table,
4228 lifecycle,
4229 runtime_state,
4230 ..
4231 } = &mut *inner;
4232 lifecycle.flush_pending_drops();
4233 if outcome.compacted {
4234 table.compact_storage();
4235 lifecycle.compact_storage();
4236 }
4237 if let Some(state) = runtime_state.clone() {
4238 state.compact_table_identity_storage_for_host(
4239 self,
4240 table,
4241 outcome.compact_anchor_registry_storage,
4242 outcome.compact_payload_storage,
4243 );
4244 } else {
4245 if outcome.compact_anchor_registry_storage {
4246 table.compact_anchor_registry_storage(None);
4247 }
4248 if outcome.compact_payload_storage {
4249 table.compact_payload_anchor_registry_storage(None);
4250 }
4251 }
4252 table.assert_fast_integrity("slot pass cleanup");
4253 #[cfg(any(test, debug_assertions))]
4254 {
4255 table.debug_verify();
4256 if let Some(state) = runtime_state.clone() {
4257 state.debug_verify_host(self, table);
4258 }
4259 }
4260 }
4261}
4262
4263fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
4264 let mut positions = HashMap::default();
4265 positions.reserve(children.len());
4266 for (index, &child) in children.iter().enumerate() {
4267 positions.insert(child, index);
4268 }
4269 positions
4270}
4271
4272fn refresh_child_positions(
4273 current: &[NodeId],
4274 positions: &mut HashMap<NodeId, usize>,
4275 start: usize,
4276 end: usize,
4277) {
4278 if current.is_empty() || start >= current.len() {
4279 return;
4280 }
4281 let end = end.min(current.len() - 1);
4282 for (offset, &child) in current[start..=end].iter().enumerate() {
4283 positions.insert(child, start + offset);
4284 }
4285}
4286
4287fn insert_child_into_diff_state(
4288 current: &mut ChildList,
4289 positions: &mut HashMap<NodeId, usize>,
4290 index: usize,
4291 child: NodeId,
4292) {
4293 let index = index.min(current.len());
4294 current.insert(index, child);
4295 refresh_child_positions(current, positions, index, current.len() - 1);
4296}
4297
4298fn move_child_in_diff_state(
4299 current: &mut ChildList,
4300 positions: &mut HashMap<NodeId, usize>,
4301 from_index: usize,
4302 target_index: usize,
4303) -> usize {
4304 let child = current.remove(from_index);
4305 let to_index = target_index.min(current.len());
4306 current.insert(to_index, child);
4307 refresh_child_positions(
4308 current,
4309 positions,
4310 from_index.min(to_index),
4311 from_index.max(to_index),
4312 );
4313 to_index
4314}
4315
4316pub(crate) use state::MutableStateInner;
4317pub use state::{MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State};
4318
4319fn hash_key<K: Hash>(key: &K) -> Key {
4320 let mut hasher = hash::default::new();
4321 key.hash(&mut hasher);
4322 hasher.finish()
4323}
4324
4325pub(crate) fn explicit_group_key_seed<K: Hash>(
4326 key: &K,
4327 caller: &'static std::panic::Location<'static>,
4328) -> slot::GroupKeySeed {
4329 let source_key = location_key(caller.file(), caller.line(), caller.column());
4330 let explicit_key = hash_key(key);
4331 slot::GroupKeySeed::keyed(source_key, explicit_key)
4332}
4333
4334#[cfg(test)]
4335#[path = "tests/mod.rs"]
4336mod tests;
4337
4338#[cfg(test)]
4339#[path = "tests/recursive_decrease_increase_test.rs"]
4340mod recursive_decrease_increase_test;
4341
4342pub mod collections;
4343pub mod hash;