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