1use std::cell::RefCell;
2
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use crate::View;
6
7struct ScopeFrame {
8 key: String,
9 children: FxHashSet<String>,
10 locals: FxHashMap<usize, u64>,
11 animations: FxHashSet<String>,
12}
13
14thread_local! {
15 static CURRENT_SCOPE_STACK: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
16 static CURRENT_SCOPE_KEY: RefCell<Option<String>> = const { RefCell::new(None) };
17 static SCOPE_SIGNAL_DEPS: RefCell<FxHashMap<usize, FxHashSet<String>>> =
18 RefCell::new(FxHashMap::default());
19 static SCOPE_TO_SIGNALS: RefCell<FxHashMap<String, FxHashSet<usize>>> =
20 RefCell::new(FxHashMap::default());
21 static SCOPE_DIRTY_EPOCHS: RefCell<FxHashMap<String, u64>> =
22 RefCell::new(FxHashMap::default());
23 static SCOPE_PENDING_DIRTY: RefCell<FxHashSet<String>> =
24 RefCell::new(FxHashSet::default());
25 static SCOPE_CACHE_CHILDREN: RefCell<FxHashMap<String, FxHashSet<String>>> =
26 RefCell::new(FxHashMap::default());
27 static SCOPE_CACHE_LOCALS: RefCell<FxHashMap<String, FxHashMap<usize, u64>>> =
28 RefCell::new(FxHashMap::default());
29 static SCOPE_CACHE_ANIMATIONS: RefCell<FxHashMap<String, FxHashSet<String>>> =
30 RefCell::new(FxHashMap::default());
31 static SCOPE_LAST_CHILDREN: RefCell<FxHashMap<String, FxHashSet<String>>> =
32 RefCell::new(FxHashMap::default());
33 static SCOPE_LAST_LOCALS: RefCell<FxHashMap<String, FxHashMap<usize, u64>>> =
34 RefCell::new(FxHashMap::default());
35 static SCOPE_LAST_ANIMATIONS: RefCell<FxHashMap<String, FxHashSet<String>>> =
36 RefCell::new(FxHashMap::default());
37 static SCOPE_RUN_EPOCHS: RefCell<FxHashMap<String, u64>> =
38 RefCell::new(FxHashMap::default());
39 static SCOPE_FRAMES: RefCell<Vec<ScopeFrame>> = const { RefCell::new(Vec::new()) };
40}
41
42fn current_scope_stack() -> Vec<String> {
43 let stack = CURRENT_SCOPE_STACK.with(|stack| stack.borrow().clone());
44 if stack.is_empty() {
45 CURRENT_SCOPE_KEY
46 .with(|key| key.borrow().clone())
47 .into_iter()
48 .collect()
49 } else {
50 stack
51 }
52}
53
54fn for_each_current_scope(mut f: impl FnMut(&str)) {
55 let has_stack = CURRENT_SCOPE_STACK.with(|stack| !stack.borrow().is_empty());
56 if has_stack {
57 CURRENT_SCOPE_STACK.with(|stack| {
58 for key in stack.borrow().iter() {
59 f(key);
60 }
61 });
62 } else if let Some(key) = CURRENT_SCOPE_KEY.with(|key| key.borrow().clone()) {
63 f(&key);
64 }
65}
66
67pub fn record_scope_signal_dep(signal: usize) {
68 for_each_current_scope(|key| {
69 SCOPE_SIGNAL_DEPS.with(|deps| {
70 deps.borrow_mut()
71 .entry(signal)
72 .or_default()
73 .insert(key.to_string());
74 });
75 SCOPE_TO_SIGNALS.with(|map| {
76 map.borrow_mut()
77 .entry(key.to_string())
78 .or_default()
79 .insert(signal);
80 });
81 });
82}
83
84fn mark_dirty(key: &str) {
85 SCOPE_DIRTY_EPOCHS.with(|epochs| {
86 let mut epochs = epochs.borrow_mut();
87 let epoch = epochs.entry(key.to_string()).or_insert(0);
88 *epoch = epoch.wrapping_add(1);
89 });
90 let marked = crate::runtime::COMPOSER
91 .try_with(|composer| {
92 composer
93 .try_borrow_mut()
94 .ok()
95 .map(|mut composer| {
96 if let Some(cache) = composer.scope_caches.get_mut(key) {
97 cache.clean = false;
98 }
99 })
100 .is_some()
101 })
102 .unwrap_or(false);
103 if !marked {
104 let _ = SCOPE_PENDING_DIRTY.try_with(|pending| {
105 if let Ok(mut pending) = pending.try_borrow_mut() {
106 pending.insert(key.to_string());
107 true
108 } else {
109 false
110 }
111 });
112 crate::request_frame();
113 }
114}
115
116pub fn mark_current_scope_dirty() {
117 for_each_current_scope(mark_dirty);
118}
119
120pub fn mark_current_scope_dirty_for_signal(signal: usize) {
121 for_each_current_scope(|key| {
122 let dependent = SCOPE_SIGNAL_DEPS.with(|deps| {
123 deps.borrow()
124 .get(&signal)
125 .is_some_and(|keys| keys.contains(key))
126 });
127 if !dependent {
128 mark_dirty(key);
129 }
130 });
131}
132
133pub fn mark_scope_dirty(key: &str) {
134 mark_dirty(key);
135}
136
137pub fn mark_scope_deps_dirty(signal: usize) {
138 SCOPE_SIGNAL_DEPS.with(|deps| {
139 let deps = deps.borrow();
140 if let Some(keys) = deps.get(&signal) {
141 for key in keys {
142 mark_dirty(key);
143 }
144 }
145 });
146}
147
148pub fn with_scope_key<R>(key: &str, function: impl FnOnce() -> R) -> R {
149 struct Guard {
150 key: String,
151 }
152
153 impl Drop for Guard {
154 fn drop(&mut self) {
155 let frame = SCOPE_FRAMES
156 .try_with(|frames| match frames.try_borrow_mut() {
157 Ok(mut frames) => frames
158 .iter()
159 .rposition(|frame| frame.key == self.key)
160 .map(|index| frames.remove(index)),
161 Err(_) => None,
162 })
163 .ok()
164 .flatten();
165 if let Some(frame) = frame {
166 let _ = SCOPE_LAST_CHILDREN.try_with(|map| {
167 if let Ok(mut map) = map.try_borrow_mut() {
168 map.insert(self.key.clone(), frame.children);
169 }
170 });
171 let _ = SCOPE_LAST_LOCALS.try_with(|map| {
172 if let Ok(mut map) = map.try_borrow_mut() {
173 map.insert(self.key.clone(), frame.locals);
174 }
175 });
176 let _ = SCOPE_LAST_ANIMATIONS.try_with(|map| {
177 if let Ok(mut map) = map.try_borrow_mut() {
178 map.insert(self.key.clone(), frame.animations);
179 }
180 });
181 }
182 let _ = CURRENT_SCOPE_STACK.try_with(|stack| {
183 if let Ok(mut stack) = stack.try_borrow_mut()
184 && stack.last() == Some(&self.key)
185 {
186 stack.pop();
187 }
188 });
189 let top = CURRENT_SCOPE_STACK
190 .try_with(|stack| {
191 stack
192 .try_borrow()
193 .ok()
194 .and_then(|stack| stack.last().cloned())
195 })
196 .ok()
197 .flatten();
198 let _ = CURRENT_SCOPE_KEY.try_with(|current| {
199 if let Ok(mut current) = current.try_borrow_mut() {
200 *current = top;
201 }
202 });
203 }
204 }
205
206 let key = key.to_string();
207 let parent = CURRENT_SCOPE_STACK.with(|stack| stack.borrow().last().cloned());
208 if let Some(parent) = parent {
209 let _ = SCOPE_FRAMES.try_with(|frames| {
210 if let Ok(mut frames) = frames.try_borrow_mut()
211 && let Some(frame) = frames.last_mut()
212 && frame.key == parent
213 {
214 frame.children.insert(key.clone());
215 }
216 });
217 }
218 CURRENT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(key.clone()));
219 CURRENT_SCOPE_KEY.with(|current| *current.borrow_mut() = Some(key.clone()));
220 SCOPE_FRAMES.with(|frames| {
221 frames.borrow_mut().push(ScopeFrame {
222 key: key.clone(),
223 children: FxHashSet::default(),
224 locals: FxHashMap::default(),
225 animations: FxHashSet::default(),
226 })
227 });
228 let _guard = Guard { key };
229 function()
230}
231
232pub fn record_scope_local_read(local: usize, value: u64) {
233 let _ = SCOPE_FRAMES.try_with(|frames| {
234 if let Ok(mut frames) = frames.try_borrow_mut()
235 && let Some(frame) = frames.last_mut()
236 {
237 frame.locals.insert(local, value);
238 }
239 });
240}
241
242pub fn record_scope_animation_key(key: &str) {
243 let _ = SCOPE_FRAMES.try_with(|frames| {
244 if let Ok(mut frames) = frames.try_borrow_mut()
245 && let Some(frame) = frames.last_mut()
246 {
247 frame.animations.insert(key.to_string());
248 }
249 });
250}
251
252pub fn clear_scope_deps(key: &str) {
253 let signals = SCOPE_TO_SIGNALS.with(|map| map.borrow_mut().remove(key));
254 if let Some(signals) = signals {
255 SCOPE_SIGNAL_DEPS.with(|deps| {
256 let mut deps = deps.borrow_mut();
257 for signal in signals {
258 if let Some(scopes) = deps.get_mut(&signal) {
259 scopes.remove(key);
260 if scopes.is_empty() {
261 deps.remove(&signal);
262 }
263 }
264 }
265 });
266 }
267 let epoch = SCOPE_DIRTY_EPOCHS.with(|epochs| epochs.borrow().get(key).copied().unwrap_or(0));
268 SCOPE_RUN_EPOCHS.with(|epochs| epochs.borrow_mut().insert(key.to_string(), epoch));
269}
270
271pub struct ScopeCache {
272 pub input_hash: u64,
273 pub view: View,
274 pub slot_delta: usize,
275 pub clean: bool,
276}
277
278fn collect_view_scope_keys(view: &View, keys: &mut FxHashSet<String>) {
279 if let Some(key) = &view.scope_key {
280 keys.insert(key.clone());
281 }
282 for child in &view.children {
283 collect_view_scope_keys(child, keys);
284 }
285}
286
287fn view_has_nested_scope(view: &View, parent_key: &str) -> bool {
288 if view
289 .scope_key
290 .as_deref()
291 .is_some_and(|scope_key| scope_key != parent_key)
292 {
293 return true;
294 }
295 view.children
296 .iter()
297 .any(|child| view_has_nested_scope(child, parent_key))
298}
299
300fn has_cached_scope_children(key: &str) -> bool {
301 let cached = SCOPE_CACHE_CHILDREN.with(|map| {
302 map.borrow()
303 .get(key)
304 .is_some_and(|children| !children.is_empty())
305 });
306 if cached {
307 return true;
308 }
309 crate::runtime::COMPOSER
310 .try_with(|composer| {
311 composer.try_borrow().ok().is_some_and(|composer| {
312 composer
313 .scope_caches
314 .get(key)
315 .is_some_and(|cache| view_has_nested_scope(&cache.view, key))
316 })
317 })
318 .ok()
319 .unwrap_or(false)
320}
321
322fn cached_scope_children(key: &str) -> FxHashSet<String> {
323 let mut children =
324 SCOPE_CACHE_CHILDREN.with(|map| map.borrow().get(key).cloned().unwrap_or_default());
325 if children.is_empty() {
326 let _ = crate::runtime::COMPOSER.try_with(|composer| {
327 if let Ok(composer) = composer.try_borrow()
328 && let Some(cache) = composer.scope_caches.get(key)
329 {
330 let mut keys = FxHashSet::default();
331 collect_view_scope_keys(&cache.view, &mut keys);
332 keys.remove(key);
333 children.extend(keys);
334 }
335 });
336 }
337 children
338}
339
340fn cached_locals_changed(key: &str) -> bool {
341 SCOPE_CACHE_LOCALS.with(|map| {
342 map.borrow().get(key).is_some_and(|values| {
343 values
344 .iter()
345 .any(|(local, value)| crate::locals::local_fingerprint(*local) != *value)
346 })
347 })
348}
349
350pub fn should_run(key: &str, input_hash: u64) -> bool {
351 if SCOPE_PENDING_DIRTY.with(|pending| pending.borrow().contains(key)) {
352 return true;
353 }
354 if has_cached_scope_children(key) {
355 return true;
356 }
357 let locals_changed = cached_locals_changed(key);
358 crate::runtime::COMPOSER.with(|composer| {
359 let composer = composer.borrow();
360 match composer.scope_caches.get(key) {
361 Some(cache) => !cache.clean || cache.input_hash != input_hash || locals_changed,
362 None => true,
363 }
364 })
365}
366
367pub fn current_scope_key() -> Option<String> {
368 CURRENT_SCOPE_KEY.with(|key| key.borrow().clone())
369}
370
371pub fn validate_scope_key(key: &str) {
372 assert!(!key.is_empty(), "scope keys must not be empty");
373 assert!(
374 crate::runtime::scope_owner_token(Some(key)) != crate::runtime::scope_owner_token(None),
375 "scope keys must not use the private root owner sentinel"
376 );
377}
378
379fn record_cached_child(key: &str) {
380 let _ = SCOPE_FRAMES.try_with(|frames| {
381 if let Ok(mut frames) = frames.try_borrow_mut()
382 && let Some(frame) = frames.last_mut()
383 {
384 frame.children.insert(key.to_string());
385 }
386 });
387}
388
389fn restore_cached_deps(key: &str) {
390 fn visit(key: &str, ancestors: &[String], seen: &mut FxHashSet<String>) {
391 if !seen.insert(key.to_string()) {
392 return;
393 }
394 let mut path = ancestors.to_vec();
395 if !path.iter().any(|entry| entry == key) {
396 path.push(key.to_string());
397 }
398 let signals = SCOPE_TO_SIGNALS.with(|map| map.borrow().get(key).cloned());
399 if let Some(signals) = signals {
400 for signal in signals {
401 record_signal_for_keys(signal, &path);
402 }
403 }
404 for child in cached_scope_children(key) {
405 visit(&child, &path, seen);
406 }
407 }
408
409 fn record_signal_for_keys(signal: usize, keys: &[String]) {
410 SCOPE_SIGNAL_DEPS.with(|deps| {
411 let mut deps = deps.borrow_mut();
412 for key in keys {
413 deps.entry(signal).or_default().insert(key.clone());
414 }
415 });
416 SCOPE_TO_SIGNALS.with(|map| {
417 let mut map = map.borrow_mut();
418 for key in keys {
419 map.entry(key.clone()).or_default().insert(signal);
420 }
421 });
422 }
423
424 let ancestors = current_scope_stack();
425 visit(key, &ancestors, &mut FxHashSet::default());
426}
427
428fn mark_scope_live(key: &str) {
429 mark_scopes(key, false);
430}
431
432fn mark_scope_cache_preserved(key: &str) {
437 mark_scopes(key, true);
438}
439
440fn mark_scopes(key: &str, cache_preserved: bool) {
441 let mut pending = vec![key.to_string()];
442 let mut seen = FxHashSet::default();
443 while let Some(current) = pending.pop() {
444 if !seen.insert(current.clone()) {
445 continue;
446 }
447 let children = cached_scope_children(¤t);
448 let exists = crate::runtime::COMPOSER.with(|composer| {
449 let mut composer = composer.borrow_mut();
450 let exists = composer.scope_caches.contains_key(¤t);
451 if exists {
452 composer.live_scope_keys.insert(current.clone());
453 composer
454 .live_keyed_owners
455 .insert(crate::runtime::scope_owner_token(Some(¤t)));
456 if cache_preserved {
457 composer
458 .cache_preserved_owners
459 .insert(crate::runtime::scope_owner_token(Some(¤t)));
460 }
461 }
462 exists
463 });
464 if exists {
465 let animations = SCOPE_CACHE_ANIMATIONS
466 .with(|map| map.borrow().get(¤t).cloned().unwrap_or_default());
467 for animation in animations {
468 crate::animation_driver::touch_cached(&animation, ¤t);
469 }
470 pending.extend(children);
471 }
472 }
473}
474
475pub fn get_cached(key: &str, _scheduler: &mut crate::runtime::Scheduler) -> View {
476 let (slot_delta, view) = crate::runtime::COMPOSER.with(|composer| {
477 let composer = composer.borrow();
478 let cache = composer
479 .scope_caches
480 .get(key)
481 .expect("scope_cache::get_cached called but no cache entry found");
482 (cache.slot_delta, cache.view.clone())
483 });
484 crate::runtime::COMPOSER.with(|composer| composer.borrow_mut().cursor += slot_delta);
485 record_cached_child(key);
486 restore_cached_deps(key);
487 mark_scope_live(key);
488 mark_scope_cache_preserved(key);
489 view
490}
491
492pub fn set_cache(key: &str, input_hash: u64, view: View, slot_delta: usize) {
493 let dirty_before = SCOPE_RUN_EPOCHS.with(|epochs| epochs.borrow().get(key).copied());
494 let clean = dirty_before.is_none_or(|before| dirty_generation(key) == before);
495 set_cache_inner(key, input_hash, view, slot_delta, clean);
496}
497
498pub fn dirty_generation(key: &str) -> u64 {
499 SCOPE_DIRTY_EPOCHS.with(|epochs| epochs.borrow().get(key).copied().unwrap_or(0))
500}
501
502pub fn set_cache_preserving_dirty(
503 key: &str,
504 input_hash: u64,
505 view: View,
506 slot_delta: usize,
507 dirty_before: u64,
508) {
509 let clean = dirty_generation(key) == dirty_before;
510 set_cache_inner(key, input_hash, view, slot_delta, clean);
511}
512
513fn set_cache_inner(key: &str, input_hash: u64, view: View, slot_delta: usize, clean: bool) {
514 let children = SCOPE_LAST_CHILDREN
515 .with(|map| map.borrow_mut().remove(key))
516 .unwrap_or_default();
517 let locals = SCOPE_LAST_LOCALS
518 .with(|map| map.borrow_mut().remove(key))
519 .unwrap_or_default();
520 let animations = SCOPE_LAST_ANIMATIONS
521 .with(|map| map.borrow_mut().remove(key))
522 .unwrap_or_default();
523 let old_cache = crate::runtime::COMPOSER.with(|composer| {
524 let mut composer = composer.borrow_mut();
525 composer.scope_caches.insert(
526 key.to_string(),
527 ScopeCache {
528 input_hash,
529 view,
530 slot_delta,
531 clean,
532 },
533 )
534 });
535 drop(old_cache);
536 SCOPE_CACHE_CHILDREN.with(|map| {
537 let mut map = map.borrow_mut();
538 if children.is_empty() {
539 map.remove(key);
540 } else {
541 map.insert(key.to_string(), children);
542 }
543 });
544 SCOPE_CACHE_LOCALS.with(|map| {
545 let mut map = map.borrow_mut();
546 if locals.is_empty() {
547 map.remove(key);
548 } else {
549 map.insert(key.to_string(), locals);
550 }
551 });
552 SCOPE_CACHE_ANIMATIONS.with(|map| {
553 let mut map = map.borrow_mut();
554 if animations.is_empty() {
555 map.remove(key);
556 } else {
557 map.insert(key.to_string(), animations);
558 }
559 });
560 SCOPE_RUN_EPOCHS.with(|epochs| {
561 epochs.borrow_mut().remove(key);
562 });
563 SCOPE_PENDING_DIRTY.with(|pending| {
564 pending.borrow_mut().remove(key);
565 });
566 mark_scope_live(key);
567}
568
569pub fn gc_dead_scopes() {
570 let (dead_scopes, dead_keyed, removed_caches) = crate::runtime::COMPOSER.with(|composer| {
571 let mut composer = composer.borrow_mut();
572 let dead_scopes: Vec<String> = composer
573 .scope_caches
574 .keys()
575 .filter(|key| !composer.live_scope_keys.contains(*key))
576 .cloned()
577 .collect();
578 let mut dead_keyed = crate::runtime::take_dead_keyed_slots(&mut composer);
579 dead_keyed
580 .sort_by_key(|(key, _)| std::cmp::Reverse(crate::runtime::keyed_disposer_order(key)));
581 let mut removed_caches = Vec::with_capacity(dead_scopes.len());
582 for key in &dead_scopes {
583 if let Some(cache) = composer.scope_caches.remove(key) {
584 removed_caches.push(cache);
585 }
586 }
587 composer.live_scope_keys.clear();
588 composer.live_keyed_owners.clear();
589 composer.live_keyed_keys.clear();
590 composer.cache_preserved_owners.clear();
591 composer.live_scope_keys.insert(String::new());
592 composer
593 .live_keyed_owners
594 .insert(crate::runtime::scope_owner_token(None));
595 (dead_scopes, dead_keyed, removed_caches)
596 });
597
598 drop(removed_caches);
599
600 for key in dead_scopes {
601 clear_scope_deps(&key);
602 SCOPE_DIRTY_EPOCHS.with(|epochs| {
603 epochs.borrow_mut().remove(&key);
604 });
605 SCOPE_PENDING_DIRTY.with(|pending| {
606 pending.borrow_mut().remove(&key);
607 });
608 SCOPE_CACHE_CHILDREN.with(|map| {
609 map.borrow_mut().remove(&key);
610 });
611 SCOPE_CACHE_LOCALS.with(|map| {
612 map.borrow_mut().remove(&key);
613 });
614 SCOPE_CACHE_ANIMATIONS.with(|map| {
615 map.borrow_mut().remove(&key);
616 });
617 SCOPE_LAST_CHILDREN.with(|map| {
618 map.borrow_mut().remove(&key);
619 });
620 SCOPE_LAST_LOCALS.with(|map| {
621 map.borrow_mut().remove(&key);
622 });
623 SCOPE_LAST_ANIMATIONS.with(|map| {
624 map.borrow_mut().remove(&key);
625 });
626 SCOPE_RUN_EPOCHS.with(|epochs| {
627 epochs.borrow_mut().remove(&key);
628 });
629 crate::animation_driver::release_scope(&key);
630 }
631
632 for (key, _slot) in &dead_keyed {
633 clear_scope_deps(key);
634 if let Some(disposer) = crate::runtime::take_keyed_disposer(key) {
635 crate::runtime::run_keyed_disposer(disposer);
636 }
637 }
638 drop(dead_keyed);
639}
640
641pub fn touch_live_cached_animations() {
642 let live = crate::runtime::COMPOSER
643 .try_with(|composer| {
644 composer
645 .try_borrow()
646 .ok()
647 .map(|composer| composer.live_scope_keys.clone())
648 })
649 .ok()
650 .flatten()
651 .unwrap_or_default();
652 for key in live {
653 for animation in cached_animation_keys(&key) {
654 crate::animation_driver::touch_cached(&animation, &key);
655 }
656 }
657}
658
659pub fn cached_animation_keys(key: &str) -> Vec<String> {
660 SCOPE_CACHE_ANIMATIONS
661 .with(|map| {
662 map.borrow()
663 .get(key)
664 .map(|keys| keys.iter().cloned().collect())
665 })
666 .unwrap_or_default()
667}
668
669pub fn clear_all_scope_deps() {
670 SCOPE_SIGNAL_DEPS.with(|map| map.borrow_mut().clear());
671 SCOPE_TO_SIGNALS.with(|map| map.borrow_mut().clear());
672 SCOPE_DIRTY_EPOCHS.with(|map| map.borrow_mut().clear());
673 SCOPE_PENDING_DIRTY.with(|map| map.borrow_mut().clear());
674 SCOPE_CACHE_CHILDREN.with(|map| map.borrow_mut().clear());
675 SCOPE_CACHE_LOCALS.with(|map| map.borrow_mut().clear());
676 SCOPE_CACHE_ANIMATIONS.with(|map| map.borrow_mut().clear());
677 SCOPE_LAST_CHILDREN.with(|map| map.borrow_mut().clear());
678 SCOPE_LAST_LOCALS.with(|map| map.borrow_mut().clear());
679 SCOPE_LAST_ANIMATIONS.with(|map| map.borrow_mut().clear());
680 SCOPE_RUN_EPOCHS.with(|map| map.borrow_mut().clear());
681 SCOPE_FRAMES.with(|frames| frames.borrow_mut().clear());
682 CURRENT_SCOPE_STACK.with(|stack| stack.borrow_mut().clear());
683 CURRENT_SCOPE_KEY.with(|key| *key.borrow_mut() = None);
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689 use crate::signal::signal;
690
691 fn reset_maps() {
692 SCOPE_SIGNAL_DEPS.with(|map| map.borrow_mut().clear());
693 SCOPE_TO_SIGNALS.with(|map| map.borrow_mut().clear());
694 SCOPE_DIRTY_EPOCHS.with(|map| map.borrow_mut().clear());
695 SCOPE_PENDING_DIRTY.with(|map| map.borrow_mut().clear());
696 }
697
698 #[test]
699 fn scope_deps_deduplicate_keys() {
700 reset_maps();
701 let signal = signal(0);
702 with_scope_key("dedupe_scope", || {
703 let _ = signal.get();
704 let _ = signal.get();
705 });
706 SCOPE_SIGNAL_DEPS.with(|deps| {
707 assert_eq!(deps.borrow().get(&signal.id()).map(FxHashSet::len), Some(1));
708 });
709 clear_scope_deps("dedupe_scope");
710 SCOPE_SIGNAL_DEPS.with(|deps| assert!(deps.borrow().is_empty()));
711 }
712
713 #[test]
714 fn nested_scope_reads_dirty_ancestors() {
715 reset_maps();
716 let signal = signal(0);
717 with_scope_key("outer", || {
718 with_scope_key("inner", || {
719 let _ = signal.get();
720 });
721 });
722 signal.set(1);
723 assert_eq!(dirty_generation("outer"), 1);
724 assert_eq!(dirty_generation("inner"), 1);
725 }
726}