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