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