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::{
18 ReadObserver, StateObjectId, TransparentObserverMutableSnapshot, register_apply_observer,
19 },
20 state::StateObject,
21};
22
23type Executor = dyn Fn(Box<dyn FnOnce() + 'static>) + 'static;
24
25trait ScopeChangedCallback: Fn(&dyn Any) + Any {}
26
27impl<F: Fn(&dyn Any) + Any> ScopeChangedCallback for F {}
28
29#[derive(Clone)]
42pub struct SnapshotStateObserver {
43 inner: Rc<SnapshotStateObserverInner>,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
47pub struct SnapshotStateObserverDebugStats {
48 pub scopes_len: usize,
49 pub scopes_cap: usize,
50 pub fast_scopes_len: usize,
51 pub fast_scopes_cap: usize,
52 pub stateless_scope_count: usize,
53 pub observed_state_count: usize,
54 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 let inner = Rc::new(SnapshotStateObserverInner::new(on_changed_executor));
61 inner.set_self(Rc::downgrade(&inner));
62 Self { inner }
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 begin_frame(&self) {
85 self.inner.begin_frame();
86 }
87
88 pub fn prune_dead_scopes(&self) {
90 self.inner.prune_dead_scopes();
91 }
92
93 pub fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
95 self.inner.with_no_observations(block)
96 }
97
98 pub fn clear<T>(&self, scope: &T)
100 where
101 T: Any + Eq + Hash + 'static,
102 {
103 self.inner.clear(scope);
104 }
105
106 pub fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
108 self.inner.clear_if(predicate);
109 }
110
111 pub fn clear_all(&self) {
113 self.inner.clear_all();
114 }
115
116 pub fn start(&self) {
118 let weak = Rc::downgrade(&self.inner);
119 self.inner.start(weak);
120 }
121
122 pub fn stop(&self) {
124 self.inner.stop();
125 }
126
127 pub fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
128 self.inner.debug_stats()
129 }
130
131 #[cfg(test)]
132 pub fn notify_changes(&self, modified: &[Arc<dyn StateObject>]) {
133 self.inner.handle_apply(modified);
134 }
135}
136
137struct SnapshotStateObserverInner {
138 executor: Rc<Executor>,
139 owned_scopes: RefCell<HashMap<OwnedScopeIndexKey, OwnedScopeBucket>>,
140 fast_scopes: RefCell<HashMap<ScopeId, Rc<RefCell<ScopeEntry>>>>,
141 indexed_scopes: RefCell<HashMap<usize, Rc<RefCell<ScopeEntry>>>>,
142 observed_to_scopes: RefCell<HashMap<StateObjectId, HashSet<usize>>>,
143 pause_count: Rc<Cell<usize>>,
144 active_read_targets: Rc<RefCell<ReadObservationStack>>,
145 read_dispatcher: ReadObserver,
146 read_snapshot: RefCell<Option<Arc<TransparentObserverMutableSnapshot>>>,
147 apply_handle: RefCell<Option<crate::snapshot_v2::ObserverHandle>>,
148 weak_self: RefCell<Weak<SnapshotStateObserverInner>>,
149 frame_version: Cell<u64>,
150 next_entry_id: Cell<usize>,
151}
152
153#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
154struct OwnedScopeIndexKey {
155 type_id: TypeId,
156 value_hash: u64,
157}
158
159type OwnedScopeBucket = SmallVec<[Rc<RefCell<ScopeEntry>>; 1]>;
160
161fn owned_scope_index_key<T>(scope: &T) -> OwnedScopeIndexKey
162where
163 T: Any + Hash + 'static,
164{
165 let mut hasher = default_hash::new();
166 scope.hash(&mut hasher);
167 OwnedScopeIndexKey {
168 type_id: TypeId::of::<T>(),
169 value_hash: hasher.finish(),
170 }
171}
172
173impl SnapshotStateObserverInner {
174 const MIN_RETAINED_SCOPE_CAPACITY: usize = 256;
175
176 fn new(on_changed_executor: impl Fn(Box<dyn FnOnce() + 'static>) + 'static) -> Self {
177 let pause_count = Rc::new(Cell::new(0));
178 let active_read_targets = Rc::new(RefCell::new(ReadObservationStack::default()));
179 let dispatcher_pause_count = Rc::clone(&pause_count);
180 let dispatcher_targets = Rc::clone(&active_read_targets);
181 let read_dispatcher: ReadObserver = Arc::new(move |state| {
182 if dispatcher_pause_count.get() > 0 {
183 return;
184 }
185 let observed = dispatcher_targets.borrow().last().cloned();
186 if let Some(observed) = observed {
187 observed.borrow_mut().insert(state);
188 }
189 });
190
191 Self {
192 executor: Rc::new(on_changed_executor),
193 owned_scopes: RefCell::new(HashMap::default()),
194 fast_scopes: RefCell::new(HashMap::default()),
195 indexed_scopes: RefCell::new(HashMap::default()),
196 observed_to_scopes: RefCell::new(HashMap::default()),
197 pause_count,
198 active_read_targets,
199 read_dispatcher,
200 read_snapshot: RefCell::new(None),
201 apply_handle: RefCell::new(None),
202 weak_self: RefCell::new(Weak::new()),
203 frame_version: Cell::new(0),
204 next_entry_id: Cell::new(0),
205 }
206 }
207
208 fn set_self(&self, weak: Weak<SnapshotStateObserverInner>) {
209 self.weak_self.replace(weak);
210 }
211
212 fn begin_frame(&self) {
213 let next = self.frame_version.get().wrapping_add(1);
214 self.frame_version.set(next);
215 self.prune_dead_scopes();
216 }
217
218 fn observe_reads<T, R>(
219 &self,
220 scope: T,
221 on_value_changed_for_scope: impl Fn(&T) + 'static,
222 block: impl FnOnce() -> R,
223 ) -> R
224 where
225 T: Any + Clone + Eq + Hash + 'static,
226 {
227 let frame_version = self.frame_version.get();
228 let has_frame_version = frame_version != 0;
229
230 let existing_entry = self.find_scope_entry(&scope);
231 let on_changed = std::cell::LazyCell::new(|| {
232 let callback = move |scope_any: &dyn Any| {
233 if let Some(typed) = scope_any.downcast_ref::<T>() {
234 on_value_changed_for_scope(typed);
235 }
236 };
237 match existing_entry.as_ref() {
238 Some(entry) => entry.borrow_mut().callback_reusing(callback),
239 None => Rc::new(callback),
240 }
241 });
242
243 if let Some(entry) = existing_entry.as_ref() {
244 let already_observed = {
245 let mut entry_mut = entry.borrow_mut();
246 entry_mut.update_scope(scope.clone());
247 has_frame_version && entry_mut.last_seen_version == frame_version
248 };
249 let callback = on_changed.clone();
250 entry.borrow_mut().on_changed = callback;
251 if already_observed {
252 return block();
253 }
254 }
255
256 let observed = self.active_read_targets.borrow_mut().push();
257 struct ActiveObservationGuard {
258 stack: Rc<RefCell<ReadObservationStack>>,
259 }
260 impl Drop for ActiveObservationGuard {
261 fn drop(&mut self) {
262 let target = self.stack.borrow_mut().pop();
263 let discarded = target.replace(ObservedIds::new());
264 drop(discarded);
265 }
266 }
267 let _guard = ActiveObservationGuard {
268 stack: Rc::clone(&self.active_read_targets),
269 };
270
271 let result = self.run_with_read_observer(block);
272
273 if observed.borrow().is_empty() {
274 if existing_entry.is_some() {
275 self.clear(&scope);
276 }
277 return result;
278 }
279
280 let observed = {
281 let mut observed = observed.borrow_mut();
282 std::mem::replace(&mut *observed, ObservedIds::new())
283 };
284 let entry = existing_entry
285 .clone()
286 .unwrap_or_else(|| self.insert_scope_entry(scope.clone(), on_changed.clone()));
287 {
288 let mut entry_mut = entry.borrow_mut();
289 entry_mut.update(scope, Rc::clone(&on_changed));
290 entry_mut.last_seen_version = if has_frame_version {
291 frame_version
292 } else {
293 u64::MAX
294 };
295 }
296 self.replace_observed_ids(&entry, observed);
297
298 result
299 }
300
301 fn with_no_observations<R>(&self, block: impl FnOnce() -> R) -> R {
302 self.pause_count.set(self.pause_count.get() + 1);
303 let result = block();
304 self.pause_count
305 .set(self.pause_count.get().saturating_sub(1));
306 result
307 }
308
309 fn clear<T>(&self, scope: &T)
310 where
311 T: Any + Eq + Hash + 'static,
312 {
313 if let Some(rc_scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
314 if let Some(entry) = self.fast_scopes.borrow_mut().remove(&rc_scope.id()) {
315 self.unregister_entry(&entry);
316 }
317 return;
318 }
319
320 let removed = self.remove_owned_scope_entry(scope);
321 if let Some(entry) = removed {
322 self.unregister_entry(&entry);
323 }
324 }
325
326 fn clear_if(&self, predicate: impl Fn(&dyn Any) -> bool) {
327 let removed_fast = {
328 let mut fast_scopes = self.fast_scopes.borrow_mut();
329 let removed_ids: Vec<_> = fast_scopes
330 .iter()
331 .filter(|(_, entry)| entry.borrow().matches_predicate(&predicate))
332 .map(|(scope_id, _)| *scope_id)
333 .collect();
334 removed_ids
335 .into_iter()
336 .filter_map(|scope_id| fast_scopes.remove(&scope_id))
337 .collect::<Vec<_>>()
338 };
339 let removed_owned =
340 { self.partition_owned_scopes(|entry| entry.matches_predicate(&predicate)) };
341
342 for entry in removed_fast.into_iter().chain(removed_owned) {
343 self.unregister_entry(&entry);
344 }
345 }
346
347 fn clear_all(&self) {
348 self.fast_scopes.borrow_mut().clear();
349 self.owned_scopes.borrow_mut().clear();
350 self.indexed_scopes.borrow_mut().clear();
351 self.observed_to_scopes.borrow_mut().clear();
352 }
353
354 fn start(&self, weak_self: Weak<SnapshotStateObserverInner>) {
355 if self.apply_handle.borrow().is_some() {
356 return;
357 }
358
359 let handle = register_apply_observer(Rc::new(move |modified, _snapshot_id| {
360 if let Some(inner) = weak_self.upgrade() {
361 inner.handle_apply(modified);
362 }
363 }));
364 self.apply_handle.replace(Some(handle));
365 }
366
367 fn stop(&self) {
368 if let Some(handle) = self.apply_handle.borrow_mut().take() {
369 drop(handle);
370 }
371 }
372
373 fn find_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
374 where
375 T: Any + Eq + Hash + 'static,
376 {
377 if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
378 return self.fast_scopes.borrow().get(&scope.id()).cloned();
379 }
380
381 self.find_owned_scope_entry(scope)
382 }
383
384 fn insert_scope_entry(
385 &self,
386 scope: impl Any + Clone + Eq + Hash + 'static,
387 on_changed: Rc<dyn ScopeChangedCallback>,
388 ) -> Rc<RefCell<ScopeEntry>> {
389 let entry_id = self.next_entry_id.get();
390 self.next_entry_id.set(entry_id.wrapping_add(1));
391 let recompose_scope_id = (&scope as &dyn Any)
392 .downcast_ref::<RecomposeScope>()
393 .map(RecomposeScope::id);
394 let owned_scope_key = recompose_scope_id
395 .is_none()
396 .then(|| owned_scope_index_key(&scope));
397 let entry = Rc::new(RefCell::new(ScopeEntry::new(entry_id, scope, on_changed)));
398 self.indexed_scopes
399 .borrow_mut()
400 .insert(entry_id, Rc::clone(&entry));
401 if let Some(scope_id) = recompose_scope_id {
402 self.fast_scopes
403 .borrow_mut()
404 .insert(scope_id, Rc::clone(&entry));
405 } else if let Some(scope_key) = owned_scope_key {
406 self.owned_scopes
407 .borrow_mut()
408 .entry(scope_key)
409 .or_default()
410 .push(Rc::clone(&entry));
411 }
412 entry
413 }
414
415 fn prune_dead_scopes(&self) {
416 let removed_fast = {
417 let mut fast_scopes = self.fast_scopes.borrow_mut();
418 let removed_ids: Vec<_> = fast_scopes
419 .iter()
420 .filter(|(_, entry)| !entry.borrow().should_retain())
421 .map(|(scope_id, _)| *scope_id)
422 .collect();
423 let removed = removed_ids
424 .into_iter()
425 .filter_map(|scope_id| fast_scopes.remove(&scope_id))
426 .collect::<Vec<_>>();
427 shrink_map_if_sparse(&mut fast_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
428 removed
429 };
430
431 let removed_owned = { self.partition_owned_scopes(|entry| !entry.should_retain()) };
432
433 for entry in removed_fast.into_iter().chain(removed_owned) {
434 self.unregister_entry(&entry);
435 }
436 }
437
438 fn find_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 self.owned_scopes.borrow().get(&key).and_then(|bucket| {
444 bucket
445 .iter()
446 .find(|entry| entry.borrow().matches_scope(scope))
447 .cloned()
448 })
449 }
450
451 fn remove_owned_scope_entry<T>(&self, scope: &T) -> Option<Rc<RefCell<ScopeEntry>>>
452 where
453 T: Any + Eq + Hash + 'static,
454 {
455 let key = owned_scope_index_key(scope);
456 let mut owned_scopes = self.owned_scopes.borrow_mut();
457 let mut removed = None;
458 let mut remove_bucket = false;
459 if let Some(bucket) = owned_scopes.get_mut(&key)
460 && let Some(index) = bucket
461 .iter()
462 .position(|entry| entry.borrow().matches_scope(scope))
463 {
464 removed = Some(bucket.remove(index));
465 remove_bucket = bucket.is_empty();
466 }
467 if remove_bucket {
468 owned_scopes.remove(&key);
469 }
470 shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
471 removed
472 }
473
474 fn partition_owned_scopes(
475 &self,
476 should_remove: impl Fn(&ScopeEntry) -> bool,
477 ) -> Vec<Rc<RefCell<ScopeEntry>>> {
478 let mut owned_scopes = self.owned_scopes.borrow_mut();
479 let mut retained = HashMap::default();
480 let mut removed = Vec::new();
481 for (key, mut bucket) in owned_scopes.drain() {
482 let mut retained_bucket = OwnedScopeBucket::new();
483 for entry in bucket.drain(..) {
484 if should_remove(&entry.borrow()) {
485 removed.push(entry);
486 } else {
487 retained_bucket.push(entry);
488 }
489 }
490 if !retained_bucket.is_empty() {
491 retained.insert(key, retained_bucket);
492 }
493 }
494 *owned_scopes = retained;
495 shrink_map_if_sparse(&mut owned_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
496 removed
497 }
498
499 fn debug_stats(&self) -> SnapshotStateObserverDebugStats {
500 let owned_scopes = self.owned_scopes.borrow();
501 let fast_scopes = self.fast_scopes.borrow();
502 let owned_scope_len = owned_scopes.values().map(SmallVec::len).sum::<usize>();
503 let owned_scope_cap =
504 owned_scopes.capacity() + owned_scopes.values().map(SmallVec::capacity).sum::<usize>();
505 let scopes_len = owned_scope_len + fast_scopes.len();
506 let scopes_cap = owned_scope_cap + fast_scopes.capacity();
507 let mut observed_state_count = 0;
508 let mut observed_state_capacity = 0;
509 let mut stateless_scope_count = 0;
510
511 for entry in owned_scopes
512 .values()
513 .flat_map(|bucket| bucket.iter())
514 .chain(fast_scopes.values())
515 {
516 let entry = entry.borrow();
517 observed_state_count += entry.observed.len();
518 observed_state_capacity += entry.observed.capacity();
519 stateless_scope_count += usize::from(entry.observed.is_empty());
520 }
521
522 SnapshotStateObserverDebugStats {
523 scopes_len,
524 scopes_cap,
525 fast_scopes_len: fast_scopes.len(),
526 fast_scopes_cap: fast_scopes.capacity(),
527 stateless_scope_count,
528 observed_state_count,
529 observed_state_capacity,
530 }
531 }
532
533 fn run_with_read_observer<R>(&self, block: impl FnOnce() -> R) -> R {
534 use crate::snapshot_v2::take_transparent_observer_mutable_snapshot_reusing;
535
536 let mut snapshot = take_transparent_observer_mutable_snapshot_reusing(
537 Some(self.read_dispatcher.clone()),
538 None,
539 self.read_snapshot.take(),
540 );
541 let result = snapshot.enter(block);
542 snapshot.dispose();
543 if Arc::get_mut(&mut snapshot).is_some() && !snapshot.has_pending_changes() {
544 self.read_snapshot.replace(Some(snapshot));
545 }
546 result
547 }
548
549 fn handle_apply(&self, modified: &[Arc<dyn StateObject>]) {
550 if modified.is_empty() {
551 return;
552 }
553
554 let mut seen_scope_ids: HashSet<usize> = HashSet::default();
555 let mut to_notify: Vec<Rc<RefCell<ScopeEntry>>> = Vec::new();
556 {
557 let observed_to_scopes = self.observed_to_scopes.borrow();
558 let indexed_scopes = self.indexed_scopes.borrow();
559 for state in modified {
560 if let Some(scope_ids) = observed_to_scopes.get(&state.object_id().as_usize()) {
561 let mut ordered_scope_ids: SmallVec<[usize; 8]> =
562 scope_ids.iter().copied().collect();
563 ordered_scope_ids.sort_unstable();
564 for scope_id in ordered_scope_ids {
565 if seen_scope_ids.insert(scope_id)
566 && let Some(entry) = indexed_scopes.get(&scope_id)
567 {
568 to_notify.push(entry.clone());
569 }
570 }
571 }
572 }
573 }
574
575 if to_notify.is_empty() {
576 return;
577 }
578
579 for entry in to_notify {
580 let executor = self.executor.clone();
581 executor(Box::new(move || {
582 if let Ok(entry) = entry.try_borrow() {
583 entry.notify();
584 }
585 }));
586 }
587 }
588
589 fn replace_observed_ids(&self, entry: &Rc<RefCell<ScopeEntry>>, observed: ObservedIds) {
590 let (entry_id, previous) = {
591 let mut entry_mut = entry.borrow_mut();
592 let entry_id = entry_mut.id;
593 let previous = std::mem::replace(&mut entry_mut.observed, observed);
594 (entry_id, previous)
595 };
596 let entry_ref = entry.borrow();
597 if previous.iter().eq(entry_ref.observed.iter()) {
598 return;
599 }
600 self.unregister_observed_ids(entry_id, &previous);
601 self.register_observed_ids(entry_id, &entry_ref.observed);
602 }
603
604 fn register_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
605 let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
606 for state_id in observed.iter() {
607 let scope_ids = observed_to_scopes.entry(state_id).or_default();
608 scope_ids.insert(entry_id);
609 }
610 }
611
612 fn unregister_observed_ids(&self, entry_id: usize, observed: &ObservedIds) {
613 let mut observed_to_scopes = self.observed_to_scopes.borrow_mut();
614 let mut emptied = SmallVec::<[StateObjectId; MAX_OBSERVED_STATES]>::new();
615 for state_id in observed.iter() {
616 if let Some(scope_ids) = observed_to_scopes.get_mut(&state_id) {
617 scope_ids.remove(&entry_id);
618 if scope_ids.is_empty() {
619 emptied.push(state_id);
620 }
621 }
622 }
623 for state_id in emptied {
624 observed_to_scopes.remove(&state_id);
625 }
626 shrink_map_if_sparse(&mut observed_to_scopes, Self::MIN_RETAINED_SCOPE_CAPACITY);
627 }
628
629 fn unregister_entry(&self, entry: &Rc<RefCell<ScopeEntry>>) {
630 let (entry_id, observed) = {
631 let mut entry_mut = entry.borrow_mut();
632 let observed = std::mem::replace(&mut entry_mut.observed, ObservedIds::new());
633 (entry_mut.id, observed)
634 };
635 self.unregister_observed_ids(entry_id, &observed);
636 self.indexed_scopes.borrow_mut().remove(&entry_id);
637 }
638}
639
640fn shrink_map_if_sparse<K, V>(map: &mut HashMap<K, V>, min_retained_capacity: usize)
641where
642 K: Eq + std::hash::Hash,
643{
644 if map.capacity() <= map.len().max(min_retained_capacity).saturating_mul(4) {
645 return;
646 }
647
648 let retained = map.len().max(min_retained_capacity);
649 let mut rebuilt = HashMap::default();
650 rebuilt.reserve(retained);
651 rebuilt.extend(map.drain());
652 *map = rebuilt;
653}
654
655#[derive(Default)]
656struct ReadObservationStack {
657 targets: Vec<Rc<RefCell<ObservedIds>>>,
658 depth: usize,
659}
660
661impl ReadObservationStack {
662 fn push(&mut self) -> Rc<RefCell<ObservedIds>> {
663 if self.depth == self.targets.len() {
664 self.targets.push(Rc::new(RefCell::new(ObservedIds::new())));
665 }
666 let target = Rc::clone(&self.targets[self.depth]);
667 self.depth += 1;
668 target
669 }
670
671 fn last(&self) -> Option<&Rc<RefCell<ObservedIds>>> {
672 self.depth.checked_sub(1).map(|index| &self.targets[index])
673 }
674
675 fn pop(&mut self) -> Rc<RefCell<ObservedIds>> {
676 self.depth -= 1;
677 Rc::clone(&self.targets[self.depth])
678 }
679}
680
681enum ObservedIds {
682 Small(SmallVec<[ObservedState; MAX_OBSERVED_STATES]>),
683 Large(HashMap<StateObjectId, Option<Rc<dyn Any>>>),
684}
685
686struct ObservedState {
687 id: StateObjectId,
688 _lease: Option<Rc<dyn Any>>,
689}
690
691impl ObservedIds {
692 fn new() -> Self {
693 ObservedIds::Small(SmallVec::new())
694 }
695
696 fn insert(&mut self, state: &dyn StateObject) {
697 let id = state.object_id().as_usize();
698 match self {
699 ObservedIds::Small(small) => {
700 if small.iter().any(|observed| observed.id == id) {
701 return;
702 }
703 if small.len() < MAX_OBSERVED_STATES {
704 small.push(ObservedState {
705 id,
706 _lease: state.observation_lease(),
707 });
708 } else {
709 let mut large =
710 HashMap::with_capacity_and_hasher(small.len() + 1, Default::default());
711 for observed in small.drain(..) {
712 large.insert(observed.id, observed._lease);
713 }
714 large.insert(id, state.observation_lease());
715 *self = ObservedIds::Large(large);
716 }
717 }
718 ObservedIds::Large(large) => {
719 large.entry(id).or_insert_with(|| state.observation_lease());
720 }
721 }
722 }
723
724 fn is_empty(&self) -> bool {
725 match self {
726 ObservedIds::Small(small) => small.is_empty(),
727 ObservedIds::Large(large) => large.is_empty(),
728 }
729 }
730
731 fn len(&self) -> usize {
732 match self {
733 ObservedIds::Small(small) => small.len(),
734 ObservedIds::Large(large) => large.len(),
735 }
736 }
737
738 fn capacity(&self) -> usize {
739 match self {
740 ObservedIds::Small(small) => small.capacity(),
741 ObservedIds::Large(large) => large.capacity(),
742 }
743 }
744
745 fn iter(&self) -> impl Iterator<Item = StateObjectId> + '_ {
746 let (small, large) = match self {
747 ObservedIds::Small(small) => (Some(small.as_slice()), None),
748 ObservedIds::Large(large) => (None, Some(large)),
749 };
750 small
751 .into_iter()
752 .flatten()
753 .map(|observed| observed.id)
754 .chain(large.into_iter().flat_map(|states| states.keys().copied()))
755 }
756}
757
758const MAX_OBSERVED_STATES: usize = 8;
759
760enum ScopeStorage {
761 Owned(Box<dyn Any>),
762 RecomposeScope {
763 id: ScopeId,
764 weak: Weak<RecomposeScopeInner>,
765 },
766}
767
768struct ScopeEntry {
769 id: usize,
770 scope: ScopeStorage,
771 on_changed: Rc<dyn ScopeChangedCallback>,
772 observed: ObservedIds,
773 last_seen_version: u64,
774}
775
776impl ScopeEntry {
777 fn new<T>(id: usize, scope: T, on_changed: Rc<dyn ScopeChangedCallback>) -> Self
778 where
779 T: Any + 'static,
780 {
781 Self {
782 id,
783 scope: ScopeStorage::from_value(scope),
784 on_changed,
785 observed: ObservedIds::new(),
786 last_seen_version: u64::MAX,
787 }
788 }
789
790 fn callback_reusing<F: Fn(&dyn Any) + 'static>(
791 &mut self,
792 callback: F,
793 ) -> Rc<dyn ScopeChangedCallback> {
794 if let Some(stored) = Rc::get_mut(&mut self.on_changed)
795 .and_then(|stored| (stored as &mut dyn Any).downcast_mut::<F>())
796 {
797 *stored = callback;
798 Rc::clone(&self.on_changed)
799 } else {
800 Rc::new(callback)
801 }
802 }
803
804 fn update<T>(&mut self, new_scope: T, on_changed: Rc<dyn ScopeChangedCallback>)
805 where
806 T: Any + 'static,
807 {
808 self.update_scope(new_scope);
809 self.on_changed = on_changed;
810 }
811
812 fn update_scope<T>(&mut self, new_scope: T)
813 where
814 T: Any + 'static,
815 {
816 if let ScopeStorage::Owned(stored) = &mut self.scope
817 && let Some(stored) = stored.downcast_mut::<T>()
818 {
819 *stored = new_scope;
820 } else {
821 self.scope = ScopeStorage::from_value(new_scope);
822 }
823 }
824
825 fn matches_scope<T>(&self, scope: &T) -> bool
826 where
827 T: Any + Eq + 'static,
828 {
829 if let Some(scope) = (scope as &dyn Any).downcast_ref::<RecomposeScope>() {
830 return matches!(
831 &self.scope,
832 ScopeStorage::RecomposeScope { id, .. } if *id == scope.id()
833 );
834 }
835
836 match &self.scope {
837 ScopeStorage::Owned(stored) => stored
838 .downcast_ref::<T>()
839 .is_some_and(|stored| stored == scope),
840 ScopeStorage::RecomposeScope { .. } => false,
841 }
842 }
843
844 fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
845 match &self.scope {
846 ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
847 ScopeStorage::RecomposeScope { weak, .. } => weak
848 .upgrade()
849 .is_none_or(|inner| predicate(&RecomposeScope { inner })),
850 }
851 }
852
853 fn should_retain(&self) -> bool {
854 match &self.scope {
855 ScopeStorage::Owned(_) => true,
856 ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
857 }
858 }
859
860 fn notify(&self) {
861 match &self.scope {
862 ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
863 ScopeStorage::RecomposeScope { weak, .. } => {
864 if let Some(inner) = weak.upgrade() {
865 (self.on_changed)(&RecomposeScope { inner });
866 }
867 }
868 }
869 }
870}
871
872impl ScopeStorage {
873 fn from_value<T>(value: T) -> Self
874 where
875 T: Any + 'static,
876 {
877 let any = &value as &dyn Any;
878 if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
879 Self::RecomposeScope {
880 id: scope.id(),
881 weak: scope.downgrade(),
882 }
883 } else {
884 Self::Owned(Box::new(value))
885 }
886 }
887}
888
889#[cfg(test)]
890mod tests {
891 use std::cell::Cell;
892
893 use super::*;
894 use crate::{
895 snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
896 state::{NeverEqual, SnapshotMutableState},
897 };
898
899 fn reset_runtime() -> TestRuntimeGuard {
900 reset_runtime_for_tests()
901 }
902
903 #[derive(Clone, Eq, Hash, PartialEq)]
904 struct TestScope(&'static str);
905
906 #[test]
907 fn scope_update_reuses_storage_and_replaces_payload_and_callback() {
908 let first = Rc::new(String::from("first"));
909 let second = Rc::new(String::from("second"));
910 let delivered = Rc::new(RefCell::new(Vec::new()));
911 let mut entry = ScopeEntry::new(0, first.clone(), Rc::new(|_| panic!("stale callback")));
912 let ScopeStorage::Owned(stored) = &entry.scope else {
913 panic!("expected owned scope");
914 };
915 let address = stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>;
916 let received = delivered.clone();
917 entry.update(
918 second.clone(),
919 Rc::new(move |scope| {
920 received.borrow_mut().push(
921 scope
922 .downcast_ref::<Rc<String>>()
923 .unwrap()
924 .as_str()
925 .to_owned(),
926 );
927 }),
928 );
929 assert_eq!(Rc::strong_count(&first), 1);
930 assert_eq!(Rc::strong_count(&second), 2);
931 entry.notify();
932 assert_eq!(*delivered.borrow(), vec!["second"]);
933 let ScopeStorage::Owned(stored) = &entry.scope else {
934 panic!("expected owned scope");
935 };
936 assert_eq!(
937 stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>,
938 address
939 );
940 drop(entry);
941 assert_eq!(Rc::strong_count(&second), 1);
942 }
943
944 #[test]
945 fn reobservation_refreshes_captures_and_preserves_shared_callbacks() {
946 let _guard = reset_runtime();
947 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
948 let delivered = Rc::new(RefCell::new(Vec::new()));
949 let callback = |generation| {
950 let delivered = delivered.clone();
951 move |scope: &TestScope| delivered.borrow_mut().push((generation, scope.0))
952 };
953 let scope = TestScope("callback");
954 let observer = SnapshotStateObserver::new(|callback| callback());
955 let read = || {
956 let _ = state.get();
957 };
958 observer.observe_reads(scope.clone(), callback(1), read);
959 let entry = observer.inner.find_scope_entry(&scope).unwrap();
960 let held = entry.borrow().on_changed.clone();
961 observer.observe_reads(scope.clone(), callback(2), read);
962 held(&scope);
963 entry.borrow().notify();
964 drop(held);
965
966 let allocation = Rc::as_ptr(&entry.borrow().on_changed);
967 observer.observe_reads(scope.clone(), callback(3), read);
968 assert!(std::ptr::addr_eq(
969 allocation,
970 Rc::as_ptr(&entry.borrow().on_changed)
971 ));
972 entry.borrow().notify();
973
974 let received = delivered.clone();
975 observer.observe_reads(
976 scope,
977 move |scope| received.borrow_mut().push((4, scope.0)),
978 read,
979 );
980 entry.borrow().notify();
981 assert_eq!(
982 *delivered.borrow(),
983 [
984 (1, "callback"),
985 (2, "callback"),
986 (3, "callback"),
987 (4, "callback")
988 ]
989 );
990 }
991
992 #[test]
993 fn reobservation_across_storage_thresholds_replaces_dependencies_and_callbacks() {
994 let _guard = reset_runtime();
995 let states: Vec<_> = (0..MAX_OBSERVED_STATES + 2)
996 .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
997 .collect();
998 let notifications = Rc::new(RefCell::new(Vec::new()));
999 let observer = SnapshotStateObserver::new(|callback| callback());
1000 observer.start();
1001 for (generation, count) in [MAX_OBSERVED_STATES, MAX_OBSERVED_STATES + 1, 2, 0]
1002 .into_iter()
1003 .enumerate()
1004 {
1005 observer.begin_frame();
1006 let recorded = notifications.clone();
1007 observer.observe_reads(
1008 TestScope("changing"),
1009 move |scope| {
1010 assert_eq!(scope.0, "changing");
1011 recorded.borrow_mut().push(generation);
1012 },
1013 || {
1014 for state in states.iter().take(count) {
1015 let _ = state.get();
1016 let _ = state.get();
1017 }
1018 },
1019 );
1020 notifications.borrow_mut().clear();
1021 for (index, state) in states.iter().enumerate() {
1022 let snapshot = take_mutable_snapshot(None, None);
1023 snapshot.enter(|| state.set(generation as i32));
1024 snapshot.apply().check();
1025 let expected = (index + 1).min(count);
1026 assert_eq!(
1027 *notifications.borrow(),
1028 vec![generation; expected],
1029 "count={count}, changed state={index}"
1030 );
1031 }
1032 }
1033 }
1034
1035 #[test]
1036 fn reobservation_preserves_notifications_when_dependencies_repeat_or_change() {
1037 let _guard = reset_runtime();
1038 let states: Vec<_> = (0..3)
1039 .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
1040 .collect();
1041 let notifications = Rc::new(RefCell::new(Vec::new()));
1042 let observer = SnapshotStateObserver::new(|callback| callback());
1043 observer.start();
1044 for (generation, indices) in [[0, 1], [0, 1], [1, 0], [1, 2], [1, 2]]
1045 .into_iter()
1046 .enumerate()
1047 {
1048 observer.begin_frame();
1049 let received = notifications.clone();
1050 observer.observe_reads(
1051 TestScope("repeated"),
1052 move |_| received.borrow_mut().push(generation),
1053 || {
1054 for index in indices {
1055 let _ = states[index].get();
1056 }
1057 },
1058 );
1059 for (index, state) in states.iter().enumerate() {
1060 notifications.borrow_mut().clear();
1061 let snapshot = take_mutable_snapshot(None, None);
1062 snapshot.enter(|| state.set(generation as i32));
1063 snapshot.apply().check();
1064 assert_eq!(
1065 *notifications.borrow(),
1066 if indices.contains(&index) {
1067 vec![generation]
1068 } else {
1069 vec![]
1070 },
1071 "generation={generation}, state={index}"
1072 );
1073 }
1074 }
1075 observer.clear(&TestScope("repeated"));
1076 notifications.borrow_mut().clear();
1077 for state in states {
1078 observer.notify_changes(&[state]);
1079 }
1080 assert!(notifications.borrow().is_empty());
1081 }
1082
1083 #[test]
1084 fn stateless_scope_can_start_observing_and_replace_its_callback_before_the_block() {
1085 let _guard = reset_runtime();
1086 let observer = SnapshotStateObserver::new(|callback| callback());
1087 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1088 let observed: Arc<dyn StateObject> = state.clone();
1089 let notifications = Rc::new(RefCell::new(Vec::new()));
1090 let discarded = notifications.clone();
1091 observer.observe_reads(
1092 TestScope("changing"),
1093 move |_| discarded.borrow_mut().push(0),
1094 || {},
1095 );
1096 assert_eq!(Rc::strong_count(¬ifications), 1);
1097 assert_eq!(observer.debug_stats().scopes_len, 0);
1098 for generation in 1..=2 {
1099 observer.begin_frame();
1100 let delivered = notifications.clone();
1101 observer.observe_reads(
1102 TestScope("changing"),
1103 move |_| delivered.borrow_mut().push(generation),
1104 || {
1105 observer.notify_changes(std::slice::from_ref(&observed));
1106 let _ = state.get();
1107 },
1108 );
1109 observer.notify_changes(std::slice::from_ref(&observed));
1110 }
1111 assert_eq!(*notifications.borrow(), vec![1, 2, 2]);
1112 observer.clear(&TestScope("changing"));
1113 assert_eq!(Rc::strong_count(¬ifications), 1);
1114 }
1115
1116 #[test]
1117 fn callback_captures_are_released_on_replacement_and_clear() {
1118 let _guard = reset_runtime();
1119 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1120 let observer = SnapshotStateObserver::new(|callback| callback());
1121 let owners = [Rc::new(Cell::new(0)), Rc::new(Cell::new(0))];
1122 for owner in &owners {
1123 let captured = owner.clone();
1124 observer.observe_reads(
1125 TestScope("owner"),
1126 move |_| captured.set(captured.get() + 1),
1127 || {
1128 let _ = state.get();
1129 },
1130 );
1131 assert_eq!(Rc::strong_count(owner), 2);
1132 }
1133 assert_eq!(Rc::strong_count(&owners[0]), 1);
1134 observer.clear(&TestScope("owner"));
1135 assert_eq!(Rc::strong_count(&owners[1]), 1);
1136 }
1137
1138 #[test]
1139 fn notifies_scope_when_state_changes() {
1140 let _guard = reset_runtime();
1141
1142 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1143 let triggered = Rc::new(Cell::new(0));
1144 let observer_trigger = triggered.clone();
1145
1146 let observer = SnapshotStateObserver::new(|callback| callback());
1147 observer.start();
1148
1149 let scope = TestScope("scope");
1150 observer.observe_reads(
1151 scope,
1152 move |_| {
1153 observer_trigger.set(observer_trigger.get() + 1);
1154 },
1155 || {
1156 let _ = state.get();
1157 },
1158 );
1159
1160 let snapshot = take_mutable_snapshot(None, None);
1161 snapshot.enter(|| {
1162 state.set(1);
1163 });
1164 snapshot.apply().check();
1165
1166 assert_eq!(triggered.get(), 1);
1167 observer.stop();
1168 }
1169
1170 #[test]
1171 fn clear_removes_scope_observation() {
1172 let _guard = reset_runtime();
1173
1174 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1175 let triggered = Rc::new(Cell::new(0));
1176 let observer_trigger = triggered.clone();
1177
1178 let observer = SnapshotStateObserver::new(|callback| callback());
1179 observer.start();
1180
1181 let scope = TestScope("scope");
1182 observer.observe_reads(
1183 scope.clone(),
1184 move |_| {
1185 observer_trigger.set(observer_trigger.get() + 1);
1186 },
1187 || {
1188 let _ = state.get();
1189 },
1190 );
1191
1192 observer.clear(&scope);
1193
1194 let snapshot = take_mutable_snapshot(None, None);
1195 snapshot.enter(|| {
1196 state.set(1);
1197 });
1198 snapshot.apply().check();
1199
1200 assert_eq!(triggered.get(), 0);
1201 observer.stop();
1202 }
1203
1204 #[test]
1205 fn repeated_owned_scope_observations_reuse_the_same_entry() {
1206 let _guard = reset_runtime();
1207
1208 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1209 let observer = SnapshotStateObserver::new(|callback| callback());
1210 let scope = TestScope("scope");
1211
1212 observer.observe_reads(
1213 scope.clone(),
1214 |_| {},
1215 || {
1216 let _ = state.get();
1217 },
1218 );
1219 observer.observe_reads(
1220 scope,
1221 |_| {},
1222 || {
1223 let _ = state.get();
1224 },
1225 );
1226
1227 let stats = observer.debug_stats();
1228 assert_eq!(stats.scopes_len, 1);
1229 assert_eq!(stats.fast_scopes_len, 0);
1230 }
1231
1232 #[test]
1233 fn with_no_observations_skips_reads() {
1234 let _guard = reset_runtime();
1235
1236 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1237 let triggered = Rc::new(Cell::new(0));
1238 let observer_trigger = triggered.clone();
1239
1240 let observer = SnapshotStateObserver::new(|callback| callback());
1241 observer.start();
1242
1243 let scope = TestScope("scope");
1244 observer.observe_reads(
1245 scope,
1246 move |_| {
1247 observer_trigger.set(observer_trigger.get() + 1);
1248 },
1249 || {
1250 observer.with_no_observations(|| {
1251 let _ = state.get();
1252 });
1253 },
1254 );
1255
1256 let snapshot = take_mutable_snapshot(None, None);
1257 snapshot.enter(|| {
1258 state.set(1);
1259 });
1260 snapshot.apply().check();
1261
1262 assert_eq!(triggered.get(), 0);
1263 observer.stop();
1264 }
1265
1266 #[test]
1267 fn recycled_observation_refreshes_snapshot_state() {
1268 let _guard = reset_runtime();
1269 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1270 let observer = SnapshotStateObserver::new(|callback| callback());
1271 let mut allocation = None;
1272 for value in 1..=3 {
1273 let parent = take_mutable_snapshot(None, None);
1274 parent.enter(|| {
1275 state.set(value);
1276 let expected = crate::snapshot_v2::current_snapshot().unwrap();
1277 observer.inner.run_with_read_observer(|| {
1278 let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1279 crate::snapshot_v2::current_snapshot().unwrap()
1280 else {
1281 panic!("expected an observation snapshot");
1282 };
1283 assert_eq!(current.snapshot_id(), expected.snapshot_id());
1284 assert_eq!(current.invalid(), expected.invalid());
1285 assert!(!current.is_disposed());
1286 assert!(!current.has_pending_changes());
1287 assert_eq!(state.get(), value);
1288 let address = Arc::as_ptr(¤t) as usize;
1289 assert_eq!(*allocation.get_or_insert(address), address);
1290 });
1291 });
1292 parent.apply().check();
1293 }
1294 }
1295
1296 #[test]
1297 fn recycled_observation_preserves_escaped_snapshots() {
1298 let _guard = reset_runtime();
1299 let observer = SnapshotStateObserver::new(|callback| callback());
1300 let escaped = observer
1301 .inner
1302 .run_with_read_observer(|| crate::snapshot_v2::current_snapshot().unwrap());
1303 let id = escaped.snapshot_id();
1304 observer.inner.run_with_read_observer(|| {
1305 let current = crate::snapshot_v2::current_snapshot().unwrap();
1306 let crate::snapshot_v2::AnySnapshot::TransparentMutable(escaped) = &escaped else {
1307 panic!("expected an observation snapshot");
1308 };
1309 assert!(!current.is_same_transparent(escaped));
1310 assert_eq!(escaped.snapshot_id(), id);
1311 assert!(escaped.is_disposed());
1312 });
1313 let weak = observer.inner.run_with_read_observer(|| {
1314 let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1315 crate::snapshot_v2::current_snapshot().unwrap()
1316 else {
1317 panic!("expected an observation snapshot");
1318 };
1319 Arc::downgrade(¤t)
1320 });
1321 assert!(weak.upgrade().is_none());
1322 observer.inner.run_with_read_observer(|| {
1323 assert!(weak.upgrade().is_none());
1324 });
1325 }
1326
1327 #[test]
1328 fn recycled_observation_does_not_retain_written_state() {
1329 let _guard = reset_runtime();
1330 let observer = SnapshotStateObserver::new(|callback| callback());
1331 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1332 let owners = Arc::strong_count(&state);
1333 observer.inner.run_with_read_observer(|| {
1334 let current = crate::snapshot_v2::current_snapshot().unwrap();
1335 current.record_write(state.clone());
1336 });
1337 assert_eq!(Arc::strong_count(&state), owners);
1338 }
1339
1340 #[test]
1341 fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
1342 let _guard = reset_runtime();
1343
1344 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1345 let outer_state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1346 let outer_triggered = Rc::new(Cell::new(0));
1347 let inner_triggered = Rc::new(Cell::new(0));
1348
1349 let observer = SnapshotStateObserver::new(|callback| callback());
1350 observer.start();
1351
1352 let outer_scope = TestScope("outer");
1353 let inner_scope = TestScope("inner");
1354 observer.observe_reads(
1355 outer_scope,
1356 {
1357 let outer_triggered = Rc::clone(&outer_triggered);
1358 move |_| outer_triggered.set(outer_triggered.get() + 1)
1359 },
1360 || {
1361 let _ = outer_state.get();
1362 observer.observe_reads(
1363 inner_scope.clone(),
1364 {
1365 let inner_triggered = Rc::clone(&inner_triggered);
1366 move |_| inner_triggered.set(inner_triggered.get() + 1)
1367 },
1368 || {
1369 let _ = state.get();
1370 },
1371 );
1372 },
1373 );
1374
1375 let snapshot = take_mutable_snapshot(None, None);
1376 snapshot.enter(|| {
1377 state.set(1);
1378 });
1379 snapshot.apply().check();
1380
1381 assert_eq!(outer_triggered.get(), 0);
1382 assert_eq!(inner_triggered.get(), 1);
1383 let snapshot = take_mutable_snapshot(None, None);
1384 snapshot.enter(|| outer_state.set(1));
1385 snapshot.apply().check();
1386 assert_eq!(outer_triggered.get(), 1);
1387 assert_eq!(inner_triggered.get(), 1);
1388 observer.stop();
1389 }
1390
1391 #[test]
1392 fn unwound_observation_does_not_leak_reads_into_reused_storage() {
1393 let _guard = reset_runtime();
1394 let abandoned = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1395 let live = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1396 let triggered = Rc::new(Cell::new(0));
1397 let observer = SnapshotStateObserver::new(|callback| callback());
1398 observer.start();
1399
1400 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1401 observer.observe_reads(
1402 TestScope("abandoned"),
1403 |_| {},
1404 || {
1405 let _ = abandoned.get();
1406 panic!("abandon observation");
1407 },
1408 );
1409 }));
1410 assert!(result.is_err());
1411 observer.observe_reads(
1412 TestScope("live"),
1413 {
1414 let triggered = Rc::clone(&triggered);
1415 move |_| triggered.set(triggered.get() + 1)
1416 },
1417 || {
1418 let _ = live.get();
1419 },
1420 );
1421
1422 let snapshot = take_mutable_snapshot(None, None);
1423 snapshot.enter(|| abandoned.set(1));
1424 snapshot.apply().check();
1425 assert_eq!(triggered.get(), 0);
1426 let snapshot = take_mutable_snapshot(None, None);
1427 snapshot.enter(|| live.set(1));
1428 snapshot.apply().check();
1429 assert_eq!(triggered.get(), 1);
1430 observer.stop();
1431 }
1432
1433 #[test]
1434 fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1435 let _guard = reset_runtime();
1436
1437 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1438 let first_triggered = Rc::new(Cell::new(0));
1439 let second_triggered = Rc::new(Cell::new(0));
1440
1441 let observer = SnapshotStateObserver::new(|callback| callback());
1442 observer.start();
1443
1444 let first_scope = TestScope("first");
1445 let second_scope = TestScope("second");
1446 observer.observe_reads(
1447 first_scope.clone(),
1448 {
1449 let first_triggered = Rc::clone(&first_triggered);
1450 move |_| first_triggered.set(first_triggered.get() + 1)
1451 },
1452 || {
1453 let _ = state.get();
1454 },
1455 );
1456 observer.observe_reads(
1457 second_scope,
1458 {
1459 let second_triggered = Rc::clone(&second_triggered);
1460 move |_| second_triggered.set(second_triggered.get() + 1)
1461 },
1462 || {
1463 let _ = state.get();
1464 },
1465 );
1466
1467 observer.clear(&first_scope);
1468
1469 let snapshot = take_mutable_snapshot(None, None);
1470 snapshot.enter(|| {
1471 state.set(1);
1472 });
1473 snapshot.apply().check();
1474
1475 assert_eq!(first_triggered.get(), 0);
1476 assert_eq!(second_triggered.get(), 1);
1477 observer.stop();
1478 }
1479
1480 #[test]
1481 fn shared_state_notifies_scopes_in_registration_order() {
1482 let _guard = reset_runtime();
1483
1484 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1485 let notifications = Rc::new(RefCell::new(Vec::new()));
1486
1487 let observer = SnapshotStateObserver::new(|callback| callback());
1488 observer.start();
1489
1490 observer.observe_reads(
1491 TestScope("first"),
1492 {
1493 let notifications = Rc::clone(¬ifications);
1494 move |_| notifications.borrow_mut().push("first")
1495 },
1496 || {
1497 let _ = state.get();
1498 },
1499 );
1500 observer.observe_reads(
1501 TestScope("second"),
1502 {
1503 let notifications = Rc::clone(¬ifications);
1504 move |_| notifications.borrow_mut().push("second")
1505 },
1506 || {
1507 let _ = state.get();
1508 },
1509 );
1510
1511 let snapshot = take_mutable_snapshot(None, None);
1512 snapshot.enter(|| {
1513 state.set(1);
1514 });
1515 snapshot.apply().check();
1516
1517 assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1518 observer.stop();
1519 }
1520
1521 #[test]
1522 fn stateless_recompose_scope_does_not_retain_observer_entry() {
1523 let _guard = reset_runtime();
1524
1525 let observer = SnapshotStateObserver::new(|callback| callback());
1526 let runtime = crate::TestRuntime::new();
1527 let scope = RecomposeScope::new_for_test(runtime.handle());
1528
1529 observer.observe_reads(scope, |_| {}, || {});
1530
1531 let stats = observer.debug_stats();
1532 assert_eq!(stats.scopes_len, 0);
1533 assert_eq!(stats.fast_scopes_len, 0);
1534 assert_eq!(stats.stateless_scope_count, 0);
1535 }
1536
1537 #[test]
1538 fn scope_that_stops_reading_state_is_removed_immediately() {
1539 let _guard = reset_runtime();
1540
1541 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1542 let observer = SnapshotStateObserver::new(|callback| callback());
1543 let runtime = crate::TestRuntime::new();
1544 let scope = RecomposeScope::new_for_test(runtime.handle());
1545 let triggered = Rc::new(Cell::new(0));
1546 let observer_trigger = Rc::clone(&triggered);
1547
1548 observer.observe_reads(
1549 scope.clone(),
1550 move |_| observer_trigger.set(observer_trigger.get() + 1),
1551 || {
1552 let _ = state.get();
1553 },
1554 );
1555
1556 let after_stateful = observer.debug_stats();
1557 assert_eq!(after_stateful.scopes_len, 1);
1558 assert_eq!(after_stateful.fast_scopes_len, 1);
1559
1560 observer.observe_reads(scope, |_| {}, || {});
1561
1562 let after_stateless = observer.debug_stats();
1563 assert_eq!(after_stateless.scopes_len, 0);
1564 assert_eq!(after_stateless.fast_scopes_len, 0);
1565
1566 let snapshot = take_mutable_snapshot(None, None);
1567 snapshot.enter(|| {
1568 state.set(1);
1569 });
1570 snapshot.apply().check();
1571
1572 assert_eq!(triggered.get(), 0);
1573 }
1574
1575 #[test]
1576 fn begin_frame_prunes_dropped_recompose_scope_entries() {
1577 let _guard = reset_runtime();
1578
1579 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1580 let observer = SnapshotStateObserver::new(|callback| callback());
1581 let runtime = crate::TestRuntime::new();
1582 let scope = RecomposeScope::new_for_test(runtime.handle());
1583
1584 observer.observe_reads(
1585 scope.clone(),
1586 |_| {},
1587 || {
1588 let _ = state.get();
1589 },
1590 );
1591
1592 let before_prune = observer.debug_stats();
1593 assert_eq!(before_prune.scopes_len, 1);
1594 assert_eq!(before_prune.fast_scopes_len, 1);
1595
1596 drop(scope);
1597 observer.begin_frame();
1598
1599 let after_prune = observer.debug_stats();
1600 assert_eq!(after_prune.scopes_len, 0);
1601 assert_eq!(after_prune.fast_scopes_len, 0);
1602 }
1603}