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