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::{ReadObserver, StateObjectId, register_apply_observer},
18    state::StateObject,
19};
20
21type Executor = dyn Fn(Box<dyn FnOnce() + 'static>) + 'static;
22
23/// Observer that records state object reads performed inside a given scope and
24/// notifies the caller when any of the observed objects change.
25///
26/// This is a pragmatic Rust translation of Jetpack Compose's
27/// `SnapshotStateObserver`. The implementation focuses on the core behaviour
28/// needed by the Cranpose runtime:
29/// - Tracking state object reads per logical scope.
30/// - Reacting to snapshot apply notifications.
31/// - Scheduling invalidation callbacks via the supplied executor.
32///
33/// Advanced features from the Kotlin version (derived state tracking, change
34/// coalescing, queue minimisation) are deferred
35#[derive(Clone)]
36pub struct SnapshotStateObserver {
37    inner: Rc<SnapshotStateObserverInner>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub struct SnapshotStateObserverDebugStats {
42    pub scopes_len: usize,
43    pub scopes_cap: usize,
44    pub fast_scopes_len: usize,
45    pub fast_scopes_cap: usize,
46    pub stateless_scope_count: usize,
47    pub observed_state_count: usize,
48    pub observed_state_capacity: usize,
49}
50
51impl SnapshotStateObserver {
52    /// Create a new observer that schedules callbacks using `on_changed_executor`.
53    pub fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
54        let inner = Rc::new(SnapshotStateObserverInner::new(on_changed_executor));
55        inner.set_self(Rc::downgrade(&inner));
56        Self { inner }
57    }
58
59    /// Observe state object reads performed while executing `block`.
60    ///
61    /// Subsequent calls to `observe_reads` replace any previously recorded
62    /// observations for the provided `scope`. When one of the observed objects
63    /// mutates, `on_value_changed_for_scope` will be invoked on the executor.
64    pub fn observe_reads<T, R>(
65        &self,
66        scope: T,
67        on_value_changed_for_scope: impl Fn(&T) + 'static,
68        block: impl FnOnce() -> R,
69    ) -> R
70    where
71        T: Any + Clone + Eq + Hash + 'static,
72    {
73        self.inner
74            .observe_reads(scope, on_value_changed_for_scope, block)
75    }
76
77    /// Notify the observer that a new composition frame is starting.
78    pub fn begin_frame(&self) {
79        self.inner.begin_frame();
80    }
81
82    /// Drop bookkeeping for scopes that were released during the current frame.
83    pub fn prune_dead_scopes(&self) {
84        self.inner.prune_dead_scopes();
85    }
86
87    /// Temporarily pause read observation while executing `block`.
88    pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
89        self.inner.with_no_observations(block)
90    }
91
92    /// Remove any recorded reads for `scope`.
93    pub fn clear<T>(&self, scope: &T)
94    where
95        T: Any + Eq + Hash + 'static,
96    {
97        self.inner.clear(scope);
98    }
99
100    /// Remove recorded reads for scopes that satisfy `predicate`.
101    pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
102        self.inner.clear_if(predicate);
103    }
104
105    /// Remove all recorded observations.
106    pub fn clear_all(&self) {
107        self.inner.clear_all();
108    }
109
110    /// Begin listening for snapshot apply notifications.
111    pub fn start(&self) {
112        let weak = Rc::downgrade(&self.inner);
113        self.inner.start(weak);
114    }
115
116    /// Stop listening for snapshot apply notifications.
117    pub fn stop(&self) {
118        self.inner.stop();
119    }
120
121    pub fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
122        self.inner.debug_stats()
123    }
124
125    #[cfg(test)]
126    pub fn notify_changes(&self, modified: &[Arc<dyn StateObject>]) {
127        self.inner.handle_apply(modified);
128    }
129}
130
131struct SnapshotStateObserverInner {
132    executor: Rc<Executor>,
133    owned_scopes: RefCell<HashMap<OwnedScopeIndexKey, OwnedScopeBucket>>,
134    fast_scopes: RefCell<HashMap<ScopeId, Rc<RefCell<ScopeEntry>>>>,
135    indexed_scopes: RefCell<HashMap<usize, Rc<RefCell<ScopeEntry>>>>,
136    observed_to_scopes: RefCell<HashMap<StateObjectId, HashSet<usize>>>,
137    pause_count: Rc<Cell<usize>>,
138    active_read_targets: Rc<RefCell<Vec<Rc<RefCell<ObservedIds>>>>>,
139    read_dispatcher: ReadObserver,
140    apply_handle: RefCell<Option<crate::snapshot_v2::ObserverHandle>>,
141    weak_self: RefCell<Weak<SnapshotStateObserverInner>>,
142    frame_version: Cell<u64>,
143    next_entry_id: Cell<usize>,
144}
145
146#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
147struct OwnedScopeIndexKey {
148    type_id: TypeId,
149    value_hash: u64,
150}
151
152type OwnedScopeBucket = SmallVec<[Rc<RefCell<ScopeEntry>>; 1]>;
153
154fn owned_scope_index_key<T>(scope: &T) -> OwnedScopeIndexKey
155where
156    T: Any + Hash + 'static,
157{
158    let mut hasher = default_hash::new();
159    scope.hash(&mut hasher);
160    OwnedScopeIndexKey {
161        type_id: TypeId::of::<T>(),
162        value_hash: hasher.finish(),
163    }
164}
165
166impl SnapshotStateObserverInner {
167    const MIN_RETAINED_SCOPE_CAPACITY: usize = 256;
168
169    fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
170        let pause_count = Rc::new(Cell::new(0));
171        let active_read_targets = Rc::new(RefCell::new(Vec::<Rc<RefCell<ObservedIds>>>::new()));
172        let dispatcher_pause_count = Rc::clone(&pause_count);
173        let dispatcher_targets = Rc::clone(&active_read_targets);
174        let read_dispatcher: ReadObserver = Arc::new(move |state| {
175            if dispatcher_pause_count.get() > 0 {
176                return;
177            }
178            let observed = dispatcher_targets.borrow().last().cloned();
179            if let Some(observed) = observed {
180                observed.borrow_mut().insert(state);
181            }
182        });
183
184        Self {
185            executor: Rc::new(on_changed_executor),
186            owned_scopes: RefCell::new(HashMap::default()),
187            fast_scopes: RefCell::new(HashMap::default()),
188            indexed_scopes: RefCell::new(HashMap::default()),
189            observed_to_scopes: RefCell::new(HashMap::default()),
190            pause_count,
191            active_read_targets,
192            read_dispatcher,
193            apply_handle: RefCell::new(None),
194            weak_self: RefCell::new(Weak::new()),
195            frame_version: Cell::new(0),
196            next_entry_id: Cell::new(0),
197        }
198    }
199
200    fn set_self(&self, weak: Weak<SnapshotStateObserverInner>) {
201        self.weak_self.replace(weak);
202    }
203
204    fn begin_frame(&self) {
205        let next = self.frame_version.get().wrapping_add(1);
206        self.frame_version.set(next);
207        self.prune_dead_scopes();
208    }
209
210    fn observe_reads<T, R>(
211        &self,
212        scope: T,
213        on_value_changed_for_scope: impl Fn(&T) + 'static,
214        block: impl FnOnce() -> R,
215    ) -> R
216    where
217        T: Any + Clone + Eq + Hash + 'static,
218    {
219        let frame_version = self.frame_version.get();
220        let has_frame_version = frame_version != 0;
221
222        let on_changed: Rc<dyn Fn(&dyn Any)> = {
223            let callback = Rc::new(on_value_changed_for_scope);
224            Rc::new(move |scope_any: &dyn Any| {
225                if let Some(typed) = scope_any.downcast_ref::<T>() {
226                    callback(typed);
227                }
228            })
229        };
230
231        let existing_entry = self.find_scope_entry(&scope);
232        if let Some(entry) = existing_entry.as_ref() {
233            let already_observed = {
234                let mut entry_mut = entry.borrow_mut();
235                entry_mut.update(scope.clone(), on_changed.clone());
236                has_frame_version && entry_mut.last_seen_version == frame_version
237            };
238            if already_observed {
239                return block();
240            }
241        }
242
243        let observed = Rc::new(RefCell::new(ObservedIds::new()));
244        self.active_read_targets
245            .borrow_mut()
246            .push(Rc::clone(&observed));
247        struct ActiveObservationGuard {
248            stack: Rc<RefCell<Vec<Rc<RefCell<ObservedIds>>>>>,
249        }
250        impl Drop for ActiveObservationGuard {
251            fn drop(&mut self) {
252                self.stack.borrow_mut().pop();
253            }
254        }
255        let _guard = ActiveObservationGuard {
256            stack: Rc::clone(&self.active_read_targets),
257        };
258
259        let result = self.run_with_read_observer(block);
260
261        if observed.borrow().is_empty() {
262            if existing_entry.is_some() {
263                self.clear(&scope);
264            }
265            return result;
266        }
267
268        let observed = {
269            let mut observed = observed.borrow_mut();
270            std::mem::replace(&mut *observed, ObservedIds::new())
271        };
272        let entry = existing_entry
273            .unwrap_or_else(|| self.insert_scope_entry(scope.clone(), on_changed.clone()));
274        {
275            let mut entry_mut = entry.borrow_mut();
276            entry_mut.update(scope, on_changed);
277            entry_mut.last_seen_version = if has_frame_version {
278                frame_version
279            } else {
280                u64::MAX
281            };
282        }
283        self.replace_observed_ids(&entry, observed);
284
285        result
286    }
287
288    fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
289        self.pause_count.set(self.pause_count.get() + 1);
290        let result = block();
291        self.pause_count
292            .set(self.pause_count.get().saturating_sub(1));
293        result
294    }
295
296    fn clear<T>(&self, scope: &T)
297    where
298        T: Any + Eq + Hash + 'static,
299    {
300        if let Some(rc_scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
301            if let Some(entry) = self.fast_scopes.borrow_mut().remove(&rc_scope.id()) {
302                self.unregister_entry(&entry);
303            }
304            return;
305        }
306
307        let removed = self.remove_owned_scope_entry(scope);
308        if let Some(entry) = removed {
309            self.unregister_entry(&entry);
310        }
311    }
312
313    fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
314        let removed_fast = {
315            let mut fast_scopes = self.fast_scopes.borrow_mut();
316            let removed_ids: Vec<_> = fast_scopes
317                .iter()
318                .filter(|(_, entry)| entry.borrow().matches_predicate(&predicate))
319                .map(|(scope_id, _)| *scope_id)
320                .collect();
321            removed_ids
322                .into_iter()
323                .filter_map(|scope_id| fast_scopes.remove(&scope_id))
324                .collect::<Vec<_>>()
325        };
326        let removed_owned =
327            { self.partition_owned_scopes(|entry| entry.matches_predicate(&predicate)) };
328
329        for entry in removed_fast.into_iter().chain(removed_owned) {
330            self.unregister_entry(&entry);
331        }
332    }
333
334    fn clear_all(&self) {
335        self.fast_scopes.borrow_mut().clear();
336        self.owned_scopes.borrow_mut().clear();
337        self.indexed_scopes.borrow_mut().clear();
338        self.observed_to_scopes.borrow_mut().clear();
339    }
340
341    fn start(&self, weak_self: Weak<SnapshotStateObserverInner>) {
342        if self.apply_handle.borrow().is_some() {
343            return;
344        }
345
346        let handle = register_apply_observer(Rc::new(move |modified, _snapshot_id| {
347            if let Some(inner) = weak_self.upgrade() {
348                inner.handle_apply(modified);
349            }
350        }));
351        self.apply_handle.replace(Some(handle));
352    }
353
354    fn stop(&self) {
355        if let Some(handle) = self.apply_handle.borrow_mut().take() {
356            drop(handle);
357        }
358    }
359
360    fn find_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
361    where
362        T: Any + Eq + Hash + 'static,
363    {
364        if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
365            return self.fast_scopes.borrow().get(&scope.id()).cloned();
366        }
367
368        self.find_owned_scope_entry(scope)
369    }
370
371    fn insert_scope_entry(
372        &self,
373        scope: impl Any + Clone + Eq + Hash + 'static,
374        on_changed: Rc<dyn Fn(&dyn Any)>,
375    ) -> Rc<RefCell<ScopeEntry>> {
376        let entry_id = self.next_entry_id.get();
377        self.next_entry_id.set(entry_id.wrapping_add(1));
378        let recompose_scope_id = (&scope as &dyn Any)
379            .downcast_ref::<RecomposeScope>()
380            .map(RecomposeScope::id);
381        let owned_scope_key = recompose_scope_id
382            .is_none()
383            .then(|| owned_scope_index_key(&scope));
384        let entry = Rc::new(RefCell::new(ScopeEntry::new(entry_id, scope, on_changed)));
385        self.indexed_scopes
386            .borrow_mut()
387            .insert(entry_id, Rc::clone(&entry));
388        if let Some(scope_id) = recompose_scope_id {
389            self.fast_scopes
390                .borrow_mut()
391                .insert(scope_id, Rc::clone(&entry));
392        } else if let Some(scope_key) = owned_scope_key {
393            self.owned_scopes
394                .borrow_mut()
395                .entry(scope_key)
396                .or_default()
397                .push(Rc::clone(&entry));
398        }
399        entry
400    }
401
402    fn prune_dead_scopes(&self) {
403        let removed_fast = {
404            let mut fast_scopes = self.fast_scopes.borrow_mut();
405            let removed_ids: Vec<_> = fast_scopes
406                .iter()
407                .filter(|(_, entry)| !entry.borrow().should_retain())
408                .map(|(scope_id, _)| *scope_id)
409                .collect();
410            let removed = removed_ids
411                .into_iter()
412                .filter_map(|scope_id| fast_scopes.remove(&scope_id))
413                .collect::<Vec<_>>();
414            shrink_map_if_sparse(&mut fast_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
415            removed
416        };
417
418        let removed_owned = { self.partition_owned_scopes(|entry| !entry.should_retain()) };
419
420        for entry in removed_fast.into_iter().chain(removed_owned) {
421            self.unregister_entry(&entry);
422        }
423    }
424
425    fn find_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
426    where
427        T: Any + Eq + Hash + 'static,
428    {
429        let key = owned_scope_index_key(scope);
430        self.owned_scopes.borrow().get(&key).and_then(|bucket| {
431            bucket
432                .iter()
433                .find(|entry| entry.borrow().matches_scope(scope))
434                .cloned()
435        })
436    }
437
438    fn remove_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        let mut owned_scopes = self.owned_scopes.borrow_mut();
444        let mut removed = None;
445        let mut remove_bucket = false;
446        if let Some(bucket) = owned_scopes.get_mut(&key)
447            && let Some(index) = bucket
448                .iter()
449                .position(|entry| entry.borrow().matches_scope(scope))
450        {
451            removed = Some(bucket.remove(index));
452            remove_bucket = bucket.is_empty();
453        }
454        if remove_bucket {
455            owned_scopes.remove(&key);
456        }
457        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
458        removed
459    }
460
461    fn partition_owned_scopes(
462        &self,
463        should_remove: impl Fn(&ScopeEntry) -> bool,
464    ) -> Vec<Rc<RefCell<ScopeEntry>>> {
465        let mut owned_scopes = self.owned_scopes.borrow_mut();
466        let mut retained = HashMap::default();
467        let mut removed = Vec::new();
468        for (key, mut bucket) in owned_scopes.drain() {
469            let mut retained_bucket = OwnedScopeBucket::new();
470            for entry in bucket.drain(..) {
471                if should_remove(&entry.borrow()) {
472                    removed.push(entry);
473                } else {
474                    retained_bucket.push(entry);
475                }
476            }
477            if !retained_bucket.is_empty() {
478                retained.insert(key, retained_bucket);
479            }
480        }
481        *owned_scopes = retained;
482        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
483        removed
484    }
485
486    fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
487        let owned_scopes = self.owned_scopes.borrow();
488        let fast_scopes = self.fast_scopes.borrow();
489        let owned_scope_len = owned_scopes.values().map(SmallVec::len).sum::<usize>();
490        let owned_scope_cap =
491            owned_scopes.capacity() + owned_scopes.values().map(SmallVec::capacity).sum::<usize>();
492        let scopes_len = owned_scope_len + fast_scopes.len();
493        let scopes_cap = owned_scope_cap + fast_scopes.capacity();
494        let mut observed_state_count = 0;
495        let mut observed_state_capacity = 0;
496        let mut stateless_scope_count = 0;
497
498        for entry in owned_scopes
499            .values()
500            .flat_map(|bucket| bucket.iter())
501            .chain(fast_scopes.values())
502        {
503            let entry = entry.borrow();
504            observed_state_count += entry.observed.len();
505            observed_state_capacity += entry.observed.capacity();
506            stateless_scope_count += usize::from(entry.observed.is_empty());
507        }
508
509        SnapshotStateObserverDebugStats {
510            scopes_len,
511            scopes_cap,
512            fast_scopes_len: fast_scopes.len(),
513            fast_scopes_cap: fast_scopes.capacity(),
514            stateless_scope_count,
515            observed_state_count,
516            observed_state_capacity,
517        }
518    }
519
520    fn run_with_read_observer<R>(&self, block: impl FnOnce() -> R) -> R {
521        use crate::snapshot_v2::take_transparent_observer_mutable_snapshot;
522
523        let snapshot =
524            take_transparent_observer_mutable_snapshot(Some(self.read_dispatcher.clone()), None);
525        let result = snapshot.enter(block);
526        snapshot.dispose();
527        result
528    }
529
530    fn handle_apply(&self, modified: &[Arc<dyn StateObject>]) {
531        if modified.is_empty() {
532            return;
533        }
534
535        let mut seen_scope_ids: HashSet<usize> = HashSet::default();
536        let mut to_notify: Vec<Rc<RefCell<ScopeEntry>>> = Vec::new();
537        {
538            let observed_to_scopes = self.observed_to_scopes.borrow();
539            let indexed_scopes = self.indexed_scopes.borrow();
540            for state in modified {
541                if let Some(scope_ids) = observed_to_scopes.get(&state.object_id().as_usize()) {
542                    let mut ordered_scope_ids: SmallVec<[usize; 8]> =
543                        scope_ids.iter().copied().collect();
544                    ordered_scope_ids.sort_unstable();
545                    for scope_id in ordered_scope_ids {
546                        if seen_scope_ids.insert(scope_id)
547                            && let Some(entry) = indexed_scopes.get(&scope_id)
548                        {
549                            to_notify.push(entry.clone());
550                        }
551                    }
552                }
553            }
554        }
555
556        if to_notify.is_empty() {
557            return;
558        }
559
560        for entry in to_notify {
561            let executor = self.executor.clone();
562            executor(Box::new(move || {
563                if let Ok(entry) = entry.try_borrow() {
564                    entry.notify();
565                }
566            }));
567        }
568    }
569
570    fn replace_observed_ids(&self, entry: &Rc<RefCell<ScopeEntry>>, observed: ObservedIds) {
571        let (entry_id, previous) = {
572            let mut entry_mut = entry.borrow_mut();
573            let entry_id = entry_mut.id;
574            let previous = std::mem::replace(&mut entry_mut.observed, observed);
575            (entry_id, previous)
576        };
577        self.unregister_observed_ids(entry_id, &previous);
578        let entry_ref = entry.borrow();
579        self.register_observed_ids(entry_id, &entry_ref.observed);
580    }
581
582    fn register_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
583        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
584        for state_id in observed.iter() {
585            let scope_ids = observed_to_scopes.entry(state_id).or_default();
586            scope_ids.insert(entry_id);
587        }
588    }
589
590    fn unregister_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
591        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
592        let mut emptied = SmallVec::<[StateObjectId; MAX_OBSERVED_STATES]>::new();
593        for state_id in observed.iter() {
594            if let Some(scope_ids) = observed_to_scopes.get_mut(&state_id) {
595                scope_ids.remove(&entry_id);
596                if scope_ids.is_empty() {
597                    emptied.push(state_id);
598                }
599            }
600        }
601        for state_id in emptied {
602            observed_to_scopes.remove(&state_id);
603        }
604        shrink_map_if_sparse(&mut observed_to_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
605    }
606
607    fn unregister_entry(&self, entry: &Rc<RefCell<ScopeEntry>>) {
608        let (entry_id, observed) = {
609            let mut entry_mut = entry.borrow_mut();
610            let observed = std::mem::replace(&mut entry_mut.observed, ObservedIds::new());
611            (entry_mut.id, observed)
612        };
613        self.unregister_observed_ids(entry_id, &observed);
614        self.indexed_scopes.borrow_mut().remove(&entry_id);
615    }
616}
617
618fn shrink_map_if_sparse<K, V>(map: &mut HashMap<K, V>, min_retained_capacity: usize)
619where
620    K: Eq + std::hash::Hash,
621{
622    if map.capacity() <= map.len().max(min_retained_capacity).saturating_mul(4) {
623        return;
624    }
625
626    let retained = map.len().max(min_retained_capacity);
627    let mut rebuilt = HashMap::default();
628    rebuilt.reserve(retained);
629    rebuilt.extend(map.drain());
630    *map = rebuilt;
631}
632
633enum ObservedIds {
634    Small(SmallVec<[ObservedState; MAX_OBSERVED_STATES]>),
635    Large(HashMap<StateObjectId, Option<Box<dyn Any>>>),
636}
637
638struct ObservedState {
639    id: StateObjectId,
640    _lease: Option<Box<dyn Any>>,
641}
642
643impl ObservedIds {
644    fn new() -> Self {
645        ObservedIds::Small(SmallVec::new())
646    }
647
648    fn insert(&mut self, state: &dyn StateObject) {
649        let id = state.object_id().as_usize();
650        match self {
651            ObservedIds::Small(small) => {
652                if small.iter().any(|observed| observed.id == id) {
653                    return;
654                }
655                if small.len() < MAX_OBSERVED_STATES {
656                    small.push(ObservedState {
657                        id,
658                        _lease: state.observation_lease(),
659                    });
660                } else {
661                    let mut large =
662                        HashMap::with_capacity_and_hasher(small.len() + 1, Default::default());
663                    for observed in small.drain(..) {
664                        large.insert(observed.id, observed._lease);
665                    }
666                    large.insert(id, state.observation_lease());
667                    *self = ObservedIds::Large(large);
668                }
669            }
670            ObservedIds::Large(large) => {
671                large.entry(id).or_insert_with(|| state.observation_lease());
672            }
673        }
674    }
675
676    fn is_empty(&self) -> bool {
677        match self {
678            ObservedIds::Small(small) => small.is_empty(),
679            ObservedIds::Large(large) => large.is_empty(),
680        }
681    }
682
683    fn len(&self) -> usize {
684        match self {
685            ObservedIds::Small(small) => small.len(),
686            ObservedIds::Large(large) => large.len(),
687        }
688    }
689
690    fn capacity(&self) -> usize {
691        match self {
692            ObservedIds::Small(small) => small.capacity(),
693            ObservedIds::Large(large) => large.capacity(),
694        }
695    }
696
697    fn iter(&self) -> Box<dyn Iterator<Item = StateObjectId> + '_> {
698        match self {
699            ObservedIds::Small(small) => Box::new(small.iter().map(|observed| observed.id)),
700            ObservedIds::Large(large) => Box::new(large.keys().copied()),
701        }
702    }
703}
704
705const MAX_OBSERVED_STATES: usize = 8;
706
707enum ScopeStorage {
708    Owned(Box<dyn Any>),
709    RecomposeScope {
710        id: ScopeId,
711        weak: Weak<RecomposeScopeInner>,
712    },
713}
714
715struct ScopeEntry {
716    id: usize,
717    scope: ScopeStorage,
718    on_changed: Rc<dyn Fn(&dyn Any)>,
719    observed: ObservedIds,
720    last_seen_version: u64,
721}
722
723impl ScopeEntry {
724    fn new<T>(id: usize, scope: T, on_changed: Rc<dyn Fn(&dyn Any)>) -> Self
725    where
726        T: Any + 'static,
727    {
728        Self {
729            id,
730            scope: ScopeStorage::from_value(scope),
731            on_changed,
732            observed: ObservedIds::new(),
733            last_seen_version: u64::MAX,
734        }
735    }
736
737    fn update<T>(&mut self, new_scope: T, on_changed: Rc<dyn Fn(&dyn Any)>)
738    where
739        T: Any + 'static,
740    {
741        self.scope = ScopeStorage::from_value(new_scope);
742        self.on_changed = on_changed;
743    }
744
745    fn matches_scope<T>(&self, scope: &T) -> bool
746    where
747        T: Any + Eq + 'static,
748    {
749        if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
750            return matches!(
751                &self.scope,
752                ScopeStorage::RecomposeScope { id, .. } if *id == scope.id()
753            );
754        }
755
756        match &self.scope {
757            ScopeStorage::Owned(stored) => stored
758                .downcast_ref::<T>()
759                .map(|stored| stored == scope)
760                .unwrap_or(false),
761            ScopeStorage::RecomposeScope { .. } => false,
762        }
763    }
764
765    fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
766        match &self.scope {
767            ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
768            ScopeStorage::RecomposeScope { weak, .. } => weak
769                .upgrade()
770                .map(|inner| predicate(&RecomposeScope { inner }))
771                .unwrap_or(true),
772        }
773    }
774
775    fn should_retain(&self) -> bool {
776        match &self.scope {
777            ScopeStorage::Owned(_) => true,
778            ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
779        }
780    }
781
782    fn notify(&self) {
783        match &self.scope {
784            ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
785            ScopeStorage::RecomposeScope { weak, .. } => {
786                if let Some(inner) = weak.upgrade() {
787                    (self.on_changed)(&RecomposeScope { inner });
788                }
789            }
790        }
791    }
792}
793
794impl ScopeStorage {
795    fn from_value<T>(value: T) -> Self
796    where
797        T: Any + 'static,
798    {
799        let any = &value as &dyn Any;
800        if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
801            Self::RecomposeScope {
802                id: scope.id(),
803                weak: scope.downgrade(),
804            }
805        } else {
806            Self::Owned(Box::new(value))
807        }
808    }
809}
810
811#[cfg(test)]
812mod tests {
813    use std::cell::Cell;
814
815    use super::*;
816    use crate::{
817        snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
818        state::{NeverEqual, SnapshotMutableState},
819    };
820
821    fn reset_runtime() -> TestRuntimeGuard {
822        reset_runtime_for_tests()
823    }
824
825    #[derive(Clone, Eq, Hash, PartialEq)]
826    struct TestScope(&'static str);
827
828    #[test]
829    fn notifies_scope_when_state_changes() {
830        let _guard = reset_runtime();
831
832        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
833        let triggered = Rc::new(Cell::new(0));
834        let observer_trigger = triggered.clone();
835
836        let observer = SnapshotStateObserver::new(|callback| callback());
837        observer.start();
838
839        let scope = TestScope("scope");
840        observer.observe_reads(
841            scope.clone(),
842            move |_| {
843                observer_trigger.set(observer_trigger.get() + 1);
844            },
845            || {
846                let _ = state.get();
847            },
848        );
849
850        let snapshot = take_mutable_snapshot(None, None);
851        snapshot.enter(|| {
852            state.set(1);
853        });
854        snapshot.apply().check();
855
856        assert_eq!(triggered.get(), 1);
857        observer.stop();
858    }
859
860    #[test]
861    fn clear_removes_scope_observation() {
862        let _guard = reset_runtime();
863
864        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
865        let triggered = Rc::new(Cell::new(0));
866        let observer_trigger = triggered.clone();
867
868        let observer = SnapshotStateObserver::new(|callback| callback());
869        observer.start();
870
871        let scope = TestScope("scope");
872        observer.observe_reads(
873            scope.clone(),
874            move |_| {
875                observer_trigger.set(observer_trigger.get() + 1);
876            },
877            || {
878                let _ = state.get();
879            },
880        );
881
882        observer.clear(&scope);
883
884        let snapshot = take_mutable_snapshot(None, None);
885        snapshot.enter(|| {
886            state.set(1);
887        });
888        snapshot.apply().check();
889
890        assert_eq!(triggered.get(), 0);
891        observer.stop();
892    }
893
894    #[test]
895    fn repeated_owned_scope_observations_reuse_the_same_entry() {
896        let _guard = reset_runtime();
897
898        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
899        let observer = SnapshotStateObserver::new(|callback| callback());
900        let scope = TestScope("scope");
901
902        observer.observe_reads(
903            scope.clone(),
904            |_| {},
905            || {
906                let _ = state.get();
907            },
908        );
909        observer.observe_reads(
910            scope,
911            |_| {},
912            || {
913                let _ = state.get();
914            },
915        );
916
917        let stats = observer.debug_stats();
918        assert_eq!(stats.scopes_len, 1);
919        assert_eq!(stats.fast_scopes_len, 0);
920    }
921
922    #[test]
923    fn with_no_observations_skips_reads() {
924        let _guard = reset_runtime();
925
926        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
927        let triggered = Rc::new(Cell::new(0));
928        let observer_trigger = triggered.clone();
929
930        let observer = SnapshotStateObserver::new(|callback| callback());
931        observer.start();
932
933        let scope = TestScope("scope");
934        observer.observe_reads(
935            scope.clone(),
936            move |_| {
937                observer_trigger.set(observer_trigger.get() + 1);
938            },
939            || {
940                observer.with_no_observations(|| {
941                    let _ = state.get();
942                });
943            },
944        );
945
946        let snapshot = take_mutable_snapshot(None, None);
947        snapshot.enter(|| {
948            state.set(1);
949        });
950        snapshot.apply().check();
951
952        assert_eq!(triggered.get(), 0);
953        observer.stop();
954    }
955
956    #[test]
957    fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
958        let _guard = reset_runtime();
959
960        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
961        let outer_triggered = Rc::new(Cell::new(0));
962        let inner_triggered = Rc::new(Cell::new(0));
963
964        let observer = SnapshotStateObserver::new(|callback| callback());
965        observer.start();
966
967        let outer_scope = TestScope("outer");
968        let inner_scope = TestScope("inner");
969        observer.observe_reads(
970            outer_scope.clone(),
971            {
972                let outer_triggered = Rc::clone(&outer_triggered);
973                move |_| outer_triggered.set(outer_triggered.get() + 1)
974            },
975            || {
976                observer.observe_reads(
977                    inner_scope.clone(),
978                    {
979                        let inner_triggered = Rc::clone(&inner_triggered);
980                        move |_| inner_triggered.set(inner_triggered.get() + 1)
981                    },
982                    || {
983                        let _ = state.get();
984                    },
985                );
986            },
987        );
988
989        let snapshot = take_mutable_snapshot(None, None);
990        snapshot.enter(|| {
991            state.set(1);
992        });
993        snapshot.apply().check();
994
995        assert_eq!(outer_triggered.get(), 0);
996        assert_eq!(inner_triggered.get(), 1);
997        observer.stop();
998    }
999
1000    #[test]
1001    fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1002        let _guard = reset_runtime();
1003
1004        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1005        let first_triggered = Rc::new(Cell::new(0));
1006        let second_triggered = Rc::new(Cell::new(0));
1007
1008        let observer = SnapshotStateObserver::new(|callback| callback());
1009        observer.start();
1010
1011        let first_scope = TestScope("first");
1012        let second_scope = TestScope("second");
1013        observer.observe_reads(
1014            first_scope.clone(),
1015            {
1016                let first_triggered = Rc::clone(&first_triggered);
1017                move |_| first_triggered.set(first_triggered.get() + 1)
1018            },
1019            || {
1020                let _ = state.get();
1021            },
1022        );
1023        observer.observe_reads(
1024            second_scope.clone(),
1025            {
1026                let second_triggered = Rc::clone(&second_triggered);
1027                move |_| second_triggered.set(second_triggered.get() + 1)
1028            },
1029            || {
1030                let _ = state.get();
1031            },
1032        );
1033
1034        observer.clear(&first_scope);
1035
1036        let snapshot = take_mutable_snapshot(None, None);
1037        snapshot.enter(|| {
1038            state.set(1);
1039        });
1040        snapshot.apply().check();
1041
1042        assert_eq!(first_triggered.get(), 0);
1043        assert_eq!(second_triggered.get(), 1);
1044        observer.stop();
1045    }
1046
1047    #[test]
1048    fn shared_state_notifies_scopes_in_registration_order() {
1049        let _guard = reset_runtime();
1050
1051        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1052        let notifications = Rc::new(RefCell::new(Vec::new()));
1053
1054        let observer = SnapshotStateObserver::new(|callback| callback());
1055        observer.start();
1056
1057        observer.observe_reads(
1058            TestScope("first"),
1059            {
1060                let notifications = Rc::clone(&notifications);
1061                move |_| notifications.borrow_mut().push("first")
1062            },
1063            || {
1064                let _ = state.get();
1065            },
1066        );
1067        observer.observe_reads(
1068            TestScope("second"),
1069            {
1070                let notifications = Rc::clone(&notifications);
1071                move |_| notifications.borrow_mut().push("second")
1072            },
1073            || {
1074                let _ = state.get();
1075            },
1076        );
1077
1078        let snapshot = take_mutable_snapshot(None, None);
1079        snapshot.enter(|| {
1080            state.set(1);
1081        });
1082        snapshot.apply().check();
1083
1084        assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1085        observer.stop();
1086    }
1087
1088    #[test]
1089    fn stateless_recompose_scope_does_not_retain_observer_entry() {
1090        let _guard = reset_runtime();
1091
1092        let observer = SnapshotStateObserver::new(|callback| callback());
1093        let runtime = crate::TestRuntime::new();
1094        let scope = RecomposeScope::new_for_test(runtime.handle());
1095
1096        observer.observe_reads(scope, |_| {}, || {});
1097
1098        let stats = observer.debug_stats();
1099        assert_eq!(stats.scopes_len, 0);
1100        assert_eq!(stats.fast_scopes_len, 0);
1101        assert_eq!(stats.stateless_scope_count, 0);
1102    }
1103
1104    #[test]
1105    fn scope_that_stops_reading_state_is_removed_immediately() {
1106        let _guard = reset_runtime();
1107
1108        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1109        let observer = SnapshotStateObserver::new(|callback| callback());
1110        let runtime = crate::TestRuntime::new();
1111        let scope = RecomposeScope::new_for_test(runtime.handle());
1112        let triggered = Rc::new(Cell::new(0));
1113        let observer_trigger = Rc::clone(&triggered);
1114
1115        observer.observe_reads(
1116            scope.clone(),
1117            move |_| observer_trigger.set(observer_trigger.get() + 1),
1118            || {
1119                let _ = state.get();
1120            },
1121        );
1122
1123        let after_stateful = observer.debug_stats();
1124        assert_eq!(after_stateful.scopes_len, 1);
1125        assert_eq!(after_stateful.fast_scopes_len, 1);
1126
1127        observer.observe_reads(scope, |_| {}, || {});
1128
1129        let after_stateless = observer.debug_stats();
1130        assert_eq!(after_stateless.scopes_len, 0);
1131        assert_eq!(after_stateless.fast_scopes_len, 0);
1132
1133        let snapshot = take_mutable_snapshot(None, None);
1134        snapshot.enter(|| {
1135            state.set(1);
1136        });
1137        snapshot.apply().check();
1138
1139        assert_eq!(triggered.get(), 0);
1140    }
1141
1142    #[test]
1143    fn begin_frame_prunes_dropped_recompose_scope_entries() {
1144        let _guard = reset_runtime();
1145
1146        let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1147        let observer = SnapshotStateObserver::new(|callback| callback());
1148        let runtime = crate::TestRuntime::new();
1149        let scope = RecomposeScope::new_for_test(runtime.handle());
1150
1151        observer.observe_reads(
1152            scope.clone(),
1153            |_| {},
1154            || {
1155                let _ = state.get();
1156            },
1157        );
1158
1159        let before_prune = observer.debug_stats();
1160        assert_eq!(before_prune.scopes_len, 1);
1161        assert_eq!(before_prune.fast_scopes_len, 1);
1162
1163        drop(scope);
1164        observer.begin_frame();
1165
1166        let after_prune = observer.debug_stats();
1167        assert_eq!(after_prune.scopes_len, 0);
1168        assert_eq!(after_prune.fast_scopes_len, 0);
1169    }
1170}