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 .map(|stored| stored == scope)
840 .unwrap_or(false),
841 ScopeStorage::RecomposeScope { .. } => false,
842 }
843 }
844
845 fn matches_predicate(&self, predicate: &impl Fn(&dyn Any) -> bool) -> bool {
846 match &self.scope {
847 ScopeStorage::Owned(scope) => predicate(scope.as_ref()),
848 ScopeStorage::RecomposeScope { weak, .. } => weak
849 .upgrade()
850 .map(|inner| predicate(&RecomposeScope { inner }))
851 .unwrap_or(true),
852 }
853 }
854
855 fn should_retain(&self) -> bool {
856 match &self.scope {
857 ScopeStorage::Owned(_) => true,
858 ScopeStorage::RecomposeScope { weak, .. } => weak.upgrade().is_some(),
859 }
860 }
861
862 fn notify(&self) {
863 match &self.scope {
864 ScopeStorage::Owned(scope) => (self.on_changed)(scope.as_ref()),
865 ScopeStorage::RecomposeScope { weak, .. } => {
866 if let Some(inner) = weak.upgrade() {
867 (self.on_changed)(&RecomposeScope { inner });
868 }
869 }
870 }
871 }
872}
873
874impl ScopeStorage {
875 fn from_value<T>(value: T) -> Self
876 where
877 T: Any + 'static,
878 {
879 let any = &value as &dyn Any;
880 if let Some(scope) = any.downcast_ref::<RecomposeScope>() {
881 Self::RecomposeScope {
882 id: scope.id(),
883 weak: scope.downgrade(),
884 }
885 } else {
886 Self::Owned(Box::new(value))
887 }
888 }
889}
890
891#[cfg(test)]
892mod tests {
893 use std::cell::Cell;
894
895 use super::*;
896 use crate::{
897 snapshot_v2::{TestRuntimeGuard, reset_runtime_for_tests, take_mutable_snapshot},
898 state::{NeverEqual, SnapshotMutableState},
899 };
900
901 fn reset_runtime() -> TestRuntimeGuard {
902 reset_runtime_for_tests()
903 }
904
905 #[derive(Clone, Eq, Hash, PartialEq)]
906 struct TestScope(&'static str);
907
908 #[test]
909 fn scope_update_reuses_storage_and_replaces_payload_and_callback() {
910 let first = Rc::new(String::from("first"));
911 let second = Rc::new(String::from("second"));
912 let delivered = Rc::new(RefCell::new(Vec::new()));
913 let mut entry = ScopeEntry::new(0, first.clone(), Rc::new(|_| panic!("stale callback")));
914 let ScopeStorage::Owned(stored) = &entry.scope else {
915 panic!("expected owned scope");
916 };
917 let address = stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>;
918 let received = delivered.clone();
919 entry.update(
920 second.clone(),
921 Rc::new(move |scope| {
922 received.borrow_mut().push(
923 scope
924 .downcast_ref::<Rc<String>>()
925 .unwrap()
926 .as_str()
927 .to_owned(),
928 );
929 }),
930 );
931 assert_eq!(Rc::strong_count(&first), 1);
932 assert_eq!(Rc::strong_count(&second), 2);
933 entry.notify();
934 assert_eq!(*delivered.borrow(), vec!["second"]);
935 let ScopeStorage::Owned(stored) = &entry.scope else {
936 panic!("expected owned scope");
937 };
938 assert_eq!(
939 stored.downcast_ref::<Rc<String>>().unwrap() as *const Rc<String>,
940 address
941 );
942 drop(entry);
943 assert_eq!(Rc::strong_count(&second), 1);
944 }
945
946 #[test]
947 fn reobservation_refreshes_captures_and_preserves_shared_callbacks() {
948 let _guard = reset_runtime();
949 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
950 let delivered = Rc::new(RefCell::new(Vec::new()));
951 let callback = |generation| {
952 let delivered = delivered.clone();
953 move |scope: &TestScope| delivered.borrow_mut().push((generation, scope.0))
954 };
955 let scope = TestScope("callback");
956 let observer = SnapshotStateObserver::new(|callback| callback());
957 let read = || {
958 let _ = state.get();
959 };
960 observer.observe_reads(scope.clone(), callback(1), read);
961 let entry = observer.inner.find_scope_entry(&scope).unwrap();
962 let held = entry.borrow().on_changed.clone();
963 observer.observe_reads(scope.clone(), callback(2), read);
964 held(&scope);
965 entry.borrow().notify();
966 drop(held);
967
968 let allocation = Rc::as_ptr(&entry.borrow().on_changed);
969 observer.observe_reads(scope.clone(), callback(3), read);
970 assert!(std::ptr::addr_eq(
971 allocation,
972 Rc::as_ptr(&entry.borrow().on_changed)
973 ));
974 entry.borrow().notify();
975
976 let received = delivered.clone();
977 observer.observe_reads(
978 scope,
979 move |scope| received.borrow_mut().push((4, scope.0)),
980 read,
981 );
982 entry.borrow().notify();
983 assert_eq!(
984 *delivered.borrow(),
985 [
986 (1, "callback"),
987 (2, "callback"),
988 (3, "callback"),
989 (4, "callback")
990 ]
991 );
992 }
993
994 #[test]
995 fn reobservation_across_storage_thresholds_replaces_dependencies_and_callbacks() {
996 let _guard = reset_runtime();
997 let states: Vec<_> = (0..MAX_OBSERVED_STATES + 2)
998 .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
999 .collect();
1000 let notifications = Rc::new(RefCell::new(Vec::new()));
1001 let observer = SnapshotStateObserver::new(|callback| callback());
1002 observer.start();
1003 for (generation, count) in [MAX_OBSERVED_STATES, MAX_OBSERVED_STATES + 1, 2, 0]
1004 .into_iter()
1005 .enumerate()
1006 {
1007 observer.begin_frame();
1008 let recorded = notifications.clone();
1009 observer.observe_reads(
1010 TestScope("changing"),
1011 move |scope| {
1012 assert_eq!(scope.0, "changing");
1013 recorded.borrow_mut().push(generation);
1014 },
1015 || {
1016 for state in states.iter().take(count) {
1017 let _ = state.get();
1018 let _ = state.get();
1019 }
1020 },
1021 );
1022 notifications.borrow_mut().clear();
1023 for (index, state) in states.iter().enumerate() {
1024 let snapshot = take_mutable_snapshot(None, None);
1025 snapshot.enter(|| state.set(generation as i32));
1026 snapshot.apply().check();
1027 let expected = (index + 1).min(count);
1028 assert_eq!(
1029 *notifications.borrow(),
1030 vec![generation; expected],
1031 "count={count}, changed state={index}"
1032 );
1033 }
1034 }
1035 }
1036
1037 #[test]
1038 fn reobservation_preserves_notifications_when_dependencies_repeat_or_change() {
1039 let _guard = reset_runtime();
1040 let states: Vec<_> = (0..3)
1041 .map(|_| SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual)))
1042 .collect();
1043 let notifications = Rc::new(RefCell::new(Vec::new()));
1044 let observer = SnapshotStateObserver::new(|callback| callback());
1045 observer.start();
1046 for (generation, indices) in [[0, 1], [0, 1], [1, 0], [1, 2], [1, 2]]
1047 .into_iter()
1048 .enumerate()
1049 {
1050 observer.begin_frame();
1051 let received = notifications.clone();
1052 observer.observe_reads(
1053 TestScope("repeated"),
1054 move |_| received.borrow_mut().push(generation),
1055 || {
1056 for index in indices {
1057 let _ = states[index].get();
1058 }
1059 },
1060 );
1061 for (index, state) in states.iter().enumerate() {
1062 notifications.borrow_mut().clear();
1063 let snapshot = take_mutable_snapshot(None, None);
1064 snapshot.enter(|| state.set(generation as i32));
1065 snapshot.apply().check();
1066 assert_eq!(
1067 *notifications.borrow(),
1068 if indices.contains(&index) {
1069 vec![generation]
1070 } else {
1071 vec![]
1072 },
1073 "generation={generation}, state={index}"
1074 );
1075 }
1076 }
1077 observer.clear(&TestScope("repeated"));
1078 notifications.borrow_mut().clear();
1079 for state in states {
1080 observer.notify_changes(&[state]);
1081 }
1082 assert!(notifications.borrow().is_empty());
1083 }
1084
1085 #[test]
1086 fn stateless_scope_can_start_observing_and_replace_its_callback_before_the_block() {
1087 let _guard = reset_runtime();
1088 let observer = SnapshotStateObserver::new(|callback| callback());
1089 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1090 let observed: Arc<dyn StateObject> = state.clone();
1091 let notifications = Rc::new(RefCell::new(Vec::new()));
1092 let discarded = notifications.clone();
1093 observer.observe_reads(
1094 TestScope("changing"),
1095 move |_| discarded.borrow_mut().push(0),
1096 || {},
1097 );
1098 assert_eq!(Rc::strong_count(¬ifications), 1);
1099 assert_eq!(observer.debug_stats().scopes_len, 0);
1100 for generation in 1..=2 {
1101 observer.begin_frame();
1102 let delivered = notifications.clone();
1103 observer.observe_reads(
1104 TestScope("changing"),
1105 move |_| delivered.borrow_mut().push(generation),
1106 || {
1107 observer.notify_changes(std::slice::from_ref(&observed));
1108 let _ = state.get();
1109 },
1110 );
1111 observer.notify_changes(std::slice::from_ref(&observed));
1112 }
1113 assert_eq!(*notifications.borrow(), vec![1, 2, 2]);
1114 observer.clear(&TestScope("changing"));
1115 assert_eq!(Rc::strong_count(¬ifications), 1);
1116 }
1117
1118 #[test]
1119 fn callback_captures_are_released_on_replacement_and_clear() {
1120 let _guard = reset_runtime();
1121 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1122 let observer = SnapshotStateObserver::new(|callback| callback());
1123 let owners = [Rc::new(Cell::new(0)), Rc::new(Cell::new(0))];
1124 for owner in &owners {
1125 let captured = owner.clone();
1126 observer.observe_reads(
1127 TestScope("owner"),
1128 move |_| captured.set(captured.get() + 1),
1129 || {
1130 let _ = state.get();
1131 },
1132 );
1133 assert_eq!(Rc::strong_count(owner), 2);
1134 }
1135 assert_eq!(Rc::strong_count(&owners[0]), 1);
1136 observer.clear(&TestScope("owner"));
1137 assert_eq!(Rc::strong_count(&owners[1]), 1);
1138 }
1139
1140 #[test]
1141 fn notifies_scope_when_state_changes() {
1142 let _guard = reset_runtime();
1143
1144 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1145 let triggered = Rc::new(Cell::new(0));
1146 let observer_trigger = triggered.clone();
1147
1148 let observer = SnapshotStateObserver::new(|callback| callback());
1149 observer.start();
1150
1151 let scope = TestScope("scope");
1152 observer.observe_reads(
1153 scope.clone(),
1154 move |_| {
1155 observer_trigger.set(observer_trigger.get() + 1);
1156 },
1157 || {
1158 let _ = state.get();
1159 },
1160 );
1161
1162 let snapshot = take_mutable_snapshot(None, None);
1163 snapshot.enter(|| {
1164 state.set(1);
1165 });
1166 snapshot.apply().check();
1167
1168 assert_eq!(triggered.get(), 1);
1169 observer.stop();
1170 }
1171
1172 #[test]
1173 fn clear_removes_scope_observation() {
1174 let _guard = reset_runtime();
1175
1176 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1177 let triggered = Rc::new(Cell::new(0));
1178 let observer_trigger = triggered.clone();
1179
1180 let observer = SnapshotStateObserver::new(|callback| callback());
1181 observer.start();
1182
1183 let scope = TestScope("scope");
1184 observer.observe_reads(
1185 scope.clone(),
1186 move |_| {
1187 observer_trigger.set(observer_trigger.get() + 1);
1188 },
1189 || {
1190 let _ = state.get();
1191 },
1192 );
1193
1194 observer.clear(&scope);
1195
1196 let snapshot = take_mutable_snapshot(None, None);
1197 snapshot.enter(|| {
1198 state.set(1);
1199 });
1200 snapshot.apply().check();
1201
1202 assert_eq!(triggered.get(), 0);
1203 observer.stop();
1204 }
1205
1206 #[test]
1207 fn repeated_owned_scope_observations_reuse_the_same_entry() {
1208 let _guard = reset_runtime();
1209
1210 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1211 let observer = SnapshotStateObserver::new(|callback| callback());
1212 let scope = TestScope("scope");
1213
1214 observer.observe_reads(
1215 scope.clone(),
1216 |_| {},
1217 || {
1218 let _ = state.get();
1219 },
1220 );
1221 observer.observe_reads(
1222 scope,
1223 |_| {},
1224 || {
1225 let _ = state.get();
1226 },
1227 );
1228
1229 let stats = observer.debug_stats();
1230 assert_eq!(stats.scopes_len, 1);
1231 assert_eq!(stats.fast_scopes_len, 0);
1232 }
1233
1234 #[test]
1235 fn with_no_observations_skips_reads() {
1236 let _guard = reset_runtime();
1237
1238 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1239 let triggered = Rc::new(Cell::new(0));
1240 let observer_trigger = triggered.clone();
1241
1242 let observer = SnapshotStateObserver::new(|callback| callback());
1243 observer.start();
1244
1245 let scope = TestScope("scope");
1246 observer.observe_reads(
1247 scope.clone(),
1248 move |_| {
1249 observer_trigger.set(observer_trigger.get() + 1);
1250 },
1251 || {
1252 observer.with_no_observations(|| {
1253 let _ = state.get();
1254 });
1255 },
1256 );
1257
1258 let snapshot = take_mutable_snapshot(None, None);
1259 snapshot.enter(|| {
1260 state.set(1);
1261 });
1262 snapshot.apply().check();
1263
1264 assert_eq!(triggered.get(), 0);
1265 observer.stop();
1266 }
1267
1268 #[test]
1269 fn recycled_observation_refreshes_snapshot_state() {
1270 let _guard = reset_runtime();
1271 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1272 let observer = SnapshotStateObserver::new(|callback| callback());
1273 let mut allocation = None;
1274 for value in 1..=3 {
1275 let parent = take_mutable_snapshot(None, None);
1276 parent.enter(|| {
1277 state.set(value);
1278 let expected = crate::snapshot_v2::current_snapshot().unwrap();
1279 observer.inner.run_with_read_observer(|| {
1280 let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1281 crate::snapshot_v2::current_snapshot().unwrap()
1282 else {
1283 panic!("expected an observation snapshot");
1284 };
1285 assert_eq!(current.snapshot_id(), expected.snapshot_id());
1286 assert_eq!(current.invalid(), expected.invalid());
1287 assert!(!current.is_disposed());
1288 assert!(!current.has_pending_changes());
1289 assert_eq!(state.get(), value);
1290 let address = Arc::as_ptr(¤t) as usize;
1291 assert_eq!(*allocation.get_or_insert(address), address);
1292 });
1293 });
1294 parent.apply().check();
1295 }
1296 }
1297
1298 #[test]
1299 fn recycled_observation_preserves_escaped_snapshots() {
1300 let _guard = reset_runtime();
1301 let observer = SnapshotStateObserver::new(|callback| callback());
1302 let escaped = observer
1303 .inner
1304 .run_with_read_observer(|| crate::snapshot_v2::current_snapshot().unwrap());
1305 let id = escaped.snapshot_id();
1306 observer.inner.run_with_read_observer(|| {
1307 let current = crate::snapshot_v2::current_snapshot().unwrap();
1308 let crate::snapshot_v2::AnySnapshot::TransparentMutable(escaped) = &escaped else {
1309 panic!("expected an observation snapshot");
1310 };
1311 assert!(!current.is_same_transparent(escaped));
1312 assert_eq!(escaped.snapshot_id(), id);
1313 assert!(escaped.is_disposed());
1314 });
1315 let weak = observer.inner.run_with_read_observer(|| {
1316 let crate::snapshot_v2::AnySnapshot::TransparentMutable(current) =
1317 crate::snapshot_v2::current_snapshot().unwrap()
1318 else {
1319 panic!("expected an observation snapshot");
1320 };
1321 Arc::downgrade(¤t)
1322 });
1323 assert!(weak.upgrade().is_none());
1324 observer.inner.run_with_read_observer(|| {
1325 assert!(weak.upgrade().is_none());
1326 });
1327 }
1328
1329 #[test]
1330 fn recycled_observation_does_not_retain_written_state() {
1331 let _guard = reset_runtime();
1332 let observer = SnapshotStateObserver::new(|callback| callback());
1333 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1334 let owners = Arc::strong_count(&state);
1335 observer.inner.run_with_read_observer(|| {
1336 let current = crate::snapshot_v2::current_snapshot().unwrap();
1337 current.record_write(state.clone());
1338 });
1339 assert_eq!(Arc::strong_count(&state), owners);
1340 }
1341
1342 #[test]
1343 fn nested_observe_reads_attributes_state_to_innermost_scope_only() {
1344 let _guard = reset_runtime();
1345
1346 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1347 let outer_state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1348 let outer_triggered = Rc::new(Cell::new(0));
1349 let inner_triggered = Rc::new(Cell::new(0));
1350
1351 let observer = SnapshotStateObserver::new(|callback| callback());
1352 observer.start();
1353
1354 let outer_scope = TestScope("outer");
1355 let inner_scope = TestScope("inner");
1356 observer.observe_reads(
1357 outer_scope.clone(),
1358 {
1359 let outer_triggered = Rc::clone(&outer_triggered);
1360 move |_| outer_triggered.set(outer_triggered.get() + 1)
1361 },
1362 || {
1363 let _ = outer_state.get();
1364 observer.observe_reads(
1365 inner_scope.clone(),
1366 {
1367 let inner_triggered = Rc::clone(&inner_triggered);
1368 move |_| inner_triggered.set(inner_triggered.get() + 1)
1369 },
1370 || {
1371 let _ = state.get();
1372 },
1373 );
1374 },
1375 );
1376
1377 let snapshot = take_mutable_snapshot(None, None);
1378 snapshot.enter(|| {
1379 state.set(1);
1380 });
1381 snapshot.apply().check();
1382
1383 assert_eq!(outer_triggered.get(), 0);
1384 assert_eq!(inner_triggered.get(), 1);
1385 let snapshot = take_mutable_snapshot(None, None);
1386 snapshot.enter(|| outer_state.set(1));
1387 snapshot.apply().check();
1388 assert_eq!(outer_triggered.get(), 1);
1389 assert_eq!(inner_triggered.get(), 1);
1390 observer.stop();
1391 }
1392
1393 #[test]
1394 fn unwound_observation_does_not_leak_reads_into_reused_storage() {
1395 let _guard = reset_runtime();
1396 let abandoned = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1397 let live = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1398 let triggered = Rc::new(Cell::new(0));
1399 let observer = SnapshotStateObserver::new(|callback| callback());
1400 observer.start();
1401
1402 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1403 observer.observe_reads(
1404 TestScope("abandoned"),
1405 |_| {},
1406 || {
1407 let _ = abandoned.get();
1408 panic!("abandon observation");
1409 },
1410 );
1411 }));
1412 assert!(result.is_err());
1413 observer.observe_reads(
1414 TestScope("live"),
1415 {
1416 let triggered = Rc::clone(&triggered);
1417 move |_| triggered.set(triggered.get() + 1)
1418 },
1419 || {
1420 let _ = live.get();
1421 },
1422 );
1423
1424 let snapshot = take_mutable_snapshot(None, None);
1425 snapshot.enter(|| abandoned.set(1));
1426 snapshot.apply().check();
1427 assert_eq!(triggered.get(), 0);
1428 let snapshot = take_mutable_snapshot(None, None);
1429 snapshot.enter(|| live.set(1));
1430 snapshot.apply().check();
1431 assert_eq!(triggered.get(), 1);
1432 observer.stop();
1433 }
1434
1435 #[test]
1436 fn clearing_one_scope_keeps_shared_state_registered_for_other_scope() {
1437 let _guard = reset_runtime();
1438
1439 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1440 let first_triggered = Rc::new(Cell::new(0));
1441 let second_triggered = Rc::new(Cell::new(0));
1442
1443 let observer = SnapshotStateObserver::new(|callback| callback());
1444 observer.start();
1445
1446 let first_scope = TestScope("first");
1447 let second_scope = TestScope("second");
1448 observer.observe_reads(
1449 first_scope.clone(),
1450 {
1451 let first_triggered = Rc::clone(&first_triggered);
1452 move |_| first_triggered.set(first_triggered.get() + 1)
1453 },
1454 || {
1455 let _ = state.get();
1456 },
1457 );
1458 observer.observe_reads(
1459 second_scope.clone(),
1460 {
1461 let second_triggered = Rc::clone(&second_triggered);
1462 move |_| second_triggered.set(second_triggered.get() + 1)
1463 },
1464 || {
1465 let _ = state.get();
1466 },
1467 );
1468
1469 observer.clear(&first_scope);
1470
1471 let snapshot = take_mutable_snapshot(None, None);
1472 snapshot.enter(|| {
1473 state.set(1);
1474 });
1475 snapshot.apply().check();
1476
1477 assert_eq!(first_triggered.get(), 0);
1478 assert_eq!(second_triggered.get(), 1);
1479 observer.stop();
1480 }
1481
1482 #[test]
1483 fn shared_state_notifies_scopes_in_registration_order() {
1484 let _guard = reset_runtime();
1485
1486 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1487 let notifications = Rc::new(RefCell::new(Vec::new()));
1488
1489 let observer = SnapshotStateObserver::new(|callback| callback());
1490 observer.start();
1491
1492 observer.observe_reads(
1493 TestScope("first"),
1494 {
1495 let notifications = Rc::clone(¬ifications);
1496 move |_| notifications.borrow_mut().push("first")
1497 },
1498 || {
1499 let _ = state.get();
1500 },
1501 );
1502 observer.observe_reads(
1503 TestScope("second"),
1504 {
1505 let notifications = Rc::clone(¬ifications);
1506 move |_| notifications.borrow_mut().push("second")
1507 },
1508 || {
1509 let _ = state.get();
1510 },
1511 );
1512
1513 let snapshot = take_mutable_snapshot(None, None);
1514 snapshot.enter(|| {
1515 state.set(1);
1516 });
1517 snapshot.apply().check();
1518
1519 assert_eq!(notifications.borrow().as_slice(), &["first", "second"]);
1520 observer.stop();
1521 }
1522
1523 #[test]
1524 fn stateless_recompose_scope_does_not_retain_observer_entry() {
1525 let _guard = reset_runtime();
1526
1527 let observer = SnapshotStateObserver::new(|callback| callback());
1528 let runtime = crate::TestRuntime::new();
1529 let scope = RecomposeScope::new_for_test(runtime.handle());
1530
1531 observer.observe_reads(scope, |_| {}, || {});
1532
1533 let stats = observer.debug_stats();
1534 assert_eq!(stats.scopes_len, 0);
1535 assert_eq!(stats.fast_scopes_len, 0);
1536 assert_eq!(stats.stateless_scope_count, 0);
1537 }
1538
1539 #[test]
1540 fn scope_that_stops_reading_state_is_removed_immediately() {
1541 let _guard = reset_runtime();
1542
1543 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1544 let observer = SnapshotStateObserver::new(|callback| callback());
1545 let runtime = crate::TestRuntime::new();
1546 let scope = RecomposeScope::new_for_test(runtime.handle());
1547 let triggered = Rc::new(Cell::new(0));
1548 let observer_trigger = Rc::clone(&triggered);
1549
1550 observer.observe_reads(
1551 scope.clone(),
1552 move |_| observer_trigger.set(observer_trigger.get() + 1),
1553 || {
1554 let _ = state.get();
1555 },
1556 );
1557
1558 let after_stateful = observer.debug_stats();
1559 assert_eq!(after_stateful.scopes_len, 1);
1560 assert_eq!(after_stateful.fast_scopes_len, 1);
1561
1562 observer.observe_reads(scope, |_| {}, || {});
1563
1564 let after_stateless = observer.debug_stats();
1565 assert_eq!(after_stateless.scopes_len, 0);
1566 assert_eq!(after_stateless.fast_scopes_len, 0);
1567
1568 let snapshot = take_mutable_snapshot(None, None);
1569 snapshot.enter(|| {
1570 state.set(1);
1571 });
1572 snapshot.apply().check();
1573
1574 assert_eq!(triggered.get(), 0);
1575 }
1576
1577 #[test]
1578 fn begin_frame_prunes_dropped_recompose_scope_entries() {
1579 let _guard = reset_runtime();
1580
1581 let state = SnapshotMutableState::new_in_arc(0, Arc::new(NeverEqual));
1582 let observer = SnapshotStateObserver::new(|callback| callback());
1583 let runtime = crate::TestRuntime::new();
1584 let scope = RecomposeScope::new_for_test(runtime.handle());
1585
1586 observer.observe_reads(
1587 scope.clone(),
1588 |_| {},
1589 || {
1590 let _ = state.get();
1591 },
1592 );
1593
1594 let before_prune = observer.debug_stats();
1595 assert_eq!(before_prune.scopes_len, 1);
1596 assert_eq!(before_prune.fast_scopes_len, 1);
1597
1598 drop(scope);
1599 observer.begin_frame();
1600
1601 let after_prune = observer.debug_stats();
1602 assert_eq!(after_prune.scopes_len, 0);
1603 assert_eq!(after_prune.fast_scopes_len, 0);
1604 }
1605}