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