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#[derive(Clone)]
36pub struct SnapshotStateObserver {
37 inner: Rc<SnapshotStateObserverInner>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub struct SnapshotStateObserverDebugStats {
43 pub scopes_len: usize,
45 pub scopes_cap: usize,
47 pub stateless_scope_count: usize,
49 pub observed_state_count: usize,
51 pub observed_state_capacity: usize,
53}
54
55impl SnapshotStateObserver {
56 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 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 pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
83 self.inner.with_no_observations(block)
84 }
85
86 pub fn clear<T>(&self, scope: &T)
88 where
89 T: Any + Eq + Hash + 'static,
90 {
91 self.inner.clear(scope);
92 }
93
94 pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
96 self.inner.clear_if(predicate);
97 }
98
99 pub fn clear_all(&self) {
101 self.inner.clear_all();
102 }
103
104 pub fn start(&self) {
106 let weak = Rc::downgrade(&self.inner);
107 self.inner.start(weak);
108 }
109
110 pub fn stop(&self) {
112 self.inner.stop();
113 }
114
115 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 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
150type 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 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 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 (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 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;