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