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