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