repose_core/
scope_cache.rs1use rustc_hash::{FxHashMap, FxHashSet};
2use std::cell::RefCell;
3
4use crate::View;
5
6thread_local! {
7 static CURRENT_SCOPE_KEY: RefCell<Option<String>> =
9 const { RefCell::new(None) };
10
11 static SCOPE_SIGNAL_DEPS: RefCell<FxHashMap<usize, FxHashSet<String>>> =
15 RefCell::new(FxHashMap::default());
16
17 static SCOPE_TO_SIGNALS: RefCell<FxHashMap<String, FxHashSet<usize>>> =
20 RefCell::new(FxHashMap::default());
21}
22
23pub fn record_scope_signal_dep(signal_id: usize) {
26 let key = CURRENT_SCOPE_KEY.with(|k| k.borrow().clone());
27 if let Some(key) = key {
28 SCOPE_SIGNAL_DEPS.with(|deps| {
29 deps.borrow_mut()
30 .entry(signal_id)
31 .or_default()
32 .insert(key.clone());
33 });
34 SCOPE_TO_SIGNALS.with(|m| {
35 m.borrow_mut().entry(key).or_default().insert(signal_id);
36 });
37 }
38}
39
40pub fn mark_scope_deps_dirty(signal_id: usize) {
43 let keys = SCOPE_SIGNAL_DEPS.with(|deps| deps.borrow().get(&signal_id).cloned());
44 if let Some(keys) = keys {
45 for key in keys {
46 crate::runtime::COMPOSER.with(|c| {
47 let mut c = c.borrow_mut();
48 if let Some(cache) = c.scope_caches.get_mut(&key) {
49 cache.clean = false;
50 }
51 });
52 }
53 }
54}
55
56pub fn with_scope_key<R>(key: &str, f: impl FnOnce() -> R) -> R {
58 CURRENT_SCOPE_KEY.with(|k| {
59 let prev = k.borrow_mut().take();
60 *k.borrow_mut() = Some(key.to_string());
61 let result = f();
62 *k.borrow_mut() = prev;
63 result
64 })
65}
66
67pub fn clear_scope_deps(key: &str) {
71 let signals = SCOPE_TO_SIGNALS.with(|m| m.borrow_mut().remove(key));
72 if let Some(signals) = signals {
73 SCOPE_SIGNAL_DEPS.with(|deps| {
74 let mut deps = deps.borrow_mut();
75 for signal_id in signals {
76 if let Some(scopes) = deps.get_mut(&signal_id) {
77 scopes.remove(key);
78 if scopes.is_empty() {
79 deps.remove(&signal_id);
80 }
81 }
82 }
83 });
84 }
85}
86
87pub struct ScopeCache {
89 pub input_hash: u64,
91 pub view: View,
93 pub slot_delta: usize,
95 pub clean: bool,
97}
98
99pub fn should_run(key: &str, input_hash: u64) -> bool {
101 crate::runtime::COMPOSER.with(|c| {
102 let c = c.borrow();
103 match c.scope_caches.get(key) {
104 Some(cache) => !cache.clean || cache.input_hash != input_hash,
105 None => true,
106 }
107 })
108}
109
110pub fn get_cached(key: &str, _s: &mut crate::runtime::Scheduler) -> View {
114 crate::runtime::COMPOSER.with(|c| {
115 let mut c = c.borrow_mut();
116 let (slot_delta, view) = {
117 let cache = c
118 .scope_caches
119 .get(key)
120 .expect("scope_cache::get_cached called but no cache entry found");
121 (cache.slot_delta, cache.view.clone())
122 };
123
124 c.cursor += slot_delta;
125 view
126 })
127}
128
129pub fn set_cache(key: &str, input_hash: u64, view: View, slot_delta: usize) {
131 crate::runtime::COMPOSER.with(|c| {
132 let mut c = c.borrow_mut();
133 c.scope_caches.insert(
134 key.to_string(),
135 ScopeCache {
136 input_hash,
137 view,
138 slot_delta,
139 clean: true,
140 },
141 );
142 });
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::signal::signal;
149
150 fn reset_maps() {
151 SCOPE_SIGNAL_DEPS.with(|d| d.borrow_mut().clear());
152 SCOPE_TO_SIGNALS.with(|d| d.borrow_mut().clear());
153 }
154
155 #[test]
156 fn scope_deps_deduplicate_keys() {
157 reset_maps();
158 let sig = signal(0);
159
160 with_scope_key("dedupe_scope", || {
162 let _ = sig.get();
163 let _ = sig.get();
164 });
165
166 SCOPE_SIGNAL_DEPS.with(|d| {
167 let d = d.borrow();
168 assert_eq!(
169 d.get(&sig.id()).map(|s| s.len()),
170 Some(1),
171 "duplicate signal reads must collapse to a single scope dep"
172 );
173 });
174 SCOPE_TO_SIGNALS.with(|d| {
175 let d = d.borrow();
176 assert_eq!(d.get("dedupe_scope").map(|s| s.len()), Some(1));
177 });
178
179 clear_scope_deps("dedupe_scope");
181 SCOPE_TO_SIGNALS.with(|d| assert!(d.borrow().is_empty()));
182 SCOPE_SIGNAL_DEPS.with(|d| assert!(d.borrow().is_empty()));
183 }
184
185 #[test]
186 fn scope_deps_multiple_scopes_share_signal() {
187 reset_maps();
188 let sig = signal(0);
189
190 with_scope_key("scope_a", || {
191 let _ = sig.get();
192 });
193 with_scope_key("scope_b", || {
194 let _ = sig.get();
195 });
196
197 SCOPE_SIGNAL_DEPS.with(|d| {
198 let d = d.borrow();
199 let scopes = d.get(&sig.id()).unwrap();
200 assert!(scopes.contains("scope_a"));
201 assert!(scopes.contains("scope_b"));
202 });
203
204 clear_scope_deps("scope_a");
206 SCOPE_SIGNAL_DEPS.with(|d| {
207 let d = d.borrow();
208 let scopes = d.get(&sig.id()).unwrap();
209 assert!(!scopes.contains("scope_a"));
210 assert!(scopes.contains("scope_b"));
211 });
212 SCOPE_TO_SIGNALS.with(|d| {
213 assert!(d.borrow().get("scope_a").is_none());
214 assert!(d.borrow().get("scope_b").is_some());
215 });
216 }
217}