1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3#![deny(unsafe_code)]
4
5pub extern crate self as cranpose_core;
6
7mod applier_host;
8mod blocking;
9mod bubble_passes;
10mod callbacks;
11mod composer;
12pub mod composer_context;
15mod composition;
16mod composition_locals;
17mod computed_state;
18pub mod concurrency;
19mod debug_trace;
20mod effect_key;
21mod emit;
22pub mod env_flags;
23#[cfg(any(feature = "internal", test))]
24mod frame_clock;
25mod hooks;
26mod launched_effect;
27mod layout_dirt_roots;
28mod node_marks;
29pub mod owned;
31pub mod platform;
32mod recompose;
33mod retention;
34pub mod runtime;
37mod runtime_cell;
38mod slot;
39pub mod snapshot_double_index_heap;
41pub mod snapshot_id_set;
43pub mod snapshot_pinning;
45pub mod snapshot_state_observer;
47pub mod snapshot_v2;
48mod snapshot_weak_set;
49pub mod source_trace;
50mod stable_index;
51mod state;
52#[doc(hidden)]
53pub use source_trace::__source_scope;
54#[doc(hidden)]
55pub use thread_state::with_or_fresh;
56mod stage_times;
57pub mod subcompose;
58mod thread_state;
59#[doc(hidden)]
60pub use stage_times::{StageTimes, stage_ms};
61
62#[cfg(feature = "internal")]
63#[doc(hidden)]
64pub mod internal {
65 pub use crate::frame_clock::{FrameCallbackRegistration, FrameClock};
66}
67pub use applier_host::{ApplierGuard, ApplierHost, ConcreteApplierHost, NodeDisposal};
68pub use blocking::{
69 BlockingError, BlockingExecutor, BlockingExecutorConfig, BlockingTask, launchBlocking,
70 withBlocking,
71};
72pub use callbacks::{
73 CallbackHolder, CallbackHolder1, ParamReturnState, ParamSlot, ParamState, ReturnSlot,
74 SharedParam, refresh_param, refresh_shared_param, store_param,
75};
76pub use composer::{
77 __branch_group_scope_advance, __branch_group_scope_deferred, BranchGroupGuard,
78 CapturedCompositionContext, ComposableGroup, Composer, ScopedBranchGroupGuard, ValueSlotHandle,
79};
80pub(crate) use composer::{ComposerCore, EmittedNode, ParentAttachMode, ParentFrame};
81pub use composition::{Composition, ROOT_RENDER_REPLAY_LIMIT};
82pub use composition_locals::{
83 CompositionLocal, CompositionLocalAccessorScope, CompositionLocalProvider, ProvidedValue,
84 ReadableCompositionLocal, StaticCompositionLocal, compositionLocalOf,
85 compositionLocalOfWithPolicy, compositionLocalWithComputedDefaultOf, staticCompositionLocalOf,
86 staticCompositionLocalOfWithPolicy, staticCompositionLocalWithComputedDefaultOf,
87};
88pub(crate) use composition_locals::{ComputedLocalEntry, LocalStateEntry};
89#[doc(hidden)]
90pub use computed_state::{ComputedState, computedStateOf};
91pub use concurrency::{
92 CollectEvents, CoroutineScope, Delay, EventChannel, EventSender, EventStream, EventStreamNext,
93 ProduceScope, collectAsState, delay, interval, produceState, rememberCoroutineScope,
94 rememberEventStream, spawn_ui_task,
95};
96#[doc(hidden)]
97pub use debug_trace::{
98 debug_label_current_scope, debug_live_recompose_scope_count,
99 debug_recompose_scope_registry_stats, debug_scope_invalidation_sources, debug_scope_label,
100};
101pub use hooks::{
102 derivedStateOf, mutableStateList, mutableStateListOf, mutableStateMap, mutableStateMapOf,
103 mutableStateOf, mutableStateOfNeverEqual, ownedMutableStateOf, ownedMutableStateOfNeverEqual,
104 remember, rememberKeyed, rememberMutableStateOf, rememberMutableStateOfNeverEqual,
105 rememberUpdatedState, try_mutableStateOf,
106};
107#[cfg(feature = "internal")]
108#[doc(hidden)]
109pub use hooks::{withFrameMillis, withFrameNanos};
110pub use launched_effect::{
111 __launched_effect_async_impl, __launched_effect_impl, CancelToken, LaunchedEffect,
112 LaunchedEffectAsync, LaunchedEffectScope, TaskSite,
113};
114pub use node_marks::NodeMarks;
115pub use owned::Owned;
116pub use platform::{Clock, RuntimeScheduler, SchedulerRef, scheduler_ref};
117pub use retention::{RetentionBudget, RetentionEvictionPolicy, RetentionMode, RetentionPolicy};
118#[doc(hidden)]
119pub use runtime::{
120 DefaultScheduler, Runtime, RuntimeHandle, StateId, TaskHandle, UiDispatcher,
121 current_runtime_handle, label_next_ui_task, schedule_frame, schedule_node_update,
122};
123pub use runtime_cell::RuntimeCell;
124pub use slot::{
125 SlotDebugAnchor, SlotDebugEntry, SlotDebugEntryKind, SlotDebugGroup, SlotDebugScope,
126 SlotDebugSnapshot, SlotRetentionDebugStats, SlotTable, SlotTableDebugStats,
127 SlotTableLocalDebugStats, SlotTableMutationDebugStats,
128};
129pub use snapshot_state_observer::SnapshotStateObserver;
130
131pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
156 let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
157
158 let _applied_guard = AppliedSnapshotFlagGuard::enter();
159 let value = snapshot.enter(block);
160
161 match snapshot.apply() {
162 snapshot_v2::SnapshotApplyResult::Success => Ok(value),
163 snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
164 }
165}
166
167struct AppliedSnapshotFlagGuard {
168 previous: bool,
169}
170
171impl AppliedSnapshotFlagGuard {
172 fn enter() -> Self {
173 let previous = thread_state::cells(|cells| cells.in_applied_snapshot.replace(true));
174 Self { previous }
175 }
176}
177
178impl Drop for AppliedSnapshotFlagGuard {
179 fn drop(&mut self) {
180 thread_state::cells(|cells| cells.in_applied_snapshot.set(self.previous));
181 }
182}
183
184pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
199 run_in_mutable_snapshot(block).ok()
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
205pub struct CompositionPassDebugStats {
206 pub commands_len: usize,
208 pub commands_cap: usize,
210 pub command_payload_len_bytes: usize,
212 pub command_payload_cap_bytes: usize,
214 pub sync_children_len: usize,
216 pub sync_children_cap: usize,
218 pub sync_child_ids_len: usize,
220 pub sync_child_ids_cap: usize,
222 pub side_effects_len: usize,
224 pub side_effects_cap: usize,
226}
227
228#[must_use]
230pub struct EventHandlerScopeGuard {
231 previous: bool,
232}
233
234impl Drop for EventHandlerScopeGuard {
235 fn drop(&mut self) {
236 thread_state::cells(|cells| cells.in_event_handler.set(self.previous));
237 }
238}
239
240pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
243 let previous = thread_state::cells(|cells| cells.in_event_handler.replace(true));
244 EventHandlerScopeGuard { previous }
245}
246
247pub fn in_event_handler() -> bool {
249 thread_state::cells(|cells| cells.in_event_handler.get())
250}
251
252pub fn in_applied_snapshot() -> bool {
254 thread_state::cells(|cells| cells.in_applied_snapshot.get())
255}
256
257use std::{
258 any::{Any, TypeId},
259 cell::{Cell, Ref, RefCell, RefMut},
260 cmp::Reverse,
261 collections::BinaryHeap,
262 hash::{Hash, Hasher},
263 rc::{Rc, Weak},
264 sync::OnceLock,
265};
266
267#[cfg(test)]
268pub(crate) use runtime::{TestRuntime, TestScheduler};
269use smallvec::SmallVec;
270
271use crate::collections::map::{HashMap, HashSet};
272
273pub type Key = u64;
276pub type NodeId = usize;
278
279#[cfg(any(test, debug_assertions))]
280#[derive(Clone, Debug, PartialEq, Eq)]
281pub(crate) struct LocationKeyDebugInfo {
282 file: String,
283 line: u32,
284 column: u32,
285}
286
287#[cfg(any(test, debug_assertions))]
288fn register_location_key_debug_info(key: Key, file: &str, line: u32, column: u32) {
289 let info = || LocationKeyDebugInfo {
290 file: file.to_owned(),
291 line,
292 column,
293 };
294 let collision = thread_state::runtimes(|state| {
295 let mut registry = state.location_keys.borrow_mut();
296 match registry.entry(key) {
297 std::collections::hash_map::Entry::Vacant(entry) => {
298 thread_state::watch_thread_exit();
299 entry.insert(info());
300 None
301 }
302 std::collections::hash_map::Entry::Occupied(entry) => {
303 let existing = entry.get();
304 (existing.file != file || existing.line != line || existing.column != column)
305 .then(|| (existing.clone(), info()))
306 }
307 }
308 });
309 if let Some((existing, incoming)) = collision {
310 thread_state::cells(|cells| {
311 let count = &cells.location_key_collisions;
312 count.set(count.get().saturating_add(1));
313 });
314 log::error!("location key collision: key={key} first={existing:?} second={incoming:?}");
315 }
316}
317
318#[cfg(all(debug_assertions, not(test)))]
319fn location_key_diagnostics_enabled() -> bool {
320 crate::env_flag!("CRANPOSE_LOCATION_KEY_DIAGNOSTICS")
321}
322
323#[cfg(test)]
324pub(crate) fn register_location_key_debug_info_for_test(
325 key: Key,
326 file: &str,
327 line: u32,
328 column: u32,
329) {
330 register_location_key_debug_info(key, file, line, column);
331}
332
333#[cfg(test)]
334pub(crate) fn location_key_debug_collision_count_for_test() -> usize {
335 thread_state::cells(|cells| cells.location_key_collisions.get())
336}
337
338#[cfg(test)]
339pub(crate) fn location_key_debug_info_for_test(key: Key) -> Option<LocationKeyDebugInfo> {
340 thread_state::runtimes(|state| state.location_keys.borrow().get(&key).cloned())
341}
342
343#[cfg(test)]
344pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
345 true
346}
347
348#[cfg(all(debug_assertions, not(test)))]
349pub(crate) fn slot_validation_diagnostics_enabled() -> bool {
350 crate::env_flag!("CRANPOSE_VALIDATE_SLOTS")
351}
352
353const fn source_location_hash(file: &str, line: u32, column: u32) -> u64 {
354 position_location_hash(file_location_hash(file), line, column)
355}
356
357const fn file_location_hash(file: &str) -> u64 {
358 fnv1a_location_key_bytes(0xcbf2_9ce4_8422_2325u64, file.as_bytes())
359}
360
361fn static_file_location_hash(cells: &thread_state::ThreadCells, file: &'static str) -> u64 {
366 let address = file.as_ptr() as usize;
367 let cached = &cells.file_hashes[(address >> 4) % thread_state::FILE_HASH_SLOTS];
368 let (cached_address, cached_len, hash) = cached.get();
369 if cached_address == address && cached_len == file.len() {
370 return hash;
371 }
372 let hash = file_location_hash(file);
373 cached.set((address, file.len(), hash));
374 hash
375}
376
377const fn position_location_hash(mut hash: u64, line: u32, column: u32) -> u64 {
378 hash = fnv1a_location_key_bytes(hash, &[0xff]);
379 hash = fnv1a_location_key_bytes(hash, &line.to_le_bytes());
380 hash = fnv1a_location_key_bytes(hash, &[0xfe]);
381 hash = fnv1a_location_key_bytes(hash, &column.to_le_bytes());
382 hash
383}
384
385const fn fnv1a_location_key_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
386 let mut index = 0;
387 while index < bytes.len() {
388 hash = (hash ^ bytes[index] as u64).wrapping_mul(0x0000_0100_0000_01b3);
389 index += 1;
390 }
391 hash
392}
393
394const fn avalanche_location_key(mut value: u64) -> u64 {
395 value ^= value >> 33;
396 value = value.wrapping_mul(0xff51_afd7_ed55_8ccd);
397 value ^= value >> 33;
398 value = value.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
399 value ^ (value >> 33)
400}
401
402#[doc(hidden)]
403#[track_caller]
404pub fn caller_location_key() -> Key {
405 let caller = std::panic::Location::caller();
406 let file = caller.file();
407 if cfg!(feature = "hot-reload")
408 && let Some(key) = hot_call_site_key(file, caller.line(), caller.column())
409 {
410 return key;
411 }
412 call_site_key(caller)
413}
414
415fn call_site_key(caller: &'static std::panic::Location<'static>) -> Key {
421 let address = std::ptr::from_ref(caller).addr();
422 thread_state::cells(|cells| {
423 let cached = &cells.call_site_keys[(address >> 3) % thread_state::CALL_SITE_KEY_SLOTS];
424 let (cached_address, key) = cached.get();
425 if cached_address == address {
426 return key;
427 }
428 let file = caller.file();
429 let key = registered_location_key(
430 static_file_location_hash(cells, file),
431 file,
432 caller.line(),
433 caller.column(),
434 );
435 cached.set((address, key));
436 key
437 })
438}
439
440#[derive(Clone, Copy)]
441pub(crate) struct HotOrigin {
442 file: &'static str,
443 line: u32,
444 end: u32,
445 identity: Key,
446}
447
448impl HotOrigin {
449 pub(crate) const NONE: Self = Self {
450 file: "",
451 line: 0,
452 end: 0,
453 identity: 0,
454 };
455}
456
457#[doc(hidden)]
459pub struct HotOriginGuard {
460 previous: HotOrigin,
461}
462
463impl Drop for HotOriginGuard {
464 fn drop(&mut self) {
465 let previous = self.previous;
466 thread_state::cells(|cells| cells.hot_origin.set(previous));
467 }
468}
469
470#[doc(hidden)]
477pub fn hot_origin(file: &'static str, line: u32, end: u32, identity: Key) -> HotOriginGuard {
478 let next = HotOrigin {
479 file,
480 line,
481 end,
482 identity,
483 };
484 HotOriginGuard {
485 previous: thread_state::cells(|cells| cells.hot_origin.replace(next)),
486 }
487}
488
489fn hot_call_site_key(file: &str, line: u32, column: u32) -> Option<Key> {
490 let origin = thread_state::cells(|cells| cells.hot_origin.get());
491 if origin.file.is_empty() || !(origin.line..=origin.end).contains(&line) || origin.file != file
492 {
493 return None;
494 }
495 let relative = line.wrapping_sub(origin.line);
496 let hash = fnv1a_location_key_bytes(origin.identity, &relative.to_le_bytes());
497 Some(avalanche_location_key(fnv1a_location_key_bytes(
498 hash ^ 0xfc,
499 &column.to_le_bytes(),
500 )))
501}
502
503#[doc(hidden)]
506pub const fn hot_branch_key(file: &str, path_hash: u64) -> Key {
507 let hash = file_location_hash(file);
508 avalanche_location_key(fnv1a_location_key_bytes(
509 hash ^ 0xfd,
510 &path_hash.to_le_bytes(),
511 ))
512}
513
514#[doc(hidden)]
517pub const fn hot_definition_key(file: &str, module: &str, name: &str) -> Key {
518 let mut hash = file_location_hash(file);
519 hash = fnv1a_location_key_bytes(hash ^ 0xfe, module.as_bytes());
520 avalanche_location_key(fnv1a_location_key_bytes(hash ^ 0xff, name.as_bytes()))
521}
522
523#[doc(hidden)]
524#[track_caller]
525pub fn composable_identity_key(definition: Key) -> Key {
526 (definition.wrapping_mul(0x0000_0100_0000_01b3) ^ caller_location_key())
527 .wrapping_mul(0x0000_0100_0000_01b3)
528}
529
530#[doc(hidden)]
531pub fn composable_definition_key(
532 file: &str,
533 line: u32,
534 column: u32,
535 marker: std::any::TypeId,
536) -> Key {
537 let mut hasher = std::collections::hash_map::DefaultHasher::new();
538 std::hash::Hash::hash(&marker, &mut hasher);
539 location_key(file, line, column) ^ avalanche_location_key(std::hash::Hasher::finish(&hasher))
540}
541
542#[doc(hidden)]
545pub const fn const_location_key(file: &str, line: u32, column: u32) -> Key {
546 avalanche_location_key(source_location_hash(file, line, column))
547}
548
549#[macro_export]
552macro_rules! location_key {
553 () => {{
554 const KEY: $crate::Key = $crate::const_location_key(file!(), line!(), column!());
555 $crate::noted_location_key(KEY, file!(), line!(), column!())
556 }};
557}
558
559#[doc(hidden)]
562#[inline(always)]
563pub fn noted_location_key(key: Key, file: &str, line: u32, column: u32) -> Key {
564 note_location_key(key, file, line, column);
565 key
566}
567
568#[doc(hidden)]
569pub fn cached_composable_definition_key(
570 cell: &OnceLock<Key>,
571 file: &str,
572 line: u32,
573 column: u32,
574 marker: TypeId,
575) -> Key {
576 *cell.get_or_init(|| composable_definition_key(file, line, column, marker))
577}
578
579pub fn location_key(file: &str, line: u32, column: u32) -> Key {
582 registered_location_key(file_location_hash(file), file, line, column)
583}
584
585fn registered_location_key(file_hash: u64, file: &str, line: u32, column: u32) -> Key {
586 let key = avalanche_location_key(position_location_hash(file_hash, line, column));
587 note_location_key(key, file, line, column);
588 key
589}
590
591#[cfg(any(test, debug_assertions))]
593fn note_location_key(key: Key, file: &str, line: u32, column: u32) {
594 #[cfg(test)]
595 register_location_key_debug_info(key, file, line, column);
596 #[cfg(all(debug_assertions, not(test)))]
597 if location_key_diagnostics_enabled() {
598 register_location_key_debug_info(key, file, line, column);
599 }
600}
601
602#[cfg(not(any(test, debug_assertions)))]
603fn note_location_key(_key: Key, _file: &str, _line: u32, _column: u32) {}
604
605#[doc(hidden)]
608pub const fn branch_location_key(file: &str, line: u32, column: u32, branch: u32) -> Key {
609 let mut hash = source_location_hash(file, line, column);
610 hash = fnv1a_location_key_bytes(hash, &[0xfd]);
611 hash = fnv1a_location_key_bytes(hash, &branch.to_le_bytes());
612 avalanche_location_key(hash)
613}
614
615#[doc(hidden)]
618pub const fn constant_value_key(file: &str, line: u32, column: u32, index: u32) -> Key {
619 let mut hash = source_location_hash(file, line, column);
620 hash = fnv1a_location_key_bytes(hash, &[0xfc]);
621 hash = fnv1a_location_key_bytes(hash, &index.to_le_bytes());
622 avalanche_location_key(hash)
623}
624
625const CONSTANT_SLOTS: usize = 128;
627
628#[doc(hidden)]
636pub fn __constant<T: Clone + 'static>(key: Key, make: impl FnOnce() -> T) -> T {
637 let slot = (key as usize) & (CONSTANT_SLOTS - 1);
638 let kept = thread_state::try_runtimes(|state| {
639 let constants = state.constants.try_borrow().ok()?;
640 let (kept_key, value) = constants.get(slot)?.as_ref()?;
641 (*kept_key == key)
642 .then(|| value.downcast_ref::<T>().cloned())
643 .flatten()
644 })
645 .flatten();
646 if let Some(value) = kept {
647 return value;
648 }
649 let value = make();
650 let stored: Rc<dyn Any> = Rc::new(value.clone());
651 thread_state::try_runtimes(|state| {
652 let Ok(mut constants) = state.constants.try_borrow_mut() else {
653 return;
654 };
655 if constants.is_empty() {
656 constants.resize_with(CONSTANT_SLOTS, || None);
657 }
658 if let Some(entry) = constants.get_mut(slot) {
659 *entry = Some((key, stored));
660 }
661 });
662 value
663}
664
665#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Default)]
671pub struct AnchorId {
672 id: u32,
673 generation: u32,
674}
675
676impl AnchorId {
677 pub(crate) const INVALID: AnchorId = AnchorId {
678 id: 0,
679 generation: 0,
680 };
681
682 pub(crate) fn new(id: usize) -> Self {
683 Self {
684 id: crate::slot::checked_usize_to_u32(id, "anchor id"),
685 generation: 1,
686 }
687 }
688
689 pub fn is_valid(&self) -> bool {
691 self.id != 0
692 }
693}
694
695pub(crate) type ScopeId = usize;
696pub(crate) type FrameCallbackId = u64;
697type LocalStackSnapshot = Option<Rc<composer::LocalFrame>>;
702
703#[derive(Clone)]
704pub(crate) struct LocalKey(Rc<()>);
705
706impl LocalKey {
707 fn new() -> Self {
708 Self(Rc::new(()))
709 }
710
711 pub(crate) fn entry_source(&self) -> Key {
712 avalanche_location_key(Rc::as_ptr(&self.0) as usize as u64)
713 }
714}
715
716impl std::fmt::Debug for LocalKey {
717 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
718 f.debug_tuple("LocalKey")
719 .field(&(Rc::as_ptr(&self.0) as usize))
720 .finish()
721 }
722}
723
724impl PartialEq for LocalKey {
725 fn eq(&self, other: &Self) -> bool {
726 Rc::ptr_eq(&self.0, &other.0)
727 }
728}
729
730impl Eq for LocalKey {}
731
732impl Hash for LocalKey {
733 fn hash<H: Hasher>(&self, state: &mut H) {
734 Rc::as_ptr(&self.0).hash(state);
735 }
736}
737
738struct RecomposeCallback {
742 body: Box<dyn FnMut(&Composer) + 'static>,
743 stateless_body: Option<fn() -> TypeId>,
744}
745
746fn scope_activity_epoch() -> u64 {
747 thread_state::cells(|cells| cells.scope_activity_epoch.get())
748}
749
750fn note_scope_activity_change() {
751 thread_state::cells(|cells| {
752 let epoch = &cells.scope_activity_epoch;
753 epoch.set(epoch.get() + 1);
754 });
755}
756
757fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
762 let value = cell.take();
763 let result = read(&value);
764 cell.set(value);
765 result
766}
767
768fn update_cell<T: Default, R>(cell: &Cell<T>, update: impl FnOnce(&mut T) -> R) -> R {
770 let mut value = cell.take();
771 let result = update(&mut value);
772 cell.set(value);
773 result
774}
775
776#[derive(Clone, Copy, PartialEq, Eq)]
778enum OwnerKind {
779 Parent,
781 Lifetime,
784}
785
786type OwnerLink = Cell<Option<(OwnerKind, Weak<RecomposeScopeInner>)>>;
788
789const NO_PARENT_HINT: NodeId = NodeId::MAX;
792
793pub(crate) struct RecomposeScopeInner {
794 runtime: runtime::ScopeRuntime,
795 invalid: Cell<bool>,
796 enqueued: Cell<bool>,
797 active: Cell<bool>,
798 active_epoch: Cell<u64>,
799 deactivations: Cell<u64>,
800 composed_once: Cell<bool>,
801 pending_recompose: Cell<bool>,
802 recomposing: Cell<bool>,
805 force_reuse: Cell<bool>,
806 force_recompose: Cell<bool>,
807 derivation: Cell<bool>,
808 retention_mode: Cell<RetentionMode>,
809 parent_hint: Cell<NodeId>,
811 group_anchor: Cell<AnchorId>,
812 recompose: Cell<Option<RecomposeCallback>>,
813 owner: OwnerLink,
814 local_stack: Cell<LocalStackSnapshot>,
815 #[cfg(feature = "inspection")]
816 source_trace: RefCell<Rc<[source_trace::SourceLocation]>>,
817 #[cfg(all(feature = "inspection", debug_assertions))]
818 recompositions: std::cell::OnceCell<source_trace::RecompositionCounter>,
819 slots_storage_key: Cell<usize>,
820 slots_runtime_state: Cell<Option<std::rc::Weak<crate::composer::ComposerRuntimeState>>>,
821 state_subscriptions: RefCell<StateIds>,
824 run_reads: RefCell<StateIds>,
827 read_cursor: Cell<u32>,
830 invalidation_sources: Cell<StateIds>,
831 unknown_invalidation_source: Cell<bool>,
832}
833
834type StateIds = SmallVec<[StateId; 2]>;
835
836pub(crate) enum ScopeRead {
838 Watched,
840 Collected,
843 NotReading,
845}
846
847const NOT_READING: u32 = u32::MAX;
849const DIVERGED: u32 = u32::MAX - 1;
852
853enum ScopeOwner {
854 Root,
855 Live(Rc<RecomposeScopeInner>),
856 Dropped,
857}
858
859impl RecomposeScopeInner {
860 fn owner(&self) -> ScopeOwner {
861 read_cell(&self.owner, |owner| {
862 owner.as_ref().map_or(ScopeOwner::Root, |(_, owner)| {
863 owner
864 .upgrade()
865 .map_or(ScopeOwner::Dropped, ScopeOwner::Live)
866 })
867 })
868 }
869
870 fn is_effectively_active(&self, epoch: u64) -> bool {
871 if self.active_epoch.get() == epoch {
872 return true;
873 }
874 if !self.active.get() {
875 return false;
876 }
877 let active = match self.owner() {
878 ScopeOwner::Root => true,
879 ScopeOwner::Dropped => false,
880 ScopeOwner::Live(owner) => owner.is_effectively_active(epoch),
881 };
882 if active {
883 self.active_epoch.set(epoch);
884 }
885 active
886 }
887
888 fn new(runtime: runtime::ScopeRuntime) -> Self {
889 runtime.increment_live_recompose_scope_count();
890 Self {
891 runtime,
892 invalid: Cell::new(false),
893 enqueued: Cell::new(false),
894 active: Cell::new(true),
895 active_epoch: Cell::new(0),
896 deactivations: Cell::new(0),
897 composed_once: Cell::new(false),
898 pending_recompose: Cell::new(false),
899 recomposing: Cell::new(false),
900 force_reuse: Cell::new(false),
901 force_recompose: Cell::new(false),
902 derivation: Cell::new(false),
903 retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
904 parent_hint: Cell::new(NO_PARENT_HINT),
905 group_anchor: Cell::new(AnchorId::INVALID),
906 recompose: Cell::new(None),
907 owner: Cell::new(None),
908 local_stack: Cell::new(None),
909 #[cfg(feature = "inspection")]
910 source_trace: RefCell::new(Rc::from([])),
911 #[cfg(all(feature = "inspection", debug_assertions))]
912 recompositions: std::cell::OnceCell::new(),
913 slots_storage_key: Cell::new(0),
914 slots_runtime_state: Cell::new(None),
915 state_subscriptions: RefCell::new(StateIds::new()),
916 run_reads: RefCell::new(StateIds::new()),
917 read_cursor: Cell::new(NOT_READING),
918 invalidation_sources: Cell::new(StateIds::new()),
919 unknown_invalidation_source: Cell::new(false),
920 }
921 }
922
923 fn id(&self) -> ScopeId {
924 std::ptr::from_ref(self).addr()
925 }
926}
927
928fn push_unique_state_id(ids: &mut StateIds, state_id: StateId) {
929 if !ids.contains(&state_id) {
930 ids.push(state_id);
931 }
932}
933
934impl Drop for RecomposeScopeInner {
935 fn drop(&mut self) {
936 note_scope_activity_change();
937 let id = self.id();
938 self.runtime.decrement_live_recompose_scope_count();
939 let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
940 let run_reads = std::mem::take(self.run_reads.get_mut());
941 for state_id in subscriptions.into_iter().chain(run_reads) {
942 self.runtime.unregister_state_scope(state_id, id);
943 }
944 #[cfg(debug_assertions)]
945 {
946 thread_state::try_runtimes(|state| {
947 state.scope_labels.borrow_mut().remove(&id);
948 state.scope_invalidation_sources.borrow_mut().remove(&id);
949 });
950 }
951 if self.enqueued.replace(false) {
952 self.runtime.mark_scope_recomposed();
953 }
954 }
955}
956
957#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
959pub struct RecomposeScopeRegistryDebugStats {
960 pub len: usize,
962 pub capacity: usize,
964}
965
966#[derive(Clone)]
969pub struct RecomposeScope {
970 inner: Rc<RecomposeScopeInner>,
971}
972
973impl PartialEq for RecomposeScope {
974 fn eq(&self, other: &Self) -> bool {
975 Rc::ptr_eq(&self.inner, &other.inner)
976 }
977}
978
979impl Eq for RecomposeScope {}
980
981impl Hash for RecomposeScope {
982 fn hash<H: Hasher>(&self, state: &mut H) {
983 self.id().hash(state);
984 }
985}
986
987impl RecomposeScope {
988 pub(crate) fn mark_derivation(&self) {
992 self.inner.derivation.set(true);
993 }
994
995 pub(crate) fn is_derivation(&self) -> bool {
996 self.inner.derivation.get()
997 }
998
999 fn new(runtime: RuntimeHandle) -> Self {
1000 Self {
1001 inner: Rc::new(RecomposeScopeInner::new(runtime.scope_runtime())),
1002 }
1003 }
1004
1005 pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
1006 Rc::downgrade(&self.inner)
1007 }
1008
1009 pub(crate) fn upgrade(weak: &Weak<RecomposeScopeInner>) -> Option<Self> {
1010 weak.upgrade().map(|inner| Self { inner })
1011 }
1012
1013 pub fn id(&self) -> ScopeId {
1015 self.inner.id()
1016 }
1017
1018 pub fn is_invalid(&self) -> bool {
1020 self.inner.invalid.get()
1021 }
1022
1023 pub(crate) fn is_enqueued(&self) -> bool {
1024 self.inner.enqueued.get()
1025 }
1026
1027 pub fn is_active(&self) -> bool {
1029 self.inner.active.get()
1030 }
1031
1032 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
1039 let mut total = self.inner.deactivations.get();
1040 let mut owner = self.inner.owner();
1041 while let ScopeOwner::Live(scope) = owner {
1042 total = total.wrapping_add(scope.deactivations.get());
1043 owner = scope.owner();
1044 }
1045 total
1046 }
1047
1048 pub(crate) fn is_effectively_active(&self) -> bool {
1049 self.inner.is_effectively_active(scope_activity_epoch())
1050 }
1051
1052 fn record_state_subscription(&self, state_id: StateId) {
1053 push_unique_state_id(&mut self.inner.state_subscriptions.borrow_mut(), state_id);
1054 }
1055
1056 pub(crate) fn begin_reads(&self) {
1058 self.inner.read_cursor.set(0);
1059 }
1060
1061 #[inline]
1067 fn note_read(&self, state_id: StateId) -> ScopeRead {
1068 let cursor = self.inner.read_cursor.get();
1069 if cursor == NOT_READING {
1070 return ScopeRead::NotReading;
1071 }
1072 if cursor == DIVERGED {
1073 let mut reads = self.inner.run_reads.borrow_mut();
1074 if reads.last() != Some(&state_id) {
1075 reads.push(state_id);
1076 }
1077 return ScopeRead::Collected;
1078 }
1079 let at = cursor as usize;
1080 let subscribed = self.inner.state_subscriptions.borrow();
1081 if subscribed.get(at) == Some(&state_id) {
1082 self.inner.read_cursor.set(cursor + 1);
1083 return ScopeRead::Watched;
1084 }
1085 if at > 0 && subscribed[at - 1] == state_id {
1086 return ScopeRead::Watched;
1087 }
1088 let mut reads = self.inner.run_reads.borrow_mut();
1089 reads.clear();
1090 reads.extend_from_slice(&subscribed[..at]);
1091 reads.push(state_id);
1092 self.inner.read_cursor.set(DIVERGED);
1093 ScopeRead::Collected
1094 }
1095
1096 #[inline]
1100 pub(crate) fn end_reads(&self, ran: bool) {
1101 let cursor = self.inner.read_cursor.replace(NOT_READING);
1102 if cursor == NOT_READING || (!ran && cursor != DIVERGED) {
1105 return;
1106 }
1107 self.settle_reads(cursor, ran);
1108 }
1109
1110 #[inline(never)]
1113 fn settle_reads(&self, cursor: u32, ran: bool) {
1114 let id = self.id();
1115 let mut subscribed = self.inner.state_subscriptions.borrow_mut();
1116 if cursor != DIVERGED {
1117 let read = cursor as usize;
1119 if ran && read < subscribed.len() {
1120 for &state_id in &subscribed[read..] {
1121 if !subscribed[..read].contains(&state_id) {
1122 self.inner.runtime.forget_state_scope(state_id, id);
1123 }
1124 }
1125 subscribed.truncate(read);
1126 }
1127 return;
1128 }
1129 let mut reads = self.inner.run_reads.borrow_mut();
1130 if ran {
1131 for &state_id in subscribed.iter() {
1132 if !reads.contains(&state_id) {
1133 self.inner.runtime.forget_state_scope(state_id, id);
1134 }
1135 }
1136 std::mem::swap(&mut *reads, &mut *subscribed);
1137 reads.clear();
1138 } else {
1139 for state_id in reads.drain(..) {
1140 push_unique_state_id(&mut subscribed, state_id);
1141 }
1142 }
1143 }
1144
1145 fn record_unknown_invalidation_source(&self) {
1146 self.inner.unknown_invalidation_source.set(true);
1147 update_cell(&self.inner.invalidation_sources, StateIds::clear);
1148 }
1149
1150 fn record_state_invalidation_source(&self, state_id: StateId) {
1151 if !self.inner.unknown_invalidation_source.get() {
1152 update_cell(&self.inner.invalidation_sources, |ids| {
1153 push_unique_state_id(ids, state_id);
1154 });
1155 }
1156 }
1157
1158 fn enqueue_invalidation(&self) {
1159 self.inner.invalid.set(true);
1160 if !self.is_effectively_active() {
1161 return;
1162 }
1163 if !self.inner.enqueued.replace(true) {
1164 self.inner.runtime.register_invalid_scope(self.downgrade());
1165 }
1166 }
1167
1168 fn invalidate(&self) {
1169 self.record_unknown_invalidation_source();
1170 self.enqueue_invalidation();
1171 }
1172
1173 pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
1174 self.record_state_invalidation_source(state_id);
1175 self.enqueue_invalidation();
1176 }
1177
1178 fn mark_recomposed(&self) {
1179 if self.inner.invalid.replace(false) {
1182 self.inner.unknown_invalidation_source.set(false);
1183 update_cell(&self.inner.invalidation_sources, StateIds::clear);
1184 }
1185 self.inner.force_reuse.set(false);
1186 self.inner.force_recompose.set(false);
1187 if self.inner.enqueued.replace(false) {
1188 self.inner.runtime.mark_scope_recomposed();
1189 }
1190 let pending = self.inner.pending_recompose.replace(false);
1191 if pending {
1192 if self.inner.active.get() {
1193 self.invalidate();
1194 } else {
1195 self.inner.invalid.set(true);
1196 }
1197 }
1198 }
1199
1200 pub(crate) fn set_recompose_callback<F>(&self, callback: F)
1204 where
1205 F: FnMut(&Composer) + 'static,
1206 {
1207 let body: fn() -> TypeId = TypeId::of::<F>;
1208 let stateless = std::mem::size_of::<F>() == 0;
1209 if stateless && self.reruns_stateless(body) {
1210 return;
1211 }
1212 self.set_boxed_recompose(Box::new(callback), stateless.then_some(body));
1213 }
1214
1215 #[inline(never)]
1216 fn set_boxed_recompose(
1217 &self,
1218 body: Box<dyn FnMut(&Composer) + 'static>,
1219 stateless_body: Option<fn() -> TypeId>,
1220 ) {
1221 #[cfg(feature = "inspection")]
1222 self.inner
1223 .source_trace
1224 .replace(source_trace::current_source_trace());
1225 self.inner.recompose.set(Some(RecomposeCallback {
1226 body,
1227 stateless_body,
1228 }));
1229 }
1230
1231 fn reruns_stateless(&self, body: fn() -> TypeId) -> bool {
1235 read_cell(&self.inner.recompose, |callback| {
1236 callback
1237 .as_ref()
1238 .and_then(|callback| callback.stateless_body)
1239 .is_some_and(|current| std::ptr::fn_addr_eq(current, body) || current() == body())
1240 })
1241 }
1242
1243 fn run_recompose(&self, composer: &Composer) -> bool {
1244 #[cfg(feature = "inspection")]
1245 let _source_context = source_trace::restore_source_trace(&self.inner.source_trace.borrow());
1246 let Some(mut callback) = self.inner.recompose.take() else {
1247 return false;
1248 };
1249 self.begin_reads();
1250 self.inner.recomposing.set(true);
1251 (callback.body)(composer);
1252 self.inner.recomposing.set(false);
1253 self.end_reads(true);
1254 update_cell(&self.inner.recompose, |slot| {
1256 if slot.is_none() {
1257 *slot = Some(callback);
1258 }
1259 });
1260 true
1261 }
1262
1263 fn has_recompose_callback(&self) -> bool {
1264 self.inner.recomposing.get() || read_cell(&self.inner.recompose, Option::is_some)
1265 }
1266
1267 pub(crate) fn depth(&self) -> usize {
1269 let mut depth = 0;
1270 let mut current = self.parent_scope();
1271 while let Some(scope) = current {
1272 depth += 1;
1273 current = scope.parent_scope();
1274 }
1275 depth
1276 }
1277
1278 fn snapshot_locals(&self, stack: &LocalStackSnapshot) {
1279 update_cell(&self.inner.local_stack, |locals| {
1280 let unchanged = match (&*locals, stack) {
1281 (Some(current), Some(stack)) => Rc::ptr_eq(current, stack),
1282 (current, stack) => current.is_none() && stack.is_none(),
1283 };
1284 if !unchanged {
1285 locals.clone_from(stack);
1286 }
1287 });
1288 }
1289
1290 fn local_stack(&self) -> LocalStackSnapshot {
1291 read_cell(&self.inner.local_stack, Clone::clone)
1292 }
1293
1294 fn set_parent_hint(&self, parent: Option<NodeId>) {
1295 self.inner.parent_hint.set(parent.unwrap_or(NO_PARENT_HINT));
1296 }
1297
1298 fn set_owner(&self, kind: OwnerKind, owner: Option<&RecomposeScope>) {
1300 update_cell(&self.inner.owner, |link| {
1301 let current = link
1302 .as_ref()
1303 .map(|(kind, owner)| (*kind, Weak::as_ptr(owner)));
1304 if current != owner.map(|owner| (kind, Rc::as_ptr(&owner.inner))) {
1305 *link = owner.map(|owner| (kind, owner.downgrade()));
1306 note_scope_activity_change();
1307 }
1308 });
1309 }
1310
1311 fn set_parent_scope(&self, parent: Option<&RecomposeScope>) {
1313 self.set_owner(OwnerKind::Parent, parent);
1314 }
1315
1316 fn owner_of_kind(&self, kind: OwnerKind) -> Option<RecomposeScope> {
1317 read_cell(&self.inner.owner, |owner| {
1318 owner
1319 .as_ref()
1320 .filter(|(owner_kind, _)| *owner_kind == kind)
1321 .and_then(|(_, owner)| RecomposeScope::upgrade(owner))
1322 })
1323 }
1324
1325 fn parent_scope(&self) -> Option<RecomposeScope> {
1326 self.owner_of_kind(OwnerKind::Parent)
1327 }
1328
1329 fn set_lifetime_owner_scope(&self, owner: Option<&RecomposeScope>) {
1332 self.set_owner(OwnerKind::Lifetime, owner);
1333 }
1334
1335 #[cfg(test)]
1336 fn lifetime_owner_scope(&self) -> Option<RecomposeScope> {
1337 self.owner_of_kind(OwnerKind::Lifetime)
1338 }
1339
1340 fn callback_promotion_target(&self) -> Option<RecomposeScope> {
1341 let mut current = self.parent_scope();
1342 while let Some(scope) = current {
1343 if scope.has_recompose_callback() {
1344 return Some(scope);
1345 }
1346 current = scope.parent_scope();
1347 }
1348 None
1349 }
1350
1351 fn parent_hint(&self) -> Option<NodeId> {
1352 Some(self.inner.parent_hint.get()).filter(|&hint| hint != NO_PARENT_HINT)
1353 }
1354
1355 pub(crate) fn group_anchor(&self) -> AnchorId {
1356 self.inner.group_anchor.get()
1357 }
1358
1359 pub(crate) fn set_group_anchor(&self, anchor: AnchorId) {
1360 self.inner.group_anchor.set(anchor);
1361 }
1362
1363 fn set_slots_host(&self, host: &SlotsHost) {
1364 let storage_key = host.storage_key();
1365 update_cell(&self.inner.slots_runtime_state, |runtime_state| {
1366 if self.inner.slots_storage_key.get() == storage_key
1367 && runtime_state.as_ref().map(std::rc::Weak::as_ptr) == host.runtime_state_ptr()
1368 {
1369 return;
1370 }
1371 self.inner.slots_storage_key.set(storage_key);
1372 *runtime_state = host.runtime_state().map(|state| Rc::downgrade(&state));
1373 });
1374 }
1375
1376 pub(crate) fn slots_storage_key(&self) -> Option<usize> {
1377 let key = self.inner.slots_storage_key.get();
1378 (key != 0).then_some(key)
1379 }
1380
1381 pub(crate) fn slots_host_identity(
1384 &self,
1385 ) -> (usize, *const crate::composer::ComposerRuntimeState) {
1386 let state = read_cell(&self.inner.slots_runtime_state, |state| {
1387 state
1388 .as_ref()
1389 .map_or(std::ptr::null(), std::rc::Weak::as_ptr)
1390 });
1391 (self.inner.slots_storage_key.get(), state)
1392 }
1393
1394 pub(crate) fn slots_runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
1395 read_cell(&self.inner.slots_runtime_state, |state| {
1396 state.as_ref().and_then(std::rc::Weak::upgrade)
1397 })
1398 }
1399
1400 pub fn deactivate(&self) {
1403 if !self.inner.active.replace(false) {
1404 return;
1405 }
1406 note_scope_activity_change();
1407 self.inner
1408 .deactivations
1409 .set(self.inner.deactivations.get() + 1);
1410 if self.inner.enqueued.replace(false) {
1411 self.inner.runtime.mark_scope_recomposed();
1412 }
1413 }
1414
1415 pub(crate) fn defer_until_reactivated(&self) {
1416 if self.inner.enqueued.replace(false) {
1417 self.inner.runtime.mark_scope_recomposed();
1418 }
1419 }
1420
1421 pub fn reactivate(&self) {
1424 self.inner.active.set(true);
1425 if self.inner.invalid.get()
1426 && self.is_effectively_active()
1427 && !self.inner.enqueued.replace(true)
1428 {
1429 self.inner.runtime.register_invalid_scope(self.downgrade());
1430 }
1431 }
1432
1433 pub fn force_reuse(&self) {
1436 self.inner.force_reuse.set(true);
1437 self.inner.force_recompose.set(false);
1438 self.inner.pending_recompose.set(true);
1439 }
1440
1441 pub(crate) fn request_pending_recompose(&self) {
1442 self.inner.pending_recompose.set(true);
1443 }
1444
1445 pub fn force_recompose(&self) {
1448 self.inner.force_recompose.set(true);
1449 self.inner.force_reuse.set(false);
1450 self.inner.pending_recompose.set(false);
1451 }
1452
1453 pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
1454 self.inner.retention_mode.set(mode);
1455 }
1456
1457 pub(crate) fn retention_mode(&self) -> RetentionMode {
1458 self.inner.retention_mode.get()
1459 }
1460
1461 pub fn should_recompose(&self) -> bool {
1465 if self.inner.force_recompose.replace(false) {
1466 self.inner.force_reuse.set(false);
1467 return true;
1468 }
1469 if self.inner.force_reuse.replace(false) {
1470 return false;
1471 }
1472 self.is_invalid()
1473 }
1474
1475 pub fn has_composed_once(&self) -> bool {
1477 self.inner.composed_once.get()
1478 }
1479
1480 fn mark_composed_once(&self) {
1481 self.inner.composed_once.set(true);
1482 }
1483
1484 fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
1485 if self.inner.unknown_invalidation_source.get() {
1486 return None;
1487 }
1488 read_cell(&self.inner.invalidation_sources, |sources| {
1489 (!sources.is_empty()).then(|| {
1490 sources
1491 .iter()
1492 .all(|source| allowed_sources.contains(source))
1493 })
1494 })
1495 }
1496
1497 fn has_unknown_invalidation_source(&self) -> bool {
1498 self.inner.unknown_invalidation_source.get()
1499 }
1500}
1501
1502#[cfg(test)]
1503impl RecomposeScope {
1504 pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
1505 Self::new(runtime)
1506 }
1507}
1508
1509#[derive(Debug, Clone, Copy, Default)]
1511pub struct RecomposeOptions {
1512 pub force_reuse: bool,
1514 pub force_recompose: bool,
1516 pub retention: RetentionMode,
1518}
1519
1520#[derive(Debug, Clone, PartialEq, Eq)]
1522pub enum NodeError {
1523 Missing {
1525 id: NodeId,
1527 },
1528 TypeMismatch {
1530 id: NodeId,
1532 expected: &'static str,
1534 },
1535 MissingContext {
1537 id: NodeId,
1539 reason: &'static str,
1541 },
1542 AlreadyExists {
1544 id: NodeId,
1546 },
1547 MalformedCommandPayload {
1549 tag: &'static str,
1551 },
1552 SlotHostUnavailable {
1554 operation: &'static str,
1556 reason: &'static str,
1558 },
1559 RecompositionLimitExceeded {
1561 operation: &'static str,
1563 limit: usize,
1565 },
1566}
1567
1568impl std::fmt::Display for NodeError {
1569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1570 match self {
1571 NodeError::Missing { id } => write!(f, "node {id} missing"),
1572 NodeError::TypeMismatch { id, expected } => {
1573 write!(f, "node {id} type mismatch; expected {expected}")
1574 }
1575 NodeError::MissingContext { id, reason } => {
1576 write!(f, "missing context for node {id}: {reason}")
1577 }
1578 NodeError::AlreadyExists { id } => {
1579 write!(f, "node {id} already exists")
1580 }
1581 NodeError::MalformedCommandPayload { tag } => {
1582 write!(f, "command queue missing or invalid {tag} payload")
1583 }
1584 NodeError::SlotHostUnavailable { operation, reason } => {
1585 write!(f, "{operation} cannot access slot host: {reason}")
1586 }
1587 NodeError::RecompositionLimitExceeded { operation, limit } => {
1588 write!(
1589 f,
1590 "{operation} exceeded {limit} iterations while reconciling composition"
1591 )
1592 }
1593 }
1594 }
1595}
1596
1597impl std::error::Error for NodeError {}
1598
1599pub use subcompose::{
1600 ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
1601 TypedSlot,
1602};
1603
1604#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1606pub enum Phase {
1607 Compose,
1609 Measure,
1611 Layout,
1613}
1614
1615pub use composer_context::{
1616 __current_composer, note_nested_slots_host, with_composer as with_current_composer,
1617};
1618
1619#[expect(non_snake_case)]
1622pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
1623 composer_context::with_composer(f)
1624}
1625
1626fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
1627 composer_context::try_with_composer(f)
1628}
1629
1630#[doc(hidden)]
1631pub fn current_recompose_scope_invalidated_only_by(
1632 allowed_sources: impl IntoIterator<Item = StateId>,
1633) -> Option<bool> {
1634 with_current_composer_opt(|composer| {
1635 let allowed_sources = allowed_sources.into_iter().collect();
1636 let mut scope = composer.current_recompose_scope();
1637 let mut saw_unknown_source = false;
1638 while let Some(current) = scope {
1639 if current.has_unknown_invalidation_source() {
1640 saw_unknown_source = true;
1641 scope = current.parent_scope();
1642 continue;
1643 }
1644 if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
1645 return Some(matches);
1646 }
1647 scope = current.parent_scope();
1648 }
1649 saw_unknown_source.then_some(false)
1650 })
1651 .flatten()
1652}
1653
1654fn key_scoped<K: Hash, R>(
1655 key: &K,
1656 caller: &'static std::panic::Location<'static>,
1657 content: impl FnOnce() -> R,
1658) -> R {
1659 let seed = explicit_group_key_seed(key, caller);
1660 with_current_composer(|composer| composer.with_group_seed(seed, |_| content()))
1661}
1662
1663#[track_caller]
1666pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
1667 key_scoped(key, std::panic::Location::caller(), content);
1668}
1669
1670#[track_caller]
1681pub fn key<K: Hash, R>(keys: K, content: impl FnOnce() -> R) -> R {
1682 key_scoped(&keys, std::panic::Location::caller(), content)
1683}
1684
1685#[track_caller]
1704pub fn movable<K: Hash>(key: K, content: impl FnOnce()) {
1705 let id = hash_key(&key);
1706 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1707}
1708
1709#[expect(non_snake_case)]
1743#[track_caller]
1744pub fn rememberMovableContentOf(content: impl Fn() + 'static) -> MovableContent {
1745 let runtime = with_current_composer(composer::Composer::runtime_handle);
1746 let id = remember(|| runtime.next_movable_content_id()).with(|id| *id);
1747 MovableContent {
1748 id,
1749 content: Rc::new(content),
1750 }
1751}
1752
1753#[expect(non_snake_case)]
1761pub fn movableContentOf<K: Hash>(key: K, content: impl Fn() + 'static) -> MovableContent {
1762 MovableContent {
1763 id: hash_key(&key),
1764 content: Rc::new(content),
1765 }
1766}
1767
1768#[derive(Clone)]
1770pub struct MovableContent {
1771 id: Key,
1772 content: Rc<dyn Fn()>,
1773}
1774
1775impl MovableContent {
1776 pub fn show(&self) {
1780 let content = Rc::clone(&self.content);
1781 let id = self.id;
1782 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1783 }
1784
1785 pub fn forget(&self) {
1788 forget_movable_id(self.id);
1789 }
1790}
1791
1792impl std::fmt::Debug for MovableContent {
1793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1794 f.debug_struct("MovableContent")
1795 .field("id", &self.id)
1796 .finish()
1797 }
1798}
1799
1800impl PartialEq for MovableContent {
1801 fn eq(&self, other: &Self) -> bool {
1802 self.id == other.id && Rc::ptr_eq(&self.content, &other.content)
1803 }
1804}
1805
1806pub fn forget_movable<K: Hash>(key: K) {
1812 let id = hash_key(&key);
1813 forget_movable_id(id);
1814}
1815
1816fn forget_movable_id(id: Key) {
1817 let runtime = composer_context::try_with_composer(composer::Composer::runtime_handle)
1818 .or_else(runtime::current_runtime_handle);
1819 match runtime {
1820 Some(runtime) => runtime.forget_movable(id),
1821 None => log::error!("forget_movable called without an active runtime"),
1822 }
1823}
1824
1825#[derive(Default)]
1826struct DisposableEffectState {
1827 key: Option<effect_key::EffectKey>,
1828 cleanup: Option<Box<dyn FnOnce()>>,
1829}
1830
1831impl DisposableEffectState {
1832 fn should_run<K: PartialEq + 'static>(&self, keys: &K) -> bool {
1833 self.key.as_ref().is_none_or(|current| !current.holds(keys))
1834 }
1835
1836 fn set_key(&mut self, key: effect_key::EffectKey) {
1837 self.key = Some(key);
1838 }
1839
1840 fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
1841 self.cleanup = cleanup;
1842 }
1843
1844 fn run_cleanup(&mut self) {
1845 if let Some(cleanup) = self.cleanup.take() {
1846 cleanup();
1847 }
1848 }
1849}
1850
1851impl Drop for DisposableEffectState {
1852 fn drop(&mut self) {
1853 self.run_cleanup();
1854 }
1855}
1856
1857#[derive(Clone, Copy, Debug, Default)]
1860pub struct DisposableEffectScope;
1861
1862#[derive(Default)]
1865pub struct DisposableEffectResult {
1866 cleanup: Option<Box<dyn FnOnce()>>,
1867}
1868
1869impl DisposableEffectScope {
1870 pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
1873 DisposableEffectResult::new(cleanup)
1874 }
1875}
1876
1877impl DisposableEffectResult {
1878 pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
1880 Self {
1881 cleanup: Some(Box::new(cleanup)),
1882 }
1883 }
1884
1885 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
1886 self.cleanup
1887 }
1888}
1889
1890#[expect(non_snake_case)]
1893pub fn SideEffect(effect: impl FnOnce() + 'static) {
1894 with_current_composer(|composer| composer.register_side_effect(effect));
1895}
1896
1897#[doc(hidden)]
1900pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
1901where
1902 K: PartialEq + 'static,
1903 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1904{
1905 with_current_composer(|composer| {
1906 let state = composer.remember_effect::<DisposableEffectState>(group_key);
1907 if state.with(|state| state.should_run(&keys)) {
1908 let key = effect_key::EffectKey::new(keys);
1909 state.update(|state| {
1910 state.run_cleanup();
1911 state.set_key(key);
1912 });
1913 let mut effect_opt = Some(effect);
1914 composer.register_side_effect(move || {
1915 if let Some(effect) = effect_opt.take() {
1916 let result = effect(DisposableEffectScope);
1917 state.update(|state| state.set_cleanup(result.into_cleanup()));
1918 }
1919 });
1920 }
1921 });
1922}
1923
1924#[expect(non_snake_case)]
1932#[track_caller]
1933pub fn DisposableEffect<K, F>(keys: K, effect: F)
1934where
1935 K: PartialEq + 'static,
1936 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1937{
1938 __disposable_effect_impl(crate::caller_location_key(), keys, effect);
1939}
1940
1941#[macro_export]
1946macro_rules! clone_captures {
1947 ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1948 $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1949 $body
1950 }};
1951 (@clone $alias:ident = $value:expr) => {
1952 ($value).clone()
1953 };
1954 (@clone $alias:ident) => {
1955 $alias.clone()
1956 };
1957}
1958
1959pub fn with_node_mut<N: Node + 'static, R>(
1962 id: NodeId,
1963 f: impl FnOnce(&mut N) -> R,
1964) -> Result<R, NodeError> {
1965 with_current_composer(|composer| composer.with_node_mut(id, f))
1966}
1967
1968pub fn push_parent(id: NodeId) {
1971 with_current_composer(|composer| composer.push_parent(id));
1972}
1973
1974pub fn pop_parent() {
1977 with_current_composer(composer::Composer::pop_parent);
1978}
1979
1980pub trait Node: Any {
1983 fn mount(&mut self) {}
1985 fn unmount(&mut self) {}
1987 fn insert_child(&mut self, _child: NodeId) -> bool {
1993 false
1994 }
1995 fn remove_child(&mut self, _child: NodeId) -> bool {
2000 false
2001 }
2002 fn move_child(&mut self, _from: usize, _to: usize) {}
2004 fn update_children(&mut self, _children: &[NodeId]) {}
2006 fn keeps_own_children(&self) -> bool {
2011 false
2012 }
2013 fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
2017 out.clear();
2018 }
2019 fn for_each_child(&self, f: &mut dyn FnMut(NodeId)) {
2023 let mut children = SmallVec::<[NodeId; 8]>::new();
2024 self.collect_children_into(&mut children);
2025 for child in children {
2026 f(child);
2027 }
2028 }
2029 fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
2033 self.collect_children_into(out);
2034 }
2035 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
2047 let mut children = SmallVec::<[NodeId; 8]>::new();
2048 self.collect_owned_children_into(&mut children);
2049 children.iter().position(|&id| id == child)
2050 }
2051 fn set_node_id(&mut self, _id: NodeId) {}
2054 fn on_attached_to_parent(&mut self, _parent: NodeId) {}
2057 fn on_removed_from_parent(&mut self) {}
2060 fn parent(&self) -> Option<NodeId> {
2063 None
2064 }
2065 fn mark_needs_layout(&self) {}
2068 fn needs_layout(&self) -> bool {
2070 false
2071 }
2072 fn mark_needs_measure(&self) {}
2075 fn needs_measure(&self) -> bool {
2077 false
2078 }
2079 fn mark_descendant_needs_layout(&self, measure: bool) {
2084 if measure {
2085 self.mark_needs_measure();
2086 } else {
2087 self.mark_needs_layout();
2088 }
2089 }
2090 fn descendant_needs_layout(&self) -> bool {
2092 false
2093 }
2094 fn descendant_needs_measure(&self) -> bool {
2096 false
2097 }
2098 fn layout_dirty(&self) -> bool {
2101 self.needs_measure() || self.needs_layout() || self.descendant_needs_layout()
2102 }
2103 fn is_virtual(&self) -> bool {
2106 false
2107 }
2108 fn mark_needs_semantics(&self) {}
2110 fn mark_descendant_needs_semantics(&self) {
2114 self.mark_needs_semantics();
2115 }
2116 fn needs_semantics(&self) -> bool {
2118 false
2119 }
2120 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
2129 if self.parent().is_none() {
2130 self.on_attached_to_parent(parent);
2131 }
2132 }
2133
2134 fn recycle_key(&self) -> Option<TypeId> {
2136 None
2137 }
2138
2139 fn recycle_pool_limit(&self) -> Option<usize> {
2141 None
2142 }
2143
2144 fn prepare_for_recycle(&mut self) {}
2146
2147 fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
2152 None
2153 }
2154
2155 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
2161 None
2162 }
2163
2164 fn debug_heap_bytes(&self) -> usize {
2166 0
2167 }
2168}
2169
2170pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2188 DirtyBubble::LAYOUT.apply(applier, node_id);
2189}
2190
2191pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2201 DirtyBubble::MEASURE.apply(applier, node_id);
2202}
2203
2204pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
2210 DirtyBubble::SEMANTICS.apply(applier, node_id);
2211}
2212
2213pub fn queue_semantics_invalidation(node_id: NodeId) {
2218 let _ = composer_context::try_with_composer(|composer| {
2219 composer.enqueue_semantics_invalidation(node_id);
2220 });
2221}
2222
2223pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
2245 bubble_layout_dirty_composer::<N>(node_id);
2246}
2247
2248pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
2254 with_current_composer(|composer| {
2255 composer.commands_mut().push(Command::BubbleDirty {
2256 node_id,
2257 bubble: DirtyBubble {
2258 layout: false,
2259 measure: true,
2260 semantics: false,
2261 },
2262 });
2263 });
2264}
2265
2266pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
2273 bubble_semantics_dirty_composer::<N>(node_id);
2274}
2275
2276fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
2277 let _ = with_node_mut(node_id, |node: &mut N| {
2278 node.mark_needs_layout();
2279 });
2280 with_current_composer(|composer| composer.borrow_applier().note_layout_dirt(node_id));
2281
2282 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
2283 let parent_id = pid;
2284
2285 let advanced = with_node_mut(parent_id, |node: &mut N| {
2286 node.mark_descendant_needs_layout(false);
2287 true
2288 })
2289 .unwrap_or(false);
2290
2291 if advanced {
2292 node_id = parent_id;
2293 } else {
2294 break;
2295 }
2296 }
2297}
2298
2299fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
2300 let _ = with_node_mut(node_id, |node: &mut N| {
2301 node.mark_needs_semantics();
2302 });
2303
2304 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
2305 let parent_id = pid;
2306
2307 let advanced = with_node_mut(parent_id, |node: &mut N| {
2308 if !node.needs_semantics() {
2309 node.mark_descendant_needs_semantics();
2310 }
2311 true
2312 })
2313 .unwrap_or(false);
2314
2315 if advanced {
2316 node_id = parent_id;
2317 } else {
2318 break;
2319 }
2320 }
2321}
2322
2323impl dyn Node {
2324 pub fn as_any_mut(&mut self) -> &mut dyn Any {
2326 self
2327 }
2328}
2329
2330pub struct RecycledNode {
2333 stable_id: NodeId,
2334 node: Box<dyn Node>,
2335 warm_origin: bool,
2336}
2337
2338impl RecycledNode {
2339 fn new(stable_id: NodeId, mut node: Box<dyn Node>, warm_origin: bool) -> Self {
2343 let node = node.rehouse_for_recycle().unwrap_or_else(|| {
2344 node.prepare_for_recycle();
2345 node
2346 });
2347 Self {
2348 stable_id,
2349 node,
2350 warm_origin,
2351 }
2352 }
2353
2354 fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
2355 Self {
2356 stable_id,
2357 node,
2358 warm_origin,
2359 }
2360 }
2361
2362 pub fn stable_id(&self) -> NodeId {
2364 self.stable_id
2365 }
2366
2367 fn warm_origin(&self) -> bool {
2368 self.warm_origin
2369 }
2370
2371 fn set_warm_origin(&mut self, warm_origin: bool) {
2372 self.warm_origin = warm_origin;
2373 }
2374
2375 pub fn node_mut(&mut self) -> &mut dyn Node {
2377 self.node.as_mut()
2378 }
2379
2380 pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
2383 (self.stable_id, self.node, self.warm_origin)
2384 }
2385}
2386
2387#[derive(Debug, Clone, PartialEq, Eq)]
2389pub struct RecycledNodeInsertion {
2390 pub id: NodeId,
2392 pub stable_id_reused: bool,
2394 pub fallback_error: Option<NodeError>,
2396}
2397
2398impl RecycledNodeInsertion {
2399 fn reused(stable_id: NodeId) -> Self {
2400 Self {
2401 id: stable_id,
2402 stable_id_reused: true,
2403 fallback_error: None,
2404 }
2405 }
2406
2407 fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
2408 Self {
2409 id,
2410 stable_id_reused: false,
2411 fallback_error,
2412 }
2413 }
2414}
2415
2416pub trait Applier: Any {
2419 fn create(&mut self, node: Box<dyn Node>) -> NodeId;
2421 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
2423 fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError>;
2425 fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
2427
2428 fn begin_bubble_batch(&mut self) {}
2432
2433 fn note_layout_dirt(&mut self, _id: NodeId) {}
2438
2439 fn end_bubble_batch(&mut self) {}
2441
2442 fn forget_bubble_passes(&mut self) {}
2445
2446 fn note_bubble_pass(&mut self, _id: NodeId, _flags: u8) -> bool {
2451 false
2452 }
2453
2454 fn bubble_node(&mut self, id: NodeId, flags: u8, note: bool) -> Option<&mut dyn Node> {
2458 if note && self.note_bubble_pass(id, flags) {
2459 return None;
2460 }
2461 self.get_mut(id).ok()
2462 }
2463
2464 fn record_structural_change(&mut self, _parent_id: NodeId) {}
2470
2471 fn node_generation(&self, id: NodeId) -> u32;
2475
2476 fn create_virtual(&mut self, node: Box<dyn Node>) -> NodeId;
2479
2480 fn insert_recycled_node_or_create(
2483 &mut self,
2484 stable_id: NodeId,
2485 node: Box<dyn Node>,
2486 ) -> RecycledNodeInsertion {
2487 let id = self.create(node);
2488 RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
2489 }
2490
2491 fn as_any(&self) -> &dyn Any
2493 where
2494 Self: Sized,
2495 {
2496 self
2497 }
2498
2499 fn as_any_mut(&mut self) -> &mut dyn Any
2501 where
2502 Self: Sized,
2503 {
2504 self
2505 }
2506
2507 fn compact(&mut self) {}
2509
2510 fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
2512 None
2513 }
2514
2515 fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
2517
2518 fn seed_recycled_node_shell(
2520 &mut self,
2521 _key: TypeId,
2522 _recycle_pool_limit: Option<usize>,
2523 _shell: Box<dyn Node>,
2524 ) {
2525 }
2526
2527 fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
2529
2530 fn clear_recycled_nodes(&mut self) {}
2532}
2533
2534type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
2535
2536#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2537pub(crate) struct DirtyBubble {
2538 layout: bool,
2539 measure: bool,
2540 semantics: bool,
2541}
2542
2543impl DirtyBubble {
2544 const LAYOUT: Self = Self {
2545 layout: true,
2546 measure: false,
2547 semantics: false,
2548 };
2549
2550 const MEASURE: Self = Self {
2551 layout: false,
2552 measure: true,
2553 semantics: false,
2554 };
2555
2556 pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
2557 layout: true,
2558 measure: true,
2559 semantics: false,
2560 };
2561
2562 pub(crate) const SEMANTICS: Self = Self {
2563 layout: false,
2564 measure: false,
2565 semantics: true,
2566 };
2567
2568 fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
2571 self.walk(applier, Some(node_id), true);
2572 }
2573
2574 fn apply_to_ancestors(self, applier: &mut dyn Applier, node_id: NodeId) {
2577 if self.layout || self.measure {
2578 applier.note_layout_dirt(node_id);
2579 }
2580 let parent = applier.get_mut(node_id).ok().and_then(|node| node.parent());
2581 self.walk(applier, parent, false);
2582 }
2583
2584 fn walk(self, applier: &mut dyn Applier, first: Option<NodeId>, mut start: bool) {
2593 let flags = u8::from(self.layout || self.measure)
2594 | u8::from(self.measure) << 1
2595 | u8::from(self.semantics) << 2;
2596 let mut next = first;
2597 while let Some(id) = next {
2598 let Some(node) = applier.bubble_node(id, flags, !start) else {
2599 break;
2600 };
2601 self.mark(node, start);
2602 next = node.parent();
2603 let started = start;
2604 start = start && node.is_virtual();
2605 if started && !start && (self.layout || self.measure) {
2606 applier.note_layout_dirt(id);
2607 }
2608 }
2609 }
2610
2611 fn mark(self, node: &mut dyn Node, start: bool) {
2614 if start {
2615 if self.layout {
2616 node.mark_needs_layout();
2617 }
2618 if self.measure {
2619 node.mark_needs_measure();
2620 }
2621 if self.semantics {
2622 node.mark_needs_semantics();
2623 }
2624 return;
2625 }
2626 let marked = if self.measure {
2627 node.descendant_needs_measure()
2628 } else {
2629 node.descendant_needs_layout()
2630 };
2631 if (self.layout || self.measure) && !marked {
2632 node.mark_descendant_needs_layout(self.measure);
2633 }
2634 if self.semantics && !node.needs_semantics() {
2635 node.mark_descendant_needs_semantics();
2636 }
2637 }
2638}
2639
2640pub(crate) enum Command {
2641 BubbleDirty {
2642 node_id: NodeId,
2643 bubble: DirtyBubble,
2644 },
2645 RemoveNode {
2646 id: NodeId,
2647 },
2648 MountNode {
2649 id: NodeId,
2650 },
2651 AttachChild {
2652 parent_id: NodeId,
2653 child_id: NodeId,
2654 insert_index: Option<usize>,
2655 bubble: DirtyBubble,
2656 },
2657 InsertChild {
2658 parent_id: NodeId,
2659 child_id: NodeId,
2660 appended_index: usize,
2661 insert_index: usize,
2662 bubble: DirtyBubble,
2663 },
2664 MoveChild {
2665 parent_id: NodeId,
2666 from_index: usize,
2667 to_index: usize,
2668 bubble: DirtyBubble,
2669 },
2670 RemoveChild {
2671 parent_id: NodeId,
2672 child_id: NodeId,
2673 },
2674 DetachChild {
2675 parent_id: NodeId,
2676 child_id: NodeId,
2677 },
2678 SyncChildren {
2681 parent_id: NodeId,
2682 expected_children: Option<ChildList>,
2683 },
2684 Callback(CommandCallback),
2685}
2686
2687#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2688struct DeferredChildCleanup {
2689 child_id: NodeId,
2690 generation: u32,
2691 removed_from_parent: bool,
2692}
2693
2694#[derive(Default)]
2695struct DeferredChildCleanupQueue {
2696 pending: Vec<DeferredChildCleanup>,
2697 preserved: Vec<(NodeId, u32)>,
2698}
2699
2700impl DeferredChildCleanupQueue {
2701 fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
2702 if self
2703 .preserved
2704 .iter()
2705 .any(|&(preserved_id, preserved_generation)| {
2706 preserved_id == child_id && preserved_generation == generation
2707 })
2708 {
2709 return;
2710 }
2711 self.pending.push(DeferredChildCleanup {
2712 child_id,
2713 generation,
2714 removed_from_parent,
2715 });
2716 }
2717
2718 fn preserve(&mut self, child_id: NodeId, generation: u32) {
2719 if !self
2720 .preserved
2721 .iter()
2722 .any(|&(preserved_id, preserved_generation)| {
2723 preserved_id == child_id && preserved_generation == generation
2724 })
2725 {
2726 self.preserved.push((child_id, generation));
2727 }
2728 self.pending
2729 .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
2730 }
2731
2732 fn flush(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2733 for cleanup in self.pending.drain(..) {
2734 cleanup_detached_child(applier, cleanup)?;
2735 }
2736 Ok(())
2737 }
2738
2739 fn clear(&mut self) {
2740 self.pending.clear();
2741 self.preserved.clear();
2742 }
2743}
2744
2745impl Command {
2746 pub(crate) fn callback(
2747 callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
2748 ) -> Self {
2749 Self::Callback(Box::new(callback))
2750 }
2751
2752 pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2753 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2754 self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
2755 deferred_cleanup.flush(applier)
2756 }
2757
2758 fn apply_with_cleanup(
2759 self,
2760 applier: &mut dyn Applier,
2761 deferred_cleanup: &mut DeferredChildCleanupQueue,
2762 ) -> Result<(), NodeError> {
2763 match self {
2764 Self::BubbleDirty { node_id, bubble } => {
2765 bubble.apply(applier, node_id);
2766 Ok(())
2767 }
2768 Self::RemoveNode { id } => {
2769 if let Ok(node) = applier.get_mut(id) {
2770 node.unmount();
2771 }
2772 match applier.remove(id) {
2773 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2774 Err(err) => Err(err),
2775 }
2776 }
2777 Self::MountNode { id } => {
2778 let node = match applier.get_mut(id) {
2779 Ok(node) => node,
2780 Err(NodeError::Missing { .. }) => return Ok(()),
2781 Err(err) => return Err(err),
2782 };
2783 node.set_node_id(id);
2784 node.mount();
2785 Ok(())
2786 }
2787 Self::AttachChild {
2788 parent_id,
2789 child_id,
2790 insert_index,
2791 bubble,
2792 } => {
2793 attach_child_at(applier, parent_id, child_id, insert_index, bubble);
2794 Ok(())
2795 }
2796 Self::InsertChild {
2797 parent_id,
2798 child_id,
2799 appended_index,
2800 insert_index,
2801 bubble,
2802 } => {
2803 insert_child_with_reparenting(applier, parent_id, child_id);
2804 bubble.apply(applier, parent_id);
2805 if insert_index != appended_index
2806 && let Ok(parent_node) = applier.get_mut(parent_id)
2807 {
2808 parent_node.move_child(appended_index, insert_index);
2809 }
2810 Ok(())
2811 }
2812 Self::MoveChild {
2813 parent_id,
2814 from_index,
2815 to_index,
2816 bubble,
2817 } => {
2818 if let Ok(parent_node) = applier.get_mut(parent_id) {
2819 parent_node.move_child(from_index, to_index);
2820 }
2821 bubble.apply(applier, parent_id);
2822 note_structural_move(parent_id, from_index, to_index);
2823 applier.record_structural_change(parent_id);
2824 Ok(())
2825 }
2826 Self::RemoveChild {
2827 parent_id,
2828 child_id,
2829 } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
2830 Self::DetachChild {
2831 parent_id,
2832 child_id,
2833 } => {
2834 let generation = applier.node_generation(child_id);
2835 detach_child_from_parent(applier, parent_id, child_id)?;
2836 deferred_cleanup.preserve(child_id, generation);
2837 Ok(())
2838 }
2839 Self::SyncChildren {
2840 parent_id,
2841 expected_children,
2842 } => sync_or_settle_children(
2843 applier,
2844 parent_id,
2845 expected_children.as_deref(),
2846 deferred_cleanup,
2847 ),
2848 Self::Callback(callback) => callback(applier),
2849 }
2850 }
2851}
2852
2853const COMMAND_FLUSH_THRESHOLD: usize = 4096;
2854type ChildList = SmallVec<[NodeId; 4]>;
2855const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
2856
2857#[derive(Copy, Clone)]
2858enum CommandTag {
2859 BubbleDirty,
2860 RemoveNode,
2861 MountNode,
2862 AttachChild,
2863 InsertChild,
2864 MoveChild,
2865 RemoveChild,
2866 DetachChild,
2867 SyncChildren,
2868 Callback,
2869}
2870
2871impl CommandTag {
2872 fn label(self) -> &'static str {
2873 match self {
2874 Self::BubbleDirty => "BubbleDirty",
2875 Self::RemoveNode => "RemoveNode",
2876 Self::MountNode => "MountNode",
2877 Self::AttachChild => "AttachChild",
2878 Self::InsertChild => "InsertChild",
2879 Self::MoveChild => "MoveChild",
2880 Self::RemoveChild => "RemoveChild",
2881 Self::DetachChild => "DetachChild",
2882 Self::SyncChildren => "SyncChildren",
2883 Self::Callback => "Callback",
2884 }
2885 }
2886}
2887
2888#[derive(Copy, Clone)]
2889struct BubbleDirtyCommand {
2890 node_id: NodeId,
2891 bubble: DirtyBubble,
2892}
2893
2894#[derive(Copy, Clone)]
2895struct AttachChildCommand {
2896 parent_id: NodeId,
2897 child_id: NodeId,
2898 insert_index: Option<usize>,
2899 bubble: DirtyBubble,
2900}
2901
2902#[derive(Copy, Clone)]
2903struct InsertChildCommand {
2904 parent_id: NodeId,
2905 child_id: NodeId,
2906 appended_index: usize,
2907 insert_index: usize,
2908 bubble: DirtyBubble,
2909}
2910
2911#[derive(Copy, Clone)]
2912struct MoveChildCommand {
2913 parent_id: NodeId,
2914 from_index: usize,
2915 to_index: usize,
2916 bubble: DirtyBubble,
2917}
2918
2919#[derive(Copy, Clone)]
2920struct RemoveChildCommand {
2921 parent_id: NodeId,
2922 child_id: NodeId,
2923}
2924
2925#[derive(Copy, Clone)]
2926struct DetachChildCommand {
2927 parent_id: NodeId,
2928 child_id: NodeId,
2929}
2930
2931struct SyncChildrenCommand {
2932 parent_id: NodeId,
2933 children: Option<(usize, usize)>,
2936}
2937
2938#[derive(Default)]
2942pub(crate) struct CommandQueue {
2943 tags: Vec<CommandTag>,
2944 bubble_dirty: Vec<BubbleDirtyCommand>,
2945 remove_nodes: Vec<NodeId>,
2946 mount_nodes: Vec<NodeId>,
2947 attach_children: Vec<AttachChildCommand>,
2948 insert_children: Vec<InsertChildCommand>,
2949 move_children: Vec<MoveChildCommand>,
2950 remove_children: Vec<RemoveChildCommand>,
2951 detach_children: Vec<DetachChildCommand>,
2952 sync_children: Vec<SyncChildrenCommand>,
2953 sync_child_ids: Vec<NodeId>,
2954 callbacks: Vec<CommandCallback>,
2955 cleanup: DeferredChildCleanupQueue,
2956}
2957
2958impl CommandQueue {
2959 fn push_tag(&mut self, tag: CommandTag) {
2960 self.tags.push(tag);
2961 }
2962
2963 fn push(&mut self, command: Command) {
2964 match command {
2965 Command::BubbleDirty { node_id, bubble } => {
2966 self.bubble_dirty
2967 .push(BubbleDirtyCommand { node_id, bubble });
2968 self.push_tag(CommandTag::BubbleDirty);
2969 }
2970 Command::RemoveNode { id } => {
2971 self.remove_nodes.push(id);
2972 self.push_tag(CommandTag::RemoveNode);
2973 }
2974 Command::MountNode { id } => {
2975 self.mount_nodes.push(id);
2976 self.push_tag(CommandTag::MountNode);
2977 }
2978 Command::AttachChild {
2979 parent_id,
2980 child_id,
2981 insert_index,
2982 bubble,
2983 } => {
2984 self.attach_children.push(AttachChildCommand {
2985 parent_id,
2986 child_id,
2987 insert_index,
2988 bubble,
2989 });
2990 self.push_tag(CommandTag::AttachChild);
2991 }
2992 Command::InsertChild {
2993 parent_id,
2994 child_id,
2995 appended_index,
2996 insert_index,
2997 bubble,
2998 } => {
2999 self.insert_children.push(InsertChildCommand {
3000 parent_id,
3001 child_id,
3002 appended_index,
3003 insert_index,
3004 bubble,
3005 });
3006 self.push_tag(CommandTag::InsertChild);
3007 }
3008 Command::MoveChild {
3009 parent_id,
3010 from_index,
3011 to_index,
3012 bubble,
3013 } => {
3014 self.move_children.push(MoveChildCommand {
3015 parent_id,
3016 from_index,
3017 to_index,
3018 bubble,
3019 });
3020 self.push_tag(CommandTag::MoveChild);
3021 }
3022 Command::RemoveChild {
3023 parent_id,
3024 child_id,
3025 } => {
3026 self.remove_children.push(RemoveChildCommand {
3027 parent_id,
3028 child_id,
3029 });
3030 self.push_tag(CommandTag::RemoveChild);
3031 }
3032 Command::DetachChild {
3033 parent_id,
3034 child_id,
3035 } => {
3036 self.detach_children.push(DetachChildCommand {
3037 parent_id,
3038 child_id,
3039 });
3040 self.push_tag(CommandTag::DetachChild);
3041 }
3042 Command::SyncChildren {
3043 parent_id,
3044 expected_children,
3045 } => {
3046 let children = expected_children.map(|expected| {
3047 let child_start = self.sync_child_ids.len();
3048 self.sync_child_ids.extend_from_slice(&expected);
3049 (child_start, expected.len())
3050 });
3051 self.sync_children.push(SyncChildrenCommand {
3052 parent_id,
3053 children,
3054 });
3055 self.push_tag(CommandTag::SyncChildren);
3056 }
3057 Command::Callback(callback) => {
3058 self.callbacks.push(callback);
3059 self.push_tag(CommandTag::Callback);
3060 }
3061 }
3062 }
3063
3064 fn len(&self) -> usize {
3065 self.tags.len()
3066 }
3067
3068 fn capacity(&self) -> usize {
3069 self.tags.capacity()
3070 }
3071
3072 fn payload_len_bytes(&self) -> usize {
3073 self.bubble_dirty
3074 .len()
3075 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
3076 .saturating_add(
3077 self.remove_nodes
3078 .len()
3079 .saturating_mul(std::mem::size_of::<NodeId>()),
3080 )
3081 .saturating_add(
3082 self.mount_nodes
3083 .len()
3084 .saturating_mul(std::mem::size_of::<NodeId>()),
3085 )
3086 .saturating_add(
3087 self.attach_children
3088 .len()
3089 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
3090 )
3091 .saturating_add(
3092 self.insert_children
3093 .len()
3094 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
3095 )
3096 .saturating_add(
3097 self.move_children
3098 .len()
3099 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
3100 )
3101 .saturating_add(
3102 self.remove_children
3103 .len()
3104 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
3105 )
3106 .saturating_add(
3107 self.detach_children
3108 .len()
3109 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
3110 )
3111 .saturating_add(
3112 self.sync_children
3113 .len()
3114 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
3115 )
3116 .saturating_add(
3117 self.sync_child_ids
3118 .len()
3119 .saturating_mul(std::mem::size_of::<NodeId>()),
3120 )
3121 .saturating_add(
3122 self.callbacks
3123 .len()
3124 .saturating_mul(std::mem::size_of::<CommandCallback>()),
3125 )
3126 }
3127
3128 fn payload_capacity_bytes(&self) -> usize {
3129 self.bubble_dirty
3130 .capacity()
3131 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
3132 .saturating_add(
3133 self.remove_nodes
3134 .capacity()
3135 .saturating_mul(std::mem::size_of::<NodeId>()),
3136 )
3137 .saturating_add(
3138 self.mount_nodes
3139 .capacity()
3140 .saturating_mul(std::mem::size_of::<NodeId>()),
3141 )
3142 .saturating_add(
3143 self.attach_children
3144 .capacity()
3145 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
3146 )
3147 .saturating_add(
3148 self.insert_children
3149 .capacity()
3150 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
3151 )
3152 .saturating_add(
3153 self.move_children
3154 .capacity()
3155 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
3156 )
3157 .saturating_add(
3158 self.remove_children
3159 .capacity()
3160 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
3161 )
3162 .saturating_add(
3163 self.detach_children
3164 .capacity()
3165 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
3166 )
3167 .saturating_add(
3168 self.sync_children
3169 .capacity()
3170 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
3171 )
3172 .saturating_add(
3173 self.sync_child_ids
3174 .capacity()
3175 .saturating_mul(std::mem::size_of::<NodeId>()),
3176 )
3177 .saturating_add(
3178 self.callbacks
3179 .capacity()
3180 .saturating_mul(std::mem::size_of::<CommandCallback>()),
3181 )
3182 }
3183
3184 pub(crate) fn apply(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
3187 applier.begin_bubble_batch();
3188 let result = self.apply_in_order(applier);
3189 applier.end_bubble_batch();
3190 self.clear();
3191 result
3192 }
3193
3194 fn clear(&mut self) {
3195 let Self {
3196 tags,
3197 bubble_dirty,
3198 remove_nodes,
3199 mount_nodes,
3200 attach_children,
3201 insert_children,
3202 move_children,
3203 remove_children,
3204 detach_children,
3205 sync_children,
3206 sync_child_ids,
3207 callbacks,
3208 cleanup,
3209 } = self;
3210 tags.clear();
3211 bubble_dirty.clear();
3212 remove_nodes.clear();
3213 mount_nodes.clear();
3214 attach_children.clear();
3215 insert_children.clear();
3216 move_children.clear();
3217 remove_children.clear();
3218 detach_children.clear();
3219 sync_children.clear();
3220 sync_child_ids.clear();
3221 callbacks.clear();
3222 cleanup.clear();
3223 }
3224
3225 fn apply_in_order(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
3226 let mut payloads = CommandPayloads {
3227 bubble_dirty: self.bubble_dirty.drain(..),
3228 remove_nodes: self.remove_nodes.drain(..),
3229 mount_nodes: self.mount_nodes.drain(..),
3230 attach_children: self.attach_children.drain(..),
3231 insert_children: self.insert_children.drain(..),
3232 move_children: self.move_children.drain(..),
3233 remove_children: self.remove_children.drain(..),
3234 detach_children: self.detach_children.drain(..),
3235 sync_children: self.sync_children.drain(..),
3236 sync_child_ids: &self.sync_child_ids,
3237 callbacks: self.callbacks.drain(..),
3238 };
3239 for &tag in &self.tags {
3240 payloads.apply(tag, applier, &mut self.cleanup)?;
3241 }
3242 payloads.assert_all_taken();
3243 self.cleanup.flush(applier)
3244 }
3245}
3246
3247struct CommandPayloads<'a> {
3250 bubble_dirty: std::vec::Drain<'a, BubbleDirtyCommand>,
3251 remove_nodes: std::vec::Drain<'a, NodeId>,
3252 mount_nodes: std::vec::Drain<'a, NodeId>,
3253 attach_children: std::vec::Drain<'a, AttachChildCommand>,
3254 insert_children: std::vec::Drain<'a, InsertChildCommand>,
3255 move_children: std::vec::Drain<'a, MoveChildCommand>,
3256 remove_children: std::vec::Drain<'a, RemoveChildCommand>,
3257 detach_children: std::vec::Drain<'a, DetachChildCommand>,
3258 sync_children: std::vec::Drain<'a, SyncChildrenCommand>,
3259 sync_child_ids: &'a [NodeId],
3260 callbacks: std::vec::Drain<'a, CommandCallback>,
3261}
3262
3263impl CommandPayloads<'_> {
3264 fn apply(
3265 &mut self,
3266 tag: CommandTag,
3267 applier: &mut dyn Applier,
3268 cleanup: &mut DeferredChildCleanupQueue,
3269 ) -> Result<(), NodeError> {
3270 match tag {
3271 CommandTag::BubbleDirty
3272 | CommandTag::RemoveNode
3273 | CommandTag::MountNode
3274 | CommandTag::Callback => self.apply_node_command(tag, applier, cleanup),
3275 CommandTag::AttachChild
3276 | CommandTag::InsertChild
3277 | CommandTag::MoveChild
3278 | CommandTag::RemoveChild
3279 | CommandTag::DetachChild
3280 | CommandTag::SyncChildren => self.apply_child_command(tag, applier, cleanup),
3281 }
3282 }
3283
3284 fn apply_node_command(
3285 &mut self,
3286 tag: CommandTag,
3287 applier: &mut dyn Applier,
3288 cleanup: &mut DeferredChildCleanupQueue,
3289 ) -> Result<(), NodeError> {
3290 match tag {
3291 CommandTag::BubbleDirty => {
3292 let BubbleDirtyCommand { node_id, bubble } =
3293 next_command_payload(&mut self.bubble_dirty, tag)?;
3294 Command::BubbleDirty { node_id, bubble }.apply_with_cleanup(applier, cleanup)?;
3295 }
3296 CommandTag::RemoveNode => {
3297 let id = next_command_payload(&mut self.remove_nodes, tag)?;
3298 Command::RemoveNode { id }.apply_with_cleanup(applier, cleanup)?;
3299 }
3300 CommandTag::MountNode => {
3301 let id = next_command_payload(&mut self.mount_nodes, tag)?;
3302 Command::MountNode { id }.apply_with_cleanup(applier, cleanup)?;
3303 }
3304 CommandTag::Callback => {
3305 let callback = next_command_payload(&mut self.callbacks, tag)?;
3306 Command::Callback(callback).apply_with_cleanup(applier, cleanup)?;
3307 applier.forget_bubble_passes();
3308 }
3309 CommandTag::AttachChild
3310 | CommandTag::InsertChild
3311 | CommandTag::MoveChild
3312 | CommandTag::RemoveChild
3313 | CommandTag::DetachChild
3314 | CommandTag::SyncChildren => return Err(command_payload_error(tag)),
3315 }
3316 Ok(())
3317 }
3318
3319 fn apply_child_command(
3320 &mut self,
3321 tag: CommandTag,
3322 applier: &mut dyn Applier,
3323 cleanup: &mut DeferredChildCleanupQueue,
3324 ) -> Result<(), NodeError> {
3325 match tag {
3326 CommandTag::AttachChild => {
3327 let AttachChildCommand {
3328 parent_id,
3329 child_id,
3330 insert_index,
3331 bubble,
3332 } = next_command_payload(&mut self.attach_children, tag)?;
3333 Command::AttachChild {
3334 parent_id,
3335 child_id,
3336 insert_index,
3337 bubble,
3338 }
3339 .apply_with_cleanup(applier, cleanup)?;
3340 }
3341 CommandTag::InsertChild => {
3342 let InsertChildCommand {
3343 parent_id,
3344 child_id,
3345 appended_index,
3346 insert_index,
3347 bubble,
3348 } = next_command_payload(&mut self.insert_children, tag)?;
3349 Command::InsertChild {
3350 parent_id,
3351 child_id,
3352 appended_index,
3353 insert_index,
3354 bubble,
3355 }
3356 .apply_with_cleanup(applier, cleanup)?;
3357 }
3358 CommandTag::MoveChild => {
3359 let MoveChildCommand {
3360 parent_id,
3361 from_index,
3362 to_index,
3363 bubble,
3364 } = next_command_payload(&mut self.move_children, tag)?;
3365 Command::MoveChild {
3366 parent_id,
3367 from_index,
3368 to_index,
3369 bubble,
3370 }
3371 .apply_with_cleanup(applier, cleanup)?;
3372 }
3373 CommandTag::RemoveChild => {
3374 let RemoveChildCommand {
3375 parent_id,
3376 child_id,
3377 } = next_command_payload(&mut self.remove_children, tag)?;
3378 Command::RemoveChild {
3379 parent_id,
3380 child_id,
3381 }
3382 .apply_with_cleanup(applier, cleanup)?;
3383 }
3384 CommandTag::DetachChild => {
3385 let DetachChildCommand {
3386 parent_id,
3387 child_id,
3388 } = next_command_payload(&mut self.detach_children, tag)?;
3389 Command::DetachChild {
3390 parent_id,
3391 child_id,
3392 }
3393 .apply_with_cleanup(applier, cleanup)?;
3394 }
3395 CommandTag::SyncChildren => self.sync_children(applier, cleanup)?,
3396 CommandTag::BubbleDirty
3397 | CommandTag::RemoveNode
3398 | CommandTag::MountNode
3399 | CommandTag::Callback => return Err(command_payload_error(tag)),
3400 }
3401 Ok(())
3402 }
3403
3404 fn sync_children(
3405 &mut self,
3406 applier: &mut dyn Applier,
3407 cleanup: &mut DeferredChildCleanupQueue,
3408 ) -> Result<(), NodeError> {
3409 let tag = CommandTag::SyncChildren;
3410 let SyncChildrenCommand {
3411 parent_id,
3412 children,
3413 } = next_command_payload(&mut self.sync_children, tag)?;
3414 let expected_children = children
3415 .map(|(child_start, child_len)| {
3416 child_start
3417 .checked_add(child_len)
3418 .and_then(|child_end| self.sync_child_ids.get(child_start..child_end))
3419 .ok_or_else(|| command_payload_error(tag))
3420 })
3421 .transpose()?;
3422 sync_or_settle_children(applier, parent_id, expected_children, cleanup)
3423 }
3424
3425 fn assert_all_taken(&mut self) {
3426 debug_assert!(self.bubble_dirty.next().is_none());
3427 debug_assert!(self.remove_nodes.next().is_none());
3428 debug_assert!(self.mount_nodes.next().is_none());
3429 debug_assert!(self.attach_children.next().is_none());
3430 debug_assert!(self.insert_children.next().is_none());
3431 debug_assert!(self.move_children.next().is_none());
3432 debug_assert!(self.remove_children.next().is_none());
3433 debug_assert!(self.detach_children.next().is_none());
3434 debug_assert!(self.sync_children.next().is_none());
3435 debug_assert!(self.callbacks.next().is_none());
3436 }
3437}
3438
3439fn command_payload_error(tag: CommandTag) -> NodeError {
3440 NodeError::MalformedCommandPayload { tag: tag.label() }
3441}
3442
3443fn next_command_payload<T>(
3444 payloads: &mut impl Iterator<Item = T>,
3445 tag: CommandTag,
3446) -> Result<T, NodeError> {
3447 payloads.next().ok_or_else(|| command_payload_error(tag))
3448}
3449
3450fn attach_child_at(
3451 applier: &mut dyn Applier,
3452 parent_id: NodeId,
3453 child_id: NodeId,
3454 insert_index: Option<usize>,
3455 bubble: DirtyBubble,
3456) {
3457 if insert_child_with_reparenting(applier, parent_id, child_id) {
3458 if let Some(target) = insert_index {
3459 move_attached_child_left_to(applier, parent_id, child_id, target);
3460 }
3461 bubble.apply(applier, parent_id);
3462 } else if let Ok(child) = applier.get_mut(child_id) {
3463 let dirty_bubble = DirtyBubble {
3464 layout: child.layout_dirty(),
3465 measure: child.needs_measure() || child.descendant_needs_measure(),
3466 semantics: false,
3467 };
3468 dirty_bubble.apply_to_ancestors(applier, child_id);
3469 }
3470}
3471
3472fn move_attached_child_left_to(
3473 applier: &mut dyn Applier,
3474 parent_id: NodeId,
3475 child_id: NodeId,
3476 target: usize,
3477) {
3478 let Ok(parent_node) = applier.get_mut(parent_id) else {
3479 return;
3480 };
3481 let Some(current_index) = parent_node.owned_child_index(child_id) else {
3482 return;
3483 };
3484 if target < current_index {
3485 parent_node.move_child(current_index, target);
3486 note_structural_move(parent_id, current_index, target);
3487 }
3488}
3489
3490fn insert_child_with_reparenting(
3491 applier: &mut dyn Applier,
3492 parent_id: NodeId,
3493 child_id: NodeId,
3494) -> bool {
3495 if parent_id == child_id {
3496 debug_assert_ne!(
3497 parent_id, child_id,
3498 "a node cannot be attached as its own child"
3499 );
3500 return false;
3501 }
3502
3503 let old_parent = applier
3504 .get_mut(child_id)
3505 .ok()
3506 .and_then(|node| node.parent());
3507 if old_parent != Some(parent_id) {
3508 applier.forget_bubble_passes();
3509 }
3510 if let Some(old_parent_id) = old_parent
3511 && old_parent_id != parent_id
3512 {
3513 let removed = applier
3514 .get_mut(old_parent_id)
3515 .is_ok_and(|old_parent_node| old_parent_node.remove_child(child_id));
3516 if let Ok(child_node) = applier.get_mut(child_id) {
3517 child_node.on_removed_from_parent();
3518 }
3519 if removed {
3520 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, old_parent_id);
3521 note_structural("reparent-detach", old_parent_id, child_id);
3522 applier.record_structural_change(old_parent_id);
3523 }
3524 }
3525
3526 let inserted = applier
3527 .get_mut(parent_id)
3528 .is_ok_and(|parent_node| parent_node.insert_child(child_id));
3529 if inserted {
3530 note_structural("attach", parent_id, child_id);
3531 applier.record_structural_change(parent_id);
3532 }
3533 if let Ok(child_node) = applier.get_mut(child_id) {
3534 child_node.on_attached_to_parent(parent_id);
3535 }
3536 inserted
3537}
3538
3539fn apply_remove_child(
3540 applier: &mut dyn Applier,
3541 parent_id: NodeId,
3542 child_id: NodeId,
3543 deferred_cleanup: &mut DeferredChildCleanupQueue,
3544) -> Result<(), NodeError> {
3545 detach_child_from_parent(applier, parent_id, child_id)?;
3546
3547 let generation = applier.node_generation(child_id);
3548 let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
3549 node.parent().is_none()
3550 } else {
3551 return Ok(());
3552 };
3553 deferred_cleanup.push(child_id, generation, removed_from_parent);
3554 Ok(())
3555}
3556
3557fn detach_child_from_parent(
3558 applier: &mut dyn Applier,
3559 parent_id: NodeId,
3560 child_id: NodeId,
3561) -> Result<(), NodeError> {
3562 let removed = applier
3563 .get_mut(parent_id)
3564 .is_ok_and(|parent_node| parent_node.remove_child(child_id));
3565 if removed {
3566 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3567 note_structural("detach", parent_id, child_id);
3568 applier.record_structural_change(parent_id);
3569 }
3570
3571 if let Ok(node) = applier.get_mut(child_id) {
3572 match node.parent() {
3573 Some(existing_parent_id) if existing_parent_id == parent_id => {
3574 node.on_removed_from_parent();
3575 applier.forget_bubble_passes();
3576 }
3577 None => {}
3578 Some(_) => return Ok(()),
3579 }
3580 } else {
3581 return Ok(());
3582 }
3583
3584 Ok(())
3585}
3586
3587fn cleanup_detached_child(
3588 applier: &mut dyn Applier,
3589 cleanup: DeferredChildCleanup,
3590) -> Result<(), NodeError> {
3591 if applier.node_generation(cleanup.child_id) != cleanup.generation {
3592 return Ok(());
3593 }
3594
3595 let parent_id = match applier.get_mut(cleanup.child_id) {
3596 Ok(node) => node.parent(),
3597 Err(NodeError::Missing { .. }) => return Ok(()),
3598 Err(err) => return Err(err),
3599 };
3600 if parent_id.is_some() {
3601 return Ok(());
3602 }
3603
3604 if let Ok(node) = applier.get_mut(cleanup.child_id) {
3605 if !cleanup.removed_from_parent {
3606 node.on_removed_from_parent();
3607 }
3608 node.unmount();
3609 }
3610 match applier.remove(cleanup.child_id) {
3611 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
3612 Err(err) => Err(err),
3613 }
3614}
3615
3616fn remove_child_and_cleanup_now(
3617 applier: &mut dyn Applier,
3618 parent_id: NodeId,
3619 child_id: NodeId,
3620) -> Result<(), NodeError> {
3621 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
3622 apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
3623 deferred_cleanup.flush(applier)
3624}
3625
3626fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
3627 let mut scratch = SmallVec::<[NodeId; 8]>::new();
3628 if let Ok(node) = applier.get_mut(parent_id) {
3629 node.collect_children_into(&mut scratch);
3630 }
3631 let mut current = ChildList::new();
3632 current.extend(scratch);
3633 current
3634}
3635
3636fn sync_children(
3637 applier: &mut dyn Applier,
3638 parent_id: NodeId,
3639 expected_children: &[NodeId],
3640 deferred_cleanup: &mut DeferredChildCleanupQueue,
3641) -> Result<(), NodeError> {
3642 let mut current = collect_current_children(applier, parent_id);
3643 let children_changed = current.as_slice() != expected_children;
3644
3645 if children_changed {
3646 if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
3647 sync_children_small(
3648 applier,
3649 parent_id,
3650 &mut current,
3651 expected_children,
3652 deferred_cleanup,
3653 )?;
3654 } else {
3655 let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
3656 target_positions.reserve(expected_children.len());
3657 for (index, &child) in expected_children.iter().enumerate() {
3658 target_positions.insert(child, index);
3659 }
3660
3661 for index in (0..current.len()).rev() {
3662 let child = current[index];
3663 if !target_positions.contains_key(&child) {
3664 current.remove(index);
3665 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3666 }
3667 }
3668
3669 let mut current_positions = build_child_positions(¤t);
3670 for (target_index, &child) in expected_children.iter().enumerate() {
3671 if let Some(current_index) = current_positions.get(&child).copied() {
3672 if current_index != target_index {
3673 let from_index = current_index;
3674 let to_index = move_child_in_diff_state(
3675 &mut current,
3676 &mut current_positions,
3677 from_index,
3678 target_index,
3679 );
3680 Command::MoveChild {
3681 parent_id,
3682 from_index,
3683 to_index,
3684 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3685 }
3686 .apply(applier)?;
3687 }
3688 } else {
3689 let insert_index = target_index.min(current.len());
3690 let appended_index = current.len();
3691 insert_child_into_diff_state(
3692 &mut current,
3693 &mut current_positions,
3694 insert_index,
3695 child,
3696 );
3697 Command::InsertChild {
3698 parent_id,
3699 child_id: child,
3700 appended_index,
3701 insert_index,
3702 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3703 }
3704 .apply(applier)?;
3705 }
3706 }
3707 }
3708 }
3709
3710 reconcile_children(applier, parent_id, expected_children, !children_changed)
3711}
3712
3713fn sync_children_small(
3714 applier: &mut dyn Applier,
3715 parent_id: NodeId,
3716 current: &mut ChildList,
3717 expected_children: &[NodeId],
3718 deferred_cleanup: &mut DeferredChildCleanupQueue,
3719) -> Result<(), NodeError> {
3720 for index in (0..current.len()).rev() {
3721 let child = current[index];
3722 if !expected_children.contains(&child) {
3723 current.remove(index);
3724 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3725 }
3726 }
3727
3728 for (target_index, &child) in expected_children.iter().enumerate() {
3729 if let Some(current_index) = current
3730 .iter()
3731 .position(|¤t_child| current_child == child)
3732 {
3733 if current_index != target_index {
3734 let child = current.remove(current_index);
3735 let to_index = target_index.min(current.len());
3736 current.insert(to_index, child);
3737 Command::MoveChild {
3738 parent_id,
3739 from_index: current_index,
3740 to_index,
3741 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3742 }
3743 .apply(applier)?;
3744 }
3745 } else {
3746 let insert_index = target_index.min(current.len());
3747 let appended_index = current.len();
3748 current.insert(insert_index, child);
3749 Command::InsertChild {
3750 parent_id,
3751 child_id: child,
3752 appended_index,
3753 insert_index,
3754 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3755 }
3756 .apply(applier)?;
3757 }
3758 }
3759
3760 Ok(())
3761}
3762
3763fn reconcile_children(
3768 applier: &mut dyn Applier,
3769 parent_id: NodeId,
3770 expected_children: &[NodeId],
3771 children_unchanged: bool,
3772) -> Result<(), NodeError> {
3773 if children_unchanged {
3774 settle_kept_children(applier, parent_id);
3775 return Ok(());
3776 }
3777 let mut repaired = false;
3778 for &child_id in expected_children {
3779 let needs_attach = applier
3780 .get_mut(child_id)
3781 .is_ok_and(|node| node.parent() != Some(parent_id));
3782 if needs_attach {
3783 insert_child_with_reparenting(applier, parent_id, child_id);
3784 repaired = true;
3785 }
3786 }
3787 if repaired {
3788 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3789 }
3790 Ok(())
3791}
3792
3793fn sync_or_settle_children(
3796 applier: &mut dyn Applier,
3797 parent_id: NodeId,
3798 expected_children: Option<&[NodeId]>,
3799 deferred_cleanup: &mut DeferredChildCleanupQueue,
3800) -> Result<(), NodeError> {
3801 if applier
3802 .get_mut(parent_id)
3803 .is_ok_and(|parent| parent.keeps_own_children())
3804 {
3805 return Ok(());
3806 }
3807 match expected_children {
3808 Some(expected_children) => {
3809 sync_children(applier, parent_id, expected_children, deferred_cleanup)
3810 }
3811 None => {
3812 settle_kept_children(applier, parent_id);
3813 Ok(())
3814 }
3815 }
3816}
3817
3818fn settle_kept_children(applier: &mut dyn Applier, parent_id: NodeId) {
3821 debug_assert!(
3822 collect_current_children(applier, parent_id)
3823 .iter()
3824 .all(|&child_id| applier
3825 .get_mut(child_id)
3826 .map_or(true, |node| node.parent().is_none_or(|p| p == parent_id))),
3827 "the children a parent holds must name no other parent"
3828 );
3829 if applier
3830 .get_mut(parent_id)
3831 .is_ok_and(|node| node.layout_dirty())
3832 {
3833 DirtyBubble::LAYOUT.apply_to_ancestors(applier, parent_id);
3834 }
3835}
3836
3837#[derive(Default)]
3840pub struct MemoryApplier {
3841 state: Box<MemoryApplierState>,
3844}
3845
3846#[derive(Default)]
3847struct MemoryApplierState {
3848 nodes: Vec<Option<Box<dyn Node>>>,
3849 physical_stable_ids: Vec<u32>,
3850 physical_warm_recycled_origins: Vec<bool>,
3851 stable_index: stable_index::StableIndex,
3852 free_ids: BinaryHeap<Reverse<usize>>,
3853 high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
3854 high_id_warm_recycled_origins: HashMap<NodeId, bool>,
3855 high_id_generations: HashMap<NodeId, u32>,
3856 next_stable_id: NodeId,
3857 layout_runtime: Option<RuntimeHandle>,
3858 recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3859 returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3860 cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3861 recycled_node_limits: HashMap<TypeId, usize>,
3862 warm_recycled_node_targets: HashMap<TypeId, usize>,
3863 fresh_recyclable_creations: HashMap<TypeId, usize>,
3864 recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
3865 structural_change_parents: Vec<NodeId>,
3866 virtual_node_ids: HashSet<NodeId>,
3867 bubble_passes: bubble_passes::BubblePasses,
3868 layout_dirt_roots: layout_dirt_roots::LayoutDirtRoots,
3869}
3870
3871#[derive(Default)]
3876pub struct SceneNodeAttachmentScratch {
3877 attached: NodeMarks,
3878 path: Vec<NodeId>,
3879}
3880
3881const ATTACHED: u8 = 1;
3882const DETACHED: u8 = 2;
3883
3884struct RemovalFrame {
3885 node_id: NodeId,
3886 children: SmallVec<[NodeId; 8]>,
3887 next_child: usize,
3888}
3889
3890#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3893pub struct MemoryApplierDebugStats {
3894 pub next_stable_id: NodeId,
3896 pub nodes_len: usize,
3898 pub nodes_cap: usize,
3900 pub physical_stable_ids_len: usize,
3902 pub physical_stable_ids_cap: usize,
3904 pub stable_to_physical_len: usize,
3906 pub stable_to_physical_cap: usize,
3908 pub stable_generations_len: usize,
3910 pub stable_generations_cap: usize,
3912 pub free_ids_len: usize,
3914 pub free_ids_cap: usize,
3916 pub high_id_nodes_len: usize,
3918 pub high_id_nodes_cap: usize,
3920 pub high_id_generations_len: usize,
3922 pub high_id_generations_cap: usize,
3924 pub recycled_type_count: usize,
3926 pub recycled_type_cap: usize,
3928 pub recycled_node_count: usize,
3930 pub recycled_node_capacity: usize,
3932 pub warm_recycled_node_id_count: usize,
3934 pub warm_recycled_node_id_capacity: usize,
3936}
3937
3938impl MemoryApplier {
3939 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
3940 const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
3941 const INVALID_STABLE_ID: u32 = u32::MAX;
3942 const INITIAL_DENSE_NODE_CAP: usize = 32;
3943 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
3944 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
3945
3946 fn pack_stable_id(stable_id: NodeId) -> u32 {
3947 u32::try_from(stable_id).expect("stable id overflow")
3948 }
3949
3950 fn unpack_stable_id(stable_id: u32) -> NodeId {
3951 stable_id as NodeId
3952 }
3953
3954 fn next_dense_node_target_len(old_len: usize) -> usize {
3955 if old_len < Self::INITIAL_DENSE_NODE_CAP {
3956 return Self::INITIAL_DENSE_NODE_CAP;
3957 }
3958 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
3959 return old_len.saturating_mul(2);
3960 }
3961
3962 let incremental_growth =
3963 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
3964 old_len.saturating_add(incremental_growth)
3965 }
3966
3967 fn ensure_dense_node_storage_capacity(&mut self) {
3968 let len = self
3969 .state
3970 .nodes
3971 .len()
3972 .max(self.state.physical_stable_ids.len())
3973 .max(self.state.physical_warm_recycled_origins.len());
3974 if len < self.state.nodes.capacity()
3975 && len < self.state.physical_stable_ids.capacity()
3976 && len < self.state.physical_warm_recycled_origins.capacity()
3977 {
3978 return;
3979 }
3980
3981 let target = Self::next_dense_node_target_len(len);
3982 if self.state.nodes.capacity() < target {
3983 self.state
3984 .nodes
3985 .reserve_exact(target.saturating_sub(self.state.nodes.len()));
3986 }
3987 if self.state.physical_stable_ids.capacity() < target {
3988 self.state
3989 .physical_stable_ids
3990 .reserve_exact(target.saturating_sub(self.state.physical_stable_ids.len()));
3991 }
3992 if self.state.physical_warm_recycled_origins.capacity() < target {
3993 self.state.physical_warm_recycled_origins.reserve_exact(
3994 target.saturating_sub(self.state.physical_warm_recycled_origins.len()),
3995 );
3996 }
3997 }
3998
3999 pub fn new() -> Self {
4001 Self::default()
4002 }
4003
4004 pub fn scene_node_attached_to(&mut self, node_id: NodeId, root: NodeId) -> Option<NodeId> {
4010 let resolved = self.first_non_virtual_ancestor(node_id)?;
4011 self.is_attached_to(resolved, root).then_some(resolved)
4012 }
4013
4014 pub fn scene_nodes_attached_to(
4018 &mut self,
4019 nodes: impl IntoIterator<Item = NodeId>,
4020 root: NodeId,
4021 ) -> Vec<Option<NodeId>> {
4022 let mut result = Vec::new();
4023 self.scene_nodes_attached_to_into(
4024 nodes,
4025 root,
4026 &mut result,
4027 &mut SceneNodeAttachmentScratch::default(),
4028 );
4029 result
4030 }
4031
4032 pub fn scene_nodes_attached_to_into(
4050 &mut self,
4051 nodes: impl IntoIterator<Item = NodeId>,
4052 root: NodeId,
4053 output: &mut Vec<Option<NodeId>>,
4054 scratch: &mut SceneNodeAttachmentScratch,
4055 ) {
4056 let nodes = nodes.into_iter();
4057 output.clear();
4058 let (lower_bound, upper_bound) = nodes.size_hint();
4059 if lower_bound == 0 && upper_bound == Some(0) {
4060 scratch.path.clear();
4061 return;
4062 }
4063 scratch.attached.reset(self);
4064 scratch.attached.set(self, root, ATTACHED);
4065 output.reserve(lower_bound);
4066 scratch.path.clear();
4067 for node_id in nodes {
4068 let Some(resolved) = self.first_non_virtual_ancestor(node_id) else {
4069 output.push(None);
4070 continue;
4071 };
4072 let mut current = resolved;
4073 scratch.path.clear();
4074 let answer = loop {
4075 match scratch.attached.get(self, current) {
4076 ATTACHED => break true,
4077 DETACHED => break false,
4078 _ => {}
4079 }
4080 scratch.path.push(current);
4081 match self.get_mut(current).ok().and_then(|node| node.parent()) {
4082 Some(parent) if scratch.path.len() < 100_000 => current = parent,
4083 _ => break false,
4084 }
4085 };
4086 let mark = if answer { ATTACHED } else { DETACHED };
4087 for visited in scratch.path.drain(..) {
4088 scratch.attached.set(self, visited, mark);
4089 }
4090 output.push(answer.then_some(resolved));
4091 }
4092 }
4093
4094 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
4102 let mut candidates = Vec::new();
4103 self.take_structural_change_parents_into(&mut candidates);
4104 let mut seen = HashSet::<NodeId>::default();
4105 candidates.retain_mut(|parent_id| {
4106 let Some(resolved) = self.first_non_virtual_ancestor(*parent_id) else {
4107 return false;
4108 };
4109 *parent_id = resolved;
4110 self.is_attached_to(resolved, root) && seen.insert(resolved)
4111 });
4112 candidates
4113 }
4114
4115 pub fn take_structural_change_parents_into(&mut self, output: &mut Vec<NodeId>) {
4128 output.clear();
4129 std::mem::swap(&mut self.state.structural_change_parents, output);
4130 }
4131
4132 fn first_non_virtual_ancestor(&mut self, node_id: NodeId) -> Option<NodeId> {
4133 if self.state.virtual_node_ids.is_empty() {
4134 return Some(node_id);
4135 }
4136 let mut current = node_id;
4137 for _ in 0..100_000 {
4138 if !self.state.virtual_node_ids.contains(¤t) {
4139 return Some(current);
4140 }
4141 match self.get_mut(current) {
4142 Ok(node) => current = node.parent()?,
4143 Err(_) => return None,
4144 }
4145 }
4146 None
4147 }
4148
4149 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
4150 let mut current = node_id;
4151 for _ in 0..100_000 {
4152 if current == root {
4153 return true;
4154 }
4155 match self.get_mut(current) {
4156 Ok(node) => match node.parent() {
4157 Some(parent) => current = parent,
4158 None => return false,
4159 },
4160 Err(_) => return false,
4161 }
4162 }
4163 false
4164 }
4165
4166 pub fn with_node<N: Node + 'static, R>(
4169 &mut self,
4170 id: NodeId,
4171 f: impl FnOnce(&mut N) -> R,
4172 ) -> Result<R, NodeError> {
4173 let typed = self
4174 .get_mut(id)?
4175 .as_any_mut()
4176 .downcast_mut::<N>()
4177 .ok_or_else(|| NodeError::TypeMismatch {
4178 id,
4179 expected: std::any::type_name::<N>(),
4180 })?;
4181 Ok(f(typed))
4182 }
4183
4184 pub fn len(&self) -> usize {
4186 self.state.nodes.iter().filter(|n| n.is_some()).count()
4187 }
4188
4189 pub fn capacity(&self) -> usize {
4191 self.state.nodes.len()
4192 }
4193
4194 pub fn tombstone_count(&self) -> usize {
4196 self.state.nodes.iter().filter(|n| n.is_none()).count()
4197 }
4198
4199 pub fn freelist_len(&self) -> usize {
4201 self.state.free_ids.len()
4202 }
4203
4204 pub fn debug_recycled_node_count(&self) -> usize {
4206 self.total_recycled_node_count()
4207 }
4208
4209 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
4211 let key = TypeId::of::<N>();
4212 self.state.recycled_nodes.get(&key).map_or(0, Vec::len)
4213 + self
4214 .state
4215 .returning_recycled_nodes
4216 .get(&key)
4217 .map_or(0, Vec::len)
4218 + self.state.cold_recycled_nodes.get(&key).map_or(0, Vec::len)
4219 }
4220
4221 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
4223 let mut recycled_keys: HashSet<TypeId> = HashSet::default();
4224 recycled_keys.extend(self.state.recycled_nodes.keys().copied());
4225 recycled_keys.extend(self.state.returning_recycled_nodes.keys().copied());
4226 recycled_keys.extend(self.state.cold_recycled_nodes.keys().copied());
4227
4228 MemoryApplierDebugStats {
4229 next_stable_id: self.state.next_stable_id,
4230 nodes_len: self.len(),
4231 nodes_cap: self.state.nodes.len(),
4232 physical_stable_ids_len: self.state.physical_stable_ids.len(),
4233 physical_stable_ids_cap: self.state.physical_stable_ids.capacity(),
4234 stable_to_physical_len: self.state.stable_index.live(),
4235 stable_to_physical_cap: self.state.stable_index.capacity(),
4236 stable_generations_len: self.state.stable_index.occupied(),
4237 stable_generations_cap: self.state.stable_index.capacity(),
4238 free_ids_len: self.state.free_ids.len(),
4239 free_ids_cap: self.state.free_ids.capacity(),
4240 high_id_nodes_len: self.state.high_id_nodes.len(),
4241 high_id_nodes_cap: self.state.high_id_nodes.capacity(),
4242 high_id_generations_len: self.state.high_id_generations.len(),
4243 high_id_generations_cap: self.state.high_id_generations.capacity(),
4244 recycled_type_count: recycled_keys.len(),
4245 recycled_type_cap: self.state.recycled_nodes.capacity()
4246 + self.state.returning_recycled_nodes.capacity()
4247 + self.state.cold_recycled_nodes.capacity(),
4248 recycled_node_count: self.total_recycled_node_count(),
4249 recycled_node_capacity: self.total_recycled_node_capacity(),
4250 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
4251 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
4252 }
4253 }
4254
4255 pub fn is_empty(&self) -> bool {
4257 self.len() == 0
4258 }
4259
4260 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut dyn Node)) {
4262 for node in self.state.nodes.iter_mut().flatten() {
4263 visit(node.as_mut());
4264 }
4265 for node in self.state.high_id_nodes.values_mut() {
4266 visit(node.as_mut());
4267 }
4268 }
4269
4270 pub fn debug_live_node_heap_bytes(&self) -> usize {
4272 let dense_nodes = self
4273 .state
4274 .nodes
4275 .iter()
4276 .flatten()
4277 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
4278 .sum::<usize>();
4279 let high_id_nodes = self
4280 .state
4281 .high_id_nodes
4282 .values()
4283 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
4284 .sum::<usize>();
4285 dense_nodes + high_id_nodes
4286 }
4287
4288 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
4290 let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
4291 pools
4292 .values()
4293 .flat_map(|nodes| nodes.iter())
4294 .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
4295 .sum::<usize>()
4296 };
4297
4298 pool_bytes(&self.state.recycled_nodes)
4299 + pool_bytes(&self.state.returning_recycled_nodes)
4300 + pool_bytes(&self.state.cold_recycled_nodes)
4301 }
4302
4303 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
4306 self.state.layout_runtime = Some(handle);
4307 }
4308
4309 pub fn clear_runtime_handle(&mut self) {
4311 self.state.layout_runtime = None;
4312 }
4313
4314 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
4316 self.state.layout_runtime.clone()
4317 }
4318
4319 fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
4320 pools.values().map(Vec::len).sum()
4321 }
4322
4323 fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
4324 pools.values().map(Vec::capacity).sum()
4325 }
4326
4327 fn total_recycled_node_count(&self) -> usize {
4328 Self::pool_node_count(&self.state.recycled_nodes)
4329 + Self::pool_node_count(&self.state.returning_recycled_nodes)
4330 + Self::pool_node_count(&self.state.cold_recycled_nodes)
4331 }
4332
4333 fn total_recycled_node_capacity(&self) -> usize {
4334 Self::pool_node_capacity(&self.state.recycled_nodes)
4335 + Self::pool_node_capacity(&self.state.returning_recycled_nodes)
4336 + Self::pool_node_capacity(&self.state.cold_recycled_nodes)
4337 }
4338
4339 fn total_warm_recycled_node_id_count(&self) -> usize {
4340 self.live_warm_recycled_origin_count()
4341 + Self::pool_node_count(&self.state.recycled_nodes)
4342 + Self::pool_node_count(&self.state.returning_recycled_nodes)
4343 }
4344
4345 fn total_warm_recycled_node_id_capacity(&self) -> usize {
4346 self.live_warm_recycled_origin_capacity()
4347 + Self::pool_node_capacity(&self.state.recycled_nodes)
4348 + Self::pool_node_capacity(&self.state.returning_recycled_nodes)
4349 }
4350
4351 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
4352 if let Some(limit) = recycle_pool_limit {
4353 self.state.recycled_node_limits.insert(key, limit);
4354 } else {
4355 self.state.recycled_node_limits.remove(&key);
4356 }
4357 }
4358
4359 fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
4360 self.state.recycled_node_limits.get(&key).copied()
4361 }
4362
4363 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
4364 self.state.recycled_nodes.get(&key).map_or(0, Vec::len)
4365 }
4366
4367 fn warm_recycled_node_target(&self, key: TypeId) -> usize {
4368 self.state
4369 .warm_recycled_node_targets
4370 .get(&key)
4371 .copied()
4372 .unwrap_or(0)
4373 }
4374
4375 fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
4376 let Some(limit) = self.recycle_pool_limit_for(key) else {
4377 return usize::MAX;
4378 };
4379 if limit <= 8 { limit } else { limit / 4 }
4380 }
4381
4382 fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
4383 let target_limit = self.warm_recycled_node_target_limit(key);
4384 let existing = self.warm_recycled_node_target(key).min(target_limit);
4385 if observed_demand == 0 {
4386 return existing;
4387 }
4388
4389 let target = match self.recycle_pool_limit_for(key) {
4390 Some(limit) if limit > 8 => target_limit,
4391 Some(_) => observed_demand.min(target_limit),
4392 None => observed_demand,
4393 };
4394 self.state.warm_recycled_node_targets.insert(key, target);
4395 target
4396 }
4397
4398 fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
4399 if self.state.recycled_node_prototypes.contains_key(&key) {
4400 return;
4401 }
4402 if let Some(prototype) = shell.rehouse_for_recycle() {
4403 self.state.recycled_node_prototypes.insert(key, prototype);
4404 }
4405 }
4406
4407 fn live_warm_recycled_origin_count(&self) -> usize {
4408 self.state
4409 .physical_warm_recycled_origins
4410 .iter()
4411 .zip(self.state.nodes.iter())
4412 .filter(|(warm_origin, node)| **warm_origin && node.is_some())
4413 .count()
4414 + self
4415 .state
4416 .high_id_warm_recycled_origins
4417 .values()
4418 .filter(|warm_origin| **warm_origin)
4419 .count()
4420 }
4421
4422 fn live_warm_recycled_origin_capacity(&self) -> usize {
4423 self.state.physical_warm_recycled_origins.capacity()
4424 + self.state.high_id_warm_recycled_origins.capacity()
4425 }
4426
4427 fn push_recycled_node(
4428 &mut self,
4429 key: TypeId,
4430 recycle_pool_limit: Option<usize>,
4431 recycled: RecycledNode,
4432 ) {
4433 self.remember_recycle_pool_limit(key, recycle_pool_limit);
4434 self.remember_recycled_node_prototype(key, recycled.node.as_ref());
4435
4436 let warm_origin = recycled.warm_origin();
4437 let pool = if warm_origin {
4438 self.state.returning_recycled_nodes.entry(key).or_default()
4439 } else {
4440 self.state.cold_recycled_nodes.entry(key).or_default()
4441 };
4442 pool.push(recycled);
4443 if let Some(limit) = recycle_pool_limit
4444 && pool.len() > limit
4445 {
4446 let excess = pool.len() - limit;
4447 let dropped: Vec<_> = pool.drain(0..excess).collect();
4448 drop(dropped);
4449 }
4450 }
4451
4452 fn push_warm_recycled_node(
4453 &mut self,
4454 key: TypeId,
4455 recycle_pool_limit: Option<usize>,
4456 mut recycled: RecycledNode,
4457 ) {
4458 self.remember_recycle_pool_limit(key, recycle_pool_limit);
4459
4460 recycled.set_warm_origin(true);
4461 let mut dropped = Vec::new();
4462 let mut remove_pool_entry = false;
4463 {
4464 let pool = self.state.recycled_nodes.entry(key).or_default();
4465 pool.push(recycled);
4466 if let Some(limit) = recycle_pool_limit
4467 && pool.len() > limit
4468 {
4469 let excess = pool.len() - limit;
4470 dropped = pool.drain(0..excess).collect();
4471 remove_pool_entry = pool.is_empty();
4472 }
4473 }
4474 if remove_pool_entry {
4475 self.state.recycled_nodes.remove(&key);
4476 }
4477 drop(dropped);
4478 }
4479
4480 fn seed_recycled_node_shell_impl(
4481 &mut self,
4482 key: TypeId,
4483 recycle_pool_limit: Option<usize>,
4484 shell: Box<dyn Node>,
4485 ) {
4486 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
4487 if self.warm_recycled_pool_len(key) >= limit {
4488 return;
4489 }
4490
4491 self.remember_recycled_node_prototype(key, shell.as_ref());
4492 let stable_id = self.state.next_stable_id;
4493 self.state.next_stable_id = self.state.next_stable_id.saturating_add(1);
4494 self.push_warm_recycled_node(
4495 key,
4496 recycle_pool_limit,
4497 RecycledNode::from_shell(stable_id, shell, true),
4498 );
4499 }
4500
4501 fn take_recycled_node_from_pool(
4502 pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
4503 key: TypeId,
4504 ) -> Option<RecycledNode> {
4505 let pool = pools.get_mut(&key)?;
4506 let node = pool.pop();
4507 if pool.is_empty() {
4508 pools.remove(&key);
4509 }
4510 node
4511 }
4512
4513 fn compact_idle_warm_pool(&mut self, key: TypeId) {
4514 let Some(pool) = self.state.recycled_nodes.get_mut(&key) else {
4515 return;
4516 };
4517 if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
4518 return;
4519 }
4520
4521 let retained = pool.len();
4522 let mut compacted = Vec::with_capacity(retained);
4523 compacted.append(pool);
4524 let remove_pool_entry = compacted.is_empty();
4525 *pool = compacted;
4526 let _ = pool;
4527
4528 if remove_pool_entry {
4529 self.state.recycled_nodes.remove(&key);
4530 }
4531 }
4532
4533 fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4534 let pool_len = self.warm_recycled_pool_len(key);
4535 if pool_len <= target {
4536 return;
4537 }
4538
4539 let Some(pool) = self.state.recycled_nodes.get_mut(&key) else {
4540 return;
4541 };
4542 let removable = (pool_len - target).min(pool.len());
4543 let dropped: Vec<_> = pool.drain(0..removable).collect();
4544 let remove_pool_entry = pool.is_empty();
4545 let _ = pool;
4546
4547 if remove_pool_entry {
4548 self.state.recycled_nodes.remove(&key);
4549 }
4550 drop(dropped);
4551 }
4552
4553 fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4554 let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
4555 if missing == 0 {
4556 return;
4557 }
4558
4559 let recycle_pool_limit = self.recycle_pool_limit_for(key);
4560 let mut shells = Vec::with_capacity(missing);
4561 if let Some(prototype) = self.state.recycled_node_prototypes.get(&key) {
4562 for _ in 0..missing {
4563 let Some(shell) = prototype.rehouse_for_recycle() else {
4564 break;
4565 };
4566 shells.push(shell);
4567 }
4568 }
4569
4570 for shell in shells {
4571 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4572 }
4573 }
4574
4575 fn prune_stable_generations(&mut self) {
4576 let retained_len = self.state.stable_index.live() + self.total_recycled_node_count();
4577 if retained_len == self.state.stable_index.occupied() {
4578 return;
4579 }
4580
4581 let recycled: HashSet<NodeId> = [
4582 &self.state.recycled_nodes,
4583 &self.state.returning_recycled_nodes,
4584 &self.state.cold_recycled_nodes,
4585 ]
4586 .into_iter()
4587 .flat_map(|pools| pools.values().flatten().map(RecycledNode::stable_id))
4588 .collect();
4589 self.state
4590 .stable_index
4591 .retain_retired(|stable_id| recycled.contains(&stable_id));
4592 }
4593
4594 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
4597 let mut output = String::new();
4598 if let Some(root_id) = root {
4599 self.dump_node(&mut output, root_id, 0);
4600 } else {
4601 output.push_str("(no root)\n");
4602 }
4603 output
4604 }
4605
4606 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
4607 let indent = " ".repeat(depth);
4608 let Ok(node) = self.get_ref(id) else {
4609 if let Some(physical_id) = self.resolve_node_index(id) {
4610 output.push_str(&format!(
4611 "{indent}[{id}] (missing physical node {physical_id})\n"
4612 ));
4613 } else {
4614 output.push_str(&format!("{indent}[{id}] (missing)\n"));
4615 }
4616 return;
4617 };
4618 let type_name = std::any::type_name_of_val(node);
4619 output.push_str(&format!("{indent}[{id}] {type_name}\n"));
4620
4621 let mut children = SmallVec::<[NodeId; 8]>::new();
4622 node.collect_children_into(&mut children);
4623 for child_id in children {
4624 self.dump_node(output, child_id, depth + 1);
4625 }
4626 }
4627
4628 pub fn take_layout_dirt_roots(&mut self, out: &mut Vec<NodeId>) -> bool {
4633 self.state.layout_dirt_roots.take_into(out)
4634 }
4635
4636 #[inline]
4637 fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
4638 self.state.stable_index.slot(id)
4639 }
4640
4641 fn contains_node_id(&self, id: NodeId) -> bool {
4642 self.resolve_node_index(id).is_some() || self.state.high_id_nodes.contains_key(&id)
4643 }
4644
4645 fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
4646 self.state.high_id_nodes.insert(stable_id, node);
4647 self.state
4648 .high_id_warm_recycled_origins
4649 .insert(stable_id, warm_origin);
4650 self.state.high_id_generations.entry(stable_id).or_insert(0);
4651 }
4652
4653 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
4654 if stable_id >= Self::HIGH_ID_THRESHOLD {
4655 self.insert_high_id_node(stable_id, node, false);
4656 return;
4657 }
4658
4659 let physical_id = if let Some(Reverse(free_physical_id)) = self.state.free_ids.pop() {
4660 self.state.nodes[free_physical_id] = Some(node);
4661 self.state.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
4662 self.state.physical_warm_recycled_origins[free_physical_id] = false;
4663 free_physical_id
4664 } else {
4665 self.ensure_dense_node_storage_capacity();
4666 let physical_id = self.state.nodes.len();
4667 self.state.nodes.push(Some(node));
4668 self.state
4669 .physical_stable_ids
4670 .push(Self::pack_stable_id(stable_id));
4671 self.state.physical_warm_recycled_origins.push(false);
4672 physical_id
4673 };
4674
4675 self.state.next_stable_id = self.state.next_stable_id.max(stable_id.saturating_add(1));
4676 self.state.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
4677 self.state.stable_index.set_slot(stable_id, physical_id);
4678 }
4679
4680 #[inline]
4683 pub fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4684 match self.resolve_node_index(id) {
4685 Some(physical_id) => self
4686 .state
4687 .nodes
4688 .get(physical_id)
4689 .and_then(Option::as_deref)
4690 .ok_or(NodeError::Missing { id }),
4691 None => self.high_id_node(id),
4692 }
4693 }
4694
4695 fn high_id_node(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4696 self.state
4697 .high_id_nodes
4698 .get(&id)
4699 .map(AsRef::as_ref)
4700 .ok_or(NodeError::Missing { id })
4701 }
4702
4703 fn high_id_node_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
4704 self.state
4705 .high_id_nodes
4706 .get_mut(&id)
4707 .map(std::convert::AsMut::as_mut)
4708 .ok_or(NodeError::Missing { id })
4709 }
4710
4711 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
4712 Ok(self.get_ref(id)?.parent())
4713 }
4714
4715 fn collect_owned_children(
4716 &self,
4717 node_id: NodeId,
4718 out: &mut SmallVec<[NodeId; 8]>,
4719 ) -> Result<(), NodeError> {
4720 self.get_ref(node_id)?.collect_owned_children_into(out);
4721 out.retain(|child_id| {
4722 self.node_parent(*child_id)
4723 .is_ok_and(|parent| parent == Some(node_id))
4724 });
4725 Ok(())
4726 }
4727
4728 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
4729 self.state.virtual_node_ids.remove(&node_id);
4730 if self.state.high_id_nodes.contains_key(&node_id) {
4731 if let Some(node) = self.state.high_id_nodes.remove(&node_id)
4732 && let Some(key) = node.recycle_key()
4733 {
4734 let recycle_pool_limit = node.recycle_pool_limit();
4735 let warm_origin = self
4736 .state
4737 .high_id_warm_recycled_origins
4738 .remove(&node_id)
4739 .unwrap_or(false);
4740 self.push_recycled_node(
4741 key,
4742 recycle_pool_limit,
4743 RecycledNode::new(node_id, node, warm_origin),
4744 );
4745 }
4746 let generation = self.state.high_id_generations.entry(node_id).or_insert(0);
4747 *generation = generation.wrapping_add(1);
4748 return Ok(());
4749 }
4750
4751 let physical_id = self
4752 .resolve_node_index(node_id)
4753 .ok_or(NodeError::Missing { id: node_id })?;
4754 if let Some(node) = self.state.nodes[physical_id].take()
4755 && let Some(key) = node.recycle_key()
4756 {
4757 let recycle_pool_limit = node.recycle_pool_limit();
4758 let warm_origin = self
4759 .state
4760 .physical_warm_recycled_origins
4761 .get_mut(physical_id)
4762 .is_some_and(std::mem::take);
4763 self.push_recycled_node(
4764 key,
4765 recycle_pool_limit,
4766 RecycledNode::new(node_id, node, warm_origin),
4767 );
4768 }
4769 self.state.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
4770 self.state.stable_index.release(node_id);
4771 self.state.free_ids.push(Reverse(physical_id));
4772 Ok(())
4773 }
4774
4775 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
4776 self.get_ref(id)?;
4777
4778 let mut root_children = SmallVec::<[NodeId; 8]>::new();
4779 self.collect_owned_children(id, &mut root_children)?;
4780
4781 let mut stack = Vec::new();
4782 stack.push(RemovalFrame {
4783 node_id: id,
4784 children: root_children,
4785 next_child: 0,
4786 });
4787 let mut max_depth = stack.len();
4788
4789 while let Some(frame) = stack.last_mut() {
4790 if frame.next_child < frame.children.len() {
4791 let child_id = frame.children[frame.next_child];
4792 frame.next_child += 1;
4793
4794 if let Ok(child) = self.get_mut(child_id) {
4795 child.on_removed_from_parent();
4796 child.unmount();
4797 }
4798
4799 let mut child_children = SmallVec::<[NodeId; 8]>::new();
4800 self.collect_owned_children(child_id, &mut child_children)?;
4801 stack.push(RemovalFrame {
4802 node_id: child_id,
4803 children: child_children,
4804 next_child: 0,
4805 });
4806 max_depth = max_depth.max(stack.len());
4807 continue;
4808 }
4809
4810 let node_id = frame.node_id;
4811 stack.pop();
4812 self.remove_node_storage(node_id)?;
4813 }
4814
4815 Ok(max_depth)
4816 }
4817
4818 #[cfg(test)]
4819 fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
4820 self.remove_subtree_postorder(id)
4821 }
4822}
4823
4824impl Applier for MemoryApplier {
4825 fn record_structural_change(&mut self, parent_id: NodeId) {
4826 if self.state.structural_change_parents.last() != Some(&parent_id) {
4827 self.state.structural_change_parents.push(parent_id);
4828 }
4829 }
4830
4831 fn create(&mut self, node: Box<dyn Node>) -> NodeId {
4832 let stable_id = self.state.next_stable_id;
4833 self.state.next_stable_id = self.state.next_stable_id.saturating_add(1);
4834 if stable_id >= Self::HIGH_ID_THRESHOLD {
4835 self.insert_high_id_node(stable_id, node, false);
4836 return stable_id;
4837 }
4838
4839 let physical_id = if let Some(Reverse(id)) = self.state.free_ids.pop() {
4840 debug_assert!(
4841 self.state.nodes[id].is_none(),
4842 "freelist entry {id} is not None"
4843 );
4844 self.state.nodes[id] = Some(node);
4845 self.state.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
4846 self.state.physical_warm_recycled_origins[id] = false;
4847 id
4848 } else {
4849 self.ensure_dense_node_storage_capacity();
4850 let id = self.state.nodes.len();
4851 self.state.nodes.push(Some(node));
4852 self.state
4853 .physical_stable_ids
4854 .push(Self::pack_stable_id(stable_id));
4855 self.state.physical_warm_recycled_origins.push(false);
4856 id
4857 };
4858 self.state.stable_index.insert_fresh(stable_id, physical_id);
4859 stable_id
4860 }
4861
4862 fn node_generation(&self, id: NodeId) -> u32 {
4863 if id >= Self::HIGH_ID_THRESHOLD {
4864 return self
4865 .state
4866 .high_id_generations
4867 .get(&id)
4868 .copied()
4869 .unwrap_or(0);
4870 }
4871 self.state.stable_index.generation(id)
4872 }
4873
4874 #[inline]
4875 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
4876 match self.resolve_node_index(id) {
4877 Some(physical_id) => self
4878 .state
4879 .nodes
4880 .get_mut(physical_id)
4881 .and_then(Option::as_deref_mut)
4882 .ok_or(NodeError::Missing { id }),
4883 None => self.high_id_node_mut(id),
4884 }
4885 }
4886
4887 fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
4888 self.state.bubble_passes.forget();
4889 self.remove_subtree_postorder(id).map(|_| ())
4890 }
4891
4892 fn begin_bubble_batch(&mut self) {
4893 self.state.bubble_passes.begin_batch();
4894 }
4895
4896 fn note_layout_dirt(&mut self, id: NodeId) {
4897 self.state.layout_dirt_roots.note(id);
4898 }
4899
4900 fn end_bubble_batch(&mut self) {
4901 self.state.bubble_passes.end_batch();
4902 }
4903
4904 fn forget_bubble_passes(&mut self) {
4905 self.state.bubble_passes.forget();
4906 }
4907
4908 fn note_bubble_pass(&mut self, id: NodeId, flags: u8) -> bool {
4909 self.resolve_node_index(id)
4910 .is_some_and(|index| self.state.bubble_passes.note(index, flags))
4911 }
4912
4913 fn bubble_node(&mut self, id: NodeId, flags: u8, note: bool) -> Option<&mut dyn Node> {
4915 let Some(index) = self.resolve_node_index(id) else {
4916 return self
4917 .state
4918 .high_id_nodes
4919 .get_mut(&id)
4920 .map(std::convert::AsMut::as_mut);
4921 };
4922 if note && self.state.bubble_passes.note(index, flags) {
4923 return None;
4924 }
4925 self.state.nodes.get_mut(index)?.as_deref_mut()
4926 }
4927
4928 fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4929 MemoryApplier::get_ref(self, id)
4930 }
4931
4932 fn create_virtual(&mut self, node: Box<dyn Node>) -> NodeId {
4933 let id = self.create(node);
4934 self.state.virtual_node_ids.insert(id);
4935 id
4936 }
4937
4938 fn insert_recycled_node_or_create(
4939 &mut self,
4940 stable_id: NodeId,
4941 node: Box<dyn Node>,
4942 ) -> RecycledNodeInsertion {
4943 if self.contains_node_id(stable_id) {
4944 let id = self.create(node);
4945 return RecycledNodeInsertion::fresh(
4946 id,
4947 Some(NodeError::AlreadyExists { id: stable_id }),
4948 );
4949 }
4950
4951 self.insert_available_with_id(stable_id, node);
4952 RecycledNodeInsertion::reused(stable_id)
4953 }
4954
4955 fn compact(&mut self) {
4956 let live_count = self
4957 .state
4958 .nodes
4959 .iter()
4960 .filter(|slot| slot.is_some())
4961 .count();
4962 let tombstone_count = self.state.nodes.len().saturating_sub(live_count);
4963 if tombstone_count == 0 {
4964 return;
4965 }
4966 if self.state.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
4967 return;
4968 }
4969 let rehouse_live_nodes = tombstone_count >= live_count;
4970 let mut packed_nodes = Vec::with_capacity(live_count);
4971 let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
4972 let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
4973
4974 for physical_id in 0..self.state.nodes.len() {
4975 let Some(mut node) = self.state.nodes[physical_id].take() else {
4976 continue;
4977 };
4978 if rehouse_live_nodes && let Some(rehoused) = node.rehouse_for_live_compaction() {
4979 node = rehoused;
4980 }
4981 let stable_id = std::mem::replace(
4982 &mut self.state.physical_stable_ids[physical_id],
4983 Self::INVALID_STABLE_ID,
4984 );
4985 debug_assert_ne!(
4986 stable_id,
4987 Self::INVALID_STABLE_ID,
4988 "live physical slot must have a stable id",
4989 );
4990 let stable_id = Self::unpack_stable_id(stable_id);
4991 packed_nodes.push(Some(node));
4992 packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
4993 packed_warm_recycled_origins
4994 .push(self.state.physical_warm_recycled_origins[physical_id]);
4995 self.state
4996 .stable_index
4997 .set_slot(stable_id, packed_nodes.len() - 1);
4998 }
4999
5000 self.state.nodes = packed_nodes;
5001 self.state.physical_stable_ids = packed_physical_stable_ids;
5002 self.state.physical_warm_recycled_origins = packed_warm_recycled_origins;
5003 self.state.free_ids = BinaryHeap::new();
5004 debug_assert_eq!(
5005 self.state.stable_index.live(),
5006 live_count,
5007 "every live node keeps one indexed slot through compaction",
5008 );
5009 self.prune_stable_generations();
5010 }
5011
5012 fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
5013 Self::take_recycled_node_from_pool(&mut self.state.returning_recycled_nodes, key)
5014 .or_else(|| Self::take_recycled_node_from_pool(&mut self.state.recycled_nodes, key))
5015 }
5016
5017 fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
5018 if let Some(physical_id) = self.resolve_node_index(id) {
5019 self.state.physical_warm_recycled_origins[physical_id] = warm_origin;
5020 } else if self.state.high_id_nodes.contains_key(&id) {
5021 self.state
5022 .high_id_warm_recycled_origins
5023 .insert(id, warm_origin);
5024 }
5025 }
5026
5027 fn seed_recycled_node_shell(
5028 &mut self,
5029 key: TypeId,
5030 recycle_pool_limit: Option<usize>,
5031 shell: Box<dyn Node>,
5032 ) {
5033 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
5034 }
5035
5036 fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
5037 *self
5038 .state
5039 .fresh_recyclable_creations
5040 .entry(key)
5041 .or_insert(0) += 1;
5042 }
5043
5044 fn clear_recycled_nodes(&mut self) {
5045 let returning = std::mem::take(&mut self.state.returning_recycled_nodes);
5046 for (key, mut nodes) in returning {
5047 let pool = self.state.recycled_nodes.entry(key).or_default();
5048 pool.append(&mut nodes);
5049 }
5050
5051 let fresh_recyclable_creations = std::mem::take(&mut self.state.fresh_recyclable_creations);
5052 let cold = std::mem::take(&mut self.state.cold_recycled_nodes);
5053 for (key, mut nodes) in cold {
5054 let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
5055 if needed > 0 {
5056 let remaining_limit = self
5057 .recycle_pool_limit_for(key)
5058 .unwrap_or(usize::MAX)
5059 .saturating_sub(self.warm_recycled_pool_len(key));
5060 let promote = nodes.len().min(needed).min(remaining_limit);
5061 let split_at = nodes.len().saturating_sub(promote);
5062 let promoted = nodes.split_off(split_at);
5063 for mut recycled in promoted {
5064 recycled.set_warm_origin(true);
5065 self.state
5066 .recycled_nodes
5067 .entry(key)
5068 .or_default()
5069 .push(recycled);
5070 }
5071 }
5072 }
5073
5074 let mut keys: HashSet<TypeId> = HashSet::default();
5075 keys.extend(self.state.recycled_nodes.keys().copied());
5076 keys.extend(self.state.recycled_node_limits.keys().copied());
5077 keys.extend(self.state.warm_recycled_node_targets.keys().copied());
5078 keys.extend(self.state.recycled_node_prototypes.keys().copied());
5079 for key in keys {
5080 let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
5081 let target = self.update_warm_recycled_node_target(key, observed_demand);
5082 self.replenish_warm_pool_to_target(key, target);
5083 self.trim_idle_warm_pool_to_target(key, target);
5084 self.compact_idle_warm_pool(key);
5085 }
5086 self.prune_stable_generations();
5087 self.compact();
5088 }
5089}
5090
5091pub struct SlotsHost {
5094 storage_key: Cell<usize>,
5095 inner: RefCell<SlotsHostInner>,
5096}
5097
5098#[derive(Debug, Default)]
5099pub(crate) struct SlotPassOutcome {
5100 pub(crate) compacted: bool,
5101 pub(crate) compact_anchor_registry_storage: bool,
5102 pub(crate) compact_payload_storage: bool,
5103 pub(crate) trim_growth_slack: bool,
5104}
5105
5106#[derive(Default)]
5107pub(crate) struct FinishedSlotPass {
5108 pub(crate) outcome: SlotPassOutcome,
5109 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
5110}
5111
5112struct SlotsHostInner {
5113 table: SlotTable,
5114 nested_hosts: Vec<std::rc::Weak<SlotsHost>>,
5115 lifecycle: slot::SlotLifecycleCoordinator,
5116 runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
5117 active_pass: Option<usize>,
5120 pass_state: Option<slot::SlotWriteSessionState>,
5124}
5125
5126impl Drop for SlotsHost {
5127 fn drop(&mut self) {
5128 let storage_key = self.storage_key.get();
5129 let inner = self.inner.get_mut();
5130 if let Some(state) = inner.runtime_state.clone() {
5131 if let Err(err) = state.dispose_retained_subtrees_for_host(
5132 storage_key,
5133 &mut inner.table,
5134 &mut inner.lifecycle,
5135 ) {
5136 log::error!(
5137 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
5138 );
5139 state.abandon_retained_subtrees_for_host(
5140 storage_key,
5141 &mut inner.table,
5142 &mut inner.lifecycle,
5143 );
5144 } else {
5145 state.clear_host_storage_key(storage_key, &mut inner.table);
5146 }
5147 }
5148 inner.lifecycle.dispose_slot_table(&mut inner.table);
5149 }
5150}
5151
5152impl SlotsHost {
5153 pub fn storage_key(&self) -> usize {
5156 self.storage_key.get()
5157 }
5158
5159 pub fn new(storage: SlotTable) -> Self {
5161 let storage_key = storage.storage_id();
5162 Self {
5163 storage_key: Cell::new(storage_key),
5164 inner: RefCell::new(SlotsHostInner {
5165 table: storage,
5166 nested_hosts: Vec::new(),
5167 lifecycle: slot::SlotLifecycleCoordinator::default(),
5168 runtime_state: None,
5169 active_pass: None,
5170 pass_state: None,
5171 }),
5172 }
5173 }
5174
5175 pub fn note_nested_host(&self, nested: &Rc<SlotsHost>) {
5178 let Ok(mut inner) = self.inner.try_borrow_mut() else {
5179 return;
5180 };
5181 inner.nested_hosts.retain(|held| held.upgrade().is_some());
5182 if inner
5183 .nested_hosts
5184 .iter()
5185 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
5186 {
5187 return;
5188 }
5189 inner.nested_hosts.push(Rc::downgrade(nested));
5190 }
5191
5192 pub(crate) fn forget_effects(&self) -> bool {
5193 let (forgotten, nested, runtime_state) = {
5194 let Ok(mut inner) = self.inner.try_borrow_mut() else {
5195 return false;
5196 };
5197 if inner.active_pass.is_some() {
5198 return false;
5199 }
5200 let drops = inner.table.take_effect_drops();
5201 inner.nested_hosts.retain(|held| held.upgrade().is_some());
5202 let nested: Vec<Rc<SlotsHost>> = inner
5203 .nested_hosts
5204 .iter()
5205 .filter_map(std::rc::Weak::upgrade)
5206 .collect();
5207 (drops, nested, inner.runtime_state.clone())
5208 };
5209 let mut any = !forgotten.is_empty();
5210 drop(forgotten);
5211 for host in nested {
5212 any |= host.forget_effects();
5213 }
5214 if any {
5215 self.borrow()
5216 .scopes()
5217 .for_each(RecomposeScope::force_recompose);
5218 if let Some(runtime_state) = runtime_state {
5219 runtime_state.force_recompose_retained_scopes(self.storage_key());
5220 }
5221 }
5222 any
5223 }
5224
5225 pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
5226 let mut inner = self.inner.borrow_mut();
5227 inner.runtime_state = Some(Rc::clone(state));
5228 }
5229
5230 pub(crate) fn rebind_orphaned_runtime_state(
5231 &self,
5232 state: &Rc<crate::composer::ComposerRuntimeState>,
5233 ) -> bool {
5234 let inner = self.inner.borrow();
5235 if inner.active_pass.is_some() {
5236 log::error!("cannot rebind SlotsHost during an active pass");
5237 return false;
5238 }
5239 let Some(bound_state) = inner.runtime_state.as_ref() else {
5240 drop(inner);
5241 self.bind_runtime_state(state);
5242 return true;
5243 };
5244 if Rc::ptr_eq(bound_state, state) {
5245 return true;
5246 }
5247 if bound_state.has_live_applier_host() {
5248 return false;
5249 }
5250 drop(inner);
5251
5252 let mut inner = self.inner.borrow_mut();
5253 let Some(bound_state) = inner.runtime_state.as_ref() else {
5254 inner.runtime_state = Some(Rc::clone(state));
5255 return true;
5256 };
5257 if Rc::ptr_eq(bound_state, state) {
5258 return true;
5259 }
5260 if bound_state.has_live_applier_host() {
5261 return false;
5262 }
5263
5264 let previous_state = Rc::clone(bound_state);
5265 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5266 lifecycle.flush_pending_drops();
5267 let host_key = self.storage_key();
5268 if previous_state
5269 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5270 .is_err()
5271 {
5272 inner.lifecycle = lifecycle;
5273 return false;
5274 }
5275 previous_state.clear_host(self, &mut inner.table);
5276 lifecycle.flush_pending_drops();
5277 inner.runtime_state = Some(Rc::clone(state));
5278 inner.lifecycle = lifecycle;
5279 true
5280 }
5281
5282 pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
5283 self.inner.borrow().runtime_state.clone()
5284 }
5285
5286 fn runtime_state_ptr(&self) -> Option<*const crate::composer::ComposerRuntimeState> {
5287 self.inner.borrow().runtime_state.as_ref().map(Rc::as_ptr)
5288 }
5289
5290 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
5291 Ref::map(self.inner.borrow(), |inner| &inner.table)
5292 }
5293
5294 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
5295 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
5296 }
5297
5298 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
5301 if Rc::strong_count(&self) != 1 {
5302 return Err(NodeError::SlotHostUnavailable {
5303 operation: "SlotsHost::into_table",
5304 reason: "other host references are alive",
5305 });
5306 }
5307 self.take_table_for_transfer()
5308 }
5309
5310 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
5311 let inner = self.inner.borrow();
5312 if inner.active_pass.is_some() {
5313 return Err(NodeError::SlotHostUnavailable {
5314 operation: "SlotsHost::into_table",
5315 reason: "slot pass is active",
5316 });
5317 }
5318 drop(inner);
5319 let mut inner = self.inner.borrow_mut();
5320 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5321 lifecycle.flush_pending_drops();
5322 if let Some(state) = inner.runtime_state.clone() {
5323 let host_key = self.storage_key();
5324 if let Err(error) =
5325 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5326 {
5327 inner.lifecycle = lifecycle;
5328 return Err(error);
5329 }
5330 state.clear_host(self, &mut inner.table);
5331 lifecycle.flush_pending_drops();
5332 }
5333 let taken = std::mem::take(&mut inner.table);
5334 self.storage_key.set(inner.table.storage_id());
5335 inner.runtime_state = None;
5336 inner.lifecycle = lifecycle;
5337 Ok(taken)
5338 }
5339
5340 pub fn reset(&self) -> Result<(), NodeError> {
5343 let inner = self.inner.borrow();
5344 if inner.active_pass.is_some() {
5345 return Err(NodeError::SlotHostUnavailable {
5346 operation: "SlotsHost::reset",
5347 reason: "slot pass is active",
5348 });
5349 }
5350 let runtime_state = inner.runtime_state.clone();
5351 drop(inner);
5352 let mut inner = self.inner.borrow_mut();
5353 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5354 if let Some(state) = runtime_state {
5355 let host_key = self.storage_key();
5356 if let Err(error) =
5357 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
5358 {
5359 inner.lifecycle = lifecycle;
5360 return Err(error);
5361 }
5362 state.clear_host(self, &mut inner.table);
5363 }
5364 lifecycle.dispose_slot_table(&mut inner.table);
5365 inner.table = SlotTable::default();
5366 self.storage_key.set(inner.table.storage_id());
5367 inner.runtime_state = None;
5368 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
5369 Ok(())
5370 }
5371
5372 pub(crate) fn abandon_after_apply_failure(&self) {
5373 let inner = self.inner.borrow();
5374 if inner.active_pass.is_some() {
5375 log::error!("cannot abandon SlotsHost during an active pass");
5376 return;
5377 }
5378 let runtime_state = inner.runtime_state.clone();
5379 drop(inner);
5380 let mut inner = self.inner.borrow_mut();
5381 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
5382 if let Some(state) = runtime_state {
5383 let host_key = self.storage_key();
5384 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
5385 }
5386 lifecycle.dispose_slot_table(&mut inner.table);
5387 inner.table = SlotTable::default();
5388 self.storage_key.set(inner.table.storage_id());
5389 inner.runtime_state = None;
5390 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
5391 }
5392
5393 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
5394 let inner = self.inner.borrow();
5395 let local = inner.table.debug_stats();
5396 let lifecycle = inner.lifecycle.debug_stats();
5397 let retention = inner
5398 .runtime_state
5399 .clone()
5400 .map(|state| state.slot_retention_debug_stats(self))
5401 .unwrap_or_default();
5402 SlotTableDebugStats::from_parts(local, lifecycle, retention)
5403 }
5404
5405 #[cold]
5406 #[inline(never)]
5407 pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
5408 let inner = self.inner.borrow();
5409 let mut snapshot = inner.table.debug_snapshot();
5410 if let Some(state) = inner.runtime_state.clone() {
5411 state.fill_slot_debug_snapshot(self, &mut snapshot);
5412 }
5413 snapshot
5414 }
5415
5416 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode, folds: &Rc<slot::BranchFolds>) {
5417 let mut inner = self.inner.borrow_mut();
5418 let SlotsHostInner {
5419 table,
5420 active_pass,
5421 pass_state,
5422 ..
5423 } = &mut *inner;
5424 if active_pass.is_some() {
5425 log::error!("slot pass already active for host");
5426 return;
5427 }
5428 let state = match pass_state {
5429 Some(state) => {
5430 state.set_folds(Rc::clone(folds));
5431 state
5432 }
5433 None => pass_state.insert(slot::SlotWriteSessionState::new(Rc::clone(folds))),
5434 };
5435 state.reset_for_pass(mode);
5436 *active_pass = Some(table.storage_capacity());
5437 }
5438
5439 pub(crate) fn has_active_pass(&self) -> bool {
5440 self.inner.borrow().active_pass.is_some()
5441 }
5442
5443 pub(crate) fn abandon_active_pass(&self) {
5444 self.inner.borrow_mut().active_pass = None;
5445 }
5446
5447 pub(crate) fn with_write_session<R>(
5448 &self,
5449 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
5450 ) -> R {
5451 let mut inner = self.inner.borrow_mut();
5452 let SlotsHostInner {
5453 table,
5454 lifecycle,
5455 active_pass,
5456 pass_state,
5457 ..
5458 } = &mut *inner;
5459 let state = pass_state
5460 .as_mut()
5461 .filter(|_| active_pass.is_some())
5462 .expect("slot write session requires an active pass");
5463 let mut session = table.write_session(lifecycle, state);
5464 f(&mut session)
5465 }
5466
5467 pub(crate) fn with_table_and_lifecycle_mut<R>(
5468 &self,
5469 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
5470 ) -> R {
5471 let mut inner = self.inner.borrow_mut();
5472 let SlotsHostInner {
5473 table, lifecycle, ..
5474 } = &mut *inner;
5475 f(table, lifecycle)
5476 }
5477
5478 pub(crate) fn finish_pass(
5479 &self,
5480 applier: &mut dyn Applier,
5481 ) -> Result<FinishedSlotPass, NodeError> {
5482 let mut inner = self.inner.borrow_mut();
5483 let SlotsHostInner {
5484 table,
5485 lifecycle,
5486 active_pass,
5487 pass_state,
5488 ..
5489 } = &mut *inner;
5490 let (Some(storage_capacity), Some(state)) = (active_pass.take(), pass_state.as_mut())
5491 else {
5492 return Ok(FinishedSlotPass::default());
5493 };
5494
5495 state.flush_payload_location_refreshes(table);
5496
5497 #[cfg(any(test, debug_assertions))]
5498 if let Err(err) = state.validate(table) {
5499 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
5500 return Err(NodeError::SlotHostUnavailable {
5501 operation: "SlotsHost::finish_pass",
5502 reason: "slot writer invariant violation",
5503 });
5504 }
5505
5506 let detached_root_children = {
5507 let mut session = table.write_session(lifecycle, state);
5508 session.finalize_pass(applier)?
5509 };
5510
5511 let outcome = SlotPassOutcome {
5512 compacted: state.request_compaction,
5513 compact_anchor_registry_storage: state.request_anchor_storage_compaction,
5514 compact_payload_storage: state.request_payload_storage_compaction,
5515 trim_growth_slack: state.removed_nothing()
5516 && table.storage_capacity() > storage_capacity,
5517 };
5518 Ok(FinishedSlotPass {
5519 outcome,
5520 detached_root_children,
5521 })
5522 }
5523
5524 pub(crate) fn flush_pending_drops(&self) {
5525 self.inner.borrow_mut().lifecycle.flush_pending_drops();
5526 }
5527
5528 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
5529 let mut inner = self.inner.borrow_mut();
5530 let SlotsHostInner {
5531 table,
5532 lifecycle,
5533 runtime_state,
5534 ..
5535 } = &mut *inner;
5536 lifecycle.flush_pending_drops();
5537 if outcome.compacted {
5538 table.compact_storage();
5539 lifecycle.compact_storage();
5540 } else if outcome.trim_growth_slack {
5541 table.trim_growth_slack();
5542 }
5543 if let Some(state) = runtime_state.clone() {
5544 state.compact_table_identity_storage_for_host(
5545 self,
5546 table,
5547 outcome.compact_anchor_registry_storage,
5548 outcome.compact_payload_storage,
5549 );
5550 } else {
5551 if outcome.compact_anchor_registry_storage {
5552 table.compact_anchor_registry_storage(None);
5553 }
5554 if outcome.compact_payload_storage {
5555 table.compact_payload_anchor_registry_storage(None);
5556 }
5557 }
5558 table.assert_fast_integrity("slot pass cleanup");
5559 #[cfg(any(test, debug_assertions))]
5560 {
5561 table.debug_verify();
5562 if let Some(state) = runtime_state.clone() {
5563 state.debug_verify_host(self, table);
5564 }
5565 }
5566 }
5567}
5568
5569fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
5570 let mut positions = HashMap::default();
5571 positions.reserve(children.len());
5572 for (index, &child) in children.iter().enumerate() {
5573 positions.insert(child, index);
5574 }
5575 positions
5576}
5577
5578fn refresh_child_positions(
5579 current: &[NodeId],
5580 positions: &mut HashMap<NodeId, usize>,
5581 start: usize,
5582 end: usize,
5583) {
5584 if current.is_empty() || start >= current.len() {
5585 return;
5586 }
5587 let end = end.min(current.len() - 1);
5588 for (offset, &child) in current[start..=end].iter().enumerate() {
5589 positions.insert(child, start + offset);
5590 }
5591}
5592
5593fn insert_child_into_diff_state(
5594 current: &mut ChildList,
5595 positions: &mut HashMap<NodeId, usize>,
5596 index: usize,
5597 child: NodeId,
5598) {
5599 let index = index.min(current.len());
5600 current.insert(index, child);
5601 refresh_child_positions(current, positions, index, current.len() - 1);
5602}
5603
5604fn move_child_in_diff_state(
5605 current: &mut ChildList,
5606 positions: &mut HashMap<NodeId, usize>,
5607 from_index: usize,
5608 target_index: usize,
5609) -> usize {
5610 let child = current.remove(from_index);
5611 let to_index = target_index.min(current.len());
5612 current.insert(to_index, child);
5613 refresh_child_positions(
5614 current,
5615 positions,
5616 from_index.min(to_index),
5617 from_index.max(to_index),
5618 );
5619 to_index
5620}
5621
5622pub(crate) use state::MutableStateInner;
5623pub use state::{
5624 MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
5625 StateSubscriptionHold,
5626};
5627
5628fn hash_key<K: Hash>(key: &K) -> Key {
5629 let mut hasher = hash::default::new();
5630 key.hash(&mut hasher);
5631 hasher.finish()
5632}
5633
5634pub(crate) fn explicit_group_key_seed<K: Hash>(
5635 key: &K,
5636 caller: &'static std::panic::Location<'static>,
5637) -> slot::GroupKeySeed {
5638 let source_key = location_key(caller.file(), caller.line(), caller.column());
5639 let explicit_key = hash_key(key);
5640 slot::GroupKeySeed::keyed(source_key, explicit_key)
5641}
5642
5643#[cfg(test)]
5644#[path = "tests/mod.rs"]
5645mod tests;
5646
5647#[cfg(test)]
5648#[path = "tests/recursive_decrease_increase_test.rs"]
5649mod recursive_decrease_increase_test;
5650
5651pub mod collections;
5652pub mod hash;
5654
5655#[cfg(any(test, feature = "test-helpers"))]
5658pub mod test_scratch;
5659#[cfg(any(test, feature = "test-helpers"))]
5660pub use test_scratch::test_scratch_dir;
5661
5662pub(crate) fn note_structural(reason: &str, parent_id: NodeId, child_id: NodeId) {
5663 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5664 eprintln!("[structural] {reason} parent={parent_id} child={child_id}");
5665 }
5666}
5667
5668pub(crate) fn note_structural_move(parent_id: NodeId, from_index: usize, to_index: usize) {
5669 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5670 eprintln!("[structural] move parent={parent_id} from={from_index} to={to_index}");
5671 }
5672}