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