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                .map(|stored| stored == scope)
840                .unwrap_or(false),
841            ScopeStorage::RecomposeScope { .. } => false,
842        }
843    }
844
845    fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
846        match &self.scope {
847            ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
848            ScopeStorage::RecomposeScope { weak, .. } => weak
849                .upgrade()
850                .map(|inner| predicate(&RecomposeScope { inner }))
851                .unwrap_or(true),
852        }
853    }
854
855    fn should_retain(&self) -> bool {
856        match &self.scope {
857            ScopeStorage::Owned(_) => true,
858            ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
859        }
860    }
861
862    fn notify(&self) {
863        match &self.scope {
864            ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
865            ScopeStorage::RecomposeScope { weak, .. } => {
866                if let Some(inner) = weak.upgrade() {
867                    (self.on_changed)(&RecomposeScope { inner });
868                }
869            }
870        }
871    }
872}
873
874impl ScopeStorage {
875    fn from_value<T>(value: T) -> Self
876    where
877        T: Any + 'static,
878    {
879        let any = &value as &dyn Any;
880        if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
881            Self::RecomposeScope {
882                id: scope.id(),
883                weak: scope.downgrade(),
884            }
885        } else {
886            Self::Owned(Box::new(value))
887        }
888    }
889}
890
891#[cfg(test)]
892mod tests {
893    use std::cell::Cell;
894
895    use super::*;
896    use crate::{
897        snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
898        state::{NeverEqual, SnapshotMutableState},
899    };
900
901    fn reset_runtime() -> TestRuntimeGuard {
902        reset_runtime_for_tests()
903    }
904
905    #[derive(Clone, Eq, Hash, PartialEq)]
906    struct TestScope(&'static str);
907
908    #[test]
909    fn scope_update_reuses_storage_and_replaces_payload_and_callback() {
910        let first = Rc::new(String::from("first"));
911        let second = Rc::new(String::from("second"));
912        let delivered = Rc::new(RefCell::new(Vec::new()));
913        let mut entry = ScopeEntry::new(0, first.clone(), Rc::new(|_| panic!("stale callback")));
914        let ScopeStorage::Owned(stored) = &entry.scope else {
915            panic!("expected owned scope");
916        };
917        let address = stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>;
918        let received = delivered.clone();
919        entry.update(
920            second.clone(),
921            Rc::new(move |scope| {
922                received.borrow_mut().push(
923                    scope
924                        .downcast_ref::<Rc<String>>()
925                        .unwrap()
926                        .as_str()
927                        .to_owned(),
928                );
929            }),
930        );
931        assert_eq!(Rc::strong_count(&first), 1);
932        assert_eq!(Rc::strong_count(&second), 2);
933        entry.notify();
934        assert_eq!(*delivered.borrow(), vec!["second"]);
935        let ScopeStorage::Owned(stored) = &entry.scope else {
936            panic!("expected owned scope");
937        };
938        assert_eq!(
939            stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>,
940            address
941        );
942        drop(entry);
943        assert_eq!(Rc::strong_count(&second), 1);
944    }
945
946    #[test]
947    fn reobservation_refreshes_captures_and_preserves_shared_callbacks() {
948        let _guard = reset_runtime();
949        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
950        let delivered = Rc::new(RefCell::new(Vec::new()));
951        let callback = |generation| {
952            let delivered = delivered.clone();
953            move |scope: &TestScope| delivered.borrow_mut().push((generation, scope.0))
954        };
955        let scope = TestScope("callback");
956        let observer = SnapshotStateObserver::new(|callback| callback());
957        let read = || {
958            let _ = state.get();
959        };
960        observer.observe_reads(scope.clone(), callback(1), read);
961        let entry = observer.inner.find_scope_entry(&scope).unwrap();
962        let held = entry.borrow().on_changed.clone();
963        observer.observe_reads(scope.clone(), callback(2), read);
964        held(&scope);
965        entry.borrow().notify();
966        drop(held);
967
968        let allocation = Rc::as_ptr(&entry.borrow().on_changed);
969        observer.observe_reads(scope.clone(), callback(3), read);
970        assert!(std::ptr::addr_eq(
971            allocation,
972            Rc::as_ptr(&entry.borrow().on_changed)
973        ));
974        entry.borrow().notify();
975
976        let received = delivered.clone();
977        observer.observe_reads(
978            scope,
979            move |scope| received.borrow_mut().push((4, scope.0)),
980            read,
981        );
982        entry.borrow().notify();
983        assert_eq!(
984            *delivered.borrow(),
985            [
986                (1, "callback"),
987                (2, "callback"),
988                (3, "callback"),
989                (4, "callback")
990            ]
991        );
992    }
993
994    #[test]
995    fn reobservation_across_storage_thresholds_replaces_dependencies_and_callbacks() {
996        let _guard = reset_runtime();
997        let states: Vec<_> = (0..MAX_OBSERVED_STATES + 2)
998            .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
999            .collect();
1000        let notifications = Rc::new(RefCell::new(Vec::new()));
1001        let observer = SnapshotStateObserver::new(|callback| callback());
1002        observer.start();
1003        for (generation, count) in [MAX_OBSERVED_STATES, MAX_OBSERVED_STATES + 1, 2, 0]
1004            .into_iter()
1005            .enumerate()
1006        {
1007            observer.begin_frame();
1008            let recorded = notifications.clone();
1009            observer.observe_reads(
1010                TestScope("changing"),
1011                move |scope| {
1012                    assert_eq!(scope.0, "changing");
1013                    recorded.borrow_mut().push(generation);
1014                },
1015                || {
1016                    for state in states.iter().take(count) {
1017                        let _ = state.get();
1018                        let _ = state.get();
1019                    }
1020                },
1021            );
1022            notifications.borrow_mut().clear();
1023            for (index, state) in states.iter().enumerate() {
1024                let snapshot = take_mutable_snapshot(None, None);
1025                snapshot.enter(|| state.set(generation as i32));
1026                snapshot.apply().check();
1027                let expected = (index + 1).min(count);
1028                assert_eq!(
1029                    *notifications.borrow(),
1030                    vec![generation; expected],
1031                    "count={count}, changed state={index}"
1032                );
1033            }
1034        }
1035    }
1036
1037    #[test]
1038    fn reobservation_preserves_notifications_when_dependencies_repeat_or_change() {
1039        let _guard = reset_runtime();
1040        let states: Vec<_> = (0..3)
1041            .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
1042            .collect();
1043        let notifications = Rc::new(RefCell::new(Vec::new()));
1044        let observer = SnapshotStateObserver::new(|callback| callback());
1045        observer.start();
1046        for (generation, indices) in [[0, 1], [0, 1], [1, 0], [1, 2], [1, 2]]
1047            .into_iter()
1048            .enumerate()
1049        {
1050            observer.begin_frame();
1051            let received = notifications.clone();
1052            observer.observe_reads(
1053                TestScope("repeated"),
1054                move |_| received.borrow_mut().push(generation),
1055                || {
1056                    for index in indices {
1057                        let _ = states[index].get();
1058                    }
1059                },
1060            );
1061            for (index, state) in states.iter().enumerate() {
1062                notifications.borrow_mut().clear();
1063                let snapshot = take_mutable_snapshot(None, None);
1064                snapshot.enter(|| state.set(generation as i32));
1065                snapshot.apply().check();
1066                assert_eq!(
1067                    *notifications.borrow(),
1068                    if indices.contains(&index) {
1069                        vec![generation]
1070                    } else {
1071                        vec![]
1072                    },
1073                    "generation={generation}, state={index}"
1074                );
1075            }
1076        }
1077        observer.clear(&TestScope("repeated"));
1078        notifications.borrow_mut().clear();
1079        for state in states {
1080            observer.notify_changes(&[state]);
1081        }
1082        assert!(notifications.borrow().is_empty());
1083    }
1084
1085    #[test]
1086    fn stateless_scope_can_start_observing_and_replace_its_callback_before_the_block() {
1087        let _guard = reset_runtime();
1088        let observer = SnapshotStateObserver::new(|callback| callback());
1089        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1090        let observed: Arc<dyn StateObject> = state.clone();
1091        let notifications = Rc::new(RefCell::new(Vec::new()));
1092        let discarded = notifications.clone();
1093        observer.observe_reads(
1094            TestScope("changing"),
1095            move |_| discarded.borrow_mut().push(0),
1096            || {},
1097        );
1098        assert_eq!(Rc::strong_count(&notifications), 1);
1099        assert_eq!(observer.debug_stats().scopes_len, 0);
1100        for generation in 1..=2 {
1101            observer.begin_frame();
1102            let delivered = notifications.clone();
1103            observer.observe_reads(
1104                TestScope("changing"),
1105                move |_| delivered.borrow_mut().push(generation),
1106                || {
1107                    observer.notify_changes(std::slice::from_ref(&observed));
1108                    let _ = state.get();
1109                },
1110            );
1111            observer.notify_changes(std::slice::from_ref(&observed));
1112        }
1113        assert_eq!(*notifications.borrow(), vec![1, 2, 2]);
1114        observer.clear(&TestScope("changing"));
1115        assert_eq!(Rc::strong_count(&notifications), 1);
1116    }
1117
1118    #[test]
1119    fn callback_captures_are_released_on_replacement_and_clear() {
1120        let _guard = reset_runtime();
1121        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1122        let observer = SnapshotStateObserver::new(|callback| callback());
1123        let owners = [Rc::new(Cell::new(0)), Rc::new(Cell::new(0))];
1124        for owner in &owners {
1125            let captured = owner.clone();
1126            observer.observe_reads(
1127                TestScope("owner"),
1128                move |_| captured.set(captured.get() + 1),
1129                || {
1130                    let _ = state.get();
1131                },
1132            );
1133            assert_eq!(Rc::strong_count(owner), 2);
1134        }
1135        assert_eq!(Rc::strong_count(&owners[0]), 1);
1136        observer.clear(&TestScope("owner"));
1137        assert_eq!(Rc::strong_count(&owners[1]), 1);
1138    }
1139
1140    #[test]
1141    fn notifies_scope_when_state_changes() {
1142        let _guard = reset_runtime();
1143
1144        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1145        let triggered = Rc::new(Cell::new(0));
1146        let observer_trigger = triggered.clone();
1147
1148        let observer = SnapshotStateObserver::new(|callback| callback());
1149        observer.start();
1150
1151        let scope = TestScope("scope");
1152        observer.observe_reads(
1153            scope.clone(),
1154            move |_| {
1155                observer_trigger.set(observer_trigger.get() + 1);
1156            },
1157            || {
1158                let _ = state.get();
1159            },
1160        );
1161
1162        let snapshot = take_mutable_snapshot(None, None);
1163        snapshot.enter(|| {
1164            state.set(1);
1165        });
1166        snapshot.apply().check();
1167
1168        assert_eq!(triggered.get(), 1);
1169        observer.stop();
1170    }
1171
1172    #[test]
1173    fn clear_removes_scope_observation() {
1174        let _guard = reset_runtime();
1175
1176        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1177        let triggered = Rc::new(Cell::new(0));
1178        let observer_trigger = triggered.clone();
1179
1180        let observer = SnapshotStateObserver::new(|callback| callback());
1181        observer.start();
1182
1183        let scope = TestScope("scope");
1184        observer.observe_reads(
1185            scope.clone(),
1186            move |_| {
1187                observer_trigger.set(observer_trigger.get() + 1);
1188            },
1189            || {
1190                let _ = state.get();
1191            },
1192        );
1193
1194        observer.clear(&scope);
1195
1196        let snapshot = take_mutable_snapshot(None, None);
1197        snapshot.enter(|| {
1198            state.set(1);
1199        });
1200        snapshot.apply().check();
1201
1202        assert_eq!(triggered.get(), 0);
1203        observer.stop();
1204    }
1205
1206    #[test]
1207    fn repeated_owned_scope_observations_reuse_the_same_entry() {
1208        let _guard = reset_runtime();
1209
1210        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1211        let observer = SnapshotStateObserver::new(|callback| callback());
1212        let scope = TestScope("scope");
1213
1214        observer.observe_reads(
1215            scope.clone(),
1216            |_| {},
1217            || {
1218                let _ = state.get();
1219            },
1220        );
1221        observer.observe_reads(
1222            scope,
1223            |_| {},
1224            || {
1225                let _ = state.get();
1226            },
1227        );
1228
1229        let stats = observer.debug_stats();
1230        assert_eq!(stats.scopes_len, 1);
1231        assert_eq!(stats.fast_scopes_len, 0);
1232    }
1233
1234    #[test]
1235    fn with_no_observations_skips_reads() {
1236        let _guard = reset_runtime();
1237
1238        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1239        let triggered = Rc::new(Cell::new(0));
1240        let observer_trigger = triggered.clone();
1241
1242        let observer = SnapshotStateObserver::new(|callback| callback());
1243        observer.start();
1244
1245        let scope = TestScope("scope");
1246        observer.observe_reads(
1247            scope.clone(),
1248            move |_| {
1249                observer_trigger.set(observer_trigger.get() + 1);
1250            },
1251            || {
1252                observer.with_no_observations(|| {
1253                    let _ = state.get();
1254                });
1255            },
1256        );
1257
1258        let snapshot = take_mutable_snapshot(None, None);
1259        snapshot.enter(|| {
1260            state.set(1);
1261        });
1262        snapshot.apply().check();
1263
1264        assert_eq!(triggered.get(), 0);
1265        observer.stop();
1266    }
1267
1268    #[test]
1269    fn recycled_observation_refreshes_snapshot_state() {
1270        let _guard = reset_runtime();
1271        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1272        let observer = SnapshotStateObserver::new(|callback| callback());
1273        let mut allocation = None;
1274        for value in 1..=3 {
1275            let parent = take_mutable_snapshot(None, None);
1276            parent.enter(|| {
1277                state.set(value);
1278                let expected = crate::snapshot_v2::current_snapshot().unwrap();
1279                observer.inner.run_with_read_observer(|| {
1280                    let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1281                        crate::snapshot_v2::current_snapshot().unwrap()
1282                    else {
1283                        panic!("expected an observation snapshot");
1284                    };
1285                    assert_eq!(current.snapshot_id(), expected.snapshot_id());
1286                    assert_eq!(current.invalid(), expected.invalid());
1287                    assert!(!current.is_disposed());
1288                    assert!(!current.has_pending_changes());
1289                    assert_eq!(state.get(), value);
1290                    let address = Arc::as_ptr(&current) as usize;
1291                    assert_eq!(*allocation.get_or_insert(address), address);
1292                });
1293            });
1294            parent.apply().check();
1295        }
1296    }
1297
1298    #[test]
1299    fn recycled_observation_preserves_escaped_snapshots() {
1300        let _guard = reset_runtime();
1301        let observer = SnapshotStateObserver::new(|callback| callback());
1302        let escaped = observer
1303            .inner
1304            .run_with_read_observer(|| crate::snapshot_v2::current_snapshot().unwrap());
1305        let id = escaped.snapshot_id();
1306        observer.inner.run_with_read_observer(|| {
1307            let current = crate::snapshot_v2::current_snapshot().unwrap();
1308            let crate::snapshot_v2::AnySnapshot::TransparentMutable(escaped) = &escaped else {
1309                panic!("expected an observation snapshot");
1310            };
1311            assert!(!current.is_same_transparent(escaped));
1312            assert_eq!(escaped.snapshot_id(), id);
1313            assert!(escaped.is_disposed());
1314        });
1315        let weak = observer.inner.run_with_read_observer(|| {
1316            let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1317                crate::snapshot_v2::current_snapshot().unwrap()
1318            else {
1319                panic!("expected an observation snapshot");
1320            };
1321            Arc::downgrade(&current)
1322        });
1323        assert!(weak.upgrade().is_none());
1324        observer.inner.run_with_read_observer(|| {
1325            assert!(weak.upgrade().is_none());
1326        });
1327    }
1328
1329    #[test]
1330    fn recycled_observation_does_not_retain_written_state() {
1331        let _guard = reset_runtime();
1332        let observer = SnapshotStateObserver::new(|callback| callback());
1333        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1334        let owners = Arc::strong_count(&state);
1335        observer.inner.run_with_read_observer(|| {
1336            let current = crate::snapshot_v2::current_snapshot().unwrap();
1337            current.record_write(state.clone());
1338        });
1339        assert_eq!(Arc::strong_count(&state), owners);
1340    }
1341
1342    #[test]
1343    fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
1344        let _guard = reset_runtime();
1345
1346        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1347        let outer_state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1348        let outer_triggered = Rc::new(Cell::new(0));
1349        let inner_triggered = Rc::new(Cell::new(0));
1350
1351        let observer = SnapshotStateObserver::new(|callback| callback());
1352        observer.start();
1353
1354        let outer_scope = TestScope("outer");
1355        let inner_scope = TestScope("inner");
1356        observer.observe_reads(
1357            outer_scope.clone(),
1358            {
1359                let outer_triggered = Rc::clone(&outer_triggered);
1360                move |_| outer_triggered.set(outer_triggered.get() + 1)
1361            },
1362            || {
1363                let _ = outer_state.get();
1364                observer.observe_reads(
1365                    inner_scope.clone(),
1366                    {
1367                        let inner_triggered = Rc::clone(&inner_triggered);
1368                        move |_| inner_triggered.set(inner_triggered.get() + 1)
1369                    },
1370                    || {
1371                        let _ = state.get();
1372                    },
1373                );
1374            },
1375        );
1376
1377        let snapshot = take_mutable_snapshot(None, None);
1378        snapshot.enter(|| {
1379            state.set(1);
1380        });
1381        snapshot.apply().check();
1382
1383        assert_eq!(outer_triggered.get(), 0);
1384        assert_eq!(inner_triggered.get(), 1);
1385        let snapshot = take_mutable_snapshot(None, None);
1386        snapshot.enter(|| outer_state.set(1));
1387        snapshot.apply().check();
1388        assert_eq!(outer_triggered.get(), 1);
1389        assert_eq!(inner_triggered.get(), 1);
1390        observer.stop();
1391    }
1392
1393    #[test]
1394    fn unwound_observation_does_not_leak_reads_into_reused_storage() {
1395        let _guard = reset_runtime();
1396        let abandoned = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1397        let live = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1398        let triggered = Rc::new(Cell::new(0));
1399        let observer = SnapshotStateObserver::new(|callback| callback());
1400        observer.start();
1401
1402        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1403            observer.observe_reads(
1404                TestScope("abandoned"),
1405                |_| {},
1406                || {
1407                    let _ = abandoned.get();
1408                    panic!("abandon observation");
1409                },
1410            );
1411        }));
1412        assert!(result.is_err());
1413        observer.observe_reads(
1414            TestScope("live"),
1415            {
1416                let triggered = Rc::clone(&triggered);
1417                move |_| triggered.set(triggered.get() + 1)
1418            },
1419            || {
1420                let _ = live.get();
1421            },
1422        );
1423
1424        let snapshot = take_mutable_snapshot(None, None);
1425        snapshot.enter(|| abandoned.set(1));
1426        snapshot.apply().check();
1427        assert_eq!(triggered.get(), 0);
1428        let snapshot = take_mutable_snapshot(None, None);
1429        snapshot.enter(|| live.set(1));
1430        snapshot.apply().check();
1431        assert_eq!(triggered.get(), 1);
1432        observer.stop();
1433    }
1434
1435    #[test]
1436    fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1437        let _guard = reset_runtime();
1438
1439        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1440        let first_triggered = Rc::new(Cell::new(0));
1441        let second_triggered = Rc::new(Cell::new(0));
1442
1443        let observer = SnapshotStateObserver::new(|callback| callback());
1444        observer.start();
1445
1446        let first_scope = TestScope("first");
1447        let second_scope = TestScope("second");
1448        observer.observe_reads(
1449            first_scope.clone(),
1450            {
1451                let first_triggered = Rc::clone(&first_triggered);
1452                move |_| first_triggered.set(first_triggered.get() + 1)
1453            },
1454            || {
1455                let _ = state.get();
1456            },
1457        );
1458        observer.observe_reads(
1459            second_scope.clone(),
1460            {
1461                let second_triggered = Rc::clone(&second_triggered);
1462                move |_| second_triggered.set(second_triggered.get() + 1)
1463            },
1464            || {
1465                let _ = state.get();
1466            },
1467        );
1468
1469        observer.clear(&first_scope);
1470
1471        let snapshot = take_mutable_snapshot(None, None);
1472        snapshot.enter(|| {
1473            state.set(1);
1474        });
1475        snapshot.apply().check();
1476
1477        assert_eq!(first_triggered.get(), 0);
1478        assert_eq!(second_triggered.get(), 1);
1479        observer.stop();
1480    }
1481
1482    #[test]
1483    fn shared_state_notifies_scopes_in_registration_order() {
1484        let _guard = reset_runtime();
1485
1486        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1487        let notifications = Rc::new(RefCell::new(Vec::new()));
1488
1489        let observer = SnapshotStateObserver::new(|callback| callback());
1490        observer.start();
1491
1492        observer.observe_reads(
1493            TestScope("first"),
1494            {
1495                let notifications = Rc::clone(&notifications);
1496                move |_| notifications.borrow_mut().push("first")
1497            },
1498            || {
1499                let _ = state.get();
1500            },
1501        );
1502        observer.observe_reads(
1503            TestScope("second"),
1504            {
1505                let notifications = Rc::clone(&notifications);
1506                move |_| notifications.borrow_mut().push("second")
1507            },
1508            || {
1509                let _ = state.get();
1510            },
1511        );
1512
1513        let snapshot = take_mutable_snapshot(None, None);
1514        snapshot.enter(|| {
1515            state.set(1);
1516        });
1517        snapshot.apply().check();
1518
1519        assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1520        observer.stop();
1521    }
1522
1523    #[test]
1524    fn stateless_recompose_scope_does_not_retain_observer_entry() {
1525        let _guard = reset_runtime();
1526
1527        let observer = SnapshotStateObserver::new(|callback| callback());
1528        let runtime = crate::TestRuntime::new();
1529        let scope = RecomposeScope::new_for_test(runtime.handle());
1530
1531        observer.observe_reads(scope, |_| {}, || {});
1532
1533        let stats = observer.debug_stats();
1534        assert_eq!(stats.scopes_len, 0);
1535        assert_eq!(stats.fast_scopes_len, 0);
1536        assert_eq!(stats.stateless_scope_count, 0);
1537    }
1538
1539    #[test]
1540    fn scope_that_stops_reading_state_is_removed_immediately() {
1541        let _guard = reset_runtime();
1542
1543        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1544        let observer = SnapshotStateObserver::new(|callback| callback());
1545        let runtime = crate::TestRuntime::new();
1546        let scope = RecomposeScope::new_for_test(runtime.handle());
1547        let triggered = Rc::new(Cell::new(0));
1548        let observer_trigger = Rc::clone(&triggered);
1549
1550        observer.observe_reads(
1551            scope.clone(),
1552            move |_| observer_trigger.set(observer_trigger.get() + 1),
1553            || {
1554                let _ = state.get();
1555            },
1556        );
1557
1558        let after_stateful = observer.debug_stats();
1559        assert_eq!(after_stateful.scopes_len, 1);
1560        assert_eq!(after_stateful.fast_scopes_len, 1);
1561
1562        observer.observe_reads(scope, |_| {}, || {});
1563
1564        let after_stateless = observer.debug_stats();
1565        assert_eq!(after_stateless.scopes_len, 0);
1566        assert_eq!(after_stateless.fast_scopes_len, 0);
1567
1568        let snapshot = take_mutable_snapshot(None, None);
1569        snapshot.enter(|| {
1570            state.set(1);
1571        });
1572        snapshot.apply().check();
1573
1574        assert_eq!(triggered.get(), 0);
1575    }
1576
1577    #[test]
1578    fn begin_frame_prunes_dropped_recompose_scope_entries() {
1579        let _guard = reset_runtime();
1580
1581        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1582        let observer = SnapshotStateObserver::new(|callback| callback());
1583        let runtime = crate::TestRuntime::new();
1584        let scope = RecomposeScope::new_for_test(runtime.handle());
1585
1586        observer.observe_reads(
1587            scope.clone(),
1588            |_| {},
1589            || {
1590                let _ = state.get();
1591            },
1592        );
1593
1594        let before_prune = observer.debug_stats();
1595        assert_eq!(before_prune.scopes_len, 1);
1596        assert_eq!(before_prune.fast_scopes_len, 1);
1597
1598        drop(scope);
1599        observer.begin_frame();
1600
1601        let after_prune = observer.debug_stats();
1602        assert_eq!(after_prune.scopes_len, 0);
1603        assert_eq!(after_prune.fast_scopes_len, 0);
1604    }
1605}