Skip to main content

cranpose_core/
snapshot_state_observer.rs

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