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