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