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