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