Skip to main content

cranpose_core/
snapshot_state_observer.rs

1use std::{
2    any::{Any, TypeId},
3    cell::{Cell, RefCell},
4    hash::{Hash, Hasher},
5    rc::{Rc, Weak},
6};
7
8use smallvec::SmallVec;
9
10use crate::{
11    collections::map::{HashMap, HashSet},
12    hash::default as default_hash,
13    snapshot_v2::{
14        ReadObserver, StateObjectId, TransparentObserverMutableSnapshot, register_apply_observer,
15    },
16    state::StateObject,
17};
18
19type Executor = dyn Fn(Box<dyn FnOnce() + 'static>) + 'static;
20
21trait ScopeChangedCallback: Fn(&dyn Any) + Any {}
22
23impl<F: Fn(&dyn Any) + Any> ScopeChangedCallback for F {}
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/// Sizes of a state observer's tables, for leak and memory checks.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub struct SnapshotStateObserverDebugStats {
45    /// Number of scopes with recorded observations.
46    pub scopes_len: usize,
47    /// Capacity reserved for the scope tables.
48    pub scopes_cap: usize,
49    /// Number of scopes that recorded no state reads.
50    pub stateless_scope_count: usize,
51    /// Number of observed states, counted once for each scope that read them.
52    pub observed_state_count: usize,
53    /// Capacity reserved for the observed states of all scopes.
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        Self {
61            inner: Rc::new(SnapshotStateObserverInner::new(on_changed_executor)),
62        }
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    /// Temporarily pause read observation while executing `block`.
84    pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
85        self.inner.with_no_observations(block)
86    }
87
88    /// Remove any recorded reads for `scope`.
89    pub fn clear<T>(&self, scope: &T)
90    where
91        T: Any + Eq + Hash + 'static,
92    {
93        self.inner.clear(scope);
94    }
95
96    /// Remove recorded reads for scopes that satisfy `predicate`.
97    pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
98        self.inner.clear_if(predicate);
99    }
100
101    /// Remove all recorded observations.
102    pub fn clear_all(&self) {
103        self.inner.clear_all();
104    }
105
106    /// Begin listening for snapshot apply notifications.
107    pub fn start(&self) {
108        let weak = Rc::downgrade(&self.inner);
109        self.inner.start(weak);
110    }
111
112    /// Stop listening for snapshot apply notifications.
113    pub fn stop(&self) {
114        self.inner.stop();
115    }
116
117    /// Returns the sizes of the observer's tables, for leak and memory checks.
118    pub fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
119        self.inner.debug_stats()
120    }
121
122    #[cfg(test)]
123    pub(crate) fn notify_changes(&self, modified: &[Rc<dyn StateObject>]) {
124        self.inner.handle_apply(modified);
125    }
126}
127
128struct SnapshotStateObserverInner {
129    executor: Rc<Executor>,
130    owned_scopes: RefCell<HashMap<OwnedScopeIndexKey, OwnedScopeBucket>>,
131    indexed_scopes: RefCell<HashMap<usize, Rc<RefCell<ScopeEntry>>>>,
132    observed_to_scopes: RefCell<HashMap<StateObjectId, HashSet<usize>>>,
133    pause_count: Rc<Cell<usize>>,
134    active_read_targets: Rc<RefCell<ReadObservationStack>>,
135    read_dispatcher: ReadObserver,
136    read_snapshot: RefCell<Option<Rc<TransparentObserverMutableSnapshot>>>,
137    apply_handle: RefCell<Option<crate::snapshot_v2::ObserverHandle>>,
138    next_entry_id: Cell<usize>,
139    /// One `Rc` per type of callback that captures nothing: see
140    /// [`SnapshotStateObserverInner::capture_free_callback`].
141    capture_free_callbacks: RefCell<CaptureFreeCallbacks>,
142}
143
144#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
145struct OwnedScopeIndexKey {
146    type_id: TypeId,
147    value_hash: u64,
148}
149
150type OwnedScopeBucket = SmallVec<[Rc<RefCell<ScopeEntry>>; 1]>;
151
152/// The shared `Rc` of each type of callback that captures nothing.
153type CaptureFreeCallbacks = SmallVec<[(TypeId, Rc<dyn ScopeChangedCallback>); 2]>;
154
155fn owned_scope_index_key<T>(scope: &T) -> OwnedScopeIndexKey
156where
157    T: Any + Hash + 'static,
158{
159    let mut hasher = default_hash::new();
160    scope.hash(&mut hasher);
161    OwnedScopeIndexKey {
162        type_id: TypeId::of::<T>(),
163        value_hash: hasher.finish(),
164    }
165}
166
167impl SnapshotStateObserverInner {
168    const MIN_RETAINED_SCOPE_CAPACITY: usize = 256;
169
170    fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
171        let pause_count = Rc::new(Cell::new(0));
172        let active_read_targets = Rc::new(RefCell::new(ReadObservationStack::default()));
173        let dispatcher_pause_count = Rc::clone(&pause_count);
174        let dispatcher_targets = Rc::clone(&active_read_targets);
175        let read_dispatcher: ReadObserver = Rc::new(move |state| {
176            if dispatcher_pause_count.get() > 0 {
177                return;
178            }
179            let observed = dispatcher_targets.borrow().last().cloned();
180            if let Some(observed) = observed {
181                observed.borrow_mut().insert(state);
182            }
183        });
184
185        Self {
186            executor: Rc::new(on_changed_executor),
187            owned_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            read_snapshot: RefCell::new(None),
194            apply_handle: RefCell::new(None),
195            next_entry_id: Cell::new(0),
196            capture_free_callbacks: RefCell::new(SmallVec::new()),
197        }
198    }
199
200    fn observe_reads<T, R>(
201        &self,
202        scope: T,
203        on_value_changed_for_scope: impl Fn(&T) + 'static,
204        block: impl FnOnce() -> R,
205    ) -> R
206    where
207        T: Any + Clone + Eq + Hash + 'static,
208    {
209        let existing_entry = self.find_scope_entry(&scope);
210        let on_changed = std::cell::LazyCell::new(|| {
211            let callback = move |scope_any: &dyn Any| {
212                if let Some(typed) = scope_any.downcast_ref::<T>() {
213                    on_value_changed_for_scope(typed);
214                }
215            };
216            if std::mem::size_of_val(&callback) == 0 {
217                return self.capture_free_callback(callback);
218            }
219            match existing_entry.as_ref() {
220                Some(entry) => entry.borrow_mut().callback_reusing(callback),
221                None => Rc::new(callback),
222            }
223        });
224
225        if let Some(entry) = existing_entry.as_ref() {
226            entry.borrow_mut().update_scope(&scope);
227            let callback = on_changed.clone();
228            entry.borrow_mut().on_changed = callback;
229        }
230
231        let observed = self.active_read_targets.borrow_mut().push();
232        struct ActiveObservationGuard<'a> {
233            stack: &'a RefCell<ReadObservationStack>,
234        }
235        impl Drop for ActiveObservationGuard<'_> {
236            fn drop(&mut self) {
237                let target = self.stack.borrow_mut().pop();
238                target.borrow_mut().clear();
239            }
240        }
241        let _guard = ActiveObservationGuard {
242            stack: &self.active_read_targets,
243        };
244
245        let result = self.run_with_read_observer(block);
246
247        if observed.borrow().is_empty() {
248            if existing_entry.is_some() {
249                self.clear(&scope);
250            }
251            return result;
252        }
253
254        let callback = Rc::clone(&on_changed);
255        drop(on_changed);
256        let entry = existing_entry
257            .unwrap_or_else(|| self.insert_scope_entry(scope.clone(), Rc::clone(&callback)));
258        entry.borrow_mut().update(&scope, callback);
259        self.replace_observed_ids(&entry, &mut observed.borrow_mut());
260
261        result
262    }
263
264    fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
265        self.pause_count.set(self.pause_count.get() + 1);
266        let result = block();
267        self.pause_count
268            .set(self.pause_count.get().saturating_sub(1));
269        result
270    }
271
272    fn clear<T>(&self, scope: &T)
273    where
274        T: Any + Eq + Hash + 'static,
275    {
276        let removed = self.remove_scope_entry(scope);
277        if let Some(entry) = removed {
278            self.unregister_entry(&entry);
279        }
280    }
281
282    fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
283        let removed = self.partition_scopes(|entry| predicate(entry.scope.as_ref()));
284        for entry in removed {
285            self.unregister_entry(&entry);
286        }
287    }
288
289    fn clear_all(&self) {
290        let entries = std::mem::take(&mut *self.indexed_scopes.borrow_mut());
291        let owned = std::mem::take(&mut *self.owned_scopes.borrow_mut());
292        self.observed_to_scopes.borrow_mut().clear();
293        drop((entries, owned));
294    }
295
296    fn start(&self, weak_self: Weak<SnapshotStateObserverInner>) {
297        if self.apply_handle.borrow().is_some() {
298            return;
299        }
300
301        let handle = register_apply_observer(Rc::new(move |modified, _snapshot_id| {
302            if let Some(inner) = weak_self.upgrade() {
303                inner.handle_apply(modified);
304            }
305        }));
306        self.apply_handle.replace(Some(handle));
307    }
308
309    fn stop(&self) {
310        if let Some(handle) = self.apply_handle.borrow_mut().take() {
311            drop(handle);
312        }
313    }
314
315    /// The `Rc` of a callback that captures nothing. Every closure of such a
316    /// type does the same thing, so one `Rc` serves all its scopes instead
317    /// of one each.
318    fn capture_free_callback<F: Fn(&dyn Any) + 'static>(
319        &self,
320        callback: F,
321    ) -> Rc<dyn ScopeChangedCallback> {
322        let type_id = TypeId::of::<F>();
323        let mut shared = self.capture_free_callbacks.borrow_mut();
324        if let Some((_, callback)) = shared.iter().find(|(id, _)| *id == type_id) {
325            return Rc::clone(callback);
326        }
327        let callback: Rc<dyn ScopeChangedCallback> = Rc::new(callback);
328        shared.push((type_id, Rc::clone(&callback)));
329        callback
330    }
331
332    fn insert_scope_entry(
333        &self,
334        scope: impl Any + Clone + Eq + Hash + 'static,
335        on_changed: Rc<dyn ScopeChangedCallback>,
336    ) -> Rc<RefCell<ScopeEntry>> {
337        let entry_id = self.next_entry_id.get();
338        self.next_entry_id.set(entry_id.wrapping_add(1));
339        let scope_key = owned_scope_index_key(&scope);
340        let entry = Rc::new(RefCell::new(ScopeEntry::new(entry_id, scope, on_changed)));
341        self.indexed_scopes
342            .borrow_mut()
343            .insert(entry_id, Rc::clone(&entry));
344        self.owned_scopes
345            .borrow_mut()
346            .entry(scope_key)
347            .or_default()
348            .push(Rc::clone(&entry));
349        entry
350    }
351
352    fn find_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
353    where
354        T: Any + Eq + Hash + 'static,
355    {
356        let key = owned_scope_index_key(scope);
357        self.owned_scopes.borrow().get(&key).and_then(|bucket| {
358            bucket
359                .iter()
360                .find(|entry| entry.borrow().matches_scope(scope))
361                .cloned()
362        })
363    }
364
365    fn remove_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
366    where
367        T: Any + Eq + Hash + 'static,
368    {
369        let key = owned_scope_index_key(scope);
370        let mut owned_scopes = self.owned_scopes.borrow_mut();
371        let mut removed = None;
372        let mut remove_bucket = false;
373        if let Some(bucket) = owned_scopes.get_mut(&key)
374            && let Some(index) = bucket
375                .iter()
376                .position(|entry| entry.borrow().matches_scope(scope))
377        {
378            removed = Some(bucket.remove(index));
379            remove_bucket = bucket.is_empty();
380        }
381        if remove_bucket {
382            owned_scopes.remove(&key);
383        }
384        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
385        removed
386    }
387
388    fn partition_scopes(
389        &self,
390        should_remove: impl Fn(&ScopeEntry) -> bool,
391    ) -> Vec<Rc<RefCell<ScopeEntry>>> {
392        let mut owned_scopes = self.owned_scopes.borrow_mut();
393        let mut removed = Vec::new();
394        owned_scopes.retain(|_, bucket| {
395            let mut index = 0;
396            while index < bucket.len() {
397                let remove = should_remove(&bucket[index].borrow());
398                if remove {
399                    removed.push(bucket.swap_remove(index));
400                } else {
401                    index += 1;
402                }
403            }
404            !bucket.is_empty()
405        });
406        shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
407        removed
408    }
409
410    fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
411        let owned_scopes = self.owned_scopes.borrow();
412        let indexed_scopes = self.indexed_scopes.borrow();
413        let owned_scope_cap =
414            owned_scopes.capacity() + owned_scopes.values().map(SmallVec::capacity).sum::<usize>();
415        let scopes_cap = owned_scope_cap + indexed_scopes.capacity();
416        let mut observed_state_count = 0;
417        let mut observed_state_capacity = 0;
418        let mut stateless_scope_count = 0;
419
420        for entry in indexed_scopes.values() {
421            let entry = entry.borrow();
422            observed_state_count += entry.observed.len();
423            observed_state_capacity += entry.observed.capacity();
424            stateless_scope_count += usize::from(entry.observed.is_empty());
425        }
426
427        SnapshotStateObserverDebugStats {
428            scopes_len: indexed_scopes.len(),
429            scopes_cap,
430            stateless_scope_count,
431            observed_state_count,
432            observed_state_capacity,
433        }
434    }
435
436    fn run_with_read_observer<R>(&self, block: impl FnOnce() -> R) -> R {
437        use crate::snapshot_v2::{
438            current_snapshot_reads_into, take_transparent_observer_mutable_snapshot_reusing,
439        };
440
441        if current_snapshot_reads_into(&self.read_dispatcher) {
442            return block();
443        }
444
445        let mut snapshot = take_transparent_observer_mutable_snapshot_reusing(
446            Some(self.read_dispatcher.clone()),
447            None,
448            self.read_snapshot.take(),
449        );
450        let result = snapshot.enter(block);
451        snapshot.dispose();
452        if Rc::get_mut(&mut snapshot).is_some() && !snapshot.has_pending_changes() {
453            self.read_snapshot.replace(Some(snapshot));
454        }
455        result
456    }
457
458    fn handle_apply(&self, modified: &[Rc<dyn StateObject>]) {
459        if modified.is_empty() {
460            return;
461        }
462
463        let mut seen_scope_ids: HashSet<usize> = HashSet::default();
464        let mut to_notify: Vec<Rc<RefCell<ScopeEntry>>> = Vec::new();
465        {
466            let observed_to_scopes = self.observed_to_scopes.borrow();
467            let indexed_scopes = self.indexed_scopes.borrow();
468            for state in modified {
469                if let Some(scope_ids) = observed_to_scopes.get(&state.object_id().as_usize()) {
470                    let mut ordered_scope_ids: SmallVec<[usize; 8]> =
471                        scope_ids.iter().copied().collect();
472                    ordered_scope_ids.sort_unstable();
473                    for scope_id in ordered_scope_ids {
474                        if seen_scope_ids.insert(scope_id)
475                            && let Some(entry) = indexed_scopes.get(&scope_id)
476                        {
477                            to_notify.push(entry.clone());
478                        }
479                    }
480                }
481            }
482        }
483
484        if to_notify.is_empty() {
485            return;
486        }
487
488        for entry in to_notify {
489            let executor = self.executor.clone();
490            executor(Box::new(move || {
491                if let Ok(entry) = entry.try_borrow() {
492                    entry.notify();
493                }
494            }));
495        }
496    }
497
498    fn replace_observed_ids(&self, entry: &Rc<RefCell<ScopeEntry>>, collected: &mut ObservedIds) {
499        let (entry_id, previous) = {
500            let mut entry_mut = entry.borrow_mut();
501            if entry_mut.observed.iter().eq(collected.iter()) {
502                entry_mut.observed.take_leases(collected);
503                collected.clear();
504                return;
505            }
506            let entry_id = entry_mut.id;
507            let previous = std::mem::replace(&mut entry_mut.observed, collected.take_sized());
508            (entry_id, previous)
509        };
510        let entry_ref = entry.borrow();
511        self.unregister_observed_ids(entry_id, &previous);
512        self.register_observed_ids(entry_id, &entry_ref.observed);
513    }
514
515    fn register_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
516        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
517        for state_id in observed.iter() {
518            let scope_ids = observed_to_scopes.entry(state_id).or_default();
519            scope_ids.insert(entry_id);
520        }
521    }
522
523    fn unregister_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
524        let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
525        let mut emptied = SmallVec::<[StateObjectId; MAX_OBSERVED_STATES]>::new();
526        for state_id in observed.iter() {
527            if let Some(scope_ids) = observed_to_scopes.get_mut(&state_id) {
528                scope_ids.remove(&entry_id);
529                if scope_ids.is_empty() {
530                    emptied.push(state_id);
531                }
532            }
533        }
534        for state_id in emptied {
535            observed_to_scopes.remove(&state_id);
536        }
537        shrink_map_if_sparse(&mut observed_to_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
538    }
539
540    fn unregister_entry(&self, entry: &Rc<RefCell<ScopeEntry>>) {
541        let (entry_id, observed) = {
542            let mut entry_mut = entry.borrow_mut();
543            let observed = std::mem::replace(&mut entry_mut.observed, ObservedIds::new());
544            (entry_mut.id, observed)
545        };
546        self.unregister_observed_ids(entry_id, &observed);
547        self.indexed_scopes.borrow_mut().remove(&entry_id);
548    }
549}
550
551fn shrink_map_if_sparse<K, V>(map: &mut HashMap<K, V>, min_retained_capacity: usize)
552where
553    K: Eq + std::hash::Hash,
554{
555    if map.capacity() <= map.len().max(min_retained_capacity).saturating_mul(4) {
556        return;
557    }
558
559    let retained = map.len().max(min_retained_capacity);
560    let mut rebuilt = HashMap::default();
561    rebuilt.reserve(retained);
562    rebuilt.extend(map.drain());
563    *map = rebuilt;
564}
565
566#[derive(Default)]
567struct ReadObservationStack {
568    targets: Vec<Rc<RefCell<ObservedIds>>>,
569    depth: usize,
570}
571
572impl ReadObservationStack {
573    fn push(&mut self) -> Rc<RefCell<ObservedIds>> {
574        if self.depth == self.targets.len() {
575            self.targets.push(Rc::new(RefCell::new(ObservedIds::new())));
576        }
577        let target = Rc::clone(&self.targets[self.depth]);
578        self.depth += 1;
579        target
580    }
581
582    fn last(&self) -> Option<&Rc<RefCell<ObservedIds>>> {
583        self.depth.checked_sub(1).map(|index| &self.targets[index])
584    }
585
586    fn pop(&mut self) -> Rc<RefCell<ObservedIds>> {
587        self.depth -= 1;
588        Rc::clone(&self.targets[self.depth])
589    }
590}
591
592#[cfg_attr(feature = "std-hash", expect(clippy::box_collection))]
593enum ObservedIds {
594    Small(SmallVec<[ObservedState; 1]>),
595    Large(Box<HashMap<StateObjectId, Option<Rc<dyn Any>>>>),
596}
597
598struct ObservedState {
599    id: StateObjectId,
600    _lease: Option<Rc<dyn Any>>,
601}
602
603impl ObservedIds {
604    fn new() -> Self {
605        ObservedIds::Small(SmallVec::new())
606    }
607
608    fn insert(&mut self, state: &dyn StateObject) {
609        let id = state.object_id().as_usize();
610        match self {
611            ObservedIds::Small(small) => {
612                if small.iter().any(|observed| observed.id == id) {
613                    return;
614                }
615                if small.len() < MAX_OBSERVED_STATES {
616                    small.push(ObservedState {
617                        id,
618                        _lease: state.observation_lease(),
619                    });
620                } else {
621                    let mut large =
622                        HashMap::with_capacity_and_hasher(small.len() + 1, Default::default());
623                    for observed in small.drain(..) {
624                        large.insert(observed.id, observed._lease);
625                    }
626                    large.insert(id, state.observation_lease());
627                    *self = ObservedIds::Large(Box::new(large));
628                }
629            }
630            ObservedIds::Large(large) => {
631                large.entry(id).or_insert_with(|| state.observation_lease());
632            }
633        }
634    }
635
636    fn clear(&mut self) {
637        match self {
638            ObservedIds::Small(small) => small.clear(),
639            ObservedIds::Large(_) => *self = ObservedIds::new(),
640        }
641    }
642
643    /// Keeps `fresh`'s observation leases for the states both name, leaving
644    /// the old ones in `fresh` to drop. An id is a state's address, which a
645    /// new state can take after the old one is dropped, and only its own
646    /// lease keeps the new one observed.
647    fn take_leases(&mut self, fresh: &mut ObservedIds) {
648        match (self, fresh) {
649            (ObservedIds::Small(kept), ObservedIds::Small(fresh)) => {
650                for (kept, fresh) in kept.iter_mut().zip(fresh.iter_mut()) {
651                    std::mem::swap(&mut kept._lease, &mut fresh._lease);
652                }
653            }
654            (kept, fresh) => std::mem::swap(kept, fresh),
655        }
656    }
657
658    fn take_sized(&mut self) -> ObservedIds {
659        match self {
660            ObservedIds::Small(small) => ObservedIds::Small(small.drain(..).collect()),
661            ObservedIds::Large(_) => std::mem::replace(self, ObservedIds::new()),
662        }
663    }
664
665    fn is_empty(&self) -> bool {
666        match self {
667            ObservedIds::Small(small) => small.is_empty(),
668            ObservedIds::Large(large) => large.is_empty(),
669        }
670    }
671
672    fn len(&self) -> usize {
673        match self {
674            ObservedIds::Small(small) => small.len(),
675            ObservedIds::Large(large) => large.len(),
676        }
677    }
678
679    fn capacity(&self) -> usize {
680        match self {
681            ObservedIds::Small(small) => small.capacity(),
682            ObservedIds::Large(large) => large.capacity(),
683        }
684    }
685
686    fn iter(&self) -> impl Iterator<Item = StateObjectId> + '_ {
687        let (small, large) = match self {
688            ObservedIds::Small(small) => (Some(small.as_slice()), None),
689            ObservedIds::Large(large) => (None, Some(large)),
690        };
691        small
692            .into_iter()
693            .flatten()
694            .map(|observed| observed.id)
695            .chain(large.into_iter().flat_map(|states| states.keys().copied()))
696    }
697}
698
699const MAX_OBSERVED_STATES: usize = 8;
700
701struct ScopeEntry {
702    id: usize,
703    scope: Box<dyn Any>,
704    on_changed: Rc<dyn ScopeChangedCallback>,
705    observed: ObservedIds,
706}
707
708impl ScopeEntry {
709    fn new<T>(id: usize, scope: T, on_changed: Rc<dyn ScopeChangedCallback>) -> Self
710    where
711        T: Any + 'static,
712    {
713        Self {
714            id,
715            scope: Box::new(scope),
716            on_changed,
717            observed: ObservedIds::new(),
718        }
719    }
720
721    fn callback_reusing<F: Fn(&dyn Any) + 'static>(
722        &mut self,
723        callback: F,
724    ) -> Rc<dyn ScopeChangedCallback> {
725        if let Some(stored) = Rc::get_mut(&mut self.on_changed)
726            .and_then(|stored| (stored as &mut dyn Any).downcast_mut::<F>())
727        {
728            *stored = callback;
729            Rc::clone(&self.on_changed)
730        } else {
731            Rc::new(callback)
732        }
733    }
734
735    fn update<T>(&mut self, new_scope: &T, on_changed: Rc<dyn ScopeChangedCallback>)
736    where
737        T: Any + Clone + 'static,
738    {
739        self.update_scope(new_scope);
740        self.on_changed = on_changed;
741    }
742
743    fn update_scope<T>(&mut self, new_scope: &T)
744    where
745        T: Any + Clone + 'static,
746    {
747        match self.scope.downcast_mut::<T>() {
748            Some(stored) => stored.clone_from(new_scope),
749            None => self.scope = Box::new(new_scope.clone()),
750        }
751    }
752
753    fn matches_scope<T>(&self, scope: &T) -> bool
754    where
755        T: Any + Eq + 'static,
756    {
757        self.scope
758            .downcast_ref::<T>()
759            .is_some_and(|stored| stored == scope)
760    }
761
762    fn notify(&self) {
763        (self.on_changed)(self.scope.as_ref());
764    }
765}
766
767#[cfg(test)]
768#[path = "tests/snapshot_state_observer_tests.rs"]
769mod tests;