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