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
678struct RecomposeCallback {
682 body: Box<dyn FnMut(&Composer) + 'static>,
683 stateless_body: Option<fn() -> TypeId>,
684}
685
686thread_local! {
687 static SCOPE_ACTIVITY_EPOCH: Cell<u64> = const { Cell::new(1) };
688}
689
690fn scope_activity_epoch() -> u64 {
691 SCOPE_ACTIVITY_EPOCH.with(Cell::get)
692}
693
694fn note_scope_activity_change() {
695 let _ = SCOPE_ACTIVITY_EPOCH.try_with(|epoch| epoch.set(epoch.get() + 1));
696}
697
698fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
703 let value = cell.take();
704 let result = read(&value);
705 cell.set(value);
706 result
707}
708
709fn update_cell<T: Default, R>(cell: &Cell<T>, update: impl FnOnce(&mut T) -> R) -> R {
711 let mut value = cell.take();
712 let result = update(&mut value);
713 cell.set(value);
714 result
715}
716
717type ScopeLink = Cell<Option<Weak<RecomposeScopeInner>>>;
718
719fn link_scope(link: &ScopeLink, target: Option<&RecomposeScope>) {
720 update_cell(link, |link| {
721 if link.as_ref().map(Weak::as_ptr) != target.map(|scope| Rc::as_ptr(&scope.inner)) {
722 *link = target.map(RecomposeScope::downgrade);
723 note_scope_activity_change();
724 }
725 });
726}
727
728const NO_PARENT_HINT: NodeId = NodeId::MAX;
731
732pub(crate) struct RecomposeScopeInner {
733 runtime: runtime::ScopeRuntime,
734 invalid: Cell<bool>,
735 enqueued: Cell<bool>,
736 active: Cell<bool>,
737 active_epoch: Cell<u64>,
738 deactivations: Cell<u64>,
739 composed_once: Cell<bool>,
740 pending_recompose: Cell<bool>,
741 force_reuse: Cell<bool>,
742 force_recompose: Cell<bool>,
743 derivation: Cell<bool>,
744 retention_mode: Cell<RetentionMode>,
745 parent_hint: Cell<NodeId>,
747 group_anchor: Cell<AnchorId>,
748 recompose: Cell<Option<RecomposeCallback>>,
749 parent_scope: ScopeLink,
750 lifetime_owner_scope: ScopeLink,
751 local_stack: Cell<LocalStackSnapshot>,
752 #[cfg(feature = "inspection")]
753 source_trace: RefCell<Rc<[source_trace::SourceLocation]>>,
754 #[cfg(all(feature = "inspection", debug_assertions))]
755 recompositions: std::cell::OnceCell<source_trace::RecompositionCounter>,
756 slots_storage_key: Cell<usize>,
757 slots_runtime_state: Cell<Option<std::rc::Weak<crate::composer::ComposerRuntimeState>>>,
758 state_subscriptions: Cell<StateIds>,
759 invalidation_sources: Cell<StateIds>,
760 unknown_invalidation_source: Cell<bool>,
761}
762
763type StateIds = SmallVec<[StateId; 2]>;
764
765enum ScopeOwner {
766 Root,
767 Live(Rc<RecomposeScopeInner>),
768 Dropped,
769}
770
771impl RecomposeScopeInner {
772 fn owner(&self) -> ScopeOwner {
773 let upgrade = |owner: &Option<Weak<RecomposeScopeInner>>| {
774 owner.as_ref().map(|owner| {
775 owner
776 .upgrade()
777 .map_or(ScopeOwner::Dropped, ScopeOwner::Live)
778 })
779 };
780 read_cell(&self.parent_scope, upgrade)
781 .or_else(|| read_cell(&self.lifetime_owner_scope, upgrade))
782 .unwrap_or(ScopeOwner::Root)
783 }
784
785 fn is_effectively_active(&self, epoch: u64) -> bool {
786 if self.active_epoch.get() == epoch {
787 return true;
788 }
789 if !self.active.get() {
790 return false;
791 }
792 let active = match self.owner() {
793 ScopeOwner::Root => true,
794 ScopeOwner::Dropped => false,
795 ScopeOwner::Live(owner) => owner.is_effectively_active(epoch),
796 };
797 if active {
798 self.active_epoch.set(epoch);
799 }
800 active
801 }
802
803 fn new(runtime: runtime::ScopeRuntime) -> Self {
804 runtime.increment_live_recompose_scope_count();
805 Self {
806 runtime,
807 invalid: Cell::new(false),
808 enqueued: Cell::new(false),
809 active: Cell::new(true),
810 active_epoch: Cell::new(0),
811 deactivations: Cell::new(0),
812 composed_once: Cell::new(false),
813 pending_recompose: Cell::new(false),
814 force_reuse: Cell::new(false),
815 force_recompose: Cell::new(false),
816 derivation: Cell::new(false),
817 retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
818 parent_hint: Cell::new(NO_PARENT_HINT),
819 group_anchor: Cell::new(AnchorId::INVALID),
820 recompose: Cell::new(None),
821 parent_scope: Cell::new(None),
822 lifetime_owner_scope: Cell::new(None),
823 local_stack: Cell::new(None),
824 #[cfg(feature = "inspection")]
825 source_trace: RefCell::new(Rc::from([])),
826 #[cfg(all(feature = "inspection", debug_assertions))]
827 recompositions: std::cell::OnceCell::new(),
828 slots_storage_key: Cell::new(0),
829 slots_runtime_state: Cell::new(None),
830 state_subscriptions: Cell::new(StateIds::new()),
831 invalidation_sources: Cell::new(StateIds::new()),
832 unknown_invalidation_source: Cell::new(false),
833 }
834 }
835
836 fn id(&self) -> ScopeId {
837 std::ptr::from_ref(self).addr()
838 }
839}
840
841fn push_unique_state_id(ids: &mut StateIds, state_id: StateId) {
842 if !ids.contains(&state_id) {
843 ids.push(state_id);
844 }
845}
846
847impl Drop for RecomposeScopeInner {
848 fn drop(&mut self) {
849 note_scope_activity_change();
850 let id = self.id();
851 self.runtime.decrement_live_recompose_scope_count();
852 let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
853 for state_id in subscriptions {
854 self.runtime.unregister_state_scope(state_id, id);
855 }
856 #[cfg(debug_assertions)]
857 {
858 let _ = DEBUG_SCOPE_LABELS.try_with(|labels| {
859 labels.borrow_mut().remove(&id);
860 });
861 let _ = DEBUG_SCOPE_INVALIDATION_SOURCES.try_with(|sources| {
862 sources.borrow_mut().remove(&id);
863 });
864 }
865 if self.enqueued.replace(false) {
866 self.runtime.mark_scope_recomposed();
867 }
868 }
869}
870
871#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
872pub struct RecomposeScopeRegistryDebugStats {
873 pub len: usize,
874 pub capacity: usize,
875}
876
877#[derive(Clone)]
878pub struct RecomposeScope {
879 inner: Rc<RecomposeScopeInner>,
880}
881
882impl PartialEq for RecomposeScope {
883 fn eq(&self, other: &Self) -> bool {
884 Rc::ptr_eq(&self.inner, &other.inner)
885 }
886}
887
888impl Eq for RecomposeScope {}
889
890impl Hash for RecomposeScope {
891 fn hash<H: Hasher>(&self, state: &mut H) {
892 self.id().hash(state);
893 }
894}
895
896impl RecomposeScope {
897 pub(crate) fn mark_derivation(&self) {
901 self.inner.derivation.set(true);
902 }
903
904 pub(crate) fn is_derivation(&self) -> bool {
905 self.inner.derivation.get()
906 }
907
908 fn new(runtime: RuntimeHandle) -> Self {
909 Self {
910 inner: Rc::new(RecomposeScopeInner::new(runtime.scope_runtime())),
911 }
912 }
913
914 pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
915 Rc::downgrade(&self.inner)
916 }
917
918 pub(crate) fn upgrade(weak: &Weak<RecomposeScopeInner>) -> Option<Self> {
919 weak.upgrade().map(|inner| Self { inner })
920 }
921
922 pub fn id(&self) -> ScopeId {
923 self.inner.id()
924 }
925
926 pub fn is_invalid(&self) -> bool {
927 self.inner.invalid.get()
928 }
929
930 pub(crate) fn is_enqueued(&self) -> bool {
931 self.inner.enqueued.get()
932 }
933
934 pub fn is_active(&self) -> bool {
935 self.inner.active.get()
936 }
937
938 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
945 let mut total = self.inner.deactivations.get();
946 let mut owner = self.inner.owner();
947 while let ScopeOwner::Live(scope) = owner {
948 total = total.wrapping_add(scope.deactivations.get());
949 owner = scope.owner();
950 }
951 total
952 }
953
954 pub(crate) fn is_effectively_active(&self) -> bool {
955 self.inner.is_effectively_active(scope_activity_epoch())
956 }
957
958 fn record_state_subscription(&self, state_id: StateId) {
959 update_cell(&self.inner.state_subscriptions, |ids| {
960 push_unique_state_id(ids, state_id);
961 });
962 }
963
964 fn record_unknown_invalidation_source(&self) {
965 self.inner.unknown_invalidation_source.set(true);
966 update_cell(&self.inner.invalidation_sources, StateIds::clear);
967 }
968
969 fn record_state_invalidation_source(&self, state_id: StateId) {
970 if !self.inner.unknown_invalidation_source.get() {
971 update_cell(&self.inner.invalidation_sources, |ids| {
972 push_unique_state_id(ids, state_id);
973 });
974 }
975 }
976
977 fn enqueue_invalidation(&self) {
978 self.inner.invalid.set(true);
979 if !self.is_effectively_active() {
980 return;
981 }
982 if !self.inner.enqueued.replace(true) {
983 self.inner.runtime.register_invalid_scope(self.downgrade());
984 }
985 }
986
987 fn invalidate(&self) {
988 self.record_unknown_invalidation_source();
989 self.enqueue_invalidation();
990 }
991
992 pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
993 self.record_state_invalidation_source(state_id);
994 self.enqueue_invalidation();
995 }
996
997 fn mark_recomposed(&self) {
998 self.inner.invalid.set(false);
999 self.inner.force_reuse.set(false);
1000 self.inner.force_recompose.set(false);
1001 self.inner.unknown_invalidation_source.set(false);
1002 update_cell(&self.inner.invalidation_sources, StateIds::clear);
1003 if self.inner.enqueued.replace(false) {
1004 self.inner.runtime.mark_scope_recomposed();
1005 }
1006 let pending = self.inner.pending_recompose.replace(false);
1007 if pending {
1008 if self.inner.active.get() {
1009 self.invalidate();
1010 } else {
1011 self.inner.invalid.set(true);
1012 }
1013 }
1014 }
1015
1016 fn set_boxed_recompose(
1017 &self,
1018 body: Box<dyn FnMut(&Composer) + 'static>,
1019 stateless_body: Option<fn() -> TypeId>,
1020 ) {
1021 #[cfg(feature = "inspection")]
1022 self.inner
1023 .source_trace
1024 .replace(source_trace::current_source_trace());
1025 self.inner.recompose.set(Some(RecomposeCallback {
1026 body,
1027 stateless_body,
1028 }));
1029 }
1030
1031 fn reruns_stateless(&self, body: TypeId) -> bool {
1032 read_cell(&self.inner.recompose, |callback| {
1033 callback
1034 .as_ref()
1035 .and_then(|callback| callback.stateless_body)
1036 .is_some_and(|current_body| current_body() == body)
1037 })
1038 }
1039
1040 fn run_recompose(&self, composer: &Composer) -> bool {
1041 #[cfg(feature = "inspection")]
1042 let _source_context = source_trace::restore_source_trace(&self.inner.source_trace.borrow());
1043 let Some(mut callback) = self.inner.recompose.take() else {
1044 return false;
1045 };
1046 (callback.body)(composer);
1047 update_cell(&self.inner.recompose, |slot| {
1049 if slot.is_none() {
1050 *slot = Some(callback);
1051 }
1052 });
1053 true
1054 }
1055
1056 fn has_recompose_callback(&self) -> bool {
1057 read_cell(&self.inner.recompose, Option::is_some)
1058 }
1059
1060 fn snapshot_locals(&self, stack: &LocalStackSnapshot) {
1061 update_cell(&self.inner.local_stack, |locals| {
1062 let unchanged = match (&*locals, stack) {
1063 (Some(current), Some(stack)) => Rc::ptr_eq(current, stack),
1064 (current, stack) => current.is_none() && stack.is_none(),
1065 };
1066 if !unchanged {
1067 locals.clone_from(stack);
1068 }
1069 });
1070 }
1071
1072 fn local_stack(&self) -> LocalStackSnapshot {
1073 read_cell(&self.inner.local_stack, Clone::clone)
1074 }
1075
1076 fn set_parent_hint(&self, parent: Option<NodeId>) {
1077 self.inner.parent_hint.set(parent.unwrap_or(NO_PARENT_HINT));
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 read_cell(&self.inner.parent_scope, |parent| {
1086 parent.as_ref().and_then(RecomposeScope::upgrade)
1087 })
1088 }
1089
1090 fn set_lifetime_owner_scope(&self, owner: Option<&RecomposeScope>) {
1091 link_scope(&self.inner.lifetime_owner_scope, owner);
1092 }
1093
1094 #[cfg(test)]
1095 fn lifetime_owner_scope(&self) -> Option<RecomposeScope> {
1096 read_cell(&self.inner.lifetime_owner_scope, |owner| {
1097 owner.as_ref().and_then(RecomposeScope::upgrade)
1098 })
1099 }
1100
1101 fn callback_promotion_target(&self) -> Option<RecomposeScope> {
1102 let mut current = self.parent_scope();
1103 while let Some(scope) = current {
1104 if scope.has_recompose_callback() {
1105 return Some(scope);
1106 }
1107 current = scope.parent_scope();
1108 }
1109 None
1110 }
1111
1112 fn parent_hint(&self) -> Option<NodeId> {
1113 Some(self.inner.parent_hint.get()).filter(|&hint| hint != NO_PARENT_HINT)
1114 }
1115
1116 pub(crate) fn group_anchor(&self) -> AnchorId {
1117 self.inner.group_anchor.get()
1118 }
1119
1120 pub(crate) fn set_group_anchor(&self, anchor: AnchorId) {
1121 self.inner.group_anchor.set(anchor);
1122 }
1123
1124 fn set_slots_host(&self, host: &SlotsHost) {
1125 let storage_key = host.storage_key();
1126 update_cell(&self.inner.slots_runtime_state, |runtime_state| {
1127 if self.inner.slots_storage_key.get() == storage_key
1128 && runtime_state.as_ref().map(std::rc::Weak::as_ptr) == host.runtime_state_ptr()
1129 {
1130 return;
1131 }
1132 self.inner.slots_storage_key.set(storage_key);
1133 *runtime_state = host.runtime_state().map(|state| Rc::downgrade(&state));
1134 });
1135 }
1136
1137 pub(crate) fn slots_storage_key(&self) -> Option<usize> {
1138 let key = self.inner.slots_storage_key.get();
1139 (key != 0).then_some(key)
1140 }
1141
1142 pub(crate) fn slots_host_identity(
1145 &self,
1146 ) -> (usize, *const crate::composer::ComposerRuntimeState) {
1147 let state = read_cell(&self.inner.slots_runtime_state, |state| {
1148 state
1149 .as_ref()
1150 .map_or(std::ptr::null(), std::rc::Weak::as_ptr)
1151 });
1152 (self.inner.slots_storage_key.get(), state)
1153 }
1154
1155 pub(crate) fn slots_runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
1156 read_cell(&self.inner.slots_runtime_state, |state| {
1157 state.as_ref().and_then(std::rc::Weak::upgrade)
1158 })
1159 }
1160
1161 pub fn deactivate(&self) {
1162 if !self.inner.active.replace(false) {
1163 return;
1164 }
1165 note_scope_activity_change();
1166 self.inner
1167 .deactivations
1168 .set(self.inner.deactivations.get() + 1);
1169 if self.inner.enqueued.replace(false) {
1170 self.inner.runtime.mark_scope_recomposed();
1171 }
1172 }
1173
1174 pub(crate) fn defer_until_reactivated(&self) {
1175 if self.inner.enqueued.replace(false) {
1176 self.inner.runtime.mark_scope_recomposed();
1177 }
1178 }
1179
1180 pub fn reactivate(&self) {
1181 self.inner.active.set(true);
1182 if self.inner.invalid.get()
1183 && self.is_effectively_active()
1184 && !self.inner.enqueued.replace(true)
1185 {
1186 self.inner.runtime.register_invalid_scope(self.downgrade());
1187 }
1188 }
1189
1190 pub fn force_reuse(&self) {
1191 self.inner.force_reuse.set(true);
1192 self.inner.force_recompose.set(false);
1193 self.inner.pending_recompose.set(true);
1194 }
1195
1196 pub(crate) fn request_pending_recompose(&self) {
1197 self.inner.pending_recompose.set(true);
1198 }
1199
1200 pub fn force_recompose(&self) {
1201 self.inner.force_recompose.set(true);
1202 self.inner.force_reuse.set(false);
1203 self.inner.pending_recompose.set(false);
1204 }
1205
1206 pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
1207 self.inner.retention_mode.set(mode);
1208 }
1209
1210 pub(crate) fn retention_mode(&self) -> RetentionMode {
1211 self.inner.retention_mode.get()
1212 }
1213
1214 pub fn should_recompose(&self) -> bool {
1215 if self.inner.force_recompose.replace(false) {
1216 self.inner.force_reuse.set(false);
1217 return true;
1218 }
1219 if self.inner.force_reuse.replace(false) {
1220 return false;
1221 }
1222 self.is_invalid()
1223 }
1224
1225 pub fn has_composed_once(&self) -> bool {
1226 self.inner.composed_once.get()
1227 }
1228
1229 fn mark_composed_once(&self) {
1230 self.inner.composed_once.set(true);
1231 }
1232
1233 fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
1234 if self.inner.unknown_invalidation_source.get() {
1235 return None;
1236 }
1237 read_cell(&self.inner.invalidation_sources, |sources| {
1238 (!sources.is_empty()).then(|| {
1239 sources
1240 .iter()
1241 .all(|source| allowed_sources.contains(source))
1242 })
1243 })
1244 }
1245
1246 fn has_unknown_invalidation_source(&self) -> bool {
1247 self.inner.unknown_invalidation_source.get()
1248 }
1249}
1250
1251#[cfg(test)]
1252impl RecomposeScope {
1253 pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
1254 Self::new(runtime)
1255 }
1256}
1257
1258#[derive(Debug, Clone, Copy, Default)]
1259pub struct RecomposeOptions {
1260 pub force_reuse: bool,
1261 pub force_recompose: bool,
1262 pub retention: RetentionMode,
1263}
1264
1265#[derive(Debug, Clone, PartialEq, Eq)]
1266pub enum NodeError {
1267 Missing {
1268 id: NodeId,
1269 },
1270 TypeMismatch {
1271 id: NodeId,
1272 expected: &'static str,
1273 },
1274 MissingContext {
1275 id: NodeId,
1276 reason: &'static str,
1277 },
1278 AlreadyExists {
1279 id: NodeId,
1280 },
1281 MalformedCommandPayload {
1282 tag: &'static str,
1283 },
1284 SlotHostUnavailable {
1285 operation: &'static str,
1286 reason: &'static str,
1287 },
1288 RecompositionLimitExceeded {
1289 operation: &'static str,
1290 limit: usize,
1291 },
1292}
1293
1294impl std::fmt::Display for NodeError {
1295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1296 match self {
1297 NodeError::Missing { id } => write!(f, "node {id} missing"),
1298 NodeError::TypeMismatch { id, expected } => {
1299 write!(f, "node {id} type mismatch; expected {expected}")
1300 }
1301 NodeError::MissingContext { id, reason } => {
1302 write!(f, "missing context for node {id}: {reason}")
1303 }
1304 NodeError::AlreadyExists { id } => {
1305 write!(f, "node {id} already exists")
1306 }
1307 NodeError::MalformedCommandPayload { tag } => {
1308 write!(f, "command queue missing or invalid {tag} payload")
1309 }
1310 NodeError::SlotHostUnavailable { operation, reason } => {
1311 write!(f, "{operation} cannot access slot host: {reason}")
1312 }
1313 NodeError::RecompositionLimitExceeded { operation, limit } => {
1314 write!(
1315 f,
1316 "{operation} exceeded {limit} iterations while reconciling composition"
1317 )
1318 }
1319 }
1320 }
1321}
1322
1323impl std::error::Error for NodeError {}
1324
1325pub use subcompose::{
1326 ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotId, SlotReusePolicy, SubcomposeState,
1327};
1328
1329#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1330pub enum Phase {
1331 Compose,
1332 Measure,
1333 Layout,
1334}
1335
1336pub use composer_context::{
1337 __current_composer, note_nested_slots_host, with_composer as with_current_composer,
1338};
1339
1340#[expect(non_snake_case)]
1341pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
1342 composer_context::with_composer(f)
1343}
1344
1345fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
1346 composer_context::try_with_composer(f)
1347}
1348
1349#[doc(hidden)]
1350pub fn current_recompose_scope_invalidated_only_by(
1351 allowed_sources: impl IntoIterator<Item = StateId>,
1352) -> Option<bool> {
1353 with_current_composer_opt(|composer| {
1354 let allowed_sources = allowed_sources.into_iter().collect();
1355 let mut scope = composer.current_recompose_scope();
1356 let mut saw_unknown_source = false;
1357 while let Some(current) = scope {
1358 if current.has_unknown_invalidation_source() {
1359 saw_unknown_source = true;
1360 scope = current.parent_scope();
1361 continue;
1362 }
1363 if let Some(matches) = current.invalidated_only_by(&allowed_sources) {
1364 return Some(matches);
1365 }
1366 scope = current.parent_scope();
1367 }
1368 saw_unknown_source.then_some(false)
1369 })
1370 .flatten()
1371}
1372
1373fn key_scoped<K: Hash, R>(
1374 key: &K,
1375 caller: &'static std::panic::Location<'static>,
1376 content: impl FnOnce() -> R,
1377) -> R {
1378 let seed = explicit_group_key_seed(key, caller);
1379 with_current_composer(|composer| composer.with_group_seed(seed, |_| content()))
1380}
1381
1382#[track_caller]
1383pub fn with_key<K: Hash>(key: &K, content: impl FnOnce()) {
1384 key_scoped(key, std::panic::Location::caller(), content);
1385}
1386
1387#[track_caller]
1398pub fn key<K: Hash, R>(keys: K, content: impl FnOnce() -> R) -> R {
1399 key_scoped(&keys, std::panic::Location::caller(), content)
1400}
1401
1402#[track_caller]
1421pub fn movable<K: Hash>(key: K, content: impl FnOnce()) {
1422 let id = hash_key(&key);
1423 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1424}
1425
1426#[expect(non_snake_case)]
1460#[track_caller]
1461pub fn rememberMovableContentOf(content: impl Fn() + 'static) -> MovableContent {
1462 let runtime = with_current_composer(composer::Composer::runtime_handle);
1463 let id = remember(|| runtime.next_movable_content_id()).with(|id| *id);
1464 MovableContent {
1465 id,
1466 content: Rc::new(content),
1467 }
1468}
1469
1470#[expect(non_snake_case)]
1478pub fn movableContentOf<K: Hash>(key: K, content: impl Fn() + 'static) -> MovableContent {
1479 MovableContent {
1480 id: hash_key(&key),
1481 content: Rc::new(content),
1482 }
1483}
1484
1485#[derive(Clone)]
1487pub struct MovableContent {
1488 id: Key,
1489 content: Rc<dyn Fn()>,
1490}
1491
1492impl MovableContent {
1493 pub fn show(&self) {
1497 let content = Rc::clone(&self.content);
1498 let id = self.id;
1499 with_current_composer(|composer| composer.with_movable_group(id, |_| content()));
1500 }
1501
1502 pub fn forget(&self) {
1505 forget_movable_id(self.id);
1506 }
1507}
1508
1509impl std::fmt::Debug for MovableContent {
1510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1511 f.debug_struct("MovableContent")
1512 .field("id", &self.id)
1513 .finish()
1514 }
1515}
1516
1517impl PartialEq for MovableContent {
1518 fn eq(&self, other: &Self) -> bool {
1519 self.id == other.id && Rc::ptr_eq(&self.content, &other.content)
1520 }
1521}
1522
1523pub fn forget_movable<K: Hash>(key: K) {
1529 let id = hash_key(&key);
1530 forget_movable_id(id);
1531}
1532
1533fn forget_movable_id(id: Key) {
1534 let runtime = composer_context::try_with_composer(composer::Composer::runtime_handle)
1535 .or_else(runtime::current_runtime_handle);
1536 match runtime {
1537 Some(runtime) => runtime.forget_movable(id),
1538 None => log::error!("forget_movable called without an active runtime"),
1539 }
1540}
1541
1542#[derive(Default)]
1543struct DisposableEffectState {
1544 key: Option<effect_key::EffectKey>,
1545 cleanup: Option<Box<dyn FnOnce()>>,
1546}
1547
1548impl DisposableEffectState {
1549 fn should_run(&self, key: &effect_key::EffectKey) -> bool {
1550 match &self.key {
1551 Some(current) => key.differs_from(current),
1552 None => true,
1553 }
1554 }
1555
1556 fn set_key(&mut self, key: effect_key::EffectKey) {
1557 self.key = Some(key);
1558 }
1559
1560 fn set_cleanup(&mut self, cleanup: Option<Box<dyn FnOnce()>>) {
1561 self.cleanup = cleanup;
1562 }
1563
1564 fn run_cleanup(&mut self) {
1565 if let Some(cleanup) = self.cleanup.take() {
1566 cleanup();
1567 }
1568 }
1569}
1570
1571impl Drop for DisposableEffectState {
1572 fn drop(&mut self) {
1573 self.run_cleanup();
1574 }
1575}
1576
1577#[derive(Clone, Copy, Debug, Default)]
1578pub struct DisposableEffectScope;
1579
1580#[derive(Default)]
1581pub struct DisposableEffectResult {
1582 cleanup: Option<Box<dyn FnOnce()>>,
1583}
1584
1585impl DisposableEffectScope {
1586 pub fn on_dispose(&self, cleanup: impl FnOnce() + 'static) -> DisposableEffectResult {
1587 DisposableEffectResult::new(cleanup)
1588 }
1589}
1590
1591impl DisposableEffectResult {
1592 pub fn new(cleanup: impl FnOnce() + 'static) -> Self {
1593 Self {
1594 cleanup: Some(Box::new(cleanup)),
1595 }
1596 }
1597
1598 fn into_cleanup(self) -> Option<Box<dyn FnOnce()>> {
1599 self.cleanup
1600 }
1601}
1602
1603#[expect(non_snake_case)]
1604pub fn SideEffect(effect: impl FnOnce() + 'static) {
1605 with_current_composer(|composer| composer.register_side_effect(effect));
1606}
1607
1608pub fn __disposable_effect_impl<K, F>(group_key: Key, keys: K, effect: F)
1609where
1610 K: PartialEq + 'static,
1611 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1612{
1613 with_current_composer(|composer| {
1614 composer.with_group(group_key, |composer| {
1615 let key = effect_key::EffectKey::new(keys);
1616 let state = composer.remember_effect::<DisposableEffectState>();
1617 if state.with(|state| state.should_run(&key)) {
1618 state.update(|state| {
1619 state.run_cleanup();
1620 state.set_key(key);
1621 });
1622 let mut effect_opt = Some(effect);
1623 composer.register_side_effect(move || {
1624 if let Some(effect) = effect_opt.take() {
1625 let result = effect(DisposableEffectScope);
1626 state.update(|state| state.set_cleanup(result.into_cleanup()));
1627 }
1628 });
1629 }
1630 });
1631 });
1632}
1633
1634#[expect(non_snake_case)]
1642#[track_caller]
1643pub fn DisposableEffect<K, F>(keys: K, effect: F)
1644where
1645 K: PartialEq + 'static,
1646 F: FnOnce(DisposableEffectScope) -> DisposableEffectResult + 'static,
1647{
1648 __disposable_effect_impl(crate::caller_location_key(), keys, effect);
1649}
1650
1651#[macro_export]
1652macro_rules! clone_captures {
1653 ($($alias:ident $(= $value:expr)?),+ $(,)?; $body:expr) => {{
1654 $(let $alias = $crate::clone_captures!(@clone $alias $(= $value)?);)+
1655 $body
1656 }};
1657 (@clone $alias:ident = $value:expr) => {
1658 ($value).clone()
1659 };
1660 (@clone $alias:ident) => {
1661 $alias.clone()
1662 };
1663}
1664
1665pub fn with_node_mut<N: Node + 'static, R>(
1666 id: NodeId,
1667 f: impl FnOnce(&mut N) -> R,
1668) -> Result<R, NodeError> {
1669 with_current_composer(|composer| composer.with_node_mut(id, f))
1670}
1671
1672pub fn push_parent(id: NodeId) {
1673 with_current_composer(|composer| composer.push_parent(id));
1674}
1675
1676pub fn pop_parent() {
1677 with_current_composer(composer::Composer::pop_parent);
1678}
1679
1680pub trait Node: Any {
1681 fn mount(&mut self) {}
1682 fn unmount(&mut self) {}
1683 fn insert_child(&mut self, _child: NodeId) -> bool {
1689 false
1690 }
1691 fn remove_child(&mut self, _child: NodeId) -> bool {
1696 false
1697 }
1698 fn move_child(&mut self, _from: usize, _to: usize) {}
1699 fn update_children(&mut self, _children: &[NodeId]) {}
1700 fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1704 out.clear();
1705 }
1706 fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1707 self.collect_children_into(out);
1708 }
1709 fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1721 let mut children = SmallVec::<[NodeId; 8]>::new();
1722 self.collect_owned_children_into(&mut children);
1723 children.iter().position(|&id| id == child)
1724 }
1725 fn set_node_id(&mut self, _id: NodeId) {}
1728 fn on_attached_to_parent(&mut self, _parent: NodeId) {}
1731 fn on_removed_from_parent(&mut self) {}
1734 fn parent(&self) -> Option<NodeId> {
1737 None
1738 }
1739 fn mark_needs_layout(&self) {}
1742 fn needs_layout(&self) -> bool {
1744 false
1745 }
1746 fn mark_needs_measure(&self) {}
1749 fn needs_measure(&self) -> bool {
1751 false
1752 }
1753 fn mark_descendant_needs_layout(&self, measure: bool) {
1758 if measure {
1759 self.mark_needs_measure();
1760 } else {
1761 self.mark_needs_layout();
1762 }
1763 }
1764 fn descendant_needs_layout(&self) -> bool {
1766 false
1767 }
1768 fn descendant_needs_measure(&self) -> bool {
1770 false
1771 }
1772 fn layout_dirty(&self) -> bool {
1775 self.needs_measure() || self.needs_layout() || self.descendant_needs_layout()
1776 }
1777 fn is_virtual(&self) -> bool {
1780 false
1781 }
1782 fn mark_needs_semantics(&self) {}
1784 fn mark_descendant_needs_semantics(&self) {
1788 self.mark_needs_semantics();
1789 }
1790 fn needs_semantics(&self) -> bool {
1792 false
1793 }
1794 fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1803 if self.parent().is_none() {
1804 self.on_attached_to_parent(parent);
1805 }
1806 }
1807
1808 fn recycle_key(&self) -> Option<TypeId> {
1810 None
1811 }
1812
1813 fn recycle_pool_limit(&self) -> Option<usize> {
1815 None
1816 }
1817
1818 fn prepare_for_recycle(&mut self) {}
1820
1821 fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
1826 None
1827 }
1828
1829 fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
1835 None
1836 }
1837
1838 fn debug_heap_bytes(&self) -> usize {
1840 0
1841 }
1842}
1843
1844pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1862 DirtyBubble::LAYOUT.apply(applier, node_id);
1863}
1864
1865pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1875 DirtyBubble::MEASURE.apply(applier, node_id);
1876}
1877
1878pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
1884 DirtyBubble::SEMANTICS.apply(applier, node_id);
1885}
1886
1887pub fn queue_semantics_invalidation(node_id: NodeId) {
1892 let _ = composer_context::try_with_composer(|composer| {
1893 composer.enqueue_semantics_invalidation(node_id);
1894 });
1895}
1896
1897pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1919 bubble_layout_dirty_composer::<N>(node_id);
1920}
1921
1922pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
1928 with_current_composer(|composer| {
1929 composer.commands_mut().push(Command::BubbleDirty {
1930 node_id,
1931 bubble: DirtyBubble {
1932 layout: false,
1933 measure: true,
1934 semantics: false,
1935 },
1936 });
1937 });
1938}
1939
1940pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
1947 bubble_semantics_dirty_composer::<N>(node_id);
1948}
1949
1950fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1951 let _ = with_node_mut(node_id, |node: &mut N| {
1952 node.mark_needs_layout();
1953 });
1954
1955 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1956 let parent_id = pid;
1957
1958 let advanced = with_node_mut(parent_id, |node: &mut N| {
1959 node.mark_descendant_needs_layout(false);
1960 true
1961 })
1962 .unwrap_or(false);
1963
1964 if advanced {
1965 node_id = parent_id;
1966 } else {
1967 break;
1968 }
1969 }
1970}
1971
1972fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
1973 let _ = with_node_mut(node_id, |node: &mut N| {
1974 node.mark_needs_semantics();
1975 });
1976
1977 while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
1978 let parent_id = pid;
1979
1980 let advanced = with_node_mut(parent_id, |node: &mut N| {
1981 if !node.needs_semantics() {
1982 node.mark_descendant_needs_semantics();
1983 }
1984 true
1985 })
1986 .unwrap_or(false);
1987
1988 if advanced {
1989 node_id = parent_id;
1990 } else {
1991 break;
1992 }
1993 }
1994}
1995
1996impl dyn Node {
1997 pub fn as_any_mut(&mut self) -> &mut dyn Any {
1998 self
1999 }
2000}
2001
2002pub struct RecycledNode {
2003 stable_id: NodeId,
2004 node: Box<dyn Node>,
2005 warm_origin: bool,
2006}
2007
2008impl RecycledNode {
2009 fn new(stable_id: NodeId, mut node: Box<dyn Node>, warm_origin: bool) -> Self {
2013 let node = node.rehouse_for_recycle().unwrap_or_else(|| {
2014 node.prepare_for_recycle();
2015 node
2016 });
2017 Self {
2018 stable_id,
2019 node,
2020 warm_origin,
2021 }
2022 }
2023
2024 fn from_shell(stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) -> Self {
2025 Self {
2026 stable_id,
2027 node,
2028 warm_origin,
2029 }
2030 }
2031
2032 pub fn stable_id(&self) -> NodeId {
2033 self.stable_id
2034 }
2035
2036 fn warm_origin(&self) -> bool {
2037 self.warm_origin
2038 }
2039
2040 fn set_warm_origin(&mut self, warm_origin: bool) {
2041 self.warm_origin = warm_origin;
2042 }
2043
2044 pub fn node_mut(&mut self) -> &mut dyn Node {
2045 self.node.as_mut()
2046 }
2047
2048 pub fn into_parts(self) -> (NodeId, Box<dyn Node>, bool) {
2049 (self.stable_id, self.node, self.warm_origin)
2050 }
2051}
2052
2053#[derive(Debug, Clone, PartialEq, Eq)]
2054pub struct RecycledNodeInsertion {
2055 pub id: NodeId,
2056 pub stable_id_reused: bool,
2057 pub fallback_error: Option<NodeError>,
2058}
2059
2060impl RecycledNodeInsertion {
2061 fn reused(stable_id: NodeId) -> Self {
2062 Self {
2063 id: stable_id,
2064 stable_id_reused: true,
2065 fallback_error: None,
2066 }
2067 }
2068
2069 fn fresh(id: NodeId, fallback_error: Option<NodeError>) -> Self {
2070 Self {
2071 id,
2072 stable_id_reused: false,
2073 fallback_error,
2074 }
2075 }
2076}
2077
2078pub trait Applier: Any {
2079 fn create(&mut self, node: Box<dyn Node>) -> NodeId;
2080 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError>;
2081 fn remove(&mut self, id: NodeId) -> Result<(), NodeError>;
2082
2083 fn record_structural_change(&mut self, _parent_id: NodeId) {}
2089
2090 fn node_generation(&self, id: NodeId) -> u32;
2094
2095 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError>;
2103
2104 fn insert_recycled_node_or_create(
2107 &mut self,
2108 stable_id: NodeId,
2109 node: Box<dyn Node>,
2110 ) -> RecycledNodeInsertion {
2111 let id = self.create(node);
2112 RecycledNodeInsertion::fresh(id, Some(NodeError::AlreadyExists { id: stable_id }))
2113 }
2114
2115 fn as_any(&self) -> &dyn Any
2116 where
2117 Self: Sized,
2118 {
2119 self
2120 }
2121
2122 fn as_any_mut(&mut self) -> &mut dyn Any
2123 where
2124 Self: Sized,
2125 {
2126 self
2127 }
2128
2129 fn compact(&mut self) {}
2131
2132 fn take_recycled_node(&mut self, _key: TypeId) -> Option<RecycledNode> {
2134 None
2135 }
2136
2137 fn set_recycled_node_origin(&mut self, _id: NodeId, _warm_origin: bool) {}
2139
2140 fn seed_recycled_node_shell(
2142 &mut self,
2143 _key: TypeId,
2144 _recycle_pool_limit: Option<usize>,
2145 _shell: Box<dyn Node>,
2146 ) {
2147 }
2148
2149 fn record_fresh_recyclable_creation(&mut self, _key: TypeId) {}
2151
2152 fn clear_recycled_nodes(&mut self) {}
2154}
2155
2156type CommandCallback = Box<dyn FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static>;
2157
2158#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2159pub(crate) struct DirtyBubble {
2160 layout: bool,
2161 measure: bool,
2162 semantics: bool,
2163}
2164
2165impl DirtyBubble {
2166 const LAYOUT: Self = Self {
2167 layout: true,
2168 measure: false,
2169 semantics: false,
2170 };
2171
2172 const MEASURE: Self = Self {
2173 layout: false,
2174 measure: true,
2175 semantics: false,
2176 };
2177
2178 pub(crate) const LAYOUT_AND_MEASURE: Self = Self {
2179 layout: true,
2180 measure: true,
2181 semantics: false,
2182 };
2183
2184 pub(crate) const SEMANTICS: Self = Self {
2185 layout: false,
2186 measure: false,
2187 semantics: true,
2188 };
2189
2190 fn apply(self, applier: &mut dyn Applier, node_id: NodeId) {
2193 self.walk(applier, Some(node_id), true);
2194 }
2195
2196 fn apply_to_ancestors(self, applier: &mut dyn Applier, node_id: NodeId) {
2199 let parent = applier.get_mut(node_id).ok().and_then(|node| node.parent());
2200 self.walk(applier, parent, false);
2201 }
2202
2203 fn walk(self, applier: &mut dyn Applier, first: Option<NodeId>, mut start: bool) {
2208 let mut next = first;
2209 while let Some(id) = next {
2210 let Ok(node) = applier.get_mut(id) else {
2211 break;
2212 };
2213 self.mark(node, start);
2214 next = node.parent();
2215 start = start && node.is_virtual();
2216 }
2217 }
2218
2219 fn mark(self, node: &mut dyn Node, start: bool) {
2222 if start {
2223 if self.layout {
2224 node.mark_needs_layout();
2225 }
2226 if self.measure {
2227 node.mark_needs_measure();
2228 }
2229 if self.semantics {
2230 node.mark_needs_semantics();
2231 }
2232 return;
2233 }
2234 let marked = if self.measure {
2235 node.descendant_needs_measure()
2236 } else {
2237 node.descendant_needs_layout()
2238 };
2239 if (self.layout || self.measure) && !marked {
2240 node.mark_descendant_needs_layout(self.measure);
2241 }
2242 if self.semantics && !node.needs_semantics() {
2243 node.mark_descendant_needs_semantics();
2244 }
2245 }
2246}
2247
2248pub(crate) enum Command {
2249 BubbleDirty {
2250 node_id: NodeId,
2251 bubble: DirtyBubble,
2252 },
2253 RemoveNode {
2254 id: NodeId,
2255 },
2256 MountNode {
2257 id: NodeId,
2258 },
2259 AttachChild {
2260 parent_id: NodeId,
2261 child_id: NodeId,
2262 insert_index: Option<usize>,
2263 bubble: DirtyBubble,
2264 },
2265 InsertChild {
2266 parent_id: NodeId,
2267 child_id: NodeId,
2268 appended_index: usize,
2269 insert_index: usize,
2270 bubble: DirtyBubble,
2271 },
2272 MoveChild {
2273 parent_id: NodeId,
2274 from_index: usize,
2275 to_index: usize,
2276 bubble: DirtyBubble,
2277 },
2278 RemoveChild {
2279 parent_id: NodeId,
2280 child_id: NodeId,
2281 },
2282 DetachChild {
2283 parent_id: NodeId,
2284 child_id: NodeId,
2285 },
2286 SyncChildren {
2287 parent_id: NodeId,
2288 expected_children: ChildList,
2289 },
2290 Callback(CommandCallback),
2291}
2292
2293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2294struct DeferredChildCleanup {
2295 child_id: NodeId,
2296 generation: u32,
2297 removed_from_parent: bool,
2298}
2299
2300#[derive(Default)]
2301struct DeferredChildCleanupQueue {
2302 pending: Vec<DeferredChildCleanup>,
2303 preserved: Vec<(NodeId, u32)>,
2304}
2305
2306impl DeferredChildCleanupQueue {
2307 fn push(&mut self, child_id: NodeId, generation: u32, removed_from_parent: bool) {
2308 if self
2309 .preserved
2310 .iter()
2311 .any(|&(preserved_id, preserved_generation)| {
2312 preserved_id == child_id && preserved_generation == generation
2313 })
2314 {
2315 return;
2316 }
2317 self.pending.push(DeferredChildCleanup {
2318 child_id,
2319 generation,
2320 removed_from_parent,
2321 });
2322 }
2323
2324 fn preserve(&mut self, child_id: NodeId, generation: u32) {
2325 if !self
2326 .preserved
2327 .iter()
2328 .any(|&(preserved_id, preserved_generation)| {
2329 preserved_id == child_id && preserved_generation == generation
2330 })
2331 {
2332 self.preserved.push((child_id, generation));
2333 }
2334 self.pending
2335 .retain(|cleanup| cleanup.child_id != child_id || cleanup.generation != generation);
2336 }
2337
2338 fn flush(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2339 for cleanup in self.pending.drain(..) {
2340 cleanup_detached_child(applier, cleanup)?;
2341 }
2342 Ok(())
2343 }
2344
2345 fn clear(&mut self) {
2346 self.pending.clear();
2347 self.preserved.clear();
2348 }
2349}
2350
2351impl Command {
2352 pub(crate) fn callback(
2353 callback: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
2354 ) -> Self {
2355 Self::Callback(Box::new(callback))
2356 }
2357
2358 pub(crate) fn apply(self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2359 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
2360 self.apply_with_cleanup(applier, &mut deferred_cleanup)?;
2361 deferred_cleanup.flush(applier)
2362 }
2363
2364 fn apply_with_cleanup(
2365 self,
2366 applier: &mut dyn Applier,
2367 deferred_cleanup: &mut DeferredChildCleanupQueue,
2368 ) -> Result<(), NodeError> {
2369 match self {
2370 Self::BubbleDirty { node_id, bubble } => {
2371 bubble.apply(applier, node_id);
2372 Ok(())
2373 }
2374 Self::RemoveNode { id } => {
2375 if let Ok(node) = applier.get_mut(id) {
2376 node.unmount();
2377 }
2378 match applier.remove(id) {
2379 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
2380 Err(err) => Err(err),
2381 }
2382 }
2383 Self::MountNode { id } => {
2384 let node = match applier.get_mut(id) {
2385 Ok(node) => node,
2386 Err(NodeError::Missing { .. }) => return Ok(()),
2387 Err(err) => return Err(err),
2388 };
2389 node.set_node_id(id);
2390 node.mount();
2391 Ok(())
2392 }
2393 Self::AttachChild {
2394 parent_id,
2395 child_id,
2396 insert_index,
2397 bubble,
2398 } => {
2399 attach_child_at(applier, parent_id, child_id, insert_index, bubble);
2400 Ok(())
2401 }
2402 Self::InsertChild {
2403 parent_id,
2404 child_id,
2405 appended_index,
2406 insert_index,
2407 bubble,
2408 } => {
2409 insert_child_with_reparenting(applier, parent_id, child_id);
2410 bubble.apply(applier, parent_id);
2411 if insert_index != appended_index
2412 && let Ok(parent_node) = applier.get_mut(parent_id)
2413 {
2414 parent_node.move_child(appended_index, insert_index);
2415 }
2416 Ok(())
2417 }
2418 Self::MoveChild {
2419 parent_id,
2420 from_index,
2421 to_index,
2422 bubble,
2423 } => {
2424 if let Ok(parent_node) = applier.get_mut(parent_id) {
2425 parent_node.move_child(from_index, to_index);
2426 }
2427 bubble.apply(applier, parent_id);
2428 note_structural_move(parent_id, from_index, to_index);
2429 applier.record_structural_change(parent_id);
2430 Ok(())
2431 }
2432 Self::RemoveChild {
2433 parent_id,
2434 child_id,
2435 } => apply_remove_child(applier, parent_id, child_id, deferred_cleanup),
2436 Self::DetachChild {
2437 parent_id,
2438 child_id,
2439 } => {
2440 let generation = applier.node_generation(child_id);
2441 detach_child_from_parent(applier, parent_id, child_id)?;
2442 deferred_cleanup.preserve(child_id, generation);
2443 Ok(())
2444 }
2445 Self::SyncChildren {
2446 parent_id,
2447 expected_children,
2448 } => sync_children(applier, parent_id, &expected_children, deferred_cleanup),
2449 Self::Callback(callback) => callback(applier),
2450 }
2451 }
2452}
2453
2454const COMMAND_FLUSH_THRESHOLD: usize = 4096;
2455type ChildList = SmallVec<[NodeId; 4]>;
2456const SMALL_CHILD_SYNC_LINEAR_THRESHOLD: usize = 8;
2457
2458#[derive(Copy, Clone)]
2459enum CommandTag {
2460 BubbleDirty,
2461 RemoveNode,
2462 MountNode,
2463 AttachChild,
2464 InsertChild,
2465 MoveChild,
2466 RemoveChild,
2467 DetachChild,
2468 SyncChildren,
2469 Callback,
2470}
2471
2472impl CommandTag {
2473 fn label(self) -> &'static str {
2474 match self {
2475 Self::BubbleDirty => "BubbleDirty",
2476 Self::RemoveNode => "RemoveNode",
2477 Self::MountNode => "MountNode",
2478 Self::AttachChild => "AttachChild",
2479 Self::InsertChild => "InsertChild",
2480 Self::MoveChild => "MoveChild",
2481 Self::RemoveChild => "RemoveChild",
2482 Self::DetachChild => "DetachChild",
2483 Self::SyncChildren => "SyncChildren",
2484 Self::Callback => "Callback",
2485 }
2486 }
2487}
2488
2489#[derive(Copy, Clone)]
2490struct BubbleDirtyCommand {
2491 node_id: NodeId,
2492 bubble: DirtyBubble,
2493}
2494
2495#[derive(Copy, Clone)]
2496struct AttachChildCommand {
2497 parent_id: NodeId,
2498 child_id: NodeId,
2499 insert_index: Option<usize>,
2500 bubble: DirtyBubble,
2501}
2502
2503#[derive(Copy, Clone)]
2504struct InsertChildCommand {
2505 parent_id: NodeId,
2506 child_id: NodeId,
2507 appended_index: usize,
2508 insert_index: usize,
2509 bubble: DirtyBubble,
2510}
2511
2512#[derive(Copy, Clone)]
2513struct MoveChildCommand {
2514 parent_id: NodeId,
2515 from_index: usize,
2516 to_index: usize,
2517 bubble: DirtyBubble,
2518}
2519
2520#[derive(Copy, Clone)]
2521struct RemoveChildCommand {
2522 parent_id: NodeId,
2523 child_id: NodeId,
2524}
2525
2526#[derive(Copy, Clone)]
2527struct DetachChildCommand {
2528 parent_id: NodeId,
2529 child_id: NodeId,
2530}
2531
2532struct SyncChildrenCommand {
2533 parent_id: NodeId,
2534 child_start: usize,
2535 child_len: usize,
2536}
2537
2538#[derive(Default)]
2542pub(crate) struct CommandQueue {
2543 tags: Vec<CommandTag>,
2544 bubble_dirty: Vec<BubbleDirtyCommand>,
2545 remove_nodes: Vec<NodeId>,
2546 mount_nodes: Vec<NodeId>,
2547 attach_children: Vec<AttachChildCommand>,
2548 insert_children: Vec<InsertChildCommand>,
2549 move_children: Vec<MoveChildCommand>,
2550 remove_children: Vec<RemoveChildCommand>,
2551 detach_children: Vec<DetachChildCommand>,
2552 sync_children: Vec<SyncChildrenCommand>,
2553 sync_child_ids: Vec<NodeId>,
2554 callbacks: Vec<CommandCallback>,
2555 cleanup: DeferredChildCleanupQueue,
2556}
2557
2558impl CommandQueue {
2559 fn push_tag(&mut self, tag: CommandTag) {
2560 self.tags.push(tag);
2561 }
2562
2563 fn push(&mut self, command: Command) {
2564 match command {
2565 Command::BubbleDirty { node_id, bubble } => {
2566 self.bubble_dirty
2567 .push(BubbleDirtyCommand { node_id, bubble });
2568 self.push_tag(CommandTag::BubbleDirty);
2569 }
2570 Command::RemoveNode { id } => {
2571 self.remove_nodes.push(id);
2572 self.push_tag(CommandTag::RemoveNode);
2573 }
2574 Command::MountNode { id } => {
2575 self.mount_nodes.push(id);
2576 self.push_tag(CommandTag::MountNode);
2577 }
2578 Command::AttachChild {
2579 parent_id,
2580 child_id,
2581 insert_index,
2582 bubble,
2583 } => {
2584 self.attach_children.push(AttachChildCommand {
2585 parent_id,
2586 child_id,
2587 insert_index,
2588 bubble,
2589 });
2590 self.push_tag(CommandTag::AttachChild);
2591 }
2592 Command::InsertChild {
2593 parent_id,
2594 child_id,
2595 appended_index,
2596 insert_index,
2597 bubble,
2598 } => {
2599 self.insert_children.push(InsertChildCommand {
2600 parent_id,
2601 child_id,
2602 appended_index,
2603 insert_index,
2604 bubble,
2605 });
2606 self.push_tag(CommandTag::InsertChild);
2607 }
2608 Command::MoveChild {
2609 parent_id,
2610 from_index,
2611 to_index,
2612 bubble,
2613 } => {
2614 self.move_children.push(MoveChildCommand {
2615 parent_id,
2616 from_index,
2617 to_index,
2618 bubble,
2619 });
2620 self.push_tag(CommandTag::MoveChild);
2621 }
2622 Command::RemoveChild {
2623 parent_id,
2624 child_id,
2625 } => {
2626 self.remove_children.push(RemoveChildCommand {
2627 parent_id,
2628 child_id,
2629 });
2630 self.push_tag(CommandTag::RemoveChild);
2631 }
2632 Command::DetachChild {
2633 parent_id,
2634 child_id,
2635 } => {
2636 self.detach_children.push(DetachChildCommand {
2637 parent_id,
2638 child_id,
2639 });
2640 self.push_tag(CommandTag::DetachChild);
2641 }
2642 Command::SyncChildren {
2643 parent_id,
2644 expected_children,
2645 } => {
2646 let child_start = self.sync_child_ids.len();
2647 let child_len = expected_children.len();
2648 self.sync_child_ids.extend(expected_children);
2649 self.sync_children.push(SyncChildrenCommand {
2650 parent_id,
2651 child_start,
2652 child_len,
2653 });
2654 self.push_tag(CommandTag::SyncChildren);
2655 }
2656 Command::Callback(callback) => {
2657 self.callbacks.push(callback);
2658 self.push_tag(CommandTag::Callback);
2659 }
2660 }
2661 }
2662
2663 fn len(&self) -> usize {
2664 self.tags.len()
2665 }
2666
2667 fn capacity(&self) -> usize {
2668 self.tags.capacity()
2669 }
2670
2671 fn payload_len_bytes(&self) -> usize {
2672 self.bubble_dirty
2673 .len()
2674 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2675 .saturating_add(
2676 self.remove_nodes
2677 .len()
2678 .saturating_mul(std::mem::size_of::<NodeId>()),
2679 )
2680 .saturating_add(
2681 self.mount_nodes
2682 .len()
2683 .saturating_mul(std::mem::size_of::<NodeId>()),
2684 )
2685 .saturating_add(
2686 self.attach_children
2687 .len()
2688 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2689 )
2690 .saturating_add(
2691 self.insert_children
2692 .len()
2693 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2694 )
2695 .saturating_add(
2696 self.move_children
2697 .len()
2698 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2699 )
2700 .saturating_add(
2701 self.remove_children
2702 .len()
2703 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2704 )
2705 .saturating_add(
2706 self.detach_children
2707 .len()
2708 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2709 )
2710 .saturating_add(
2711 self.sync_children
2712 .len()
2713 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2714 )
2715 .saturating_add(
2716 self.sync_child_ids
2717 .len()
2718 .saturating_mul(std::mem::size_of::<NodeId>()),
2719 )
2720 .saturating_add(
2721 self.callbacks
2722 .len()
2723 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2724 )
2725 }
2726
2727 fn payload_capacity_bytes(&self) -> usize {
2728 self.bubble_dirty
2729 .capacity()
2730 .saturating_mul(std::mem::size_of::<BubbleDirtyCommand>())
2731 .saturating_add(
2732 self.remove_nodes
2733 .capacity()
2734 .saturating_mul(std::mem::size_of::<NodeId>()),
2735 )
2736 .saturating_add(
2737 self.mount_nodes
2738 .capacity()
2739 .saturating_mul(std::mem::size_of::<NodeId>()),
2740 )
2741 .saturating_add(
2742 self.attach_children
2743 .capacity()
2744 .saturating_mul(std::mem::size_of::<AttachChildCommand>()),
2745 )
2746 .saturating_add(
2747 self.insert_children
2748 .capacity()
2749 .saturating_mul(std::mem::size_of::<InsertChildCommand>()),
2750 )
2751 .saturating_add(
2752 self.move_children
2753 .capacity()
2754 .saturating_mul(std::mem::size_of::<MoveChildCommand>()),
2755 )
2756 .saturating_add(
2757 self.remove_children
2758 .capacity()
2759 .saturating_mul(std::mem::size_of::<RemoveChildCommand>()),
2760 )
2761 .saturating_add(
2762 self.detach_children
2763 .capacity()
2764 .saturating_mul(std::mem::size_of::<DetachChildCommand>()),
2765 )
2766 .saturating_add(
2767 self.sync_children
2768 .capacity()
2769 .saturating_mul(std::mem::size_of::<SyncChildrenCommand>()),
2770 )
2771 .saturating_add(
2772 self.sync_child_ids
2773 .capacity()
2774 .saturating_mul(std::mem::size_of::<NodeId>()),
2775 )
2776 .saturating_add(
2777 self.callbacks
2778 .capacity()
2779 .saturating_mul(std::mem::size_of::<CommandCallback>()),
2780 )
2781 }
2782
2783 pub(crate) fn apply(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2786 let result = self.apply_in_order(applier);
2787 self.clear();
2788 result
2789 }
2790
2791 fn clear(&mut self) {
2792 let Self {
2793 tags,
2794 bubble_dirty,
2795 remove_nodes,
2796 mount_nodes,
2797 attach_children,
2798 insert_children,
2799 move_children,
2800 remove_children,
2801 detach_children,
2802 sync_children,
2803 sync_child_ids,
2804 callbacks,
2805 cleanup,
2806 } = self;
2807 tags.clear();
2808 bubble_dirty.clear();
2809 remove_nodes.clear();
2810 mount_nodes.clear();
2811 attach_children.clear();
2812 insert_children.clear();
2813 move_children.clear();
2814 remove_children.clear();
2815 detach_children.clear();
2816 sync_children.clear();
2817 sync_child_ids.clear();
2818 callbacks.clear();
2819 cleanup.clear();
2820 }
2821
2822 fn apply_in_order(&mut self, applier: &mut dyn Applier) -> Result<(), NodeError> {
2823 let mut payloads = CommandPayloads {
2824 bubble_dirty: self.bubble_dirty.drain(..),
2825 remove_nodes: self.remove_nodes.drain(..),
2826 mount_nodes: self.mount_nodes.drain(..),
2827 attach_children: self.attach_children.drain(..),
2828 insert_children: self.insert_children.drain(..),
2829 move_children: self.move_children.drain(..),
2830 remove_children: self.remove_children.drain(..),
2831 detach_children: self.detach_children.drain(..),
2832 sync_children: self.sync_children.drain(..),
2833 sync_child_ids: &self.sync_child_ids,
2834 callbacks: self.callbacks.drain(..),
2835 };
2836 for &tag in &self.tags {
2837 payloads.apply(tag, applier, &mut self.cleanup)?;
2838 }
2839 payloads.assert_all_taken();
2840 self.cleanup.flush(applier)
2841 }
2842}
2843
2844struct CommandPayloads<'a> {
2847 bubble_dirty: std::vec::Drain<'a, BubbleDirtyCommand>,
2848 remove_nodes: std::vec::Drain<'a, NodeId>,
2849 mount_nodes: std::vec::Drain<'a, NodeId>,
2850 attach_children: std::vec::Drain<'a, AttachChildCommand>,
2851 insert_children: std::vec::Drain<'a, InsertChildCommand>,
2852 move_children: std::vec::Drain<'a, MoveChildCommand>,
2853 remove_children: std::vec::Drain<'a, RemoveChildCommand>,
2854 detach_children: std::vec::Drain<'a, DetachChildCommand>,
2855 sync_children: std::vec::Drain<'a, SyncChildrenCommand>,
2856 sync_child_ids: &'a [NodeId],
2857 callbacks: std::vec::Drain<'a, CommandCallback>,
2858}
2859
2860impl CommandPayloads<'_> {
2861 fn apply(
2862 &mut self,
2863 tag: CommandTag,
2864 applier: &mut dyn Applier,
2865 cleanup: &mut DeferredChildCleanupQueue,
2866 ) -> Result<(), NodeError> {
2867 match tag {
2868 CommandTag::BubbleDirty
2869 | CommandTag::RemoveNode
2870 | CommandTag::MountNode
2871 | CommandTag::Callback => self.apply_node_command(tag, applier, cleanup),
2872 CommandTag::AttachChild
2873 | CommandTag::InsertChild
2874 | CommandTag::MoveChild
2875 | CommandTag::RemoveChild
2876 | CommandTag::DetachChild
2877 | CommandTag::SyncChildren => self.apply_child_command(tag, applier, cleanup),
2878 }
2879 }
2880
2881 fn apply_node_command(
2882 &mut self,
2883 tag: CommandTag,
2884 applier: &mut dyn Applier,
2885 cleanup: &mut DeferredChildCleanupQueue,
2886 ) -> Result<(), NodeError> {
2887 match tag {
2888 CommandTag::BubbleDirty => {
2889 let BubbleDirtyCommand { node_id, bubble } =
2890 next_command_payload(&mut self.bubble_dirty, tag)?;
2891 Command::BubbleDirty { node_id, bubble }.apply_with_cleanup(applier, cleanup)?;
2892 }
2893 CommandTag::RemoveNode => {
2894 let id = next_command_payload(&mut self.remove_nodes, tag)?;
2895 Command::RemoveNode { id }.apply_with_cleanup(applier, cleanup)?;
2896 }
2897 CommandTag::MountNode => {
2898 let id = next_command_payload(&mut self.mount_nodes, tag)?;
2899 Command::MountNode { id }.apply_with_cleanup(applier, cleanup)?;
2900 }
2901 CommandTag::Callback => {
2902 let callback = next_command_payload(&mut self.callbacks, tag)?;
2903 Command::Callback(callback).apply_with_cleanup(applier, cleanup)?;
2904 }
2905 CommandTag::AttachChild
2906 | CommandTag::InsertChild
2907 | CommandTag::MoveChild
2908 | CommandTag::RemoveChild
2909 | CommandTag::DetachChild
2910 | CommandTag::SyncChildren => return Err(command_payload_error(tag)),
2911 }
2912 Ok(())
2913 }
2914
2915 fn apply_child_command(
2916 &mut self,
2917 tag: CommandTag,
2918 applier: &mut dyn Applier,
2919 cleanup: &mut DeferredChildCleanupQueue,
2920 ) -> Result<(), NodeError> {
2921 match tag {
2922 CommandTag::AttachChild => {
2923 let AttachChildCommand {
2924 parent_id,
2925 child_id,
2926 insert_index,
2927 bubble,
2928 } = next_command_payload(&mut self.attach_children, tag)?;
2929 Command::AttachChild {
2930 parent_id,
2931 child_id,
2932 insert_index,
2933 bubble,
2934 }
2935 .apply_with_cleanup(applier, cleanup)?;
2936 }
2937 CommandTag::InsertChild => {
2938 let InsertChildCommand {
2939 parent_id,
2940 child_id,
2941 appended_index,
2942 insert_index,
2943 bubble,
2944 } = next_command_payload(&mut self.insert_children, tag)?;
2945 Command::InsertChild {
2946 parent_id,
2947 child_id,
2948 appended_index,
2949 insert_index,
2950 bubble,
2951 }
2952 .apply_with_cleanup(applier, cleanup)?;
2953 }
2954 CommandTag::MoveChild => {
2955 let MoveChildCommand {
2956 parent_id,
2957 from_index,
2958 to_index,
2959 bubble,
2960 } = next_command_payload(&mut self.move_children, tag)?;
2961 Command::MoveChild {
2962 parent_id,
2963 from_index,
2964 to_index,
2965 bubble,
2966 }
2967 .apply_with_cleanup(applier, cleanup)?;
2968 }
2969 CommandTag::RemoveChild => {
2970 let RemoveChildCommand {
2971 parent_id,
2972 child_id,
2973 } = next_command_payload(&mut self.remove_children, tag)?;
2974 Command::RemoveChild {
2975 parent_id,
2976 child_id,
2977 }
2978 .apply_with_cleanup(applier, cleanup)?;
2979 }
2980 CommandTag::DetachChild => {
2981 let DetachChildCommand {
2982 parent_id,
2983 child_id,
2984 } = next_command_payload(&mut self.detach_children, tag)?;
2985 Command::DetachChild {
2986 parent_id,
2987 child_id,
2988 }
2989 .apply_with_cleanup(applier, cleanup)?;
2990 }
2991 CommandTag::SyncChildren => self.sync_children(applier, cleanup)?,
2992 CommandTag::BubbleDirty
2993 | CommandTag::RemoveNode
2994 | CommandTag::MountNode
2995 | CommandTag::Callback => return Err(command_payload_error(tag)),
2996 }
2997 Ok(())
2998 }
2999
3000 fn sync_children(
3001 &mut self,
3002 applier: &mut dyn Applier,
3003 cleanup: &mut DeferredChildCleanupQueue,
3004 ) -> Result<(), NodeError> {
3005 let tag = CommandTag::SyncChildren;
3006 let SyncChildrenCommand {
3007 parent_id,
3008 child_start,
3009 child_len,
3010 } = next_command_payload(&mut self.sync_children, tag)?;
3011 let expected_children = child_start
3012 .checked_add(child_len)
3013 .and_then(|child_end| self.sync_child_ids.get(child_start..child_end))
3014 .ok_or_else(|| command_payload_error(tag))?;
3015 sync_children(applier, parent_id, expected_children, cleanup)
3016 }
3017
3018 fn assert_all_taken(&mut self) {
3019 debug_assert!(self.bubble_dirty.next().is_none());
3020 debug_assert!(self.remove_nodes.next().is_none());
3021 debug_assert!(self.mount_nodes.next().is_none());
3022 debug_assert!(self.attach_children.next().is_none());
3023 debug_assert!(self.insert_children.next().is_none());
3024 debug_assert!(self.move_children.next().is_none());
3025 debug_assert!(self.remove_children.next().is_none());
3026 debug_assert!(self.detach_children.next().is_none());
3027 debug_assert!(self.sync_children.next().is_none());
3028 debug_assert!(self.callbacks.next().is_none());
3029 }
3030}
3031
3032fn command_payload_error(tag: CommandTag) -> NodeError {
3033 NodeError::MalformedCommandPayload { tag: tag.label() }
3034}
3035
3036fn next_command_payload<T>(
3037 payloads: &mut impl Iterator<Item = T>,
3038 tag: CommandTag,
3039) -> Result<T, NodeError> {
3040 payloads.next().ok_or_else(|| command_payload_error(tag))
3041}
3042
3043fn attach_child_at(
3044 applier: &mut dyn Applier,
3045 parent_id: NodeId,
3046 child_id: NodeId,
3047 insert_index: Option<usize>,
3048 bubble: DirtyBubble,
3049) {
3050 if insert_child_with_reparenting(applier, parent_id, child_id) {
3051 if let Some(target) = insert_index {
3052 move_attached_child_left_to(applier, parent_id, child_id, target);
3053 }
3054 bubble.apply(applier, parent_id);
3055 } else if let Ok(child) = applier.get_mut(child_id) {
3056 let dirty_bubble = DirtyBubble {
3057 layout: child.layout_dirty(),
3058 measure: child.needs_measure() || child.descendant_needs_measure(),
3059 semantics: false,
3060 };
3061 dirty_bubble.apply_to_ancestors(applier, child_id);
3062 }
3063}
3064
3065fn move_attached_child_left_to(
3066 applier: &mut dyn Applier,
3067 parent_id: NodeId,
3068 child_id: NodeId,
3069 target: usize,
3070) {
3071 let Ok(parent_node) = applier.get_mut(parent_id) else {
3072 return;
3073 };
3074 let Some(current_index) = parent_node.owned_child_index(child_id) else {
3075 return;
3076 };
3077 if target < current_index {
3078 parent_node.move_child(current_index, target);
3079 note_structural_move(parent_id, current_index, target);
3080 }
3081}
3082
3083fn insert_child_with_reparenting(
3084 applier: &mut dyn Applier,
3085 parent_id: NodeId,
3086 child_id: NodeId,
3087) -> bool {
3088 if parent_id == child_id {
3089 debug_assert_ne!(
3090 parent_id, child_id,
3091 "a node cannot be attached as its own child"
3092 );
3093 return false;
3094 }
3095
3096 let old_parent = applier
3097 .get_mut(child_id)
3098 .ok()
3099 .and_then(|node| node.parent());
3100 if let Some(old_parent_id) = old_parent
3101 && old_parent_id != parent_id
3102 {
3103 let removed = applier
3104 .get_mut(old_parent_id)
3105 .is_ok_and(|old_parent_node| old_parent_node.remove_child(child_id));
3106 if let Ok(child_node) = applier.get_mut(child_id) {
3107 child_node.on_removed_from_parent();
3108 }
3109 if removed {
3110 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, old_parent_id);
3111 note_structural("reparent-detach", old_parent_id, child_id);
3112 applier.record_structural_change(old_parent_id);
3113 }
3114 }
3115
3116 let inserted = applier
3117 .get_mut(parent_id)
3118 .is_ok_and(|parent_node| parent_node.insert_child(child_id));
3119 if inserted {
3120 note_structural("attach", parent_id, child_id);
3121 applier.record_structural_change(parent_id);
3122 }
3123 if let Ok(child_node) = applier.get_mut(child_id) {
3124 child_node.on_attached_to_parent(parent_id);
3125 }
3126 inserted
3127}
3128
3129fn apply_remove_child(
3130 applier: &mut dyn Applier,
3131 parent_id: NodeId,
3132 child_id: NodeId,
3133 deferred_cleanup: &mut DeferredChildCleanupQueue,
3134) -> Result<(), NodeError> {
3135 detach_child_from_parent(applier, parent_id, child_id)?;
3136
3137 let generation = applier.node_generation(child_id);
3138 let removed_from_parent = if let Ok(node) = applier.get_mut(child_id) {
3139 node.parent().is_none()
3140 } else {
3141 return Ok(());
3142 };
3143 deferred_cleanup.push(child_id, generation, removed_from_parent);
3144 Ok(())
3145}
3146
3147fn detach_child_from_parent(
3148 applier: &mut dyn Applier,
3149 parent_id: NodeId,
3150 child_id: NodeId,
3151) -> Result<(), NodeError> {
3152 let removed = applier
3153 .get_mut(parent_id)
3154 .is_ok_and(|parent_node| parent_node.remove_child(child_id));
3155 if removed {
3156 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3157 note_structural("detach", parent_id, child_id);
3158 applier.record_structural_change(parent_id);
3159 }
3160
3161 if let Ok(node) = applier.get_mut(child_id) {
3162 match node.parent() {
3163 Some(existing_parent_id) if existing_parent_id == parent_id => {
3164 node.on_removed_from_parent();
3165 }
3166 None => {}
3167 Some(_) => return Ok(()),
3168 }
3169 } else {
3170 return Ok(());
3171 }
3172
3173 Ok(())
3174}
3175
3176fn cleanup_detached_child(
3177 applier: &mut dyn Applier,
3178 cleanup: DeferredChildCleanup,
3179) -> Result<(), NodeError> {
3180 if applier.node_generation(cleanup.child_id) != cleanup.generation {
3181 return Ok(());
3182 }
3183
3184 let parent_id = match applier.get_mut(cleanup.child_id) {
3185 Ok(node) => node.parent(),
3186 Err(NodeError::Missing { .. }) => return Ok(()),
3187 Err(err) => return Err(err),
3188 };
3189 if parent_id.is_some() {
3190 return Ok(());
3191 }
3192
3193 if let Ok(node) = applier.get_mut(cleanup.child_id) {
3194 if !cleanup.removed_from_parent {
3195 node.on_removed_from_parent();
3196 }
3197 node.unmount();
3198 }
3199 match applier.remove(cleanup.child_id) {
3200 Ok(()) | Err(NodeError::Missing { .. }) => Ok(()),
3201 Err(err) => Err(err),
3202 }
3203}
3204
3205fn remove_child_and_cleanup_now(
3206 applier: &mut dyn Applier,
3207 parent_id: NodeId,
3208 child_id: NodeId,
3209) -> Result<(), NodeError> {
3210 let mut deferred_cleanup = DeferredChildCleanupQueue::default();
3211 apply_remove_child(applier, parent_id, child_id, &mut deferred_cleanup)?;
3212 deferred_cleanup.flush(applier)
3213}
3214
3215fn collect_current_children(applier: &mut dyn Applier, parent_id: NodeId) -> ChildList {
3216 let mut scratch = SmallVec::<[NodeId; 8]>::new();
3217 if let Ok(node) = applier.get_mut(parent_id) {
3218 node.collect_children_into(&mut scratch);
3219 }
3220 let mut current = ChildList::new();
3221 current.extend(scratch);
3222 current
3223}
3224
3225fn sync_children(
3226 applier: &mut dyn Applier,
3227 parent_id: NodeId,
3228 expected_children: &[NodeId],
3229 deferred_cleanup: &mut DeferredChildCleanupQueue,
3230) -> Result<(), NodeError> {
3231 let mut current = collect_current_children(applier, parent_id);
3232 let children_changed = current.as_slice() != expected_children;
3233
3234 if children_changed {
3235 if current.len().max(expected_children.len()) <= SMALL_CHILD_SYNC_LINEAR_THRESHOLD {
3236 sync_children_small(
3237 applier,
3238 parent_id,
3239 &mut current,
3240 expected_children,
3241 deferred_cleanup,
3242 )?;
3243 } else {
3244 let mut target_positions: HashMap<NodeId, usize> = HashMap::default();
3245 target_positions.reserve(expected_children.len());
3246 for (index, &child) in expected_children.iter().enumerate() {
3247 target_positions.insert(child, index);
3248 }
3249
3250 for index in (0..current.len()).rev() {
3251 let child = current[index];
3252 if !target_positions.contains_key(&child) {
3253 current.remove(index);
3254 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3255 }
3256 }
3257
3258 let mut current_positions = build_child_positions(¤t);
3259 for (target_index, &child) in expected_children.iter().enumerate() {
3260 if let Some(current_index) = current_positions.get(&child).copied() {
3261 if current_index != target_index {
3262 let from_index = current_index;
3263 let to_index = move_child_in_diff_state(
3264 &mut current,
3265 &mut current_positions,
3266 from_index,
3267 target_index,
3268 );
3269 Command::MoveChild {
3270 parent_id,
3271 from_index,
3272 to_index,
3273 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3274 }
3275 .apply(applier)?;
3276 }
3277 } else {
3278 let insert_index = target_index.min(current.len());
3279 let appended_index = current.len();
3280 insert_child_into_diff_state(
3281 &mut current,
3282 &mut current_positions,
3283 insert_index,
3284 child,
3285 );
3286 Command::InsertChild {
3287 parent_id,
3288 child_id: child,
3289 appended_index,
3290 insert_index,
3291 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3292 }
3293 .apply(applier)?;
3294 }
3295 }
3296 }
3297 }
3298
3299 reconcile_children(applier, parent_id, expected_children, !children_changed)
3300}
3301
3302fn sync_children_small(
3303 applier: &mut dyn Applier,
3304 parent_id: NodeId,
3305 current: &mut ChildList,
3306 expected_children: &[NodeId],
3307 deferred_cleanup: &mut DeferredChildCleanupQueue,
3308) -> Result<(), NodeError> {
3309 for index in (0..current.len()).rev() {
3310 let child = current[index];
3311 if !expected_children.contains(&child) {
3312 current.remove(index);
3313 apply_remove_child(applier, parent_id, child, deferred_cleanup)?;
3314 }
3315 }
3316
3317 for (target_index, &child) in expected_children.iter().enumerate() {
3318 if let Some(current_index) = current
3319 .iter()
3320 .position(|¤t_child| current_child == child)
3321 {
3322 if current_index != target_index {
3323 let child = current.remove(current_index);
3324 let to_index = target_index.min(current.len());
3325 current.insert(to_index, child);
3326 Command::MoveChild {
3327 parent_id,
3328 from_index: current_index,
3329 to_index,
3330 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3331 }
3332 .apply(applier)?;
3333 }
3334 } else {
3335 let insert_index = target_index.min(current.len());
3336 let appended_index = current.len();
3337 current.insert(insert_index, child);
3338 Command::InsertChild {
3339 parent_id,
3340 child_id: child,
3341 appended_index,
3342 insert_index,
3343 bubble: DirtyBubble::LAYOUT_AND_MEASURE,
3344 }
3345 .apply(applier)?;
3346 }
3347 }
3348
3349 Ok(())
3350}
3351
3352fn reconcile_children(
3357 applier: &mut dyn Applier,
3358 parent_id: NodeId,
3359 expected_children: &[NodeId],
3360 children_unchanged: bool,
3361) -> Result<(), NodeError> {
3362 let mut repaired = false;
3363 if children_unchanged {
3364 debug_assert!(
3365 expected_children.iter().all(|&child_id| applier
3366 .get_mut(child_id)
3367 .map_or(true, |node| node.parent().is_none_or(|p| p == parent_id))),
3368 "the children a parent holds must name no other parent"
3369 );
3370 } else {
3371 for &child_id in expected_children {
3372 let needs_attach = applier
3373 .get_mut(child_id)
3374 .is_ok_and(|node| node.parent() != Some(parent_id));
3375 if needs_attach {
3376 insert_child_with_reparenting(applier, parent_id, child_id);
3377 repaired = true;
3378 }
3379 }
3380 }
3381
3382 let is_dirty = children_unchanged
3383 && applier
3384 .get_mut(parent_id)
3385 .is_ok_and(|node| node.layout_dirty());
3386
3387 if repaired {
3388 DirtyBubble::LAYOUT_AND_MEASURE.apply(applier, parent_id);
3389 } else if is_dirty {
3390 DirtyBubble::LAYOUT.apply_to_ancestors(applier, parent_id);
3391 }
3392
3393 Ok(())
3394}
3395
3396#[derive(Default)]
3397pub struct MemoryApplier {
3398 nodes: Vec<Option<Box<dyn Node>>>,
3399 physical_stable_ids: Vec<u32>,
3400 physical_warm_recycled_origins: Vec<bool>,
3401 stable_index: stable_index::StableIndex,
3402 free_ids: BinaryHeap<Reverse<usize>>,
3403 high_id_nodes: HashMap<NodeId, Box<dyn Node>>,
3404 high_id_warm_recycled_origins: HashMap<NodeId, bool>,
3405 high_id_generations: HashMap<NodeId, u32>,
3406 next_stable_id: NodeId,
3407 layout_runtime: Option<RuntimeHandle>,
3408 slots: SlotTable,
3409 recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3410 returning_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3411 cold_recycled_nodes: HashMap<TypeId, Vec<RecycledNode>>,
3412 recycled_node_limits: HashMap<TypeId, usize>,
3413 warm_recycled_node_targets: HashMap<TypeId, usize>,
3414 fresh_recyclable_creations: HashMap<TypeId, usize>,
3415 recycled_node_prototypes: HashMap<TypeId, Box<dyn Node>>,
3416 structural_change_parents: Vec<NodeId>,
3417 virtual_node_ids: HashSet<NodeId>,
3418}
3419
3420#[derive(Default)]
3425pub struct SceneNodeAttachmentScratch {
3426 attached: HashMap<NodeId, bool>,
3427 path: Vec<NodeId>,
3428}
3429
3430struct RemovalFrame {
3431 node_id: NodeId,
3432 children: SmallVec<[NodeId; 8]>,
3433 next_child: usize,
3434}
3435
3436#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3437pub struct MemoryApplierDebugStats {
3438 pub next_stable_id: NodeId,
3439 pub nodes_len: usize,
3440 pub nodes_cap: usize,
3441 pub physical_stable_ids_len: usize,
3442 pub physical_stable_ids_cap: usize,
3443 pub stable_to_physical_len: usize,
3444 pub stable_to_physical_cap: usize,
3445 pub stable_generations_len: usize,
3446 pub stable_generations_cap: usize,
3447 pub free_ids_len: usize,
3448 pub free_ids_cap: usize,
3449 pub high_id_nodes_len: usize,
3450 pub high_id_nodes_cap: usize,
3451 pub high_id_generations_len: usize,
3452 pub high_id_generations_cap: usize,
3453 pub recycled_type_count: usize,
3454 pub recycled_type_cap: usize,
3455 pub recycled_node_count: usize,
3456 pub recycled_node_capacity: usize,
3457 pub warm_recycled_node_id_count: usize,
3458 pub warm_recycled_node_id_capacity: usize,
3459}
3460
3461impl MemoryApplier {
3462 const EAGER_COMPACT_NODE_LEN: usize = 1_024;
3463 const HIGH_ID_THRESHOLD: NodeId = 1_000_000_000;
3464 const INVALID_STABLE_ID: u32 = u32::MAX;
3465 const INITIAL_DENSE_NODE_CAP: usize = 32;
3466 const LARGE_DENSE_NODE_GROWTH_THRESHOLD: usize = 32 * 1024;
3467 const LARGE_DENSE_NODE_GROWTH_DIVISOR: usize = 4;
3468
3469 fn pack_stable_id(stable_id: NodeId) -> u32 {
3470 u32::try_from(stable_id).expect("stable id overflow")
3471 }
3472
3473 fn unpack_stable_id(stable_id: u32) -> NodeId {
3474 stable_id as NodeId
3475 }
3476
3477 fn next_dense_node_target_len(old_len: usize) -> usize {
3478 if old_len < Self::INITIAL_DENSE_NODE_CAP {
3479 return Self::INITIAL_DENSE_NODE_CAP;
3480 }
3481 if old_len < Self::LARGE_DENSE_NODE_GROWTH_THRESHOLD {
3482 return old_len.saturating_mul(2);
3483 }
3484
3485 let incremental_growth =
3486 (old_len / Self::LARGE_DENSE_NODE_GROWTH_DIVISOR).max(Self::INITIAL_DENSE_NODE_CAP);
3487 old_len.saturating_add(incremental_growth)
3488 }
3489
3490 fn ensure_dense_node_storage_capacity(&mut self) {
3491 let len = self
3492 .nodes
3493 .len()
3494 .max(self.physical_stable_ids.len())
3495 .max(self.physical_warm_recycled_origins.len());
3496 if len < self.nodes.capacity()
3497 && len < self.physical_stable_ids.capacity()
3498 && len < self.physical_warm_recycled_origins.capacity()
3499 {
3500 return;
3501 }
3502
3503 let target = Self::next_dense_node_target_len(len);
3504 if self.nodes.capacity() < target {
3505 self.nodes
3506 .reserve_exact(target.saturating_sub(self.nodes.len()));
3507 }
3508 if self.physical_stable_ids.capacity() < target {
3509 self.physical_stable_ids
3510 .reserve_exact(target.saturating_sub(self.physical_stable_ids.len()));
3511 }
3512 if self.physical_warm_recycled_origins.capacity() < target {
3513 self.physical_warm_recycled_origins
3514 .reserve_exact(target.saturating_sub(self.physical_warm_recycled_origins.len()));
3515 }
3516 }
3517
3518 pub fn new() -> Self {
3519 Self {
3520 nodes: Vec::new(),
3521 physical_stable_ids: Vec::new(),
3522 physical_warm_recycled_origins: Vec::new(),
3523 stable_index: stable_index::StableIndex::default(),
3524 free_ids: BinaryHeap::new(),
3525 high_id_nodes: HashMap::default(),
3526 high_id_warm_recycled_origins: HashMap::default(),
3527 high_id_generations: HashMap::default(),
3528 next_stable_id: 0,
3529 layout_runtime: None,
3530 slots: SlotTable::default(),
3531 recycled_nodes: HashMap::default(),
3532 returning_recycled_nodes: HashMap::default(),
3533 cold_recycled_nodes: HashMap::default(),
3534 recycled_node_limits: HashMap::default(),
3535 warm_recycled_node_targets: HashMap::default(),
3536 fresh_recyclable_creations: HashMap::default(),
3537 recycled_node_prototypes: HashMap::default(),
3538 structural_change_parents: Vec::new(),
3539 virtual_node_ids: HashSet::default(),
3540 }
3541 }
3542
3543 pub fn slots(&mut self) -> &mut SlotTable {
3544 &mut self.slots
3545 }
3546
3547 pub fn scene_node_attached_to(&mut self, node_id: NodeId, root: NodeId) -> Option<NodeId> {
3560 let resolved = self.first_non_virtual_ancestor(node_id)?;
3561 self.is_attached_to(resolved, root).then_some(resolved)
3562 }
3563
3564 pub fn scene_nodes_attached_to(
3568 &mut self,
3569 nodes: impl IntoIterator<Item = NodeId>,
3570 root: NodeId,
3571 ) -> Vec<Option<NodeId>> {
3572 let mut result = Vec::new();
3573 self.scene_nodes_attached_to_into(
3574 nodes,
3575 root,
3576 &mut result,
3577 &mut SceneNodeAttachmentScratch::default(),
3578 );
3579 result
3580 }
3581
3582 pub fn scene_nodes_attached_to_into(
3600 &mut self,
3601 nodes: impl IntoIterator<Item = NodeId>,
3602 root: NodeId,
3603 output: &mut Vec<Option<NodeId>>,
3604 scratch: &mut SceneNodeAttachmentScratch,
3605 ) {
3606 let nodes = nodes.into_iter();
3607 scratch.attached.clear();
3608 output.clear();
3609 let (lower_bound, upper_bound) = nodes.size_hint();
3610 if lower_bound == 0 && upper_bound == Some(0) {
3611 scratch.path.clear();
3612 return;
3613 }
3614 scratch.attached.insert(root, true);
3615 output.reserve(lower_bound);
3616 scratch.path.clear();
3617 for node_id in nodes {
3618 let Some(resolved) = self.first_non_virtual_ancestor(node_id) else {
3619 output.push(None);
3620 continue;
3621 };
3622 let mut current = resolved;
3623 scratch.path.clear();
3624 let answer = loop {
3625 if let Some(known) = scratch.attached.get(¤t) {
3626 break *known;
3627 }
3628 scratch.path.push(current);
3629 match self.get_mut(current).ok().and_then(|node| node.parent()) {
3630 Some(parent) if scratch.path.len() < 100_000 => current = parent,
3631 _ => break false,
3632 }
3633 };
3634 for visited in scratch.path.drain(..) {
3635 scratch.attached.insert(visited, answer);
3636 }
3637 output.push(answer.then_some(resolved));
3638 }
3639 }
3640
3641 pub fn take_structural_change_parents_attached_to(&mut self, root: NodeId) -> Vec<NodeId> {
3642 let mut candidates = Vec::new();
3643 self.take_structural_change_parents_into(&mut candidates);
3644 let mut seen = HashSet::<NodeId>::default();
3645 candidates.retain_mut(|parent_id| {
3646 let Some(resolved) = self.first_non_virtual_ancestor(*parent_id) else {
3647 return false;
3648 };
3649 *parent_id = resolved;
3650 self.is_attached_to(resolved, root) && seen.insert(resolved)
3651 });
3652 candidates
3653 }
3654
3655 pub fn take_structural_change_parents_into(&mut self, output: &mut Vec<NodeId>) {
3668 output.clear();
3669 std::mem::swap(&mut self.structural_change_parents, output);
3670 }
3671
3672 fn first_non_virtual_ancestor(&mut self, node_id: NodeId) -> Option<NodeId> {
3673 let mut current = node_id;
3674 for _ in 0..100_000 {
3675 if !self.virtual_node_ids.contains(¤t) {
3676 return Some(current);
3677 }
3678 match self.get_mut(current) {
3679 Ok(node) => current = node.parent()?,
3680 Err(_) => return None,
3681 }
3682 }
3683 None
3684 }
3685
3686 fn is_attached_to(&mut self, node_id: NodeId, root: NodeId) -> bool {
3687 let mut current = node_id;
3688 for _ in 0..100_000 {
3689 if current == root {
3690 return true;
3691 }
3692 match self.get_mut(current) {
3693 Ok(node) => match node.parent() {
3694 Some(parent) => current = parent,
3695 None => return false,
3696 },
3697 Err(_) => return false,
3698 }
3699 }
3700 false
3701 }
3702
3703 pub fn with_node<N: Node + 'static, R>(
3704 &mut self,
3705 id: NodeId,
3706 f: impl FnOnce(&mut N) -> R,
3707 ) -> Result<R, NodeError> {
3708 let typed = self
3709 .get_mut(id)?
3710 .as_any_mut()
3711 .downcast_mut::<N>()
3712 .ok_or_else(|| NodeError::TypeMismatch {
3713 id,
3714 expected: std::any::type_name::<N>(),
3715 })?;
3716 Ok(f(typed))
3717 }
3718
3719 pub fn len(&self) -> usize {
3720 self.nodes.iter().filter(|n| n.is_some()).count()
3721 }
3722
3723 pub fn capacity(&self) -> usize {
3724 self.nodes.len()
3725 }
3726
3727 pub fn tombstone_count(&self) -> usize {
3728 self.nodes.iter().filter(|n| n.is_none()).count()
3729 }
3730
3731 pub fn freelist_len(&self) -> usize {
3732 self.free_ids.len()
3733 }
3734
3735 pub fn debug_recycled_node_count(&self) -> usize {
3736 self.total_recycled_node_count()
3737 }
3738
3739 pub fn debug_recycled_node_count_for<N: Node + 'static>(&self) -> usize {
3740 let key = TypeId::of::<N>();
3741 self.recycled_nodes.get(&key).map_or(0, Vec::len)
3742 + self.returning_recycled_nodes.get(&key).map_or(0, Vec::len)
3743 + self.cold_recycled_nodes.get(&key).map_or(0, Vec::len)
3744 }
3745
3746 pub fn debug_stats(&self) -> MemoryApplierDebugStats {
3747 let mut recycled_keys: HashSet<TypeId> = HashSet::default();
3748 recycled_keys.extend(self.recycled_nodes.keys().copied());
3749 recycled_keys.extend(self.returning_recycled_nodes.keys().copied());
3750 recycled_keys.extend(self.cold_recycled_nodes.keys().copied());
3751
3752 MemoryApplierDebugStats {
3753 next_stable_id: self.next_stable_id,
3754 nodes_len: self.len(),
3755 nodes_cap: self.nodes.len(),
3756 physical_stable_ids_len: self.physical_stable_ids.len(),
3757 physical_stable_ids_cap: self.physical_stable_ids.capacity(),
3758 stable_to_physical_len: self.stable_index.live(),
3759 stable_to_physical_cap: self.stable_index.capacity(),
3760 stable_generations_len: self.stable_index.occupied(),
3761 stable_generations_cap: self.stable_index.capacity(),
3762 free_ids_len: self.free_ids.len(),
3763 free_ids_cap: self.free_ids.capacity(),
3764 high_id_nodes_len: self.high_id_nodes.len(),
3765 high_id_nodes_cap: self.high_id_nodes.capacity(),
3766 high_id_generations_len: self.high_id_generations.len(),
3767 high_id_generations_cap: self.high_id_generations.capacity(),
3768 recycled_type_count: recycled_keys.len(),
3769 recycled_type_cap: self.recycled_nodes.capacity()
3770 + self.returning_recycled_nodes.capacity()
3771 + self.cold_recycled_nodes.capacity(),
3772 recycled_node_count: self.total_recycled_node_count(),
3773 recycled_node_capacity: self.total_recycled_node_capacity(),
3774 warm_recycled_node_id_count: self.total_warm_recycled_node_id_count(),
3775 warm_recycled_node_id_capacity: self.total_warm_recycled_node_id_capacity(),
3776 }
3777 }
3778
3779 pub fn is_empty(&self) -> bool {
3780 self.len() == 0
3781 }
3782
3783 pub fn for_each_node_mut(&mut self, mut visit: impl FnMut(&mut dyn Node)) {
3785 for node in self.nodes.iter_mut().flatten() {
3786 visit(node.as_mut());
3787 }
3788 for node in self.high_id_nodes.values_mut() {
3789 visit(node.as_mut());
3790 }
3791 }
3792
3793 pub fn debug_live_node_heap_bytes(&self) -> usize {
3794 let dense_nodes = self
3795 .nodes
3796 .iter()
3797 .flatten()
3798 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3799 .sum::<usize>();
3800 let high_id_nodes = self
3801 .high_id_nodes
3802 .values()
3803 .map(|node| std::mem::size_of_val(&**node) + node.debug_heap_bytes())
3804 .sum::<usize>();
3805 dense_nodes + high_id_nodes
3806 }
3807
3808 pub fn debug_recycled_node_heap_bytes(&self) -> usize {
3809 let pool_bytes = |pools: &HashMap<TypeId, Vec<RecycledNode>>| {
3810 pools
3811 .values()
3812 .flat_map(|nodes| nodes.iter())
3813 .map(|node| std::mem::size_of_val(&*node.node) + node.node.debug_heap_bytes())
3814 .sum::<usize>()
3815 };
3816
3817 pool_bytes(&self.recycled_nodes)
3818 + pool_bytes(&self.returning_recycled_nodes)
3819 + pool_bytes(&self.cold_recycled_nodes)
3820 }
3821
3822 pub fn set_runtime_handle(&mut self, handle: RuntimeHandle) {
3823 self.layout_runtime = Some(handle);
3824 }
3825
3826 pub fn clear_runtime_handle(&mut self) {
3827 self.layout_runtime = None;
3828 }
3829
3830 pub fn runtime_handle(&self) -> Option<RuntimeHandle> {
3831 self.layout_runtime.clone()
3832 }
3833
3834 fn pool_node_count(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3835 pools.values().map(Vec::len).sum()
3836 }
3837
3838 fn pool_node_capacity(pools: &HashMap<TypeId, Vec<RecycledNode>>) -> usize {
3839 pools.values().map(Vec::capacity).sum()
3840 }
3841
3842 fn total_recycled_node_count(&self) -> usize {
3843 Self::pool_node_count(&self.recycled_nodes)
3844 + Self::pool_node_count(&self.returning_recycled_nodes)
3845 + Self::pool_node_count(&self.cold_recycled_nodes)
3846 }
3847
3848 fn total_recycled_node_capacity(&self) -> usize {
3849 Self::pool_node_capacity(&self.recycled_nodes)
3850 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3851 + Self::pool_node_capacity(&self.cold_recycled_nodes)
3852 }
3853
3854 fn total_warm_recycled_node_id_count(&self) -> usize {
3855 self.live_warm_recycled_origin_count()
3856 + Self::pool_node_count(&self.recycled_nodes)
3857 + Self::pool_node_count(&self.returning_recycled_nodes)
3858 }
3859
3860 fn total_warm_recycled_node_id_capacity(&self) -> usize {
3861 self.live_warm_recycled_origin_capacity()
3862 + Self::pool_node_capacity(&self.recycled_nodes)
3863 + Self::pool_node_capacity(&self.returning_recycled_nodes)
3864 }
3865
3866 fn remember_recycle_pool_limit(&mut self, key: TypeId, recycle_pool_limit: Option<usize>) {
3867 if let Some(limit) = recycle_pool_limit {
3868 self.recycled_node_limits.insert(key, limit);
3869 } else {
3870 self.recycled_node_limits.remove(&key);
3871 }
3872 }
3873
3874 fn recycle_pool_limit_for(&self, key: TypeId) -> Option<usize> {
3875 self.recycled_node_limits.get(&key).copied()
3876 }
3877
3878 fn warm_recycled_pool_len(&self, key: TypeId) -> usize {
3879 self.recycled_nodes.get(&key).map_or(0, Vec::len)
3880 }
3881
3882 fn warm_recycled_node_target(&self, key: TypeId) -> usize {
3883 self.warm_recycled_node_targets
3884 .get(&key)
3885 .copied()
3886 .unwrap_or(0)
3887 }
3888
3889 fn warm_recycled_node_target_limit(&self, key: TypeId) -> usize {
3890 let Some(limit) = self.recycle_pool_limit_for(key) else {
3891 return usize::MAX;
3892 };
3893 if limit <= 8 { limit } else { limit / 4 }
3894 }
3895
3896 fn update_warm_recycled_node_target(&mut self, key: TypeId, observed_demand: usize) -> usize {
3897 let target_limit = self.warm_recycled_node_target_limit(key);
3898 let existing = self.warm_recycled_node_target(key).min(target_limit);
3899 if observed_demand == 0 {
3900 return existing;
3901 }
3902
3903 let target = match self.recycle_pool_limit_for(key) {
3904 Some(limit) if limit > 8 => target_limit,
3905 Some(_) => observed_demand.min(target_limit),
3906 None => observed_demand,
3907 };
3908 self.warm_recycled_node_targets.insert(key, target);
3909 target
3910 }
3911
3912 fn remember_recycled_node_prototype(&mut self, key: TypeId, shell: &dyn Node) {
3913 if self.recycled_node_prototypes.contains_key(&key) {
3914 return;
3915 }
3916 if let Some(prototype) = shell.rehouse_for_recycle() {
3917 self.recycled_node_prototypes.insert(key, prototype);
3918 }
3919 }
3920
3921 fn live_warm_recycled_origin_count(&self) -> usize {
3922 self.physical_warm_recycled_origins
3923 .iter()
3924 .zip(self.nodes.iter())
3925 .filter(|(warm_origin, node)| **warm_origin && node.is_some())
3926 .count()
3927 + self
3928 .high_id_warm_recycled_origins
3929 .values()
3930 .filter(|warm_origin| **warm_origin)
3931 .count()
3932 }
3933
3934 fn live_warm_recycled_origin_capacity(&self) -> usize {
3935 self.physical_warm_recycled_origins.capacity()
3936 + self.high_id_warm_recycled_origins.capacity()
3937 }
3938
3939 fn push_recycled_node(
3940 &mut self,
3941 key: TypeId,
3942 recycle_pool_limit: Option<usize>,
3943 recycled: RecycledNode,
3944 ) {
3945 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3946 self.remember_recycled_node_prototype(key, recycled.node.as_ref());
3947
3948 let warm_origin = recycled.warm_origin();
3949 let pool = if warm_origin {
3950 self.returning_recycled_nodes.entry(key).or_default()
3951 } else {
3952 self.cold_recycled_nodes.entry(key).or_default()
3953 };
3954 pool.push(recycled);
3955 if let Some(limit) = recycle_pool_limit
3956 && pool.len() > limit
3957 {
3958 let excess = pool.len() - limit;
3959 let dropped: Vec<_> = pool.drain(0..excess).collect();
3960 drop(dropped);
3961 }
3962 }
3963
3964 fn push_warm_recycled_node(
3965 &mut self,
3966 key: TypeId,
3967 recycle_pool_limit: Option<usize>,
3968 mut recycled: RecycledNode,
3969 ) {
3970 self.remember_recycle_pool_limit(key, recycle_pool_limit);
3971
3972 recycled.set_warm_origin(true);
3973 let mut dropped = Vec::new();
3974 let mut remove_pool_entry = false;
3975 {
3976 let pool = self.recycled_nodes.entry(key).or_default();
3977 pool.push(recycled);
3978 if let Some(limit) = recycle_pool_limit
3979 && pool.len() > limit
3980 {
3981 let excess = pool.len() - limit;
3982 dropped = pool.drain(0..excess).collect();
3983 remove_pool_entry = pool.is_empty();
3984 }
3985 }
3986 if remove_pool_entry {
3987 self.recycled_nodes.remove(&key);
3988 }
3989 drop(dropped);
3990 }
3991
3992 fn seed_recycled_node_shell_impl(
3993 &mut self,
3994 key: TypeId,
3995 recycle_pool_limit: Option<usize>,
3996 shell: Box<dyn Node>,
3997 ) {
3998 let limit = recycle_pool_limit.unwrap_or(usize::MAX);
3999 if self.warm_recycled_pool_len(key) >= limit {
4000 return;
4001 }
4002
4003 self.remember_recycled_node_prototype(key, shell.as_ref());
4004 let stable_id = self.next_stable_id;
4005 self.next_stable_id = self.next_stable_id.saturating_add(1);
4006 self.push_warm_recycled_node(
4007 key,
4008 recycle_pool_limit,
4009 RecycledNode::from_shell(stable_id, shell, true),
4010 );
4011 }
4012
4013 fn take_recycled_node_from_pool(
4014 pools: &mut HashMap<TypeId, Vec<RecycledNode>>,
4015 key: TypeId,
4016 ) -> Option<RecycledNode> {
4017 let pool = pools.get_mut(&key)?;
4018 let node = pool.pop();
4019 if pool.is_empty() {
4020 pools.remove(&key);
4021 }
4022 node
4023 }
4024
4025 fn compact_idle_warm_pool(&mut self, key: TypeId) {
4026 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
4027 return;
4028 };
4029 if pool.capacity() <= pool.len().saturating_mul(4).max(64) {
4030 return;
4031 }
4032
4033 let retained = pool.len();
4034 let mut compacted = Vec::with_capacity(retained);
4035 compacted.append(pool);
4036 let remove_pool_entry = compacted.is_empty();
4037 *pool = compacted;
4038 let _ = pool;
4039
4040 if remove_pool_entry {
4041 self.recycled_nodes.remove(&key);
4042 }
4043 }
4044
4045 fn trim_idle_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4046 let pool_len = self.warm_recycled_pool_len(key);
4047 if pool_len <= target {
4048 return;
4049 }
4050
4051 let Some(pool) = self.recycled_nodes.get_mut(&key) else {
4052 return;
4053 };
4054 let removable = (pool_len - target).min(pool.len());
4055 let dropped: Vec<_> = pool.drain(0..removable).collect();
4056 let remove_pool_entry = pool.is_empty();
4057 let _ = pool;
4058
4059 if remove_pool_entry {
4060 self.recycled_nodes.remove(&key);
4061 }
4062 drop(dropped);
4063 }
4064
4065 fn replenish_warm_pool_to_target(&mut self, key: TypeId, target: usize) {
4066 let missing = target.saturating_sub(self.warm_recycled_pool_len(key));
4067 if missing == 0 {
4068 return;
4069 }
4070
4071 let recycle_pool_limit = self.recycle_pool_limit_for(key);
4072 let mut shells = Vec::with_capacity(missing);
4073 if let Some(prototype) = self.recycled_node_prototypes.get(&key) {
4074 for _ in 0..missing {
4075 let Some(shell) = prototype.rehouse_for_recycle() else {
4076 break;
4077 };
4078 shells.push(shell);
4079 }
4080 }
4081
4082 for shell in shells {
4083 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4084 }
4085 }
4086
4087 fn prune_stable_generations(&mut self) {
4088 let retained_len = self.stable_index.live() + self.total_recycled_node_count();
4089 if retained_len == self.stable_index.occupied() {
4090 return;
4091 }
4092
4093 let recycled: HashSet<NodeId> = [
4094 &self.recycled_nodes,
4095 &self.returning_recycled_nodes,
4096 &self.cold_recycled_nodes,
4097 ]
4098 .into_iter()
4099 .flat_map(|pools| pools.values().flatten().map(RecycledNode::stable_id))
4100 .collect();
4101 self.stable_index
4102 .retain_retired(|stable_id| recycled.contains(&stable_id));
4103 }
4104
4105 pub fn dump_tree(&self, root: Option<NodeId>) -> String {
4106 let mut output = String::new();
4107 if let Some(root_id) = root {
4108 self.dump_node(&mut output, root_id, 0);
4109 } else {
4110 output.push_str("(no root)\n");
4111 }
4112 output
4113 }
4114
4115 fn dump_node(&self, output: &mut String, id: NodeId, depth: usize) {
4116 let indent = " ".repeat(depth);
4117 let Ok(node) = self.get_ref(id) else {
4118 if let Some(physical_id) = self.resolve_node_index(id) {
4119 output.push_str(&format!(
4120 "{indent}[{id}] (missing physical node {physical_id})\n"
4121 ));
4122 } else {
4123 output.push_str(&format!("{indent}[{id}] (missing)\n"));
4124 }
4125 return;
4126 };
4127 let type_name = std::any::type_name_of_val(node);
4128 output.push_str(&format!("{indent}[{id}] {type_name}\n"));
4129
4130 let mut children = SmallVec::<[NodeId; 8]>::new();
4131 node.collect_children_into(&mut children);
4132 for child_id in children {
4133 self.dump_node(output, child_id, depth + 1);
4134 }
4135 }
4136
4137 fn resolve_node_index(&self, id: NodeId) -> Option<usize> {
4138 self.stable_index.slot(id)
4139 }
4140
4141 fn contains_node_id(&self, id: NodeId) -> bool {
4142 self.resolve_node_index(id).is_some() || self.high_id_nodes.contains_key(&id)
4143 }
4144
4145 fn insert_high_id_node(&mut self, stable_id: NodeId, node: Box<dyn Node>, warm_origin: bool) {
4146 self.high_id_nodes.insert(stable_id, node);
4147 self.high_id_warm_recycled_origins
4148 .insert(stable_id, warm_origin);
4149 self.high_id_generations.entry(stable_id).or_insert(0);
4150 }
4151
4152 fn insert_available_with_id(&mut self, stable_id: NodeId, node: Box<dyn Node>) {
4153 if stable_id >= Self::HIGH_ID_THRESHOLD {
4154 self.insert_high_id_node(stable_id, node, false);
4155 return;
4156 }
4157
4158 let physical_id = if let Some(Reverse(free_physical_id)) = self.free_ids.pop() {
4159 self.nodes[free_physical_id] = Some(node);
4160 self.physical_stable_ids[free_physical_id] = Self::pack_stable_id(stable_id);
4161 self.physical_warm_recycled_origins[free_physical_id] = false;
4162 free_physical_id
4163 } else {
4164 self.ensure_dense_node_storage_capacity();
4165 let physical_id = self.nodes.len();
4166 self.nodes.push(Some(node));
4167 self.physical_stable_ids
4168 .push(Self::pack_stable_id(stable_id));
4169 self.physical_warm_recycled_origins.push(false);
4170 physical_id
4171 };
4172
4173 self.next_stable_id = self.next_stable_id.max(stable_id.saturating_add(1));
4174 self.physical_stable_ids[physical_id] = Self::pack_stable_id(stable_id);
4175 self.stable_index.set_slot(stable_id, physical_id);
4176 }
4177
4178 pub fn get_ref(&self, id: NodeId) -> Result<&dyn Node, NodeError> {
4181 if let Some(physical_id) = self.resolve_node_index(id) {
4182 let slot = self
4183 .nodes
4184 .get(physical_id)
4185 .ok_or(NodeError::Missing { id })?
4186 .as_deref()
4187 .ok_or(NodeError::Missing { id })?;
4188 return Ok(slot);
4189 }
4190
4191 self.high_id_nodes
4192 .get(&id)
4193 .map(AsRef::as_ref)
4194 .ok_or(NodeError::Missing { id })
4195 }
4196
4197 fn node_parent(&self, id: NodeId) -> Result<Option<NodeId>, NodeError> {
4198 Ok(self.get_ref(id)?.parent())
4199 }
4200
4201 fn collect_owned_children(
4202 &self,
4203 node_id: NodeId,
4204 out: &mut SmallVec<[NodeId; 8]>,
4205 ) -> Result<(), NodeError> {
4206 self.get_ref(node_id)?.collect_owned_children_into(out);
4207 out.retain(|child_id| {
4208 self.node_parent(*child_id)
4209 .is_ok_and(|parent| parent == Some(node_id))
4210 });
4211 Ok(())
4212 }
4213
4214 fn remove_node_storage(&mut self, node_id: NodeId) -> Result<(), NodeError> {
4215 self.virtual_node_ids.remove(&node_id);
4216 if self.high_id_nodes.contains_key(&node_id) {
4217 if let Some(node) = self.high_id_nodes.remove(&node_id)
4218 && let Some(key) = node.recycle_key()
4219 {
4220 let recycle_pool_limit = node.recycle_pool_limit();
4221 let warm_origin = self
4222 .high_id_warm_recycled_origins
4223 .remove(&node_id)
4224 .unwrap_or(false);
4225 self.push_recycled_node(
4226 key,
4227 recycle_pool_limit,
4228 RecycledNode::new(node_id, node, warm_origin),
4229 );
4230 }
4231 let generation = self.high_id_generations.entry(node_id).or_insert(0);
4232 *generation = generation.wrapping_add(1);
4233 return Ok(());
4234 }
4235
4236 let physical_id = self
4237 .resolve_node_index(node_id)
4238 .ok_or(NodeError::Missing { id: node_id })?;
4239 if let Some(node) = self.nodes[physical_id].take()
4240 && let Some(key) = node.recycle_key()
4241 {
4242 let recycle_pool_limit = node.recycle_pool_limit();
4243 let warm_origin = self
4244 .physical_warm_recycled_origins
4245 .get_mut(physical_id)
4246 .is_some_and(std::mem::take);
4247 self.push_recycled_node(
4248 key,
4249 recycle_pool_limit,
4250 RecycledNode::new(node_id, node, warm_origin),
4251 );
4252 }
4253 self.physical_stable_ids[physical_id] = Self::INVALID_STABLE_ID;
4254 self.stable_index.release(node_id);
4255 self.free_ids.push(Reverse(physical_id));
4256 Ok(())
4257 }
4258
4259 fn remove_subtree_postorder(&mut self, id: NodeId) -> Result<usize, NodeError> {
4260 self.get_ref(id)?;
4261
4262 let mut root_children = SmallVec::<[NodeId; 8]>::new();
4263 self.collect_owned_children(id, &mut root_children)?;
4264
4265 let mut stack = Vec::new();
4266 stack.push(RemovalFrame {
4267 node_id: id,
4268 children: root_children,
4269 next_child: 0,
4270 });
4271 let mut max_depth = stack.len();
4272
4273 while let Some(frame) = stack.last_mut() {
4274 if frame.next_child < frame.children.len() {
4275 let child_id = frame.children[frame.next_child];
4276 frame.next_child += 1;
4277
4278 if let Ok(child) = self.get_mut(child_id) {
4279 child.on_removed_from_parent();
4280 child.unmount();
4281 }
4282
4283 let mut child_children = SmallVec::<[NodeId; 8]>::new();
4284 self.collect_owned_children(child_id, &mut child_children)?;
4285 stack.push(RemovalFrame {
4286 node_id: child_id,
4287 children: child_children,
4288 next_child: 0,
4289 });
4290 max_depth = max_depth.max(stack.len());
4291 continue;
4292 }
4293
4294 let node_id = frame.node_id;
4295 stack.pop();
4296 self.remove_node_storage(node_id)?;
4297 }
4298
4299 Ok(max_depth)
4300 }
4301
4302 #[cfg(test)]
4303 fn debug_remove_max_traversal_depth(&mut self, id: NodeId) -> Result<usize, NodeError> {
4304 self.remove_subtree_postorder(id)
4305 }
4306}
4307
4308impl Applier for MemoryApplier {
4309 fn record_structural_change(&mut self, parent_id: NodeId) {
4310 if self.structural_change_parents.last() != Some(&parent_id) {
4311 self.structural_change_parents.push(parent_id);
4312 }
4313 }
4314
4315 fn create(&mut self, node: Box<dyn Node>) -> NodeId {
4316 let stable_id = self.next_stable_id;
4317 self.next_stable_id = self.next_stable_id.saturating_add(1);
4318 if stable_id >= Self::HIGH_ID_THRESHOLD {
4319 self.insert_high_id_node(stable_id, node, false);
4320 return stable_id;
4321 }
4322
4323 let physical_id = if let Some(Reverse(id)) = self.free_ids.pop() {
4324 debug_assert!(self.nodes[id].is_none(), "freelist entry {id} is not None");
4325 self.nodes[id] = Some(node);
4326 self.physical_stable_ids[id] = Self::pack_stable_id(stable_id);
4327 self.physical_warm_recycled_origins[id] = false;
4328 id
4329 } else {
4330 self.ensure_dense_node_storage_capacity();
4331 let id = self.nodes.len();
4332 self.nodes.push(Some(node));
4333 self.physical_stable_ids
4334 .push(Self::pack_stable_id(stable_id));
4335 self.physical_warm_recycled_origins.push(false);
4336 id
4337 };
4338 self.stable_index.insert_fresh(stable_id, physical_id);
4339 stable_id
4340 }
4341
4342 fn node_generation(&self, id: NodeId) -> u32 {
4343 if id >= Self::HIGH_ID_THRESHOLD {
4344 return self.high_id_generations.get(&id).copied().unwrap_or(0);
4345 }
4346 self.stable_index.generation(id)
4347 }
4348
4349 fn get_mut(&mut self, id: NodeId) -> Result<&mut dyn Node, NodeError> {
4350 if let Some(physical_id) = self.resolve_node_index(id) {
4351 let slot = self
4352 .nodes
4353 .get_mut(physical_id)
4354 .ok_or(NodeError::Missing { id })?
4355 .as_deref_mut()
4356 .ok_or(NodeError::Missing { id })?;
4357 return Ok(slot);
4358 }
4359 self.high_id_nodes
4360 .get_mut(&id)
4361 .map(std::convert::AsMut::as_mut)
4362 .ok_or(NodeError::Missing { id })
4363 }
4364
4365 fn remove(&mut self, id: NodeId) -> Result<(), NodeError> {
4366 self.remove_subtree_postorder(id).map(|_| ())
4367 }
4368
4369 fn insert_with_id(&mut self, id: NodeId, node: Box<dyn Node>) -> Result<(), NodeError> {
4370 if self.contains_node_id(id) {
4371 return Err(NodeError::AlreadyExists { id });
4372 }
4373 self.insert_available_with_id(id, node);
4374 self.virtual_node_ids.insert(id);
4375 Ok(())
4376 }
4377
4378 fn insert_recycled_node_or_create(
4379 &mut self,
4380 stable_id: NodeId,
4381 node: Box<dyn Node>,
4382 ) -> RecycledNodeInsertion {
4383 if self.contains_node_id(stable_id) {
4384 let id = self.create(node);
4385 return RecycledNodeInsertion::fresh(
4386 id,
4387 Some(NodeError::AlreadyExists { id: stable_id }),
4388 );
4389 }
4390
4391 self.insert_available_with_id(stable_id, node);
4392 RecycledNodeInsertion::reused(stable_id)
4393 }
4394
4395 fn compact(&mut self) {
4396 let live_count = self.nodes.iter().filter(|slot| slot.is_some()).count();
4397 let tombstone_count = self.nodes.len().saturating_sub(live_count);
4398 if tombstone_count == 0 {
4399 return;
4400 }
4401 if self.nodes.len() > Self::EAGER_COMPACT_NODE_LEN && tombstone_count < live_count {
4402 return;
4403 }
4404 let rehouse_live_nodes = tombstone_count >= live_count;
4405 let mut packed_nodes = Vec::with_capacity(live_count);
4406 let mut packed_physical_stable_ids = Vec::with_capacity(live_count);
4407 let mut packed_warm_recycled_origins = Vec::with_capacity(live_count);
4408
4409 for physical_id in 0..self.nodes.len() {
4410 let Some(mut node) = self.nodes[physical_id].take() else {
4411 continue;
4412 };
4413 if rehouse_live_nodes && let Some(rehoused) = node.rehouse_for_live_compaction() {
4414 node = rehoused;
4415 }
4416 let stable_id = std::mem::replace(
4417 &mut self.physical_stable_ids[physical_id],
4418 Self::INVALID_STABLE_ID,
4419 );
4420 debug_assert_ne!(
4421 stable_id,
4422 Self::INVALID_STABLE_ID,
4423 "live physical slot must have a stable id",
4424 );
4425 let stable_id = Self::unpack_stable_id(stable_id);
4426 packed_nodes.push(Some(node));
4427 packed_physical_stable_ids.push(Self::pack_stable_id(stable_id));
4428 packed_warm_recycled_origins.push(self.physical_warm_recycled_origins[physical_id]);
4429 self.stable_index
4430 .set_slot(stable_id, packed_nodes.len() - 1);
4431 }
4432
4433 self.nodes = packed_nodes;
4434 self.physical_stable_ids = packed_physical_stable_ids;
4435 self.physical_warm_recycled_origins = packed_warm_recycled_origins;
4436 self.free_ids = BinaryHeap::new();
4437 debug_assert_eq!(
4438 self.stable_index.live(),
4439 live_count,
4440 "every live node keeps one indexed slot through compaction",
4441 );
4442 self.prune_stable_generations();
4443 }
4444
4445 fn take_recycled_node(&mut self, key: TypeId) -> Option<RecycledNode> {
4446 Self::take_recycled_node_from_pool(&mut self.returning_recycled_nodes, key)
4447 .or_else(|| Self::take_recycled_node_from_pool(&mut self.recycled_nodes, key))
4448 }
4449
4450 fn set_recycled_node_origin(&mut self, id: NodeId, warm_origin: bool) {
4451 if let Some(physical_id) = self.resolve_node_index(id) {
4452 self.physical_warm_recycled_origins[physical_id] = warm_origin;
4453 } else if self.high_id_nodes.contains_key(&id) {
4454 self.high_id_warm_recycled_origins.insert(id, warm_origin);
4455 }
4456 }
4457
4458 fn seed_recycled_node_shell(
4459 &mut self,
4460 key: TypeId,
4461 recycle_pool_limit: Option<usize>,
4462 shell: Box<dyn Node>,
4463 ) {
4464 self.seed_recycled_node_shell_impl(key, recycle_pool_limit, shell);
4465 }
4466
4467 fn record_fresh_recyclable_creation(&mut self, key: TypeId) {
4468 *self.fresh_recyclable_creations.entry(key).or_insert(0) += 1;
4469 }
4470
4471 fn clear_recycled_nodes(&mut self) {
4472 let returning = std::mem::take(&mut self.returning_recycled_nodes);
4473 for (key, mut nodes) in returning {
4474 let pool = self.recycled_nodes.entry(key).or_default();
4475 pool.append(&mut nodes);
4476 }
4477
4478 let fresh_recyclable_creations = std::mem::take(&mut self.fresh_recyclable_creations);
4479 let cold = std::mem::take(&mut self.cold_recycled_nodes);
4480 for (key, mut nodes) in cold {
4481 let needed = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4482 if needed > 0 {
4483 let remaining_limit = self
4484 .recycle_pool_limit_for(key)
4485 .unwrap_or(usize::MAX)
4486 .saturating_sub(self.warm_recycled_pool_len(key));
4487 let promote = nodes.len().min(needed).min(remaining_limit);
4488 let split_at = nodes.len().saturating_sub(promote);
4489 let promoted = nodes.split_off(split_at);
4490 for mut recycled in promoted {
4491 recycled.set_warm_origin(true);
4492 self.recycled_nodes.entry(key).or_default().push(recycled);
4493 }
4494 }
4495 }
4496
4497 let mut keys: HashSet<TypeId> = HashSet::default();
4498 keys.extend(self.recycled_nodes.keys().copied());
4499 keys.extend(self.recycled_node_limits.keys().copied());
4500 keys.extend(self.warm_recycled_node_targets.keys().copied());
4501 keys.extend(self.recycled_node_prototypes.keys().copied());
4502 for key in keys {
4503 let observed_demand = fresh_recyclable_creations.get(&key).copied().unwrap_or(0);
4504 let target = self.update_warm_recycled_node_target(key, observed_demand);
4505 self.replenish_warm_pool_to_target(key, target);
4506 self.trim_idle_warm_pool_to_target(key, target);
4507 self.compact_idle_warm_pool(key);
4508 }
4509 self.prune_stable_generations();
4510 self.compact();
4511 }
4512}
4513
4514pub struct SlotsHost {
4515 storage_key: Cell<usize>,
4516 inner: RefCell<SlotsHostInner>,
4517}
4518
4519#[derive(Debug, Default)]
4520pub(crate) struct SlotPassOutcome {
4521 pub(crate) compacted: bool,
4522 pub(crate) compact_anchor_registry_storage: bool,
4523 pub(crate) compact_payload_storage: bool,
4524 pub(crate) trim_growth_slack: bool,
4525}
4526
4527#[derive(Default)]
4528pub(crate) struct FinishedSlotPass {
4529 pub(crate) outcome: SlotPassOutcome,
4530 pub(crate) detached_root_children: Vec<slot::DetachedSubtree>,
4531}
4532
4533struct ActivePassState {
4534 state: slot::SlotWriteSessionState,
4535 storage_capacity: usize,
4536}
4537
4538struct SlotsHostInner {
4539 table: SlotTable,
4540 nested_hosts: Vec<std::rc::Weak<SlotsHost>>,
4541 lifecycle: slot::SlotLifecycleCoordinator,
4542 runtime_state: Option<Rc<crate::composer::ComposerRuntimeState>>,
4543 active_pass: Option<ActivePassState>,
4544}
4545
4546impl Drop for SlotsHost {
4547 fn drop(&mut self) {
4548 let storage_key = self.storage_key.get();
4549 let inner = self.inner.get_mut();
4550 if let Some(state) = inner.runtime_state.clone() {
4551 if let Err(err) = state.dispose_retained_subtrees_for_host(
4552 storage_key,
4553 &mut inner.table,
4554 &mut inner.lifecycle,
4555 ) {
4556 log::error!(
4557 "retained subtree disposal failed while dropping SlotsHost {storage_key}: {err}"
4558 );
4559 state.abandon_retained_subtrees_for_host(
4560 storage_key,
4561 &mut inner.table,
4562 &mut inner.lifecycle,
4563 );
4564 } else {
4565 state.clear_host_storage_key(storage_key, &mut inner.table);
4566 }
4567 }
4568 inner.lifecycle.dispose_slot_table(&mut inner.table);
4569 }
4570}
4571
4572impl SlotsHost {
4573 pub fn storage_key(&self) -> usize {
4574 self.storage_key.get()
4575 }
4576
4577 pub fn new(storage: SlotTable) -> Self {
4578 let storage_key = storage.storage_id();
4579 Self {
4580 storage_key: Cell::new(storage_key),
4581 inner: RefCell::new(SlotsHostInner {
4582 table: storage,
4583 nested_hosts: Vec::new(),
4584 lifecycle: slot::SlotLifecycleCoordinator::default(),
4585 runtime_state: None,
4586 active_pass: None,
4587 }),
4588 }
4589 }
4590
4591 pub fn note_nested_host(&self, nested: &Rc<SlotsHost>) {
4592 let Ok(mut inner) = self.inner.try_borrow_mut() else {
4593 return;
4594 };
4595 inner.nested_hosts.retain(|held| held.upgrade().is_some());
4596 if inner
4597 .nested_hosts
4598 .iter()
4599 .any(|held| held.upgrade().is_some_and(|host| Rc::ptr_eq(&host, nested)))
4600 {
4601 return;
4602 }
4603 inner.nested_hosts.push(Rc::downgrade(nested));
4604 }
4605
4606 pub(crate) fn forget_effects(&self) -> bool {
4607 let (forgotten, nested, runtime_state) = {
4608 let Ok(mut inner) = self.inner.try_borrow_mut() else {
4609 return false;
4610 };
4611 if inner.active_pass.is_some() {
4612 return false;
4613 }
4614 let drops = inner.table.take_effect_drops();
4615 inner.nested_hosts.retain(|held| held.upgrade().is_some());
4616 let nested: Vec<Rc<SlotsHost>> = inner
4617 .nested_hosts
4618 .iter()
4619 .filter_map(std::rc::Weak::upgrade)
4620 .collect();
4621 (drops, nested, inner.runtime_state.clone())
4622 };
4623 let mut any = !forgotten.is_empty();
4624 drop(forgotten);
4625 for host in nested {
4626 any |= host.forget_effects();
4627 }
4628 if any {
4629 self.borrow()
4630 .scopes()
4631 .for_each(RecomposeScope::force_recompose);
4632 if let Some(runtime_state) = runtime_state {
4633 runtime_state.force_recompose_retained_scopes(self.storage_key());
4634 }
4635 }
4636 any
4637 }
4638
4639 pub(crate) fn bind_runtime_state(&self, state: &Rc<crate::composer::ComposerRuntimeState>) {
4640 let mut inner = self.inner.borrow_mut();
4641 inner.runtime_state = Some(Rc::clone(state));
4642 }
4643
4644 pub(crate) fn rebind_orphaned_runtime_state(
4645 &self,
4646 state: &Rc<crate::composer::ComposerRuntimeState>,
4647 ) -> bool {
4648 let inner = self.inner.borrow();
4649 if inner.active_pass.is_some() {
4650 log::error!("cannot rebind SlotsHost during an active pass");
4651 return false;
4652 }
4653 let Some(bound_state) = inner.runtime_state.as_ref() else {
4654 drop(inner);
4655 self.bind_runtime_state(state);
4656 return true;
4657 };
4658 if Rc::ptr_eq(bound_state, state) {
4659 return true;
4660 }
4661 if bound_state.has_live_applier_host() {
4662 return false;
4663 }
4664 drop(inner);
4665
4666 let mut inner = self.inner.borrow_mut();
4667 let Some(bound_state) = inner.runtime_state.as_ref() else {
4668 inner.runtime_state = Some(Rc::clone(state));
4669 return true;
4670 };
4671 if Rc::ptr_eq(bound_state, state) {
4672 return true;
4673 }
4674 if bound_state.has_live_applier_host() {
4675 return false;
4676 }
4677
4678 let previous_state = Rc::clone(bound_state);
4679 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4680 lifecycle.flush_pending_drops();
4681 let host_key = self.storage_key();
4682 if previous_state
4683 .dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4684 .is_err()
4685 {
4686 inner.lifecycle = lifecycle;
4687 return false;
4688 }
4689 previous_state.clear_host(self, &mut inner.table);
4690 lifecycle.flush_pending_drops();
4691 inner.runtime_state = Some(Rc::clone(state));
4692 inner.lifecycle = lifecycle;
4693 true
4694 }
4695
4696 pub(crate) fn runtime_state(&self) -> Option<Rc<crate::composer::ComposerRuntimeState>> {
4697 self.inner.borrow().runtime_state.clone()
4698 }
4699
4700 fn runtime_state_ptr(&self) -> Option<*const crate::composer::ComposerRuntimeState> {
4701 self.inner.borrow().runtime_state.as_ref().map(Rc::as_ptr)
4702 }
4703
4704 pub(crate) fn borrow(&self) -> Ref<'_, SlotTable> {
4705 Ref::map(self.inner.borrow(), |inner| &inner.table)
4706 }
4707
4708 pub(crate) fn borrow_mut(&self) -> RefMut<'_, SlotTable> {
4709 RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.table)
4710 }
4711
4712 pub fn into_table(self: Rc<Self>) -> Result<SlotTable, NodeError> {
4713 if Rc::strong_count(&self) != 1 {
4714 return Err(NodeError::SlotHostUnavailable {
4715 operation: "SlotsHost::into_table",
4716 reason: "other host references are alive",
4717 });
4718 }
4719 self.take_table_for_transfer()
4720 }
4721
4722 fn take_table_for_transfer(&self) -> Result<SlotTable, NodeError> {
4723 let inner = self.inner.borrow();
4724 if inner.active_pass.is_some() {
4725 return Err(NodeError::SlotHostUnavailable {
4726 operation: "SlotsHost::into_table",
4727 reason: "slot pass is active",
4728 });
4729 }
4730 drop(inner);
4731 let mut inner = self.inner.borrow_mut();
4732 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4733 lifecycle.flush_pending_drops();
4734 if let Some(state) = inner.runtime_state.clone() {
4735 let host_key = self.storage_key();
4736 if let Err(error) =
4737 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4738 {
4739 inner.lifecycle = lifecycle;
4740 return Err(error);
4741 }
4742 state.clear_host(self, &mut inner.table);
4743 lifecycle.flush_pending_drops();
4744 }
4745 let taken = std::mem::take(&mut inner.table);
4746 self.storage_key.set(inner.table.storage_id());
4747 inner.runtime_state = None;
4748 inner.lifecycle = lifecycle;
4749 Ok(taken)
4750 }
4751
4752 pub fn reset(&self) -> Result<(), NodeError> {
4753 let inner = self.inner.borrow();
4754 if inner.active_pass.is_some() {
4755 return Err(NodeError::SlotHostUnavailable {
4756 operation: "SlotsHost::reset",
4757 reason: "slot pass is active",
4758 });
4759 }
4760 let runtime_state = inner.runtime_state.clone();
4761 drop(inner);
4762 let mut inner = self.inner.borrow_mut();
4763 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4764 if let Some(state) = runtime_state {
4765 let host_key = self.storage_key();
4766 if let Err(error) =
4767 state.dispose_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle)
4768 {
4769 inner.lifecycle = lifecycle;
4770 return Err(error);
4771 }
4772 state.clear_host(self, &mut inner.table);
4773 }
4774 lifecycle.dispose_slot_table(&mut inner.table);
4775 inner.table = SlotTable::default();
4776 self.storage_key.set(inner.table.storage_id());
4777 inner.runtime_state = None;
4778 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4779 Ok(())
4780 }
4781
4782 pub(crate) fn abandon_after_apply_failure(&self) {
4783 let inner = self.inner.borrow();
4784 if inner.active_pass.is_some() {
4785 log::error!("cannot abandon SlotsHost during an active pass");
4786 return;
4787 }
4788 let runtime_state = inner.runtime_state.clone();
4789 drop(inner);
4790 let mut inner = self.inner.borrow_mut();
4791 let mut lifecycle = std::mem::take(&mut inner.lifecycle);
4792 if let Some(state) = runtime_state {
4793 let host_key = self.storage_key();
4794 state.abandon_retained_subtrees_for_host(host_key, &mut inner.table, &mut lifecycle);
4795 }
4796 lifecycle.dispose_slot_table(&mut inner.table);
4797 inner.table = SlotTable::default();
4798 self.storage_key.set(inner.table.storage_id());
4799 inner.runtime_state = None;
4800 inner.lifecycle = slot::SlotLifecycleCoordinator::default();
4801 }
4802
4803 pub(crate) fn debug_stats(&self) -> SlotTableDebugStats {
4804 let inner = self.inner.borrow();
4805 let local = inner.table.debug_stats();
4806 let lifecycle = inner.lifecycle.debug_stats();
4807 let retention = inner
4808 .runtime_state
4809 .clone()
4810 .map(|state| state.slot_retention_debug_stats(self))
4811 .unwrap_or_default();
4812 SlotTableDebugStats::from_parts(local, lifecycle, retention)
4813 }
4814
4815 pub(crate) fn debug_snapshot(&self) -> slot::SlotDebugSnapshot {
4816 let inner = self.inner.borrow();
4817 let mut snapshot = inner.table.debug_snapshot();
4818 if let Some(state) = inner.runtime_state.clone() {
4819 state.fill_slot_debug_snapshot(self, &mut snapshot);
4820 }
4821 snapshot
4822 }
4823
4824 pub(crate) fn begin_pass(&self, mode: slot::SlotPassMode, folds: &Rc<slot::BranchFolds>) {
4825 let mut inner = self.inner.borrow_mut();
4826 if inner.active_pass.is_some() {
4827 log::error!("slot pass already active for host");
4828 return;
4829 }
4830 let mut state = slot::SlotWriteSessionState::new(Rc::clone(folds));
4831 state.reset_for_pass(mode);
4832 let storage_capacity = inner.table.storage_capacity();
4833 inner.active_pass = Some(ActivePassState {
4834 state,
4835 storage_capacity,
4836 });
4837 }
4838
4839 pub(crate) fn has_active_pass(&self) -> bool {
4840 self.inner.borrow().active_pass.is_some()
4841 }
4842
4843 pub(crate) fn abandon_active_pass(&self) {
4844 self.inner.borrow_mut().active_pass = None;
4845 }
4846
4847 pub(crate) fn with_write_session<R>(
4848 &self,
4849 f: impl FnOnce(&mut slot::SlotWriteSession<'_>) -> R,
4850 ) -> R {
4851 let mut inner = self.inner.borrow_mut();
4852 let SlotsHostInner {
4853 table,
4854 lifecycle,
4855 active_pass,
4856 ..
4857 } = &mut *inner;
4858 let active_pass = active_pass
4859 .as_mut()
4860 .expect("slot write session requires an active pass");
4861 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4862 f(&mut session)
4863 }
4864
4865 pub(crate) fn with_table_and_lifecycle_mut<R>(
4866 &self,
4867 f: impl FnOnce(&mut SlotTable, &mut slot::SlotLifecycleCoordinator) -> R,
4868 ) -> R {
4869 let mut inner = self.inner.borrow_mut();
4870 let SlotsHostInner {
4871 table, lifecycle, ..
4872 } = &mut *inner;
4873 f(table, lifecycle)
4874 }
4875
4876 pub(crate) fn finish_pass(
4877 &self,
4878 applier: &mut dyn Applier,
4879 ) -> Result<FinishedSlotPass, NodeError> {
4880 let mut inner = self.inner.borrow_mut();
4881 let SlotsHostInner {
4882 table,
4883 lifecycle,
4884 active_pass: active_pass_slot,
4885 ..
4886 } = &mut *inner;
4887 let Some(mut active_pass) = active_pass_slot.take() else {
4888 return Ok(FinishedSlotPass::default());
4889 };
4890
4891 active_pass.state.flush_payload_location_refreshes(table);
4892
4893 #[cfg(any(test, debug_assertions))]
4894 if let Err(err) = active_pass.state.validate(table) {
4895 log::error!("slot writer invariant violation before finalize_pass: {err:?}");
4896 return Err(NodeError::SlotHostUnavailable {
4897 operation: "SlotsHost::finish_pass",
4898 reason: "slot writer invariant violation",
4899 });
4900 }
4901
4902 let detached_root_children = {
4903 let mut session = table.write_session(lifecycle, &mut active_pass.state);
4904 session.finalize_pass(applier)?
4905 };
4906
4907 Ok(FinishedSlotPass {
4908 outcome: SlotPassOutcome {
4909 compacted: active_pass.state.request_compaction,
4910 compact_anchor_registry_storage: active_pass
4911 .state
4912 .request_anchor_storage_compaction,
4913 compact_payload_storage: active_pass.state.request_payload_storage_compaction,
4914 trim_growth_slack: active_pass.state.removed_nothing()
4915 && table.storage_capacity() > active_pass.storage_capacity,
4916 },
4917 detached_root_children,
4918 })
4919 }
4920
4921 pub(crate) fn flush_pending_drops(&self) {
4922 self.inner.borrow_mut().lifecycle.flush_pending_drops();
4923 }
4924
4925 pub(crate) fn complete_pass_cleanup(&self, outcome: &SlotPassOutcome) {
4926 let mut inner = self.inner.borrow_mut();
4927 let SlotsHostInner {
4928 table,
4929 lifecycle,
4930 runtime_state,
4931 ..
4932 } = &mut *inner;
4933 lifecycle.flush_pending_drops();
4934 if outcome.compacted {
4935 table.compact_storage();
4936 lifecycle.compact_storage();
4937 } else if outcome.trim_growth_slack {
4938 table.trim_growth_slack();
4939 }
4940 if let Some(state) = runtime_state.clone() {
4941 state.compact_table_identity_storage_for_host(
4942 self,
4943 table,
4944 outcome.compact_anchor_registry_storage,
4945 outcome.compact_payload_storage,
4946 );
4947 } else {
4948 if outcome.compact_anchor_registry_storage {
4949 table.compact_anchor_registry_storage(None);
4950 }
4951 if outcome.compact_payload_storage {
4952 table.compact_payload_anchor_registry_storage(None);
4953 }
4954 }
4955 table.assert_fast_integrity("slot pass cleanup");
4956 #[cfg(any(test, debug_assertions))]
4957 {
4958 table.debug_verify();
4959 if let Some(state) = runtime_state.clone() {
4960 state.debug_verify_host(self, table);
4961 }
4962 }
4963 }
4964}
4965
4966fn build_child_positions(children: &[NodeId]) -> HashMap<NodeId, usize> {
4967 let mut positions = HashMap::default();
4968 positions.reserve(children.len());
4969 for (index, &child) in children.iter().enumerate() {
4970 positions.insert(child, index);
4971 }
4972 positions
4973}
4974
4975fn refresh_child_positions(
4976 current: &[NodeId],
4977 positions: &mut HashMap<NodeId, usize>,
4978 start: usize,
4979 end: usize,
4980) {
4981 if current.is_empty() || start >= current.len() {
4982 return;
4983 }
4984 let end = end.min(current.len() - 1);
4985 for (offset, &child) in current[start..=end].iter().enumerate() {
4986 positions.insert(child, start + offset);
4987 }
4988}
4989
4990fn insert_child_into_diff_state(
4991 current: &mut ChildList,
4992 positions: &mut HashMap<NodeId, usize>,
4993 index: usize,
4994 child: NodeId,
4995) {
4996 let index = index.min(current.len());
4997 current.insert(index, child);
4998 refresh_child_positions(current, positions, index, current.len() - 1);
4999}
5000
5001fn move_child_in_diff_state(
5002 current: &mut ChildList,
5003 positions: &mut HashMap<NodeId, usize>,
5004 from_index: usize,
5005 target_index: usize,
5006) -> usize {
5007 let child = current.remove(from_index);
5008 let to_index = target_index.min(current.len());
5009 current.insert(to_index, child);
5010 refresh_child_positions(
5011 current,
5012 positions,
5013 from_index.min(to_index),
5014 from_index.max(to_index),
5015 );
5016 to_index
5017}
5018
5019pub(crate) use state::MutableStateInner;
5020pub use state::{
5021 MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
5022 StateSubscriptionHold,
5023};
5024
5025fn hash_key<K: Hash>(key: &K) -> Key {
5026 let mut hasher = hash::default::new();
5027 key.hash(&mut hasher);
5028 hasher.finish()
5029}
5030
5031pub(crate) fn explicit_group_key_seed<K: Hash>(
5032 key: &K,
5033 caller: &'static std::panic::Location<'static>,
5034) -> slot::GroupKeySeed {
5035 let source_key = location_key(caller.file(), caller.line(), caller.column());
5036 let explicit_key = hash_key(key);
5037 slot::GroupKeySeed::keyed(source_key, explicit_key)
5038}
5039
5040#[cfg(test)]
5041#[path = "tests/mod.rs"]
5042mod tests;
5043
5044#[cfg(test)]
5045#[path = "tests/recursive_decrease_increase_test.rs"]
5046mod recursive_decrease_increase_test;
5047
5048pub mod collections;
5049pub mod hash;
5050
5051#[cfg(any(test, feature = "test-helpers"))]
5054pub mod test_scratch;
5055#[cfg(any(test, feature = "test-helpers"))]
5056pub use test_scratch::test_scratch_dir;
5057
5058pub(crate) fn note_structural(reason: &str, parent_id: NodeId, child_id: NodeId) {
5059 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5060 eprintln!("[structural] {reason} parent={parent_id} child={child_id}");
5061 }
5062}
5063
5064pub(crate) fn note_structural_move(parent_id: NodeId, from_index: usize, to_index: usize) {
5065 if env_flag!("CRANPOSE_STRUCTURAL_DIAG") {
5066 eprintln!("[structural] move parent={parent_id} from={from_index} to={to_index}");
5067 }
5068}