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