1use indexmap::IndexSet;
2use serde_json::Value;
3use std::collections::{HashMap, HashSet};
4
5#[derive(Default, Clone)]
7pub struct VersionTracker {
8 versions: HashMap<String, u64>,
9}
10
11impl VersionTracker {
12 pub fn new() -> Self {
13 Self {
14 versions: HashMap::new(),
15 }
16 }
17
18 #[inline]
19 pub fn get(&self, path: &str) -> u64 {
20 self.versions.get(path).copied().unwrap_or(0)
21 }
22
23 #[inline]
24 pub fn bump(&mut self, path: &str, source: &str) {
25 let current = self.get(path);
26 if crate::utils::is_debug_cache_enabled() {
27 println!("[store_cache] BUMPING for {} -> {} ({})", path, current + 1, source);
28 }
29 self.versions.insert(path.to_string(), current + 1);
31 }
32
33 pub fn merge_from(&mut self, other: &VersionTracker) {
37 for (k, v) in &other.versions {
38 let current = self.versions.get(k).copied().unwrap_or(0);
39 self.versions.insert(k.clone(), current.max(*v));
40 }
41 }
42
43 pub fn merge_from_params(&mut self, other: &VersionTracker) {
47 for (k, v) in &other.versions {
48 if k.starts_with("/$params") {
49 let current = self.versions.get(k).copied().unwrap_or(0);
50 self.versions.insert(k.clone(), current.max(*v));
51 }
52 }
53 }
54
55 pub fn any_bumped_with_prefix(&self, prefix: &str) -> bool {
58 self.versions
59 .iter()
60 .any(|(k, &v)| k.starts_with(prefix) && v > 0)
61 }
62
63 pub fn any_newly_bumped_with_prefix(&self, prefix: &str, baseline: &VersionTracker) -> bool {
67 self.versions
68 .iter()
69 .any(|(k, &v)| k.starts_with(prefix) && v > baseline.get(k))
70 }
71
72 pub fn versions(&self) -> impl Iterator<Item = (&str, &u64)> {
74 self.versions.iter().map(|(k, v)| (k.as_str(), v))
75 }
76}
77
78#[derive(Clone)]
80pub struct CacheEntry {
81 pub dep_versions: HashMap<String, u64>,
82 pub result: Value,
83 pub computed_for_item: Option<usize>,
88}
89
90#[derive(Default, Clone)]
92pub struct SubformItemCache {
93 pub data_versions: VersionTracker,
94 pub entries: HashMap<String, CacheEntry>,
95 pub item_snapshot: Value,
96 pub evaluated_schema: Option<Value>,
100}
101
102impl SubformItemCache {
103 pub fn new() -> Self {
104 Self {
105 data_versions: VersionTracker::new(),
106 entries: HashMap::new(),
107 item_snapshot: Value::Null,
108 evaluated_schema: None,
109 }
110 }
111}
112
113#[derive(Clone)]
115pub struct EvalCache {
116 pub data_versions: VersionTracker,
117 pub params_versions: VersionTracker,
118 pub entries: HashMap<String, CacheEntry>,
119
120 pub active_item_index: Option<usize>,
121 pub subform_caches: HashMap<usize, SubformItemCache>,
122
123 pub eval_generation: u64,
127 pub last_evaluated_generation: u64,
128
129 pub main_form_snapshot: Option<Value>,
133}
134
135impl Default for EvalCache {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl EvalCache {
142 pub fn new() -> Self {
143 Self {
144 data_versions: VersionTracker::new(),
145 params_versions: VersionTracker::new(),
146 entries: HashMap::new(),
147 active_item_index: None,
148 subform_caches: HashMap::new(),
149 eval_generation: 0,
150 last_evaluated_generation: u64::MAX, main_form_snapshot: None,
152 }
153 }
154
155 pub fn clear(&mut self) {
156 self.data_versions = VersionTracker::new();
157 self.params_versions = VersionTracker::new();
158 self.entries.clear();
159 self.active_item_index = None;
160 self.subform_caches.clear();
161 self.eval_generation = 0;
162 self.last_evaluated_generation = u64::MAX;
163 self.main_form_snapshot = None;
164 }
165
166 pub fn prune_subform_caches(&mut self, current_count: usize) {
170 self.subform_caches.retain(|&idx, _| idx < current_count);
171 }
172
173 pub fn invalidate_params_tables_for_item(&mut self, idx: usize, table_keys: &[String]) {
180 for key in table_keys {
182 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(key);
183 self.params_versions.bump(&data_path, "invalidate_params_tables_for_item");
184 self.eval_generation += 1;
185 }
186
187 if let Some(item_cache) = self.subform_caches.get_mut(&idx) {
189 for key in table_keys {
190 item_cache.entries.remove(key);
191 }
192 }
193 }
194
195 pub fn needs_full_evaluation(&self) -> bool {
197 self.eval_generation != self.last_evaluated_generation
198 }
199
200 pub fn mark_evaluated(&mut self) {
202 self.last_evaluated_generation = self.eval_generation;
203 }
204
205 pub(crate) fn ensure_active_item_cache(&mut self, idx: usize) {
206 self.subform_caches
207 .entry(idx)
208 .or_insert_with(SubformItemCache::new);
209 }
210
211 pub fn set_active_item(&mut self, idx: usize) {
212 self.active_item_index = Some(idx);
213 self.ensure_active_item_cache(idx);
214 }
215
216 pub fn clear_active_item(&mut self) {
217 self.active_item_index = None;
218 }
219
220 pub fn store_snapshot_and_diff_versions(&mut self, old: &Value, new: &Value) {
222 if let Some(idx) = self.active_item_index {
223 self.ensure_active_item_cache(idx);
224 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
225 diff_and_update_versions(&mut sub_cache.data_versions, "", old, new, "subform store_snapshot_and_diff_versions");
226 sub_cache.item_snapshot = new.clone();
227 } else {
228 diff_and_update_versions(&mut self.data_versions, "", old, new, "store_snapshot_and_diff_versions");
229 }
230 }
231
232 pub fn get_active_snapshot(&self) -> Value {
233 if let Some(idx) = self.active_item_index {
234 self.subform_caches
235 .get(&idx)
236 .map(|c| c.item_snapshot.clone())
237 .unwrap_or(Value::Null)
238 } else {
239 Value::Null
240 }
241 }
242
243 pub fn diff_active_item(
244 &mut self,
245 field_key: &str,
246 old_sub_data: &Value,
247 new_sub_data: &Value,
248 ) {
249 if let Some(idx) = self.active_item_index {
250 self.ensure_active_item_cache(idx);
251 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
252
253 let empty = Value::Null;
255 let old_item = old_sub_data.get(field_key).unwrap_or(&empty);
256 let new_item = new_sub_data.get(field_key).unwrap_or(&empty);
257
258 diff_and_update_versions(
259 &mut sub_cache.data_versions,
260 &format!("/{}", field_key),
261 old_item,
262 new_item,
263 format!("diff_active_item {}", field_key).as_str(),
264 );
265 sub_cache.item_snapshot = new_sub_data.clone();
266 }
267 }
268
269 pub fn bump_data_version(&mut self, data_path: &str) {
270 self.eval_generation += 1;
273 if let Some(idx) = self.active_item_index {
274 if let Some(cache) = self.subform_caches.get_mut(&idx) {
275 cache.data_versions.bump(data_path, "bump_data_version1");
276 }
277 } else {
278 self.data_versions.bump(data_path, "bump_data_version2");
279 }
280 }
281
282 pub fn bump_params_version(&mut self, data_path: &str) {
283 self.params_versions.bump(data_path, "bump_params_version");
284 self.eval_generation += 1;
285 }
286
287 pub fn check_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
293 if let Some(idx) = self.active_item_index {
294 if let Some(cache) = self.subform_caches.get(&idx) {
296 if let Some(hit) =
297 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
298 {
299 if crate::utils::is_debug_cache_enabled() {
300 println!("Cache HIT [T1 idx={}] {}", idx, eval_key);
301 }
302 return Some(hit);
303 }
304 }
305
306 let item_data_versions = self
312 .subform_caches
313 .get(&idx)
314 .map(|c| &c.data_versions)
315 .unwrap_or(&self.data_versions);
316
317 if let Some(entry) = self.entries.get(eval_key) {
318 let index_safe = match entry.computed_for_item {
319 None => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
324 Some(stored_idx) if stored_idx == idx => true,
325 _ => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
326 };
327 if index_safe {
328 let result =
329 self.validate_entry(eval_key, deps, &self.entries, item_data_versions);
330 if result.is_some() {
331 if crate::utils::is_debug_cache_enabled() {
332 println!(
333 "Cache HIT [T2 idx={} for={:?}] {}",
334 idx, entry.computed_for_item, eval_key
335 );
336 }
337 }
338 return result;
339 }
340 }
341
342 None
343 } else {
344 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
345 }
346 }
347
348 pub fn check_table_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
360 if let Some(idx) = self.active_item_index {
361 if let Some(cache) = self.subform_caches.get(&idx) {
363 if let Some(hit) =
364 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
365 {
366 if crate::utils::is_debug_cache_enabled() {
367 println!("Cache HIT [T1 table idx={}] {}", idx, eval_key);
368 }
369 return Some(hit);
370 }
371 }
372
373 let result = self.validate_entry(eval_key, deps, &self.entries, &self.data_versions);
382 if result.is_some() {
383 if crate::utils::is_debug_cache_enabled() {
384 println!("Cache HIT [T2 table idx={}] {}", idx, eval_key);
385 }
386 }
387 result
388 } else {
389 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
390 }
391 }
392
393 fn validate_entry(
394 &self,
395 eval_key: &str,
396 deps: &IndexSet<String>,
397 entries: &HashMap<String, CacheEntry>,
398 data_versions: &VersionTracker,
399 ) -> Option<Value> {
400 let entry = entries.get(eval_key)?;
401 for dep in deps {
402 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
403
404 let current_ver = if data_dep_path.starts_with("/$params") {
405 self.params_versions.get(&data_dep_path)
406 } else {
407 data_versions.get(&data_dep_path)
408 };
409
410 if let Some(&cached_ver) = entry.dep_versions.get(data_dep_path.as_ref()) {
411 if current_ver != cached_ver {
412 if crate::utils::is_debug_cache_enabled() {
413 println!(
414 "Cache MISS {}: dep {} changed ({} -> {})",
415 eval_key, data_dep_path, cached_ver, current_ver
416 );
417 }
418 return None;
419 }
420 } else {
421 if crate::utils::is_debug_cache_enabled() {
422 println!(
423 "Cache MISS {}: dep {} missing from cache entry",
424 eval_key, data_dep_path
425 );
426 }
427 return None;
428 }
429 }
430 if crate::utils::is_debug_cache_enabled() {
431 println!("Cache HIT {}", eval_key);
432 }
433 Some(entry.result.clone())
434 }
435
436 pub fn store_cache(&mut self, eval_key: &str, deps: &IndexSet<String>, result: Value) {
444 let mut dep_versions = HashMap::with_capacity(deps.len());
448 {
449 let data_versions = if let Some(idx) = self.active_item_index {
450 self.ensure_active_item_cache(idx);
451 &self.subform_caches[&idx].data_versions
452 } else {
453 &self.data_versions
454 };
455
456 for dep in deps {
457 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
458 let ver = if data_dep_path.starts_with("/$params") {
459 self.params_versions.get(&data_dep_path)
460 } else {
461 data_versions.get(&data_dep_path)
462 };
463 dep_versions.insert(data_dep_path.into_owned(), ver);
464 }
465 }
466
467 let computed_for_item = self.active_item_index;
469
470 if eval_key.starts_with("#/$params") {
479 let existing_result: Option<&Value> = if let Some(idx) = self.active_item_index {
480 self.entries.get(eval_key).map(|e| &e.result).or_else(|| {
482 self.subform_caches
483 .get(&idx)
484 .and_then(|c| c.entries.get(eval_key))
485 .map(|e| &e.result)
486 })
487 } else {
488 self.entries.get(eval_key).map(|e| &e.result)
489 };
490
491 let value_changed = existing_result.map_or(true, |r| r != &result);
492
493 if value_changed {
494 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(eval_key);
495
496 let mut current_path = data_path.as_ref();
499 let mut slash_count = current_path.matches('/').count();
500
501 while slash_count >= 3 {
502 self.params_versions.bump(current_path, "store_cache");
503 if let Some(last_slash) = current_path.rfind('/') {
504 current_path = ¤t_path[..last_slash];
505 slash_count -= 1;
506 } else {
507 break;
508 }
509 }
510
511 self.eval_generation += 1;
512 }
513 }
514
515 let entry = CacheEntry {
516 dep_versions,
517 result,
518 computed_for_item,
519 };
520
521 if let Some(idx) = self.active_item_index {
522 self.subform_caches
524 .get_mut(&idx)
525 .unwrap()
526 .entries
527 .insert(eval_key.to_string(), entry.clone());
528
529 if eval_key.starts_with("#/$params") {
538 let t2_dep_versions: HashMap<String, u64> = entry
539 .dep_versions
540 .iter()
541 .map(|(path, &item_ver)| {
542 let parent_ver = if path.starts_with("/$params") {
543 item_ver } else {
545 self.data_versions.get(path)
547 };
548 (path.clone(), parent_ver)
549 })
550 .collect();
551
552 let t2_entry = CacheEntry {
553 dep_versions: t2_dep_versions,
554 result: entry.result.clone(),
555 computed_for_item,
556 };
557 self.entries.insert(eval_key.to_string(), t2_entry);
558 }
559 } else {
560 self.entries.insert(eval_key.to_string(), entry);
561 }
562 }
563}
564
565pub(crate) fn diff_and_update_versions(
567 tracker: &mut VersionTracker,
568 pointer: &str,
569 old: &Value,
570 new: &Value,
571 source: &str,
572) {
573 let mut pointer_buf = String::with_capacity(128);
574 pointer_buf.push_str(pointer);
575 diff_and_update_versions_internal(tracker, &mut pointer_buf, old, new, source);
576}
577
578fn diff_and_update_versions_internal(
579 tracker: &mut VersionTracker,
580 pointer: &mut String,
581 old: &Value,
582 new: &Value,
583 source: &str,
584) {
585 if old == new {
586 return;
587 }
588
589 if crate::utils::is_debug_cache_enabled() {
590 println!("[diff_and_update_versions_internal] {} pointer={}, old={:?}, new={:?}", source, pointer, old, new);
591 }
592
593 match (old, new) {
594 (Value::Object(a), Value::Object(b)) => {
595 let mut keys = HashSet::new();
596 for k in a.keys() {
597 keys.insert(k.as_str());
598 }
599 for k in b.keys() {
600 keys.insert(k.as_str());
601 }
602
603 for key in keys {
604 if key == "$params" {
608 continue;
609 }
610
611 let a_val = a.get(key).unwrap_or(&Value::Null);
612 let b_val = b.get(key).unwrap_or(&Value::Null);
613
614 let escaped_key = key.replace('~', "~0").replace('/', "~1");
615 let old_len = pointer.len();
616 pointer.push('/');
617 pointer.push_str(&escaped_key);
618 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
619 pointer.truncate(old_len);
620 }
621 }
622 (Value::Array(a), Value::Array(b)) => {
623 let max_len = a.len().max(b.len());
624 for i in 0..max_len {
625 let a_val = a.get(i).unwrap_or(&Value::Null);
626 let b_val = b.get(i).unwrap_or(&Value::Null);
627 let old_len = pointer.len();
628 use std::fmt::Write;
629 write!(pointer, "/{}", i).unwrap();
630 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
631 pointer.truncate(old_len);
632 }
633 }
634 (old_val, new_val) => {
635 if old_val != new_val {
636 if crate::utils::is_debug_cache_enabled() { println!("[store_cache] Catch-all for {}: old={}, new={}", pointer, match old_val { Value::Null => "Null", Value::Bool(_) => "Bool", Value::Number(_) => "Number", Value::String(_) => "String", Value::Array(_) => "Array", Value::Object(_) => "Object" }, match new_val { Value::Null => "Null", Value::Bool(_) => "Bool", Value::Number(_) => "Number", Value::String(_) => "String", Value::Array(_) => "Array", Value::Object(_) => "Object" }); } tracker.bump(pointer, "diff_and_update_versions_internal");
637
638 if old_val.is_object() || old_val.is_array() {
641 traverse_and_bump(tracker, pointer, old_val);
642 }
643 if new_val.is_object() || new_val.is_array() {
644 traverse_and_bump(tracker, pointer, new_val);
645 }
646 }
647 }
648 }
649}
650
651fn traverse_and_bump(tracker: &mut VersionTracker, pointer: &mut String, val: &Value) {
655 match val {
656 Value::Object(map) => {
657 for (key, v) in map {
658 if key == "$params" {
659 continue; }
661 let escaped_key = key.replace('~', "~0").replace('/', "~1");
662 let old_len = pointer.len();
663 pointer.push('/');
664 pointer.push_str(&escaped_key);
665 tracker.bump(pointer, "traverse_and_bump1");
666 traverse_and_bump(tracker, pointer, v);
667 pointer.truncate(old_len);
668 }
669 }
670 Value::Array(arr) => {
671 for (i, v) in arr.iter().enumerate() {
672 let old_len = pointer.len();
673 use std::fmt::Write;
674 write!(pointer, "/{}", i).unwrap();
675 tracker.bump(pointer, "traverse_and_bump2");
676 traverse_and_bump(tracker, pointer, v);
677 pointer.truncate(old_len);
678 }
679 }
680 _ => {}
681 }
682}