Skip to main content

cranpose_core/
snapshot_state_observer.rs

1#![allow(clippy::type_complexity)]
2
3use std::{
4    any::{Any, TypeId},
5    cell::{Cell, RefCell},
6    hash::{Hash, Hasher},
7    rc::{Rc, Weak},
8    sync::Arc,
9};
10
11use smallvec::SmallVec;
12
13use crate::{
14    RecomposeScope, RecomposeScopeInner, ScopeId,
15    collections::map::{HashMap, HashSet},
16    hash::default as default_hash,
17    snapshot_v2::{
18        ReadObserver, StateObjectId, TransparentObserverMutableSnapshot, register_apply_observer,
19    },
20    state::StateObject,
21};
22
23type Executor = dyn Fn(Box<dyn FnOnce() + 'static>) + 'static;
24
25trait ScopeChangedCallback: Fn(&dyn Any) + Any {}
26
27impl<F: Fn(&dyn Any) + Any> ScopeChangedCallback for F {}
28
29/// Observer that records state object reads performed inside a given scope and
30/// notifies the caller when any of the observed objects change.
31///
32/// This is a pragmatic Rust translation of Jetpack Compose's
33/// `SnapshotStateObserver`. The implementation focuses on the core behaviour
34/// needed by the Cranpose runtime:
35/// - Tracking state object reads per logical scope.
36/// - Reacting to snapshot apply notifications.
37/// - Scheduling invalidation callbacks via the supplied executor.
38///
39/// Advanced features from the Kotlin version (derived state tracking, change
40/// coalescing, queue minimisation) are deferred
41#[derive(Clone)]
42pub struct SnapshotStateObserver {
43    inner: Rc<SnapshotStateObserverInner>,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub struct SnapshotStateObserverDebugStats {
48    pub scopes_len: usize,
49    pub scopes_cap: usize,
50    pub fast_scopes_len: usize,
51    pub fast_scopes_cap: usize,
52    pub stateless_scope_count: usize,
53    pub observed_state_count: usize,
54    pub observed_state_capacity: usize,
55}
56
57impl SnapshotStateObserver {
58    /// Create a new observer that schedules callbacks using `on_changed_executor`.
59    pub fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
60        let inner = Rc::new(SnapshotStateObserverInner::new(on_changed_executor));
61        inner.set_self(Rc::downgrade(&inner));
62        Self { inner }
63    }
64
65    /// Observe state object reads performed while executing `block`.
66    ///
67    /// Subsequent calls to `observe_reads` replace any previously recorded
68    /// observations for the provided `scope`. When one of the observed objects
69    /// mutates, `on_value_changed_for_scope` will be invoked on the executor.
70    pub fn observe_reads<T, R>(
71        &self,
72        scope: T,
73        on_value_changed_for_scope: impl Fn(&T) + 'static,
74        block: impl FnOnce() -> R,
75    ) -> R
76    where
77        T: Any + Clone + Eq + Hash + 'static,
78    {
79        self.inner
80            .observe_reads(scope, on_value_changed_for_scope, block)
81    }
82
83    /// Notify the observer that a new composition frame is starting.
84    pub fn begin_frame(&self) {
85        self.inner.begin_frame();
86    }
87
88    /// Drop bookkeeping for scopes that were released during the current frame.
89    pub fn prune_dead_scopes(&self) {
90        self.inner.prune_dead_scopes();
91    }
92
93    /// Temporarily pause read observation while executing `block`.
94    pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
95        self.inner.with_no_observations(block)
96    }
97
98    /// Remove any recorded reads for `scope`.
99    pub fn clear<T>(&self, scope: &T)
100    where
101        T: Any + Eq + Hash + 'static,
102    {
103        self.inner.clear(scope);
104    }
105
106    /// Remove recorded reads for scopes that satisfy `predicate`.
107    pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
108        self.inner.clear_if(predicate);
109    }
110
111    /// Remove all recorded observations.
112    pub fn clear_all(&self) {
113        self.inner.clear_all();
114    }
115
116    /// Begin listening for snapshot apply notifications.
117    pub fn start(&self) {
118        let weak = Rc::downgrade(&self.inner);
119        self.inner.start(weak);
120    }
121
122    /// Stop listening for snapshot apply notifications.
123    pub fn stop(&self) {
124        self.inner.stop();
125    }
126
127    pub fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
128        self.inner.debug_stats()
129    }
130
131    #[cfg(test)]
132    pub fn notify_changes(&self, modified: &[Arc<dyn StateObject>]) {
133        self.inner.handle_apply(modified);
134    }
135}
136
137struct SnapshotStateObserverInner {
138    executor: Rc<Executor>,
139    owned_scopes: RefCell<HashMap<OwnedScopeIndexKey, OwnedScopeBucket>>,
140    fast_scopes: RefCell<HashMap<ScopeId, Rc<RefCell<ScopeEntry>>>>,
141    indexed_scopes: RefCell<HashMap<usize, Rc<RefCell<ScopeEntry>>>>,
142    observed_to_scopes: RefCell<HashMap<StateObjectId, HashSet<usize>>>,
143    pause_count: Rc<Cell<usize>>,
144    active_read_targets: Rc<RefCell<ReadObservationStack>>,
145    read_dispatcher: ReadObserver,
146    read_snapshot: RefCell<Option<Arc<TransparentObserverMutableSnapshot>>>,
147    apply_handle: RefCell<Option<crate::snapshot_v2::ObserverHandle>>,
148    weak_self: RefCell<Weak<SnapshotStateObserverInner>>,
149    frame_version: Cell<u64>,
150    next_entry_id: Cell<usize>,
151}
152
153#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
154struct OwnedScopeIndexKey {
155    type_id: TypeId,
156    value_hash: u64,
157}
158
159type OwnedScopeBucket = SmallVec<[Rc<RefCell<ScopeEntry>>; 1]>;
160
161fn owned_scope_index_key<T>(scope: &T) -> OwnedScopeIndexKey
162where
163    T: Any + Hash + 'static,
164{
165    let mut hasher = default_hash::new();
166    scope.hash(&mut hasher);
167    OwnedScopeIndexKey {
168        type_id: TypeId::of::<T>(),
169        value_hash: hasher.finish(),
170    }
171}
172
173impl SnapshotStateObserverInner {
174    const MIN_RETAINED_SCOPE_CAPACITY: usize = 256;
175
176    fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
177        let pause_count = Rc::new(Cell::new(0));
178        let active_read_targets = Rc::new(RefCell::new(ReadObservationStack::default()));
179        let dispatcher_pause_count = Rc::clone(&pause_count);
180        let dispatcher_targets = Rc::clone(&active_read_targets);
181        let read_dispatcher: ReadObserver = Arc::new(move |state| {
182            if dispatcher_pause_count.get() > 0 {
183                return;
184            }
185            let observed = dispatcher_targets.borrow().last().cloned();
186            if let Some(observed) = observed {
187                observed.borrow_mut().insert(state);
188            }
189        });
190
191        Self {
192            executor: Rc::new(on_changed_executor),
193            owned_scopes: RefCell::new(HashMap::default()),
194            fast_scopes: RefCell::new(HashMap::default()),
195            indexed_scopes: RefCell::new(HashMap::default()),
196            observed_to_scopes: RefCell::new(HashMap::default()),
197            pause_count,
198            active_read_targets,
199            read_dispatcher,
200            read_snapshot: RefCell::new(None),
201            apply_handle: RefCell::new(None),
202            weak_self: RefCell::new(Weak::new()),
203            frame_version: Cell::new(0),
204            next_entry_id: Cell::new(0),
205        }
206    }
207
208    fn set_self(&self, weak: Weak<SnapshotStateObserverInner>) {
209        self.weak_self.replace(weak);
210    }
211
212    fn begin_frame(&self) {
213        let next = self.frame_version.get().wrapping_add(1);
214        self.frame_version.set(next);
215        self.prune_dead_scopes();
216    }
217
218    fn observe_reads<T, R>(
219        &self,
220        scope: T,
221        on_value_changed_for_scope: impl Fn(&T) + 'static,
222        block: impl FnOnce() -> R,
223    ) -> R
224    where
225        T: Any + Clone + Eq + Hash + 'static,
226    {
227        let frame_version = self.frame_version.get();
228        let has_frame_version = frame_version != 0;
229
230        let existing_entry = self.find_scope_entry(&scope);
231        let on_changed = std::cell::LazyCell::new(|| {
232            let callback = move |scope_any: &dyn Any| {
233                if let Some(typed) = scope_any.downcast_ref::<T>() {
234                    on_value_changed_for_scope(typed);
235                }
236            };
237            match existing_entry.as_ref() {
238                Some(entry) => entry.borrow_mut().callback_reusing(callback),
239                None => Rc::new(callback),
240            }
241        });
242
243        if let Some(entry) = existing_entry.as_ref() {
244            let already_observed = {
245                let mut entry_mut = entry.borrow_mut();
246                entry_mut.update_scope(scope.clone());
247                has_frame_version && entry_mut.last_seen_version == frame_version
248            };
249            let callback = on_changed.clone();
250            entry.borrow_mut().on_changed = callback;
251            if already_observed {
252                return block();
253            }
254        }
255
256        let observed = self.active_read_targets.borrow_mut().push();
257        struct ActiveObservationGuard {
258            stack: Rc<RefCell<ReadObservationStack>>,
259        }
260        impl Drop for ActiveObservationGuard {
261            fn drop(&mut self) {
262                let target = self.stack.borrow_mut().pop();
263                let discarded = target.replace(ObservedIds::new());
264                drop(discarded);
265            }
266        }
267        let _guard = ActiveObservationGuard {
268            stack: Rc::clone(&self.active_read_targets),
269        };
270
271        let result = self.run_with_read_observer(block);
272
273        if observed.borrow().is_empty() {
274            if existing_entry.is_some() {
275                self.clear(&scope);
276            }
277            return result;
278        }
279
280        let observed = {
281            let mut observed = observed.borrow_mut();
282            std::mem::replace(&mut *observed, ObservedIds::new())
283        };
284        let entry = existing_entry
285            .clone()
286            .unwrap_or_else(|| self.insert_scope_entry(scope.clone(), on_changed.clone()));
287        {
288            let mut entry_mut = entry.borrow_mut();
289            entry_mut.update(scope, Rc::clone(&on_changed));
290            entry_mut.last_seen_version = if has_frame_version {
291                frame_version
292            } else {
293                u64::MAX
294            };
295        }
296        self.replace_observed_ids(&entry, observed);
297
298        result
299    }
300
301    fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
302        self.pause_count.set(self.pause_count.get() + 1);
303        let result = block();
304        self.pause_count
305            .set(self.pause_count.get().saturating_sub(1));
306        result
307    }
308
309    fn clear<T>(&self, scope: &T)
310    where
311        T: Any + Eq + Hash + 'static,
312    {
313        if let Some(rc_scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
314            if let Some(entry) = self.fast_scopes.borrow_mut().remove(&rc_scope.id()) {
315                self.unregister_entry(&entry);
316            }
317            return;
318        }
319
320        let removed = self.remove_owned_scope_entry(scope);
321        if let Some(entry) = removed {
322            self.unregister_entry(&entry);
323        }
324    }
325
326    fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
327        let removed_fast = {
328            let mut fast_scopes = self.fast_scopes.borrow_mut();
329            let removed_ids: Vec<_> = fast_scopes
330                .iter()
331                .filter(|(_, entry)| entry.borrow().matches_predicate(&predicate))
332                .map(|(scope_id, _)| *scope_id)
333                .collect();
334            removed_ids
335                .into_iter()
336                .filter_map(|scope_id| fast_scopes.remove(&scope_id))
337                .collect::<Vec<_>>()
338        };
339        let removed_owned =
340            { self.partition_owned_scopes(|entry| entry.matches_predicate(&predicate)) };
341
342        for entry in removed_fast.into_iter().chain(removed_owned) {
343            self.unregister_entry(&entry);
344        }
345    }
346
347    fn clear_all(&self) {
348        self.fast_scopes.borrow_mut().clear();
349        self.owned_scopes.borrow_mut().clear();
350        self.indexed_scopes.borrow_mut().clear();
351        self.observed_to_scopes.borrow_mut().clear();
352    }
353
354    fn start(&self, weak_self: Weak<SnapshotStateObserverInner>) {
355        if self.apply_handle.borrow().is_some() {
356            return;
357        }
358
359        let handle = register_apply_observer(Rc::new(move |modified, _snapshot_id| {
360            if let Some(inner) = weak_self.upgrade() {
361                inner.handle_apply(modified);
362            }
363        }));
364        self.apply_handle.replace(Some(handle));
365    }
366
367    fn stop(&self) {
368        if let Some(handle) = self.apply_handle.borrow_mut().take() {
369            drop(handle);
370        }
371    }
372
373    fn find_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
374    where
375        T: Any + Eq + Hash + 'static,
376    {
377        if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
378            return self.fast_scopes.borrow().get(&scope.id()).cloned();
379        }
380
381        self.find_owned_scope_entry(scope)
382    }
383
384    fn insert_scope_entry(
385        &self,
386        scope: impl Any + Clone + Eq + Hash + 'static,
387        on_changed: Rc<dyn ScopeChangedCallback>,
388    ) -> Rc<RefCell<ScopeEntry>> {
389        let entry_id = self.next_entry_id.get();
390        self.next_entry_id.set(entry_id.wrapping_add(1));
391        let recompose_scope_id = (&scope as &dyn Any)
392            .downcast_ref::<RecomposeScope>()
393            .map(RecomposeScope::id);
394        let owned_scope_key = recompose_scope_id
395            .is_none()
396            .then(|| owned_scope_index_key(&scope));
397        let entry = Rc::new(RefCell::new(ScopeEntry::new(entry_id, scope, on_changed)));
398        self.indexed_scopes
399            .borrow_mut()
400            .insert(entry_id, Rc::clone(&entry));
401        if let Some(scope_id) = recompose_scope_id {
402            self.fast_scopes
403                .borrow_mut()
404                .insert(scope_id, Rc::clone(&entry));
405        } else if let Some(scope_key) = owned_scope_key {
406            self.owned_scopes
407                .borrow_mut()
408                .entry(scope_key)
409                .or_default()
410                .push(Rc::clone(&entry));
411        }
412        entry
413    }
414
415    fn prune_dead_scopes(&self) {
416        let removed_fast = {
417            let mut fast_scopes = self.fast_scopes.borrow_mut();
418            let removed_ids: Vec<_> = fast_scopes
419                .iter()
420                .filter(|(_, entry)| !entry.borrow().should_retain())
421                .map(|(scope_id, _)| *scope_id)
422                .collect();
423            let removed = removed_ids
424                .into_iter()
425                .filter_map(|scope_id| fast_scopes.remove(&scope_id))
426                .collect::<Vec<_>>();
427            shrink_map_if_sparse(&mut fast_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
428            removed
429        };
430
431        let removed_owned = { self.partition_owned_scopes(|entry| !entry.should_retain()) };
432
433        for entry in removed_fast.into_iter().chain(removed_owned) {
434            self.unregister_entry(&entry);
435        }
436    }
437
438    fn find_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
439    where
440        T: Any + Eq + Hash + 'static,
441    {
442        let key = owned_scope_index_key(scope);
443        self.owned_scopes.borrow().get(&key).and_then(|bucket| {
444            bucket
445                .iter()
446                .find(|entry| entry.borrow().matches_scope(scope))
447                .cloned()
448        })
449    }
450
451    fn remove_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
452    where
453        T: Any + Eq + Hash + 'static,
454    {
455        let key = owned_scope_index_key(scope);
456        let mut owned_scopes = self.owned_scopes.borrow_mut();
457        let mut removed = None;
458        let mut remove_bucket = false;
459        if let Some(bucket) = owned_scopes.get_mut(&key)
460            && let Some(index) = bucket
461                .iter()
462                .position(|entry| entry.borrow().matches_scope(scope))
463        {
464            removed = Some(bucket.remove(index));
465            remove_bucket = bucket.is_empty();
466        }
467        if remove_bucket {
468            owned_scopes.remove(&key);
469        }
470        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
471        removed
472    }
473
474    fn partition_owned_scopes(
475        &self,
476        should_remove: impl Fn(&ScopeEntry) -> bool,
477    ) -> Vec<Rc<RefCell<ScopeEntry>>> {
478        let mut owned_scopes = self.owned_scopes.borrow_mut();
479        let mut retained = HashMap::default();
480        let mut removed = Vec::new();
481        for (key, mut bucket) in owned_scopes.drain() {
482            let mut retained_bucket = OwnedScopeBucket::new();
483            for entry in bucket.drain(..) {
484                if should_remove(&entry.borrow()) {
485                    removed.push(entry);
486                } else {
487                    retained_bucket.push(entry);
488                }
489            }
490            if !retained_bucket.is_empty() {
491                retained.insert(key, retained_bucket);
492            }
493        }
494        *owned_scopes = retained;
495        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
496        removed
497    }
498
499    fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
500        let owned_scopes = self.owned_scopes.borrow();
501        let fast_scopes = self.fast_scopes.borrow();
502        let owned_scope_len = owned_scopes.values().map(SmallVec::len).sum::<usize>();
503        let owned_scope_cap =
504            owned_scopes.capacity() + owned_scopes.values().map(SmallVec::capacity).sum::<usize>();
505        let scopes_len = owned_scope_len + fast_scopes.len();
506        let scopes_cap = owned_scope_cap + fast_scopes.capacity();
507        let mut observed_state_count = 0;
508        let mut observed_state_capacity = 0;
509        let mut stateless_scope_count = 0;
510
511        for entry in owned_scopes
512            .values()
513            .flat_map(|bucket| bucket.iter())
514            .chain(fast_scopes.values())
515        {
516            let entry = entry.borrow();
517            observed_state_count += entry.observed.len();
518            observed_state_capacity += entry.observed.capacity();
519            stateless_scope_count += usize::from(entry.observed.is_empty());
520        }
521
522        SnapshotStateObserverDebugStats {
523            scopes_len,
524            scopes_cap,
525            fast_scopes_len: fast_scopes.len(),
526            fast_scopes_cap: fast_scopes.capacity(),
527            stateless_scope_count,
528            observed_state_count,
529            observed_state_capacity,
530        }
531    }
532
533    fn run_with_read_observer<R>(&self, block: impl FnOnce() -> R) -> R {
534        use crate::snapshot_v2::take_transparent_observer_mutable_snapshot_reusing;
535
536        let mut snapshot = take_transparent_observer_mutable_snapshot_reusing(
537            Some(self.read_dispatcher.clone()),
538            None,
539            self.read_snapshot.take(),
540        );
541        let result = snapshot.enter(block);
542        snapshot.dispose();
543        if Arc::get_mut(&mut snapshot).is_some() && !snapshot.has_pending_changes() {
544            self.read_snapshot.replace(Some(snapshot));
545        }
546        result
547    }
548
549    fn handle_apply(&self, modified: &[Arc<dyn StateObject>]) {
550        if modified.is_empty() {
551            return;
552        }
553
554        let mut seen_scope_ids: HashSet<usize> = HashSet::default();
555        let mut to_notify: Vec<Rc<RefCell<ScopeEntry>>> = Vec::new();
556        {
557            let observed_to_scopes = self.observed_to_scopes.borrow();
558            let indexed_scopes = self.indexed_scopes.borrow();
559            for state in modified {
560                if let Some(scope_ids) = observed_to_scopes.get(&state.object_id().as_usize()) {
561                    let mut ordered_scope_ids: SmallVec<[usize; 8]> =
562                        scope_ids.iter().copied().collect();
563                    ordered_scope_ids.sort_unstable();
564                    for scope_id in ordered_scope_ids {
565                        if seen_scope_ids.insert(scope_id)
566                            && let Some(entry) = indexed_scopes.get(&scope_id)
567                        {
568                            to_notify.push(entry.clone());
569                        }
570                    }
571                }
572            }
573        }
574
575        if to_notify.is_empty() {
576            return;
577        }
578
579        for entry in to_notify {
580            let executor = self.executor.clone();
581            executor(Box::new(move || {
582                if let Ok(entry) = entry.try_borrow() {
583                    entry.notify();
584                }
585            }));
586        }
587    }
588
589    fn replace_observed_ids(&self, entry: &Rc<RefCell<ScopeEntry>>, observed: ObservedIds) {
590        let (entry_id, previous) = {
591            let mut entry_mut = entry.borrow_mut();
592            let entry_id = entry_mut.id;
593            let previous = std::mem::replace(&mut entry_mut.observed, observed);
594            (entry_id, previous)
595        };
596        let entry_ref = entry.borrow();
597        if previous.iter().eq(entry_ref.observed.iter()) {
598            return;
599        }
600        self.unregister_observed_ids(entry_id, &previous);
601        self.register_observed_ids(entry_id, &entry_ref.observed);
602    }
603
604    fn register_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
605        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
606        for state_id in observed.iter() {
607            let scope_ids = observed_to_scopes.entry(state_id).or_default();
608            scope_ids.insert(entry_id);
609        }
610    }
611
612    fn unregister_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
613        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
614        let mut emptied = SmallVec::<[StateObjectId; MAX_OBSERVED_STATES]>::new();
615        for state_id in observed.iter() {
616            if let Some(scope_ids) = observed_to_scopes.get_mut(&state_id) {
617                scope_ids.remove(&entry_id);
618                if scope_ids.is_empty() {
619                    emptied.push(state_id);
620                }
621            }
622        }
623        for state_id in emptied {
624            observed_to_scopes.remove(&state_id);
625        }
626        shrink_map_if_sparse(&mut observed_to_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
627    }
628
629    fn unregister_entry(&self, entry: &Rc<RefCell<ScopeEntry>>) {
630        let (entry_id, observed) = {
631            let mut entry_mut = entry.borrow_mut();
632            let observed = std::mem::replace(&mut entry_mut.observed, ObservedIds::new());
633            (entry_mut.id, observed)
634        };
635        self.unregister_observed_ids(entry_id, &observed);
636        self.indexed_scopes.borrow_mut().remove(&entry_id);
637    }
638}
639
640fn shrink_map_if_sparse<K, V>(map: &mut HashMap<K, V>, min_retained_capacity: usize)
641where
642    K: Eq + std::hash::Hash,
643{
644    if map.capacity() <= map.len().max(min_retained_capacity).saturating_mul(4) {
645        return;
646    }
647
648    let retained = map.len().max(min_retained_capacity);
649    let mut rebuilt = HashMap::default();
650    rebuilt.reserve(retained);
651    rebuilt.extend(map.drain());
652    *map = rebuilt;
653}
654
655#[derive(Default)]
656struct ReadObservationStack {
657    targets: Vec<Rc<RefCell<ObservedIds>>>,
658    depth: usize,
659}
660
661impl ReadObservationStack {
662    fn push(&mut self) -> Rc<RefCell<ObservedIds>> {
663        if self.depth == self.targets.len() {
664            self.targets.push(Rc::new(RefCell::new(ObservedIds::new())));
665        }
666        let target = Rc::clone(&self.targets[self.depth]);
667        self.depth += 1;
668        target
669    }
670
671    fn last(&self) -> Option<&Rc<RefCell<ObservedIds>>> {
672        self.depth.checked_sub(1).map(|index| &self.targets[index])
673    }
674
675    fn pop(&mut self) -> Rc<RefCell<ObservedIds>> {
676        self.depth -= 1;
677        Rc::clone(&self.targets[self.depth])
678    }
679}
680
681enum ObservedIds {
682    Small(SmallVec<[ObservedState; MAX_OBSERVED_STATES]>),
683    Large(HashMap<StateObjectId, Option<Rc<dyn Any>>>),
684}
685
686struct ObservedState {
687    id: StateObjectId,
688    _lease: Option<Rc<dyn Any>>,
689}
690
691impl ObservedIds {
692    fn new() -> Self {
693        ObservedIds::Small(SmallVec::new())
694    }
695
696    fn insert(&mut self, state: &dyn StateObject) {
697        let id = state.object_id().as_usize();
698        match self {
699            ObservedIds::Small(small) => {
700                if small.iter().any(|observed| observed.id == id) {
701                    return;
702                }
703                if small.len() < MAX_OBSERVED_STATES {
704                    small.push(ObservedState {
705                        id,
706                        _lease: state.observation_lease(),
707                    });
708                } else {
709                    let mut large =
710                        HashMap::with_capacity_and_hasher(small.len() + 1, Default::default());
711                    for observed in small.drain(..) {
712                        large.insert(observed.id, observed._lease);
713                    }
714                    large.insert(id, state.observation_lease());
715                    *self = ObservedIds::Large(large);
716                }
717            }
718            ObservedIds::Large(large) => {
719                large.entry(id).or_insert_with(|| state.observation_lease());
720            }
721        }
722    }
723
724    fn is_empty(&self) -> bool {
725        match self {
726            ObservedIds::Small(small) => small.is_empty(),
727            ObservedIds::Large(large) => large.is_empty(),
728        }
729    }
730
731    fn len(&self) -> usize {
732        match self {
733            ObservedIds::Small(small) => small.len(),
734            ObservedIds::Large(large) => large.len(),
735        }
736    }
737
738    fn capacity(&self) -> usize {
739        match self {
740            ObservedIds::Small(small) => small.capacity(),
741            ObservedIds::Large(large) => large.capacity(),
742        }
743    }
744
745    fn iter(&self) -> impl Iterator<Item = StateObjectId> + '_ {
746        let (small, large) = match self {
747            ObservedIds::Small(small) => (Some(small.as_slice()), None),
748            ObservedIds::Large(large) => (None, Some(large)),
749        };
750        small
751            .into_iter()
752            .flatten()
753            .map(|observed| observed.id)
754            .chain(large.into_iter().flat_map(|states| states.keys().copied()))
755    }
756}
757
758const MAX_OBSERVED_STATES: usize = 8;
759
760enum ScopeStorage {
761    Owned(Box<dyn Any>),
762    RecomposeScope {
763        id: ScopeId,
764        weak: Weak<RecomposeScopeInner>,
765    },
766}
767
768struct ScopeEntry {
769    id: usize,
770    scope: ScopeStorage,
771    on_changed: Rc<dyn ScopeChangedCallback>,
772    observed: ObservedIds,
773    last_seen_version: u64,
774}
775
776impl ScopeEntry {
777    fn new<T>(id: usize, scope: T, on_changed: Rc<dyn ScopeChangedCallback>) -> Self
778    where
779        T: Any + 'static,
780    {
781        Self {
782            id,
783            scope: ScopeStorage::from_value(scope),
784            on_changed,
785            observed: ObservedIds::new(),
786            last_seen_version: u64::MAX,
787        }
788    }
789
790    fn callback_reusing<F: Fn(&dyn Any) + 'static>(
791        &mut self,
792        callback: F,
793    ) -> Rc<dyn ScopeChangedCallback> {
794        if let Some(stored) = Rc::get_mut(&mut self.on_changed)
795            .and_then(|stored| (stored as &mut dyn Any).downcast_mut::<F>())
796        {
797            *stored = callback;
798            Rc::clone(&self.on_changed)
799        } else {
800            Rc::new(callback)
801        }
802    }
803
804    fn update<T>(&mut self, new_scope: T, on_changed: Rc<dyn ScopeChangedCallback>)
805    where
806        T: Any + 'static,
807    {
808        self.update_scope(new_scope);
809        self.on_changed = on_changed;
810    }
811
812    fn update_scope<T>(&mut self, new_scope: T)
813    where
814        T: Any + 'static,
815    {
816        if let ScopeStorage::Owned(stored) = &mut self.scope
817            && let Some(stored) = stored.downcast_mut::<T>()
818        {
819            *stored = new_scope;
820        } else {
821            self.scope = ScopeStorage::from_value(new_scope);
822        }
823    }
824
825    fn matches_scope<T>(&self, scope: &T) -> bool
826    where
827        T: Any + Eq + 'static,
828    {
829        if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
830            return matches!(
831                &self.scope,
832                ScopeStorage::RecomposeScope { id, .. } if *id == scope.id()
833            );
834        }
835
836        match &self.scope {
837            ScopeStorage::Owned(stored) => stored
838                .downcast_ref::<T>()
839                .is_some_and(|stored| stored == scope),
840            ScopeStorage::RecomposeScope { .. } => false,
841        }
842    }
843
844    fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
845        match &self.scope {
846            ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
847            ScopeStorage::RecomposeScope { weak, .. } => weak
848                .upgrade()
849                .is_none_or(|inner| predicate(&RecomposeScope { inner })),
850        }
851    }
852
853    fn should_retain(&self) -> bool {
854        match &self.scope {
855            ScopeStorage::Owned(_) => true,
856            ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
857        }
858    }
859
860    fn notify(&self) {
861        match &self.scope {
862            ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
863            ScopeStorage::RecomposeScope { weak, .. } => {
864                if let Some(inner) = weak.upgrade() {
865                    (self.on_changed)(&RecomposeScope { inner });
866                }
867            }
868        }
869    }
870}
871
872impl ScopeStorage {
873    fn from_value<T>(value: T) -> Self
874    where
875        T: Any + 'static,
876    {
877        let any = &value as &dyn Any;
878        if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
879            Self::RecomposeScope {
880                id: scope.id(),
881                weak: scope.downgrade(),
882            }
883        } else {
884            Self::Owned(Box::new(value))
885        }
886    }
887}
888
889#[cfg(test)]
890mod tests {
891    use std::cell::Cell;
892
893    use super::*;
894    use crate::{
895        snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
896        state::{NeverEqual, SnapshotMutableState},
897    };
898
899    fn reset_runtime() -> TestRuntimeGuard {
900        reset_runtime_for_tests()
901    }
902
903    #[derive(Clone, Eq, Hash, PartialEq)]
904    struct TestScope(&'static str);
905
906    #[test]
907    fn scope_update_reuses_storage_and_replaces_payload_and_callback() {
908        let first = Rc::new(String::from("first"));
909        let second = Rc::new(String::from("second"));
910        let delivered = Rc::new(RefCell::new(Vec::new()));
911        let mut entry = ScopeEntry::new(0, first.clone(), Rc::new(|_| panic!("stale callback")));
912        let ScopeStorage::Owned(stored) = &entry.scope else {
913            panic!("expected owned scope");
914        };
915        let address = stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>;
916        let received = delivered.clone();
917        entry.update(
918            second.clone(),
919            Rc::new(move |scope| {
920                received.borrow_mut().push(
921                    scope
922                        .downcast_ref::<Rc<String>>()
923                        .unwrap()
924                        .as_str()
925                        .to_owned(),
926                );
927            }),
928        );
929        assert_eq!(Rc::strong_count(&first), 1);
930        assert_eq!(Rc::strong_count(&second), 2);
931        entry.notify();
932        assert_eq!(*delivered.borrow(), vec!["second"]);
933        let ScopeStorage::Owned(stored) = &entry.scope else {
934            panic!("expected owned scope");
935        };
936        assert_eq!(
937            stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>,
938            address
939        );
940        drop(entry);
941        assert_eq!(Rc::strong_count(&second), 1);
942    }
943
944    #[test]
945    fn reobservation_refreshes_captures_and_preserves_shared_callbacks() {
946        let _guard = reset_runtime();
947        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
948        let delivered = Rc::new(RefCell::new(Vec::new()));
949        let callback = |generation| {
950            let delivered = delivered.clone();
951            move |scope: &TestScope| delivered.borrow_mut().push((generation, scope.0))
952        };
953        let scope = TestScope("callback");
954        let observer = SnapshotStateObserver::new(|callback| callback());
955        let read = || {
956            let _ = state.get();
957        };
958        observer.observe_reads(scope.clone(), callback(1), read);
959        let entry = observer.inner.find_scope_entry(&scope).unwrap();
960        let held = entry.borrow().on_changed.clone();
961        observer.observe_reads(scope.clone(), callback(2), read);
962        held(&scope);
963        entry.borrow().notify();
964        drop(held);
965
966        let allocation = Rc::as_ptr(&entry.borrow().on_changed);
967        observer.observe_reads(scope.clone(), callback(3), read);
968        assert!(std::ptr::addr_eq(
969            allocation,
970            Rc::as_ptr(&entry.borrow().on_changed)
971        ));
972        entry.borrow().notify();
973
974        let received = delivered.clone();
975        observer.observe_reads(
976            scope,
977            move |scope| received.borrow_mut().push((4, scope.0)),
978            read,
979        );
980        entry.borrow().notify();
981        assert_eq!(
982            *delivered.borrow(),
983            [
984                (1, "callback"),
985                (2, "callback"),
986                (3, "callback"),
987                (4, "callback")
988            ]
989        );
990    }
991
992    #[test]
993    fn reobservation_across_storage_thresholds_replaces_dependencies_and_callbacks() {
994        let _guard = reset_runtime();
995        let states: Vec<_> = (0..MAX_OBSERVED_STATES + 2)
996            .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
997            .collect();
998        let notifications = Rc::new(RefCell::new(Vec::new()));
999        let observer = SnapshotStateObserver::new(|callback| callback());
1000        observer.start();
1001        for (generation, count) in [MAX_OBSERVED_STATES, MAX_OBSERVED_STATES + 1, 2, 0]
1002            .into_iter()
1003            .enumerate()
1004        {
1005            observer.begin_frame();
1006            let recorded = notifications.clone();
1007            observer.observe_reads(
1008                TestScope("changing"),
1009                move |scope| {
1010                    assert_eq!(scope.0, "changing");
1011                    recorded.borrow_mut().push(generation);
1012                },
1013                || {
1014                    for state in states.iter().take(count) {
1015                        let _ = state.get();
1016                        let _ = state.get();
1017                    }
1018                },
1019            );
1020            notifications.borrow_mut().clear();
1021            for (index, state) in states.iter().enumerate() {
1022                let snapshot = take_mutable_snapshot(None, None);
1023                snapshot.enter(|| state.set(generation as i32));
1024                snapshot.apply().check();
1025                let expected = (index + 1).min(count);
1026                assert_eq!(
1027                    *notifications.borrow(),
1028                    vec![generation; expected],
1029                    "count={count}, changed state={index}"
1030                );
1031            }
1032        }
1033    }
1034
1035    #[test]
1036    fn reobservation_preserves_notifications_when_dependencies_repeat_or_change() {
1037        let _guard = reset_runtime();
1038        let states: Vec<_> = (0..3)
1039            .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
1040            .collect();
1041        let notifications = Rc::new(RefCell::new(Vec::new()));
1042        let observer = SnapshotStateObserver::new(|callback| callback());
1043        observer.start();
1044        for (generation, indices) in [[0, 1], [0, 1], [1, 0], [1, 2], [1, 2]]
1045            .into_iter()
1046            .enumerate()
1047        {
1048            observer.begin_frame();
1049            let received = notifications.clone();
1050            observer.observe_reads(
1051                TestScope("repeated"),
1052                move |_| received.borrow_mut().push(generation),
1053                || {
1054                    for index in indices {
1055                        let _ = states[index].get();
1056                    }
1057                },
1058            );
1059            for (index, state) in states.iter().enumerate() {
1060                notifications.borrow_mut().clear();
1061                let snapshot = take_mutable_snapshot(None, None);
1062                snapshot.enter(|| state.set(generation as i32));
1063                snapshot.apply().check();
1064                assert_eq!(
1065                    *notifications.borrow(),
1066                    if indices.contains(&index) {
1067                        vec![generation]
1068                    } else {
1069                        vec![]
1070                    },
1071                    "generation={generation}, state={index}"
1072                );
1073            }
1074        }
1075        observer.clear(&TestScope("repeated"));
1076        notifications.borrow_mut().clear();
1077        for state in states {
1078            observer.notify_changes(&[state]);
1079        }
1080        assert!(notifications.borrow().is_empty());
1081    }
1082
1083    #[test]
1084    fn stateless_scope_can_start_observing_and_replace_its_callback_before_the_block() {
1085        let _guard = reset_runtime();
1086        let observer = SnapshotStateObserver::new(|callback| callback());
1087        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1088        let observed: Arc<dyn StateObject> = state.clone();
1089        let notifications = Rc::new(RefCell::new(Vec::new()));
1090        let discarded = notifications.clone();
1091        observer.observe_reads(
1092            TestScope("changing"),
1093            move |_| discarded.borrow_mut().push(0),
1094            || {},
1095        );
1096        assert_eq!(Rc::strong_count(&notifications), 1);
1097        assert_eq!(observer.debug_stats().scopes_len, 0);
1098        for generation in 1..=2 {
1099            observer.begin_frame();
1100            let delivered = notifications.clone();
1101            observer.observe_reads(
1102                TestScope("changing"),
1103                move |_| delivered.borrow_mut().push(generation),
1104                || {
1105                    observer.notify_changes(std::slice::from_ref(&observed));
1106                    let _ = state.get();
1107                },
1108            );
1109            observer.notify_changes(std::slice::from_ref(&observed));
1110        }
1111        assert_eq!(*notifications.borrow(), vec![1, 2, 2]);
1112        observer.clear(&TestScope("changing"));
1113        assert_eq!(Rc::strong_count(&notifications), 1);
1114    }
1115
1116    #[test]
1117    fn callback_captures_are_released_on_replacement_and_clear() {
1118        let _guard = reset_runtime();
1119        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1120        let observer = SnapshotStateObserver::new(|callback| callback());
1121        let owners = [Rc::new(Cell::new(0)), Rc::new(Cell::new(0))];
1122        for owner in &owners {
1123            let captured = owner.clone();
1124            observer.observe_reads(
1125                TestScope("owner"),
1126                move |_| captured.set(captured.get() + 1),
1127                || {
1128                    let _ = state.get();
1129                },
1130            );
1131            assert_eq!(Rc::strong_count(owner), 2);
1132        }
1133        assert_eq!(Rc::strong_count(&owners[0]), 1);
1134        observer.clear(&TestScope("owner"));
1135        assert_eq!(Rc::strong_count(&owners[1]), 1);
1136    }
1137
1138    #[test]
1139    fn notifies_scope_when_state_changes() {
1140        let _guard = reset_runtime();
1141
1142        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1143        let triggered = Rc::new(Cell::new(0));
1144        let observer_trigger = triggered.clone();
1145
1146        let observer = SnapshotStateObserver::new(|callback| callback());
1147        observer.start();
1148
1149        let scope = TestScope("scope");
1150        observer.observe_reads(
1151            scope,
1152            move |_| {
1153                observer_trigger.set(observer_trigger.get() + 1);
1154            },
1155            || {
1156                let _ = state.get();
1157            },
1158        );
1159
1160        let snapshot = take_mutable_snapshot(None, None);
1161        snapshot.enter(|| {
1162            state.set(1);
1163        });
1164        snapshot.apply().check();
1165
1166        assert_eq!(triggered.get(), 1);
1167        observer.stop();
1168    }
1169
1170    #[test]
1171    fn clear_removes_scope_observation() {
1172        let _guard = reset_runtime();
1173
1174        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1175        let triggered = Rc::new(Cell::new(0));
1176        let observer_trigger = triggered.clone();
1177
1178        let observer = SnapshotStateObserver::new(|callback| callback());
1179        observer.start();
1180
1181        let scope = TestScope("scope");
1182        observer.observe_reads(
1183            scope.clone(),
1184            move |_| {
1185                observer_trigger.set(observer_trigger.get() + 1);
1186            },
1187            || {
1188                let _ = state.get();
1189            },
1190        );
1191
1192        observer.clear(&scope);
1193
1194        let snapshot = take_mutable_snapshot(None, None);
1195        snapshot.enter(|| {
1196            state.set(1);
1197        });
1198        snapshot.apply().check();
1199
1200        assert_eq!(triggered.get(), 0);
1201        observer.stop();
1202    }
1203
1204    #[test]
1205    fn repeated_owned_scope_observations_reuse_the_same_entry() {
1206        let _guard = reset_runtime();
1207
1208        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1209        let observer = SnapshotStateObserver::new(|callback| callback());
1210        let scope = TestScope("scope");
1211
1212        observer.observe_reads(
1213            scope.clone(),
1214            |_| {},
1215            || {
1216                let _ = state.get();
1217            },
1218        );
1219        observer.observe_reads(
1220            scope,
1221            |_| {},
1222            || {
1223                let _ = state.get();
1224            },
1225        );
1226
1227        let stats = observer.debug_stats();
1228        assert_eq!(stats.scopes_len, 1);
1229        assert_eq!(stats.fast_scopes_len, 0);
1230    }
1231
1232    #[test]
1233    fn with_no_observations_skips_reads() {
1234        let _guard = reset_runtime();
1235
1236        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1237        let triggered = Rc::new(Cell::new(0));
1238        let observer_trigger = triggered.clone();
1239
1240        let observer = SnapshotStateObserver::new(|callback| callback());
1241        observer.start();
1242
1243        let scope = TestScope("scope");
1244        observer.observe_reads(
1245            scope,
1246            move |_| {
1247                observer_trigger.set(observer_trigger.get() + 1);
1248            },
1249            || {
1250                observer.with_no_observations(|| {
1251                    let _ = state.get();
1252                });
1253            },
1254        );
1255
1256        let snapshot = take_mutable_snapshot(None, None);
1257        snapshot.enter(|| {
1258            state.set(1);
1259        });
1260        snapshot.apply().check();
1261
1262        assert_eq!(triggered.get(), 0);
1263        observer.stop();
1264    }
1265
1266    #[test]
1267    fn recycled_observation_refreshes_snapshot_state() {
1268        let _guard = reset_runtime();
1269        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1270        let observer = SnapshotStateObserver::new(|callback| callback());
1271        let mut allocation = None;
1272        for value in 1..=3 {
1273            let parent = take_mutable_snapshot(None, None);
1274            parent.enter(|| {
1275                state.set(value);
1276                let expected = crate::snapshot_v2::current_snapshot().unwrap();
1277                observer.inner.run_with_read_observer(|| {
1278                    let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1279                        crate::snapshot_v2::current_snapshot().unwrap()
1280                    else {
1281                        panic!("expected an observation snapshot");
1282                    };
1283                    assert_eq!(current.snapshot_id(), expected.snapshot_id());
1284                    assert_eq!(current.invalid(), expected.invalid());
1285                    assert!(!current.is_disposed());
1286                    assert!(!current.has_pending_changes());
1287                    assert_eq!(state.get(), value);
1288                    let address = Arc::as_ptr(&current) as usize;
1289                    assert_eq!(*allocation.get_or_insert(address), address);
1290                });
1291            });
1292            parent.apply().check();
1293        }
1294    }
1295
1296    #[test]
1297    fn recycled_observation_preserves_escaped_snapshots() {
1298        let _guard = reset_runtime();
1299        let observer = SnapshotStateObserver::new(|callback| callback());
1300        let escaped = observer
1301            .inner
1302            .run_with_read_observer(|| crate::snapshot_v2::current_snapshot().unwrap());
1303        let id = escaped.snapshot_id();
1304        observer.inner.run_with_read_observer(|| {
1305            let current = crate::snapshot_v2::current_snapshot().unwrap();
1306            let crate::snapshot_v2::AnySnapshot::TransparentMutable(escaped) = &escaped else {
1307                panic!("expected an observation snapshot");
1308            };
1309            assert!(!current.is_same_transparent(escaped));
1310            assert_eq!(escaped.snapshot_id(), id);
1311            assert!(escaped.is_disposed());
1312        });
1313        let weak = observer.inner.run_with_read_observer(|| {
1314            let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1315                crate::snapshot_v2::current_snapshot().unwrap()
1316            else {
1317                panic!("expected an observation snapshot");
1318            };
1319            Arc::downgrade(&current)
1320        });
1321        assert!(weak.upgrade().is_none());
1322        observer.inner.run_with_read_observer(|| {
1323            assert!(weak.upgrade().is_none());
1324        });
1325    }
1326
1327    #[test]
1328    fn recycled_observation_does_not_retain_written_state() {
1329        let _guard = reset_runtime();
1330        let observer = SnapshotStateObserver::new(|callback| callback());
1331        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1332        let owners = Arc::strong_count(&state);
1333        observer.inner.run_with_read_observer(|| {
1334            let current = crate::snapshot_v2::current_snapshot().unwrap();
1335            current.record_write(state.clone());
1336        });
1337        assert_eq!(Arc::strong_count(&state), owners);
1338    }
1339
1340    #[test]
1341    fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
1342        let _guard = reset_runtime();
1343
1344        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1345        let outer_state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1346        let outer_triggered = Rc::new(Cell::new(0));
1347        let inner_triggered = Rc::new(Cell::new(0));
1348
1349        let observer = SnapshotStateObserver::new(|callback| callback());
1350        observer.start();
1351
1352        let outer_scope = TestScope("outer");
1353        let inner_scope = TestScope("inner");
1354        observer.observe_reads(
1355            outer_scope,
1356            {
1357                let outer_triggered = Rc::clone(&outer_triggered);
1358                move |_| outer_triggered.set(outer_triggered.get() + 1)
1359            },
1360            || {
1361                let _ = outer_state.get();
1362                observer.observe_reads(
1363                    inner_scope.clone(),
1364                    {
1365                        let inner_triggered = Rc::clone(&inner_triggered);
1366                        move |_| inner_triggered.set(inner_triggered.get() + 1)
1367                    },
1368                    || {
1369                        let _ = state.get();
1370                    },
1371                );
1372            },
1373        );
1374
1375        let snapshot = take_mutable_snapshot(None, None);
1376        snapshot.enter(|| {
1377            state.set(1);
1378        });
1379        snapshot.apply().check();
1380
1381        assert_eq!(outer_triggered.get(), 0);
1382        assert_eq!(inner_triggered.get(), 1);
1383        let snapshot = take_mutable_snapshot(None, None);
1384        snapshot.enter(|| outer_state.set(1));
1385        snapshot.apply().check();
1386        assert_eq!(outer_triggered.get(), 1);
1387        assert_eq!(inner_triggered.get(), 1);
1388        observer.stop();
1389    }
1390
1391    #[test]
1392    fn unwound_observation_does_not_leak_reads_into_reused_storage() {
1393        let _guard = reset_runtime();
1394        let abandoned = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1395        let live = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1396        let triggered = Rc::new(Cell::new(0));
1397        let observer = SnapshotStateObserver::new(|callback| callback());
1398        observer.start();
1399
1400        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1401            observer.observe_reads(
1402                TestScope("abandoned"),
1403                |_| {},
1404                || {
1405                    let _ = abandoned.get();
1406                    panic!("abandon observation");
1407                },
1408            );
1409        }));
1410        assert!(result.is_err());
1411        observer.observe_reads(
1412            TestScope("live"),
1413            {
1414                let triggered = Rc::clone(&triggered);
1415                move |_| triggered.set(triggered.get() + 1)
1416            },
1417            || {
1418                let _ = live.get();
1419            },
1420        );
1421
1422        let snapshot = take_mutable_snapshot(None, None);
1423        snapshot.enter(|| abandoned.set(1));
1424        snapshot.apply().check();
1425        assert_eq!(triggered.get(), 0);
1426        let snapshot = take_mutable_snapshot(None, None);
1427        snapshot.enter(|| live.set(1));
1428        snapshot.apply().check();
1429        assert_eq!(triggered.get(), 1);
1430        observer.stop();
1431    }
1432
1433    #[test]
1434    fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1435        let _guard = reset_runtime();
1436
1437        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1438        let first_triggered = Rc::new(Cell::new(0));
1439        let second_triggered = Rc::new(Cell::new(0));
1440
1441        let observer = SnapshotStateObserver::new(|callback| callback());
1442        observer.start();
1443
1444        let first_scope = TestScope("first");
1445        let second_scope = TestScope("second");
1446        observer.observe_reads(
1447            first_scope.clone(),
1448            {
1449                let first_triggered = Rc::clone(&first_triggered);
1450                move |_| first_triggered.set(first_triggered.get() + 1)
1451            },
1452            || {
1453                let _ = state.get();
1454            },
1455        );
1456        observer.observe_reads(
1457            second_scope,
1458            {
1459                let second_triggered = Rc::clone(&second_triggered);
1460                move |_| second_triggered.set(second_triggered.get() + 1)
1461            },
1462            || {
1463                let _ = state.get();
1464            },
1465        );
1466
1467        observer.clear(&first_scope);
1468
1469        let snapshot = take_mutable_snapshot(None, None);
1470        snapshot.enter(|| {
1471            state.set(1);
1472        });
1473        snapshot.apply().check();
1474
1475        assert_eq!(first_triggered.get(), 0);
1476        assert_eq!(second_triggered.get(), 1);
1477        observer.stop();
1478    }
1479
1480    #[test]
1481    fn shared_state_notifies_scopes_in_registration_order() {
1482        let _guard = reset_runtime();
1483
1484        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1485        let notifications = Rc::new(RefCell::new(Vec::new()));
1486
1487        let observer = SnapshotStateObserver::new(|callback| callback());
1488        observer.start();
1489
1490        observer.observe_reads(
1491            TestScope("first"),
1492            {
1493                let notifications = Rc::clone(&notifications);
1494                move |_| notifications.borrow_mut().push("first")
1495            },
1496            || {
1497                let _ = state.get();
1498            },
1499        );
1500        observer.observe_reads(
1501            TestScope("second"),
1502            {
1503                let notifications = Rc::clone(&notifications);
1504                move |_| notifications.borrow_mut().push("second")
1505            },
1506            || {
1507                let _ = state.get();
1508            },
1509        );
1510
1511        let snapshot = take_mutable_snapshot(None, None);
1512        snapshot.enter(|| {
1513            state.set(1);
1514        });
1515        snapshot.apply().check();
1516
1517        assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1518        observer.stop();
1519    }
1520
1521    #[test]
1522    fn stateless_recompose_scope_does_not_retain_observer_entry() {
1523        let _guard = reset_runtime();
1524
1525        let observer = SnapshotStateObserver::new(|callback| callback());
1526        let runtime = crate::TestRuntime::new();
1527        let scope = RecomposeScope::new_for_test(runtime.handle());
1528
1529        observer.observe_reads(scope, |_| {}, || {});
1530
1531        let stats = observer.debug_stats();
1532        assert_eq!(stats.scopes_len, 0);
1533        assert_eq!(stats.fast_scopes_len, 0);
1534        assert_eq!(stats.stateless_scope_count, 0);
1535    }
1536
1537    #[test]
1538    fn scope_that_stops_reading_state_is_removed_immediately() {
1539        let _guard = reset_runtime();
1540
1541        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1542        let observer = SnapshotStateObserver::new(|callback| callback());
1543        let runtime = crate::TestRuntime::new();
1544        let scope = RecomposeScope::new_for_test(runtime.handle());
1545        let triggered = Rc::new(Cell::new(0));
1546        let observer_trigger = Rc::clone(&triggered);
1547
1548        observer.observe_reads(
1549            scope.clone(),
1550            move |_| observer_trigger.set(observer_trigger.get() + 1),
1551            || {
1552                let _ = state.get();
1553            },
1554        );
1555
1556        let after_stateful = observer.debug_stats();
1557        assert_eq!(after_stateful.scopes_len, 1);
1558        assert_eq!(after_stateful.fast_scopes_len, 1);
1559
1560        observer.observe_reads(scope, |_| {}, || {});
1561
1562        let after_stateless = observer.debug_stats();
1563        assert_eq!(after_stateless.scopes_len, 0);
1564        assert_eq!(after_stateless.fast_scopes_len, 0);
1565
1566        let snapshot = take_mutable_snapshot(None, None);
1567        snapshot.enter(|| {
1568            state.set(1);
1569        });
1570        snapshot.apply().check();
1571
1572        assert_eq!(triggered.get(), 0);
1573    }
1574
1575    #[test]
1576    fn begin_frame_prunes_dropped_recompose_scope_entries() {
1577        let _guard = reset_runtime();
1578
1579        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1580        let observer = SnapshotStateObserver::new(|callback| callback());
1581        let runtime = crate::TestRuntime::new();
1582        let scope = RecomposeScope::new_for_test(runtime.handle());
1583
1584        observer.observe_reads(
1585            scope.clone(),
1586            |_| {},
1587            || {
1588                let _ = state.get();
1589            },
1590        );
1591
1592        let before_prune = observer.debug_stats();
1593        assert_eq!(before_prune.scopes_len, 1);
1594        assert_eq!(before_prune.fast_scopes_len, 1);
1595
1596        drop(scope);
1597        observer.begin_frame();
1598
1599        let after_prune = observer.debug_stats();
1600        assert_eq!(after_prune.scopes_len, 0);
1601        assert_eq!(after_prune.fast_scopes_len, 0);
1602    }
1603}