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