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