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