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