1#![allow(clippy::type_complexity)]
2
3use std::{
4 any::{Any, TypeId},
5 cell::{Cell, RefCell},
6 hash::{Hash, Hasher},
7 rc::{Rc, Weak},
8 sync::Arc,
9};
10
11use smallvec::SmallVec;
12
13use crate::{
14 RecomposeScope, RecomposeScopeInner, ScopeId,
15 collections::map::{HashMap, HashSet},
16 hash::default as default_hash,
17 snapshot_v2::{ReadObserver, StateObjectId, register_apply_observer},
18 state::StateObject,
19};
20
21type Executor = dyn Fn(Box<dyn FnOnce() + 'static>) + 'static;
22
23#[derive(Clone)]
36pub struct SnapshotStateObserver {
37 inner: Rc<SnapshotStateObserverInner>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub struct SnapshotStateObserverDebugStats {
42 pub scopes_len: usize,
43 pub scopes_cap: usize,
44 pub fast_scopes_len: usize,
45 pub fast_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 pub fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
54 let inner = Rc::new(SnapshotStateObserverInner::new(on_changed_executor));
55 inner.set_self(Rc::downgrade(&inner));
56 Self { inner }
57 }
58
59 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 pub fn begin_frame(&self) {
79 self.inner.begin_frame();
80 }
81
82 pub fn prune_dead_scopes(&self) {
84 self.inner.prune_dead_scopes();
85 }
86
87 pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
89 self.inner.with_no_observations(block)
90 }
91
92 pub fn clear<T>(&self, scope: &T)
94 where
95 T: Any + Eq + Hash + 'static,
96 {
97 self.inner.clear(scope);
98 }
99
100 pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
102 self.inner.clear_if(predicate);
103 }
104
105 pub fn clear_all(&self) {
107 self.inner.clear_all();
108 }
109
110 pub fn start(&self) {
112 let weak = Rc::downgrade(&self.inner);
113 self.inner.start(weak);
114 }
115
116 pub fn stop(&self) {
118 self.inner.stop();
119 }
120
121 pub fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
122 self.inner.debug_stats()
123 }
124
125 #[cfg(test)]
126 pub fn notify_changes(&self, modified: &[Arc<dyn StateObject>]) {
127 self.inner.handle_apply(modified);
128 }
129}
130
131struct SnapshotStateObserverInner {
132 executor: Rc<Executor>,
133 owned_scopes: RefCell<HashMap<OwnedScopeIndexKey, OwnedScopeBucket>>,
134 fast_scopes: RefCell<HashMap<ScopeId, Rc<RefCell<ScopeEntry>>>>,
135 indexed_scopes: RefCell<HashMap<usize, Rc<RefCell<ScopeEntry>>>>,
136 observed_to_scopes: RefCell<HashMap<StateObjectId, HashSet<usize>>>,
137 pause_count: Rc<Cell<usize>>,
138 active_read_targets: Rc<RefCell<Vec<Rc<RefCell<ObservedIds>>>>>,
139 read_dispatcher: ReadObserver,
140 apply_handle: RefCell<Option<crate::snapshot_v2::ObserverHandle>>,
141 weak_self: RefCell<Weak<SnapshotStateObserverInner>>,
142 frame_version: Cell<u64>,
143 next_entry_id: Cell<usize>,
144}
145
146#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
147struct OwnedScopeIndexKey {
148 type_id: TypeId,
149 value_hash: u64,
150}
151
152type OwnedScopeBucket = SmallVec<[Rc<RefCell<ScopeEntry>>; 1]>;
153
154fn owned_scope_index_key<T>(scope: &T) -> OwnedScopeIndexKey
155where
156 T: Any + Hash + 'static,
157{
158 let mut hasher = default_hash::new();
159 scope.hash(&mut hasher);
160 OwnedScopeIndexKey {
161 type_id: TypeId::of::<T>(),
162 value_hash: hasher.finish(),
163 }
164}
165
166impl SnapshotStateObserverInner {
167 const MIN_RETAINED_SCOPE_CAPACITY: usize = 256;
168
169 fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
170 let pause_count = Rc::new(Cell::new(0));
171 let active_read_targets = Rc::new(RefCell::new(Vec::<Rc<RefCell<ObservedIds>>>::new()));
172 let dispatcher_pause_count = Rc::clone(&pause_count);
173 let dispatcher_targets = Rc::clone(&active_read_targets);
174 let read_dispatcher: ReadObserver = Arc::new(move |state| {
175 if dispatcher_pause_count.get() > 0 {
176 return;
177 }
178 let observed = dispatcher_targets.borrow().last().cloned();
179 if let Some(observed) = observed {
180 observed.borrow_mut().insert(state);
181 }
182 });
183
184 Self {
185 executor: Rc::new(on_changed_executor),
186 owned_scopes: RefCell::new(HashMap::default()),
187 fast_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 apply_handle: RefCell::new(None),
194 weak_self: RefCell::new(Weak::new()),
195 frame_version: Cell::new(0),
196 next_entry_id: Cell::new(0),
197 }
198 }
199
200 fn set_self(&self, weak: Weak<SnapshotStateObserverInner>) {
201 self.weak_self.replace(weak);
202 }
203
204 fn begin_frame(&self) {
205 let next = self.frame_version.get().wrapping_add(1);
206 self.frame_version.set(next);
207 self.prune_dead_scopes();
208 }
209
210 fn observe_reads<T, R>(
211 &self,
212 scope: T,
213 on_value_changed_for_scope: impl Fn(&T) + 'static,
214 block: impl FnOnce() -> R,
215 ) -> R
216 where
217 T: Any + Clone + Eq + Hash + 'static,
218 {
219 let frame_version = self.frame_version.get();
220 let has_frame_version = frame_version != 0;
221
222 let on_changed: Rc<dyn Fn(&dyn Any)> = {
223 let callback = Rc::new(on_value_changed_for_scope);
224 Rc::new(move |scope_any: &dyn Any| {
225 if let Some(typed) = scope_any.downcast_ref::<T>() {
226 callback(typed);
227 }
228 })
229 };
230
231 let existing_entry = self.find_scope_entry(&scope);
232 if let Some(entry) = existing_entry.as_ref() {
233 let already_observed = {
234 let mut entry_mut = entry.borrow_mut();
235 entry_mut.update(scope.clone(), on_changed.clone());
236 has_frame_version && entry_mut.last_seen_version == frame_version
237 };
238 if already_observed {
239 return block();
240 }
241 }
242
243 let observed = Rc::new(RefCell::new(ObservedIds::new()));
244 self.active_read_targets
245 .borrow_mut()
246 .push(Rc::clone(&observed));
247 struct ActiveObservationGuard {
248 stack: Rc<RefCell<Vec<Rc<RefCell<ObservedIds>>>>>,
249 }
250 impl Drop for ActiveObservationGuard {
251 fn drop(&mut self) {
252 self.stack.borrow_mut().pop();
253 }
254 }
255 let _guard = ActiveObservationGuard {
256 stack: Rc::clone(&self.active_read_targets),
257 };
258
259 let result = self.run_with_read_observer(block);
260
261 if observed.borrow().is_empty() {
262 if existing_entry.is_some() {
263 self.clear(&scope);
264 }
265 return result;
266 }
267
268 let observed = {
269 let mut observed = observed.borrow_mut();
270 std::mem::replace(&mut *observed, ObservedIds::new())
271 };
272 let entry = existing_entry
273 .unwrap_or_else(|| self.insert_scope_entry(scope.clone(), on_changed.clone()));
274 {
275 let mut entry_mut = entry.borrow_mut();
276 entry_mut.update(scope, on_changed);
277 entry_mut.last_seen_version = if has_frame_version {
278 frame_version
279 } else {
280 u64::MAX
281 };
282 }
283 self.replace_observed_ids(&entry, observed);
284
285 result
286 }
287
288 fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
289 self.pause_count.set(self.pause_count.get() + 1);
290 let result = block();
291 self.pause_count
292 .set(self.pause_count.get().saturating_sub(1));
293 result
294 }
295
296 fn clear<T>(&self, scope: &T)
297 where
298 T: Any + Eq + Hash + 'static,
299 {
300 if let Some(rc_scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
301 if let Some(entry) = self.fast_scopes.borrow_mut().remove(&rc_scope.id()) {
302 self.unregister_entry(&entry);
303 }
304 return;
305 }
306
307 let removed = self.remove_owned_scope_entry(scope);
308 if let Some(entry) = removed {
309 self.unregister_entry(&entry);
310 }
311 }
312
313 fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
314 let removed_fast = {
315 let mut fast_scopes = self.fast_scopes.borrow_mut();
316 let removed_ids: Vec<_> = fast_scopes
317 .iter()
318 .filter(|(_, entry)| entry.borrow().matches_predicate(&predicate))
319 .map(|(scope_id, _)| *scope_id)
320 .collect();
321 removed_ids
322 .into_iter()
323 .filter_map(|scope_id| fast_scopes.remove(&scope_id))
324 .collect::<Vec<_>>()
325 };
326 let removed_owned =
327 { self.partition_owned_scopes(|entry| entry.matches_predicate(&predicate)) };
328
329 for entry in removed_fast.into_iter().chain(removed_owned) {
330 self.unregister_entry(&entry);
331 }
332 }
333
334 fn clear_all(&self) {
335 self.fast_scopes.borrow_mut().clear();
336 self.owned_scopes.borrow_mut().clear();
337 self.indexed_scopes.borrow_mut().clear();
338 self.observed_to_scopes.borrow_mut().clear();
339 }
340
341 fn start(&self, weak_self: Weak<SnapshotStateObserverInner>) {
342 if self.apply_handle.borrow().is_some() {
343 return;
344 }
345
346 let handle = register_apply_observer(Rc::new(move |modified, _snapshot_id| {
347 if let Some(inner) = weak_self.upgrade() {
348 inner.handle_apply(modified);
349 }
350 }));
351 self.apply_handle.replace(Some(handle));
352 }
353
354 fn stop(&self) {
355 if let Some(handle) = self.apply_handle.borrow_mut().take() {
356 drop(handle);
357 }
358 }
359
360 fn find_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
361 where
362 T: Any + Eq + Hash + 'static,
363 {
364 if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
365 return self.fast_scopes.borrow().get(&scope.id()).cloned();
366 }
367
368 self.find_owned_scope_entry(scope)
369 }
370
371 fn insert_scope_entry(
372 &self,
373 scope: impl Any + Clone + Eq + Hash + 'static,
374 on_changed: Rc<dyn Fn(&dyn Any)>,
375 ) -> Rc<RefCell<ScopeEntry>> {
376 let entry_id = self.next_entry_id.get();
377 self.next_entry_id.set(entry_id.wrapping_add(1));
378 let recompose_scope_id = (&scope as &dyn Any)
379 .downcast_ref::<RecomposeScope>()
380 .map(RecomposeScope::id);
381 let owned_scope_key = recompose_scope_id
382 .is_none()
383 .then(|| owned_scope_index_key(&scope));
384 let entry = Rc::new(RefCell::new(ScopeEntry::new(entry_id, scope, on_changed)));
385 self.indexed_scopes
386 .borrow_mut()
387 .insert(entry_id, Rc::clone(&entry));
388 if let Some(scope_id) = recompose_scope_id {
389 self.fast_scopes
390 .borrow_mut()
391 .insert(scope_id, Rc::clone(&entry));
392 } else if let Some(scope_key) = owned_scope_key {
393 self.owned_scopes
394 .borrow_mut()
395 .entry(scope_key)
396 .or_default()
397 .push(Rc::clone(&entry));
398 }
399 entry
400 }
401
402 fn prune_dead_scopes(&self) {
403 let removed_fast = {
404 let mut fast_scopes = self.fast_scopes.borrow_mut();
405 let removed_ids: Vec<_> = fast_scopes
406 .iter()
407 .filter(|(_, entry)| !entry.borrow().should_retain())
408 .map(|(scope_id, _)| *scope_id)
409 .collect();
410 let removed = removed_ids
411 .into_iter()
412 .filter_map(|scope_id| fast_scopes.remove(&scope_id))
413 .collect::<Vec<_>>();
414 shrink_map_if_sparse(&mut fast_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
415 removed
416 };
417
418 let removed_owned = { self.partition_owned_scopes(|entry| !entry.should_retain()) };
419
420 for entry in removed_fast.into_iter().chain(removed_owned) {
421 self.unregister_entry(&entry);
422 }
423 }
424
425 fn find_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
426 where
427 T: Any + Eq + Hash + 'static,
428 {
429 let key = owned_scope_index_key(scope);
430 self.owned_scopes.borrow().get(&key).and_then(|bucket| {
431 bucket
432 .iter()
433 .find(|entry| entry.borrow().matches_scope(scope))
434 .cloned()
435 })
436 }
437
438 fn remove_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
439 where
440 T: Any + Eq + Hash + 'static,
441 {
442 let key = owned_scope_index_key(scope);
443 let mut owned_scopes = self.owned_scopes.borrow_mut();
444 let mut removed = None;
445 let mut remove_bucket = false;
446 if let Some(bucket) = owned_scopes.get_mut(&key)
447 && let Some(index) = bucket
448 .iter()
449 .position(|entry| entry.borrow().matches_scope(scope))
450 {
451 removed = Some(bucket.remove(index));
452 remove_bucket = bucket.is_empty();
453 }
454 if remove_bucket {
455 owned_scopes.remove(&key);
456 }
457 shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
458 removed
459 }
460
461 fn partition_owned_scopes(
462 &self,
463 should_remove: impl Fn(&ScopeEntry) -> bool,
464 ) -> Vec<Rc<RefCell<ScopeEntry>>> {
465 let mut owned_scopes = self.owned_scopes.borrow_mut();
466 let mut retained = HashMap::default();
467 let mut removed = Vec::new();
468 for (key, mut bucket) in owned_scopes.drain() {
469 let mut retained_bucket = OwnedScopeBucket::new();
470 for entry in bucket.drain(..) {
471 if should_remove(&entry.borrow()) {
472 removed.push(entry);
473 } else {
474 retained_bucket.push(entry);
475 }
476 }
477 if !retained_bucket.is_empty() {
478 retained.insert(key, retained_bucket);
479 }
480 }
481 *owned_scopes = retained;
482 shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
483 removed
484 }
485
486 fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
487 let owned_scopes = self.owned_scopes.borrow();
488 let fast_scopes = self.fast_scopes.borrow();
489 let owned_scope_len = owned_scopes.values().map(SmallVec::len).sum::<usize>();
490 let owned_scope_cap =
491 owned_scopes.capacity() + owned_scopes.values().map(SmallVec::capacity).sum::<usize>();
492 let scopes_len = owned_scope_len + fast_scopes.len();
493 let scopes_cap = owned_scope_cap + fast_scopes.capacity();
494 let mut observed_state_count = 0;
495 let mut observed_state_capacity = 0;
496 let mut stateless_scope_count = 0;
497
498 for entry in owned_scopes
499 .values()
500 .flat_map(|bucket| bucket.iter())
501 .chain(fast_scopes.values())
502 {
503 let entry = entry.borrow();
504 observed_state_count += entry.observed.len();
505 observed_state_capacity += entry.observed.capacity();
506 stateless_scope_count += usize::from(entry.observed.is_empty());
507 }
508
509 SnapshotStateObserverDebugStats {
510 scopes_len,
511 scopes_cap,
512 fast_scopes_len: fast_scopes.len(),
513 fast_scopes_cap: fast_scopes.capacity(),
514 stateless_scope_count,
515 observed_state_count,
516 observed_state_capacity,
517 }
518 }
519
520 fn run_with_read_observer<R>(&self, block: impl FnOnce() -> R) -> R {
521 use crate::snapshot_v2::take_transparent_observer_mutable_snapshot;
522
523 let snapshot =
524 take_transparent_observer_mutable_snapshot(Some(self.read_dispatcher.clone()), None);
525 let result = snapshot.enter(block);
526 snapshot.dispose();
527 result
528 }
529
530 fn handle_apply(&self, modified: &[Arc<dyn StateObject>]) {
531 if modified.is_empty() {
532 return;
533 }
534
535 let mut seen_scope_ids: HashSet<usize> = HashSet::default();
536 let mut to_notify: Vec<Rc<RefCell<ScopeEntry>>> = Vec::new();
537 {
538 let observed_to_scopes = self.observed_to_scopes.borrow();
539 let indexed_scopes = self.indexed_scopes.borrow();
540 for state in modified {
541 if let Some(scope_ids) = observed_to_scopes.get(&state.object_id().as_usize()) {
542 let mut ordered_scope_ids: SmallVec<[usize; 8]> =
543 scope_ids.iter().copied().collect();
544 ordered_scope_ids.sort_unstable();
545 for scope_id in ordered_scope_ids {
546 if seen_scope_ids.insert(scope_id)
547 && let Some(entry) = indexed_scopes.get(&scope_id)
548 {
549 to_notify.push(entry.clone());
550 }
551 }
552 }
553 }
554 }
555
556 if to_notify.is_empty() {
557 return;
558 }
559
560 for entry in to_notify {
561 let executor = self.executor.clone();
562 executor(Box::new(move || {
563 if let Ok(entry) = entry.try_borrow() {
564 entry.notify();
565 }
566 }));
567 }
568 }
569
570 fn replace_observed_ids(&self, entry: &Rc<RefCell<ScopeEntry>>, observed: ObservedIds) {
571 let (entry_id, previous) = {
572 let mut entry_mut = entry.borrow_mut();
573 let entry_id = entry_mut.id;
574 let previous = std::mem::replace(&mut entry_mut.observed, observed);
575 (entry_id, previous)
576 };
577 self.unregister_observed_ids(entry_id, &previous);
578 let entry_ref = entry.borrow();
579 self.register_observed_ids(entry_id, &entry_ref.observed);
580 }
581
582 fn register_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
583 let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
584 for state_id in observed.iter() {
585 let scope_ids = observed_to_scopes.entry(state_id).or_default();
586 scope_ids.insert(entry_id);
587 }
588 }
589
590 fn unregister_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
591 let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
592 let mut emptied = SmallVec::<[StateObjectId; MAX_OBSERVED_STATES]>::new();
593 for state_id in observed.iter() {
594 if let Some(scope_ids) = observed_to_scopes.get_mut(&state_id) {
595 scope_ids.remove(&entry_id);
596 if scope_ids.is_empty() {
597 emptied.push(state_id);
598 }
599 }
600 }
601 for state_id in emptied {
602 observed_to_scopes.remove(&state_id);
603 }
604 shrink_map_if_sparse(&mut observed_to_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
605 }
606
607 fn unregister_entry(&self, entry: &Rc<RefCell<ScopeEntry>>) {
608 let (entry_id, observed) = {
609 let mut entry_mut = entry.borrow_mut();
610 let observed = std::mem::replace(&mut entry_mut.observed, ObservedIds::new());
611 (entry_mut.id, observed)
612 };
613 self.unregister_observed_ids(entry_id, &observed);
614 self.indexed_scopes.borrow_mut().remove(&entry_id);
615 }
616}
617
618fn shrink_map_if_sparse<K, V>(map: &mut HashMap<K, V>, min_retained_capacity: usize)
619where
620 K: Eq + std::hash::Hash,
621{
622 if map.capacity() <= map.len().max(min_retained_capacity).saturating_mul(4) {
623 return;
624 }
625
626 let retained = map.len().max(min_retained_capacity);
627 let mut rebuilt = HashMap::default();
628 rebuilt.reserve(retained);
629 rebuilt.extend(map.drain());
630 *map = rebuilt;
631}
632
633enum ObservedIds {
634 Small(SmallVec<[ObservedState; MAX_OBSERVED_STATES]>),
635 Large(HashMap<StateObjectId, Option<Box<dyn Any>>>),
636}
637
638struct ObservedState {
639 id: StateObjectId,
640 _lease: Option<Box<dyn Any>>,
641}
642
643impl ObservedIds {
644 fn new() -> Self {
645 ObservedIds::Small(SmallVec::new())
646 }
647
648 fn insert(&mut self, state: &dyn StateObject) {
649 let id = state.object_id().as_usize();
650 match self {
651 ObservedIds::Small(small) => {
652 if small.iter().any(|observed| observed.id == id) {
653 return;
654 }
655 if small.len() < MAX_OBSERVED_STATES {
656 small.push(ObservedState {
657 id,
658 _lease: state.observation_lease(),
659 });
660 } else {
661 let mut large =
662 HashMap::with_capacity_and_hasher(small.len() + 1, Default::default());
663 for observed in small.drain(..) {
664 large.insert(observed.id, observed._lease);
665 }
666 large.insert(id, state.observation_lease());
667 *self = ObservedIds::Large(large);
668 }
669 }
670 ObservedIds::Large(large) => {
671 large.entry(id).or_insert_with(|| state.observation_lease());
672 }
673 }
674 }
675
676 fn is_empty(&self) -> bool {
677 match self {
678 ObservedIds::Small(small) => small.is_empty(),
679 ObservedIds::Large(large) => large.is_empty(),
680 }
681 }
682
683 fn len(&self) -> usize {
684 match self {
685 ObservedIds::Small(small) => small.len(),
686 ObservedIds::Large(large) => large.len(),
687 }
688 }
689
690 fn capacity(&self) -> usize {
691 match self {
692 ObservedIds::Small(small) => small.capacity(),
693 ObservedIds::Large(large) => large.capacity(),
694 }
695 }
696
697 fn iter(&self) -> Box<dyn Iterator<Item = StateObjectId> + '_> {
698 match self {
699 ObservedIds::Small(small) => Box::new(small.iter().map(|observed| observed.id)),
700 ObservedIds::Large(large) => Box::new(large.keys().copied()),
701 }
702 }
703}
704
705const MAX_OBSERVED_STATES: usize = 8;
706
707enum ScopeStorage {
708 Owned(Box<dyn Any>),
709 RecomposeScope {
710 id: ScopeId,
711 weak: Weak<RecomposeScopeInner>,
712 },
713}
714
715struct ScopeEntry {
716 id: usize,
717 scope: ScopeStorage,
718 on_changed: Rc<dyn Fn(&dyn Any)>,
719 observed: ObservedIds,
720 last_seen_version: u64,
721}
722
723impl ScopeEntry {
724 fn new<T>(id: usize, scope: T, on_changed: Rc<dyn Fn(&dyn Any)>) -> Self
725 where
726 T: Any + 'static,
727 {
728 Self {
729 id,
730 scope: ScopeStorage::from_value(scope),
731 on_changed,
732 observed: ObservedIds::new(),
733 last_seen_version: u64::MAX,
734 }
735 }
736
737 fn update<T>(&mut self, new_scope: T, on_changed: Rc<dyn Fn(&dyn Any)>)
738 where
739 T: Any + 'static,
740 {
741 self.scope = ScopeStorage::from_value(new_scope);
742 self.on_changed = on_changed;
743 }
744
745 fn matches_scope<T>(&self, scope: &T) -> bool
746 where
747 T: Any + Eq + 'static,
748 {
749 if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
750 return matches!(
751 &self.scope,
752 ScopeStorage::RecomposeScope { id, .. } if *id == scope.id()
753 );
754 }
755
756 match &self.scope {
757 ScopeStorage::Owned(stored) => stored
758 .downcast_ref::<T>()
759 .map(|stored| stored == scope)
760 .unwrap_or(false),
761 ScopeStorage::RecomposeScope { .. } => false,
762 }
763 }
764
765 fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
766 match &self.scope {
767 ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
768 ScopeStorage::RecomposeScope { weak, .. } => weak
769 .upgrade()
770 .map(|inner| predicate(&RecomposeScope { inner }))
771 .unwrap_or(true),
772 }
773 }
774
775 fn should_retain(&self) -> bool {
776 match &self.scope {
777 ScopeStorage::Owned(_) => true,
778 ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
779 }
780 }
781
782 fn notify(&self) {
783 match &self.scope {
784 ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
785 ScopeStorage::RecomposeScope { weak, .. } => {
786 if let Some(inner) = weak.upgrade() {
787 (self.on_changed)(&RecomposeScope { inner });
788 }
789 }
790 }
791 }
792}
793
794impl ScopeStorage {
795 fn from_value<T>(value: T) -> Self
796 where
797 T: Any + 'static,
798 {
799 let any = &value as &dyn Any;
800 if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
801 Self::RecomposeScope {
802 id: scope.id(),
803 weak: scope.downgrade(),
804 }
805 } else {
806 Self::Owned(Box::new(value))
807 }
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use std::cell::Cell;
814
815 use super::*;
816 use crate::{
817 snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
818 state::{NeverEqual, SnapshotMutableState},
819 };
820
821 fn reset_runtime() -> TestRuntimeGuard {
822 reset_runtime_for_tests()
823 }
824
825 #[derive(Clone, Eq, Hash, PartialEq)]
826 struct TestScope(&'static str);
827
828 #[test]
829 fn notifies_scope_when_state_changes() {
830 let _guard = reset_runtime();
831
832 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
833 let triggered = Rc::new(Cell::new(0));
834 let observer_trigger = triggered.clone();
835
836 let observer = SnapshotStateObserver::new(|callback| callback());
837 observer.start();
838
839 let scope = TestScope("scope");
840 observer.observe_reads(
841 scope.clone(),
842 move |_| {
843 observer_trigger.set(observer_trigger.get() + 1);
844 },
845 || {
846 let _ = state.get();
847 },
848 );
849
850 let snapshot = take_mutable_snapshot(None, None);
851 snapshot.enter(|| {
852 state.set(1);
853 });
854 snapshot.apply().check();
855
856 assert_eq!(triggered.get(), 1);
857 observer.stop();
858 }
859
860 #[test]
861 fn clear_removes_scope_observation() {
862 let _guard = reset_runtime();
863
864 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
865 let triggered = Rc::new(Cell::new(0));
866 let observer_trigger = triggered.clone();
867
868 let observer = SnapshotStateObserver::new(|callback| callback());
869 observer.start();
870
871 let scope = TestScope("scope");
872 observer.observe_reads(
873 scope.clone(),
874 move |_| {
875 observer_trigger.set(observer_trigger.get() + 1);
876 },
877 || {
878 let _ = state.get();
879 },
880 );
881
882 observer.clear(&scope);
883
884 let snapshot = take_mutable_snapshot(None, None);
885 snapshot.enter(|| {
886 state.set(1);
887 });
888 snapshot.apply().check();
889
890 assert_eq!(triggered.get(), 0);
891 observer.stop();
892 }
893
894 #[test]
895 fn repeated_owned_scope_observations_reuse_the_same_entry() {
896 let _guard = reset_runtime();
897
898 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
899 let observer = SnapshotStateObserver::new(|callback| callback());
900 let scope = TestScope("scope");
901
902 observer.observe_reads(
903 scope.clone(),
904 |_| {},
905 || {
906 let _ = state.get();
907 },
908 );
909 observer.observe_reads(
910 scope,
911 |_| {},
912 || {
913 let _ = state.get();
914 },
915 );
916
917 let stats = observer.debug_stats();
918 assert_eq!(stats.scopes_len, 1);
919 assert_eq!(stats.fast_scopes_len, 0);
920 }
921
922 #[test]
923 fn with_no_observations_skips_reads() {
924 let _guard = reset_runtime();
925
926 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
927 let triggered = Rc::new(Cell::new(0));
928 let observer_trigger = triggered.clone();
929
930 let observer = SnapshotStateObserver::new(|callback| callback());
931 observer.start();
932
933 let scope = TestScope("scope");
934 observer.observe_reads(
935 scope.clone(),
936 move |_| {
937 observer_trigger.set(observer_trigger.get() + 1);
938 },
939 || {
940 observer.with_no_observations(|| {
941 let _ = state.get();
942 });
943 },
944 );
945
946 let snapshot = take_mutable_snapshot(None, None);
947 snapshot.enter(|| {
948 state.set(1);
949 });
950 snapshot.apply().check();
951
952 assert_eq!(triggered.get(), 0);
953 observer.stop();
954 }
955
956 #[test]
957 fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
958 let _guard = reset_runtime();
959
960 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
961 let outer_triggered = Rc::new(Cell::new(0));
962 let inner_triggered = Rc::new(Cell::new(0));
963
964 let observer = SnapshotStateObserver::new(|callback| callback());
965 observer.start();
966
967 let outer_scope = TestScope("outer");
968 let inner_scope = TestScope("inner");
969 observer.observe_reads(
970 outer_scope.clone(),
971 {
972 let outer_triggered = Rc::clone(&outer_triggered);
973 move |_| outer_triggered.set(outer_triggered.get() + 1)
974 },
975 || {
976 observer.observe_reads(
977 inner_scope.clone(),
978 {
979 let inner_triggered = Rc::clone(&inner_triggered);
980 move |_| inner_triggered.set(inner_triggered.get() + 1)
981 },
982 || {
983 let _ = state.get();
984 },
985 );
986 },
987 );
988
989 let snapshot = take_mutable_snapshot(None, None);
990 snapshot.enter(|| {
991 state.set(1);
992 });
993 snapshot.apply().check();
994
995 assert_eq!(outer_triggered.get(), 0);
996 assert_eq!(inner_triggered.get(), 1);
997 observer.stop();
998 }
999
1000 #[test]
1001 fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1002 let _guard = reset_runtime();
1003
1004 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1005 let first_triggered = Rc::new(Cell::new(0));
1006 let second_triggered = Rc::new(Cell::new(0));
1007
1008 let observer = SnapshotStateObserver::new(|callback| callback());
1009 observer.start();
1010
1011 let first_scope = TestScope("first");
1012 let second_scope = TestScope("second");
1013 observer.observe_reads(
1014 first_scope.clone(),
1015 {
1016 let first_triggered = Rc::clone(&first_triggered);
1017 move |_| first_triggered.set(first_triggered.get() + 1)
1018 },
1019 || {
1020 let _ = state.get();
1021 },
1022 );
1023 observer.observe_reads(
1024 second_scope.clone(),
1025 {
1026 let second_triggered = Rc::clone(&second_triggered);
1027 move |_| second_triggered.set(second_triggered.get() + 1)
1028 },
1029 || {
1030 let _ = state.get();
1031 },
1032 );
1033
1034 observer.clear(&first_scope);
1035
1036 let snapshot = take_mutable_snapshot(None, None);
1037 snapshot.enter(|| {
1038 state.set(1);
1039 });
1040 snapshot.apply().check();
1041
1042 assert_eq!(first_triggered.get(), 0);
1043 assert_eq!(second_triggered.get(), 1);
1044 observer.stop();
1045 }
1046
1047 #[test]
1048 fn shared_state_notifies_scopes_in_registration_order() {
1049 let _guard = reset_runtime();
1050
1051 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1052 let notifications = Rc::new(RefCell::new(Vec::new()));
1053
1054 let observer = SnapshotStateObserver::new(|callback| callback());
1055 observer.start();
1056
1057 observer.observe_reads(
1058 TestScope("first"),
1059 {
1060 let notifications = Rc::clone(¬ifications);
1061 move |_| notifications.borrow_mut().push("first")
1062 },
1063 || {
1064 let _ = state.get();
1065 },
1066 );
1067 observer.observe_reads(
1068 TestScope("second"),
1069 {
1070 let notifications = Rc::clone(¬ifications);
1071 move |_| notifications.borrow_mut().push("second")
1072 },
1073 || {
1074 let _ = state.get();
1075 },
1076 );
1077
1078 let snapshot = take_mutable_snapshot(None, None);
1079 snapshot.enter(|| {
1080 state.set(1);
1081 });
1082 snapshot.apply().check();
1083
1084 assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1085 observer.stop();
1086 }
1087
1088 #[test]
1089 fn stateless_recompose_scope_does_not_retain_observer_entry() {
1090 let _guard = reset_runtime();
1091
1092 let observer = SnapshotStateObserver::new(|callback| callback());
1093 let runtime = crate::TestRuntime::new();
1094 let scope = RecomposeScope::new_for_test(runtime.handle());
1095
1096 observer.observe_reads(scope, |_| {}, || {});
1097
1098 let stats = observer.debug_stats();
1099 assert_eq!(stats.scopes_len, 0);
1100 assert_eq!(stats.fast_scopes_len, 0);
1101 assert_eq!(stats.stateless_scope_count, 0);
1102 }
1103
1104 #[test]
1105 fn scope_that_stops_reading_state_is_removed_immediately() {
1106 let _guard = reset_runtime();
1107
1108 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1109 let observer = SnapshotStateObserver::new(|callback| callback());
1110 let runtime = crate::TestRuntime::new();
1111 let scope = RecomposeScope::new_for_test(runtime.handle());
1112 let triggered = Rc::new(Cell::new(0));
1113 let observer_trigger = Rc::clone(&triggered);
1114
1115 observer.observe_reads(
1116 scope.clone(),
1117 move |_| observer_trigger.set(observer_trigger.get() + 1),
1118 || {
1119 let _ = state.get();
1120 },
1121 );
1122
1123 let after_stateful = observer.debug_stats();
1124 assert_eq!(after_stateful.scopes_len, 1);
1125 assert_eq!(after_stateful.fast_scopes_len, 1);
1126
1127 observer.observe_reads(scope, |_| {}, || {});
1128
1129 let after_stateless = observer.debug_stats();
1130 assert_eq!(after_stateless.scopes_len, 0);
1131 assert_eq!(after_stateless.fast_scopes_len, 0);
1132
1133 let snapshot = take_mutable_snapshot(None, None);
1134 snapshot.enter(|| {
1135 state.set(1);
1136 });
1137 snapshot.apply().check();
1138
1139 assert_eq!(triggered.get(), 0);
1140 }
1141
1142 #[test]
1143 fn begin_frame_prunes_dropped_recompose_scope_entries() {
1144 let _guard = reset_runtime();
1145
1146 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1147 let observer = SnapshotStateObserver::new(|callback| callback());
1148 let runtime = crate::TestRuntime::new();
1149 let scope = RecomposeScope::new_for_test(runtime.handle());
1150
1151 observer.observe_reads(
1152 scope.clone(),
1153 |_| {},
1154 || {
1155 let _ = state.get();
1156 },
1157 );
1158
1159 let before_prune = observer.debug_stats();
1160 assert_eq!(before_prune.scopes_len, 1);
1161 assert_eq!(before_prune.fast_scopes_len, 1);
1162
1163 drop(scope);
1164 observer.begin_frame();
1165
1166 let after_prune = observer.debug_stats();
1167 assert_eq!(after_prune.scopes_len, 0);
1168 assert_eq!(after_prune.fast_scopes_len, 0);
1169 }
1170}