1#[cfg(feature = "parallel")]
2use dashmap::DashMap;
3#[cfg(not(feature = "parallel"))]
4use std::cell::RefCell;
5#[cfg(not(feature = "parallel"))]
6use std::collections::HashMap;
7
8use serde_json::Value;
9use std::hash::{Hash, Hasher};
10use std::sync::Arc;
11use ahash::AHasher;
12use indexmap::IndexSet;
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[inline]
18fn compute_hash<T: Hash>(value: &T) -> u64 {
19 let mut hasher = AHasher::default();
20 value.hash(&mut hasher);
21 hasher.finish()
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct CacheKey {
28 pub eval_key: String,
30 pub deps_hash: u64,
32}
33
34impl CacheKey {
35 pub fn new(eval_key: String, dependencies: &IndexSet<String>, values: &[(String, &Value)]) -> Self {
39 let value_map: std::collections::HashMap<&str, &Value> = values
41 .iter()
42 .map(|(k, v)| (k.as_str(), *v))
43 .collect();
44
45 let mut combined = String::new();
48 for dep_key in dependencies.iter() {
49 combined.push_str(dep_key);
50 combined.push(':');
51 if let Some(value) = value_map.get(dep_key.as_str()) {
52 combined.push_str(&value.to_string());
53 } else {
54 combined.push_str("null");
55 }
56 combined.push(';');
57 }
58
59 let deps_hash = compute_hash(&combined);
61
62 Self {
63 eval_key,
64 deps_hash,
65 }
66 }
67
68 pub fn simple(eval_key: String) -> Self {
70 Self {
71 eval_key,
72 deps_hash: 0, }
74 }
75}
76
77pub struct EvalCache {
82 #[cfg(feature = "parallel")]
83 cache: DashMap<CacheKey, Arc<Value>>,
85
86 #[cfg(not(feature = "parallel"))]
87 cache: RefCell<HashMap<CacheKey, Arc<Value>>>,
89
90 hits: AtomicUsize,
92 misses: AtomicUsize,
93}
94
95impl EvalCache {
96 pub fn new() -> Self {
98 Self {
99 #[cfg(feature = "parallel")]
100 cache: DashMap::new(),
101 #[cfg(not(feature = "parallel"))]
102 cache: RefCell::new(HashMap::new()),
103 hits: AtomicUsize::new(0),
104 misses: AtomicUsize::new(0),
105 }
106 }
107
108 pub fn with_capacity(capacity: usize) -> Self {
110 Self {
111 #[cfg(feature = "parallel")]
112 cache: DashMap::with_capacity(capacity),
113 #[cfg(not(feature = "parallel"))]
114 cache: RefCell::new(HashMap::with_capacity(capacity)),
115 hits: AtomicUsize::new(0),
116 misses: AtomicUsize::new(0),
117 }
118 }
119
120 #[cfg(feature = "parallel")]
123 #[inline]
125 pub fn get(&self, key: &CacheKey) -> Option<Arc<Value>> {
126 if let Some(value) = self.cache.get(key) {
127 self.hits.fetch_add(1, Ordering::Relaxed);
128 Some(Arc::clone(value.value()))
129 } else {
130 self.misses.fetch_add(1, Ordering::Relaxed);
131 None
132 }
133 }
134
135 #[cfg(not(feature = "parallel"))]
138 #[inline]
140 pub fn get(&self, key: &CacheKey) -> Option<Arc<Value>> {
141 if let Some(value) = self.cache.borrow().get(key) {
142 self.hits.fetch_add(1, Ordering::Relaxed);
143 Some(Arc::clone(value))
144 } else {
145 self.misses.fetch_add(1, Ordering::Relaxed);
146 None
147 }
148 }
149
150 #[cfg(feature = "parallel")]
152 #[inline]
154 pub fn insert(&self, key: CacheKey, value: Value) {
155 self.cache.insert(key, Arc::new(value));
156 }
157
158 #[cfg(not(feature = "parallel"))]
160 #[inline]
162 pub fn insert(&self, key: CacheKey, value: Value) {
163 self.cache.borrow_mut().insert(key, Arc::new(value));
164 }
165
166 #[cfg(feature = "parallel")]
168 #[inline]
170 pub fn insert_arc(&self, key: CacheKey, value: Arc<Value>) {
171 self.cache.insert(key, value);
172 }
173
174 #[cfg(not(feature = "parallel"))]
176 #[inline]
178 pub fn insert_arc(&self, key: CacheKey, value: Arc<Value>) {
179 self.cache.borrow_mut().insert(key, value);
180 }
181
182 #[cfg(feature = "parallel")]
184 pub fn clear(&self) {
185 self.cache.clear();
186 self.hits.store(0, Ordering::Relaxed);
187 self.misses.store(0, Ordering::Relaxed);
188 }
189
190 #[cfg(not(feature = "parallel"))]
192 pub fn clear(&self) {
193 self.cache.borrow_mut().clear();
194 self.hits.store(0, Ordering::Relaxed);
195 self.misses.store(0, Ordering::Relaxed);
196 }
197
198 pub fn hit_rate(&self) -> f64 {
200 let hits = self.hits.load(Ordering::Relaxed);
201 let misses = self.misses.load(Ordering::Relaxed);
202 let total = hits + misses;
203 if total == 0 {
204 0.0
205 } else {
206 hits as f64 / total as f64
207 }
208 }
209
210 #[cfg(feature = "parallel")]
212 pub fn stats(&self) -> CacheStats {
213 CacheStats {
214 hits: self.hits.load(Ordering::Relaxed),
215 misses: self.misses.load(Ordering::Relaxed),
216 entries: self.cache.len(),
217 hit_rate: self.hit_rate(),
218 }
219 }
220
221 #[cfg(not(feature = "parallel"))]
223 pub fn stats(&self) -> CacheStats {
224 CacheStats {
225 hits: self.hits.load(Ordering::Relaxed),
226 misses: self.misses.load(Ordering::Relaxed),
227 entries: self.cache.borrow().len(),
228 hit_rate: self.hit_rate(),
229 }
230 }
231
232 #[cfg(feature = "parallel")]
234 #[inline]
235 pub fn len(&self) -> usize {
236 self.cache.len()
237 }
238
239 #[cfg(not(feature = "parallel"))]
241 #[inline]
242 pub fn len(&self) -> usize {
243 self.cache.borrow().len()
244 }
245
246 #[cfg(feature = "parallel")]
248 #[inline]
249 pub fn is_empty(&self) -> bool {
250 self.cache.is_empty()
251 }
252
253 #[cfg(not(feature = "parallel"))]
255 #[inline]
256 pub fn is_empty(&self) -> bool {
257 self.cache.borrow().is_empty()
258 }
259
260 #[cfg(feature = "parallel")]
262 #[inline]
263 pub fn remove(&self, key: &CacheKey) -> Option<Arc<Value>> {
264 self.cache.remove(key).map(|(_, v)| v)
265 }
266
267 #[cfg(not(feature = "parallel"))]
269 #[inline]
270 pub fn remove(&self, key: &CacheKey) -> Option<Arc<Value>> {
271 self.cache.borrow_mut().remove(key)
272 }
273
274 #[cfg(feature = "parallel")]
277 pub fn retain<F>(&self, predicate: F)
278 where
279 F: Fn(&CacheKey, &Arc<Value>) -> bool,
280 {
281 self.cache.retain(|k, v| predicate(k, v));
282 }
283
284 #[cfg(not(feature = "parallel"))]
287 pub fn retain<F>(&self, predicate: F)
288 where
289 F: Fn(&CacheKey, &Arc<Value>) -> bool,
290 {
291 self.cache.borrow_mut().retain(|k, v| predicate(k, v));
292 }
293
294 #[cfg(feature = "parallel")]
297 pub fn invalidate_dependencies(&self, changed_paths: &[String]) {
298 let changed_hashes: IndexSet<String> = changed_paths.iter().cloned().collect();
300
301 self.cache.retain(|cache_key, _| {
303 !changed_hashes.contains(&cache_key.eval_key)
304 });
305 }
306
307 #[cfg(not(feature = "parallel"))]
310 pub fn invalidate_dependencies(&self, changed_paths: &[String]) {
311 let changed_hashes: IndexSet<String> = changed_paths.iter().cloned().collect();
313
314 self.cache.borrow_mut().retain(|cache_key, _| {
316 !changed_hashes.contains(&cache_key.eval_key)
317 });
318 }
319}
320
321impl Default for EvalCache {
322 fn default() -> Self {
323 Self::new()
324 }
325}
326
327#[derive(Debug, Clone, Copy)]
329pub struct CacheStats {
330 pub hits: usize,
331 pub misses: usize,
332 pub entries: usize,
333 pub hit_rate: f64,
334}
335
336impl std::fmt::Display for CacheStats {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 write!(
339 f,
340 "Cache Stats: {} entries, {} hits, {} misses, {:.2}% hit rate",
341 self.entries,
342 self.hits,
343 self.misses,
344 self.hit_rate * 100.0
345 )
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use serde_json::json;
353
354 #[test]
355 fn test_cache_key_creation() {
356 let eval_key = "$params.foo".to_string();
357 let mut deps = IndexSet::new();
358 deps.insert("$params.bar".to_string());
359 deps.insert("data.value".to_string());
360
361 let val1 = json!(42);
362 let val2 = json!("test");
363 let values = vec![
364 ("$params.bar".to_string(), &val1),
365 ("data.value".to_string(), &val2),
366 ];
367
368 let key1 = CacheKey::new(eval_key.clone(), &deps, &values);
369 let key2 = CacheKey::new(eval_key.clone(), &deps, &values);
370
371 assert_eq!(key1, key2);
373 }
374
375 #[test]
376 fn test_cache_key_different_values() {
377 let eval_key = "$params.foo".to_string();
378 let mut deps = IndexSet::new();
379 deps.insert("data.value".to_string());
380
381 let val1 = json!(42);
382 let val2 = json!(43);
383 let values1 = vec![("data.value".to_string(), &val1)];
384 let values2 = vec![("data.value".to_string(), &val2)];
385
386 let key1 = CacheKey::new(eval_key.clone(), &deps, &values1);
387 let key2 = CacheKey::new(eval_key.clone(), &deps, &values2);
388
389 assert_ne!(key1, key2);
391 }
392
393 #[test]
394 fn test_cache_operations() {
395 let cache = EvalCache::new();
396
397 let key = CacheKey::simple("test".to_string());
398 let value = json!({"result": 42});
399
400 assert!(cache.get(&key).is_none());
402 assert_eq!(cache.stats().misses, 1);
403
404 cache.insert(key.clone(), value.clone());
406 assert_eq!(cache.get(&key).unwrap().as_ref(), &value);
407 assert_eq!(cache.stats().hits, 1);
408
409 let stats = cache.stats();
411 assert_eq!(stats.entries, 1);
412 assert_eq!(stats.hit_rate, 0.5); }
414
415 #[test]
416 fn test_cache_clear() {
417 let cache = EvalCache::new();
418 cache.insert(CacheKey::simple("test".to_string()), json!(42));
419
420 assert_eq!(cache.len(), 1);
421 cache.clear();
422 assert_eq!(cache.len(), 0);
423 assert_eq!(cache.stats().hits, 0);
424 }
425
426 #[test]
427 fn test_invalidate_dependencies() {
428 let cache = EvalCache::new();
429
430 cache.insert(CacheKey::simple("$params.foo".to_string()), json!(1));
432 cache.insert(CacheKey::simple("$params.bar".to_string()), json!(2));
433 cache.insert(CacheKey::simple("$params.baz".to_string()), json!(3));
434
435 assert_eq!(cache.len(), 3);
436
437 cache.invalidate_dependencies(&["$params.foo".to_string()]);
439
440 assert_eq!(cache.len(), 2);
441 assert!(cache.get(&CacheKey::simple("$params.foo".to_string())).is_none());
442 assert!(cache.get(&CacheKey::simple("$params.bar".to_string())).is_some());
443 }
444}