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 ahash::AHasher;
9use indexmap::IndexSet;
10use serde_json::Value;
11use std::hash::{Hash, Hasher};
12use std::sync::atomic::{AtomicUsize, Ordering};
13use std::sync::Arc;
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(
39 eval_key: String,
40 dependencies: &IndexSet<String>,
41 values: &[(String, &Value)],
42 ) -> Self {
43 let value_map: std::collections::HashMap<&str, &Value> =
45 values.iter().map(|(k, v)| (k.as_str(), *v)).collect();
46
47 let mut combined = String::new();
50 for dep_key in dependencies.iter() {
51 combined.push_str(dep_key);
52 combined.push(':');
53 if let Some(value) = value_map.get(dep_key.as_str()) {
54 combined.push_str(&value.to_string());
55 } else {
56 combined.push_str("null");
57 }
58 combined.push(';');
59 }
60
61 let deps_hash = compute_hash(&combined);
63
64 Self {
65 eval_key,
66 deps_hash,
67 }
68 }
69
70 pub fn simple(eval_key: String) -> Self {
72 Self {
73 eval_key,
74 deps_hash: 0, }
76 }
77}
78
79pub struct EvalCache {
84 #[cfg(feature = "parallel")]
85 cache: DashMap<CacheKey, Arc<Value>>,
87
88 #[cfg(not(feature = "parallel"))]
89 cache: RefCell<HashMap<CacheKey, Arc<Value>>>,
91
92 hits: AtomicUsize,
94 misses: AtomicUsize,
95}
96
97impl EvalCache {
98 pub fn new() -> Self {
100 Self {
101 #[cfg(feature = "parallel")]
102 cache: DashMap::new(),
103 #[cfg(not(feature = "parallel"))]
104 cache: RefCell::new(HashMap::new()),
105 hits: AtomicUsize::new(0),
106 misses: AtomicUsize::new(0),
107 }
108 }
109
110 pub fn with_capacity(capacity: usize) -> Self {
112 Self {
113 #[cfg(feature = "parallel")]
114 cache: DashMap::with_capacity(capacity),
115 #[cfg(not(feature = "parallel"))]
116 cache: RefCell::new(HashMap::with_capacity(capacity)),
117 hits: AtomicUsize::new(0),
118 misses: AtomicUsize::new(0),
119 }
120 }
121
122 #[cfg(feature = "parallel")]
125 #[inline]
127 pub fn get(&self, key: &CacheKey) -> Option<Arc<Value>> {
128 if let Some(value) = self.cache.get(key) {
129 self.hits.fetch_add(1, Ordering::Relaxed);
130 Some(Arc::clone(value.value()))
131 } else {
132 self.misses.fetch_add(1, Ordering::Relaxed);
133 None
134 }
135 }
136
137 #[cfg(not(feature = "parallel"))]
140 #[inline]
142 pub fn get(&self, key: &CacheKey) -> Option<Arc<Value>> {
143 if let Some(value) = self.cache.borrow().get(key) {
144 self.hits.fetch_add(1, Ordering::Relaxed);
145 Some(Arc::clone(value))
146 } else {
147 self.misses.fetch_add(1, Ordering::Relaxed);
148 None
149 }
150 }
151
152 #[cfg(feature = "parallel")]
154 #[inline]
156 pub fn insert(&self, key: CacheKey, value: Value) {
157 self.cache.insert(key, Arc::new(value));
158 }
159
160 #[cfg(not(feature = "parallel"))]
162 #[inline]
164 pub fn insert(&self, key: CacheKey, value: Value) {
165 self.cache.borrow_mut().insert(key, Arc::new(value));
166 }
167
168 #[cfg(feature = "parallel")]
170 #[inline]
172 pub fn insert_arc(&self, key: CacheKey, value: Arc<Value>) {
173 self.cache.insert(key, value);
174 }
175
176 #[cfg(not(feature = "parallel"))]
178 #[inline]
180 pub fn insert_arc(&self, key: CacheKey, value: Arc<Value>) {
181 self.cache.borrow_mut().insert(key, value);
182 }
183
184 #[cfg(feature = "parallel")]
186 pub fn clear(&self) {
187 self.cache.clear();
188 self.hits.store(0, Ordering::Relaxed);
189 self.misses.store(0, Ordering::Relaxed);
190 }
191
192 #[cfg(not(feature = "parallel"))]
194 pub fn clear(&self) {
195 self.cache.borrow_mut().clear();
196 self.hits.store(0, Ordering::Relaxed);
197 self.misses.store(0, Ordering::Relaxed);
198 }
199
200 pub fn hit_rate(&self) -> f64 {
202 let hits = self.hits.load(Ordering::Relaxed);
203 let misses = self.misses.load(Ordering::Relaxed);
204 let total = hits + misses;
205 if total == 0 {
206 0.0
207 } else {
208 hits as f64 / total as f64
209 }
210 }
211
212 #[cfg(feature = "parallel")]
214 pub fn stats(&self) -> CacheStats {
215 CacheStats {
216 hits: self.hits.load(Ordering::Relaxed),
217 misses: self.misses.load(Ordering::Relaxed),
218 entries: self.cache.len(),
219 hit_rate: self.hit_rate(),
220 }
221 }
222
223 #[cfg(not(feature = "parallel"))]
225 pub fn stats(&self) -> CacheStats {
226 CacheStats {
227 hits: self.hits.load(Ordering::Relaxed),
228 misses: self.misses.load(Ordering::Relaxed),
229 entries: self.cache.borrow().len(),
230 hit_rate: self.hit_rate(),
231 }
232 }
233
234 #[cfg(feature = "parallel")]
236 #[inline]
237 pub fn len(&self) -> usize {
238 self.cache.len()
239 }
240
241 #[cfg(not(feature = "parallel"))]
243 #[inline]
244 pub fn len(&self) -> usize {
245 self.cache.borrow().len()
246 }
247
248 #[cfg(feature = "parallel")]
250 #[inline]
251 pub fn is_empty(&self) -> bool {
252 self.cache.is_empty()
253 }
254
255 #[cfg(not(feature = "parallel"))]
257 #[inline]
258 pub fn is_empty(&self) -> bool {
259 self.cache.borrow().is_empty()
260 }
261
262 #[cfg(feature = "parallel")]
264 #[inline]
265 pub fn remove(&self, key: &CacheKey) -> Option<Arc<Value>> {
266 self.cache.remove(key).map(|(_, v)| v)
267 }
268
269 #[cfg(not(feature = "parallel"))]
271 #[inline]
272 pub fn remove(&self, key: &CacheKey) -> Option<Arc<Value>> {
273 self.cache.borrow_mut().remove(key)
274 }
275
276 #[cfg(feature = "parallel")]
279 pub fn retain<F>(&self, predicate: F)
280 where
281 F: Fn(&CacheKey, &Arc<Value>) -> bool,
282 {
283 self.cache.retain(|k, v| predicate(k, v));
284 }
285
286 #[cfg(not(feature = "parallel"))]
289 pub fn retain<F>(&self, predicate: F)
290 where
291 F: Fn(&CacheKey, &Arc<Value>) -> bool,
292 {
293 self.cache.borrow_mut().retain(|k, v| predicate(k, v));
294 }
295
296 #[cfg(feature = "parallel")]
299 pub fn invalidate_dependencies(&self, changed_paths: &[String]) {
300 let changed_hashes: IndexSet<String> = changed_paths.iter().cloned().collect();
302
303 self.cache
305 .retain(|cache_key, _| !changed_hashes.contains(&cache_key.eval_key));
306 }
307
308 #[cfg(not(feature = "parallel"))]
311 pub fn invalidate_dependencies(&self, changed_paths: &[String]) {
312 let changed_hashes: IndexSet<String> = changed_paths.iter().cloned().collect();
314
315 self.cache
317 .borrow_mut()
318 .retain(|cache_key, _| !changed_hashes.contains(&cache_key.eval_key));
319 }
320}
321
322impl Default for EvalCache {
323 fn default() -> Self {
324 Self::new()
325 }
326}
327
328#[derive(Debug, Clone, Copy)]
330pub struct CacheStats {
331 pub hits: usize,
332 pub misses: usize,
333 pub entries: usize,
334 pub hit_rate: f64,
335}
336
337impl std::fmt::Display for CacheStats {
338 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339 write!(
340 f,
341 "Cache Stats: {} entries, {} hits, {} misses, {:.2}% hit rate",
342 self.entries,
343 self.hits,
344 self.misses,
345 self.hit_rate * 100.0
346 )
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use serde_json::json;
354
355 #[test]
356 fn test_cache_key_creation() {
357 let eval_key = "$params.foo".to_string();
358 let mut deps = IndexSet::new();
359 deps.insert("$params.bar".to_string());
360 deps.insert("data.value".to_string());
361
362 let val1 = json!(42);
363 let val2 = json!("test");
364 let values = vec![
365 ("$params.bar".to_string(), &val1),
366 ("data.value".to_string(), &val2),
367 ];
368
369 let key1 = CacheKey::new(eval_key.clone(), &deps, &values);
370 let key2 = CacheKey::new(eval_key.clone(), &deps, &values);
371
372 assert_eq!(key1, key2);
374 }
375
376 #[test]
377 fn test_cache_key_different_values() {
378 let eval_key = "$params.foo".to_string();
379 let mut deps = IndexSet::new();
380 deps.insert("data.value".to_string());
381
382 let val1 = json!(42);
383 let val2 = json!(43);
384 let values1 = vec![("data.value".to_string(), &val1)];
385 let values2 = vec![("data.value".to_string(), &val2)];
386
387 let key1 = CacheKey::new(eval_key.clone(), &deps, &values1);
388 let key2 = CacheKey::new(eval_key.clone(), &deps, &values2);
389
390 assert_ne!(key1, key2);
392 }
393
394 #[test]
395 fn test_cache_operations() {
396 let cache = EvalCache::new();
397
398 let key = CacheKey::simple("test".to_string());
399 let value = json!({"result": 42});
400
401 assert!(cache.get(&key).is_none());
403 assert_eq!(cache.stats().misses, 1);
404
405 cache.insert(key.clone(), value.clone());
407 assert_eq!(cache.get(&key).unwrap().as_ref(), &value);
408 assert_eq!(cache.stats().hits, 1);
409
410 let stats = cache.stats();
412 assert_eq!(stats.entries, 1);
413 assert_eq!(stats.hit_rate, 0.5); }
415
416 #[test]
417 fn test_cache_clear() {
418 let cache = EvalCache::new();
419 cache.insert(CacheKey::simple("test".to_string()), json!(42));
420
421 assert_eq!(cache.len(), 1);
422 cache.clear();
423 assert_eq!(cache.len(), 0);
424 assert_eq!(cache.stats().hits, 0);
425 }
426
427 #[test]
428 fn test_invalidate_dependencies() {
429 let cache = EvalCache::new();
430
431 cache.insert(CacheKey::simple("$params.foo".to_string()), json!(1));
433 cache.insert(CacheKey::simple("$params.bar".to_string()), json!(2));
434 cache.insert(CacheKey::simple("$params.baz".to_string()), json!(3));
435
436 assert_eq!(cache.len(), 3);
437
438 cache.invalidate_dependencies(&["$params.foo".to_string()]);
440
441 assert_eq!(cache.len(), 2);
442 assert!(cache
443 .get(&CacheKey::simple("$params.foo".to_string()))
444 .is_none());
445 assert!(cache
446 .get(&CacheKey::simple("$params.bar".to_string()))
447 .is_some());
448 }
449}