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!(
28 "[store_cache] BUMPING for {} -> {} ({})",
29 path,
30 current + 1,
31 source
32 );
33 }
34 self.versions.insert(path.to_string(), current + 1);
36 }
37
38 pub fn merge_from(&mut self, other: &VersionTracker) {
42 for (k, v) in &other.versions {
43 let current = self.versions.get(k).copied().unwrap_or(0);
44 self.versions.insert(k.clone(), current.max(*v));
45 }
46 }
47
48 pub fn merge_from_params(&mut self, other: &VersionTracker) {
52 for (k, v) in &other.versions {
53 if k.starts_with("/$params") {
54 let current = self.versions.get(k).copied().unwrap_or(0);
55 self.versions.insert(k.clone(), current.max(*v));
56 }
57 }
58 }
59
60 pub fn any_bumped_with_prefix(&self, prefix: &str) -> bool {
63 self.versions
64 .iter()
65 .any(|(k, &v)| k.starts_with(prefix) && v > 0)
66 }
67
68 pub fn any_newly_bumped_with_prefix(&self, prefix: &str, baseline: &VersionTracker) -> bool {
72 self.versions
73 .iter()
74 .any(|(k, &v)| k.starts_with(prefix) && v > baseline.get(k))
75 }
76
77 pub fn versions(&self) -> impl Iterator<Item = (&str, &u64)> {
79 self.versions.iter().map(|(k, v)| (k.as_str(), v))
80 }
81}
82
83#[derive(Clone)]
85pub struct CacheEntry {
86 pub dep_versions: HashMap<String, u64>,
87 pub result: Value,
88 pub computed_for_item: Option<usize>,
93}
94
95#[derive(Default, Clone)]
97pub struct SubformItemCache {
98 pub data_versions: VersionTracker,
99 pub entries: HashMap<String, CacheEntry>,
100 pub item_snapshot: Value,
101 pub evaluated_schema: Option<Value>,
105}
106
107impl SubformItemCache {
108 pub fn new() -> Self {
109 Self {
110 data_versions: VersionTracker::new(),
111 entries: HashMap::new(),
112 item_snapshot: Value::Null,
113 evaluated_schema: None,
114 }
115 }
116}
117
118#[derive(Clone)]
120pub struct EvalCache {
121 pub data_versions: VersionTracker,
122 pub params_versions: VersionTracker,
123 pub entries: HashMap<String, CacheEntry>,
124
125 pub active_item_index: Option<usize>,
126 pub subform_caches: HashMap<usize, SubformItemCache>,
127
128 pub eval_generation: u64,
132 pub last_evaluated_generation: u64,
133
134 pub main_form_snapshot: Option<Value>,
138}
139
140impl Default for EvalCache {
141 fn default() -> Self {
142 Self::new()
143 }
144}
145
146impl EvalCache {
147 pub fn new() -> Self {
148 Self {
149 data_versions: VersionTracker::new(),
150 params_versions: VersionTracker::new(),
151 entries: HashMap::new(),
152 active_item_index: None,
153 subform_caches: HashMap::new(),
154 eval_generation: 0,
155 last_evaluated_generation: u64::MAX, main_form_snapshot: None,
157 }
158 }
159
160 pub fn clear(&mut self) {
161 self.data_versions = VersionTracker::new();
162 self.params_versions = VersionTracker::new();
163 self.entries.clear();
164 self.active_item_index = None;
165 self.subform_caches.clear();
166 self.eval_generation = 0;
167 self.last_evaluated_generation = u64::MAX;
168 self.main_form_snapshot = None;
169 }
170
171 pub fn prune_subform_caches(&mut self, current_count: usize) {
175 self.subform_caches.retain(|&idx, _| idx < current_count);
176 }
177
178 pub fn invalidate_params_tables_for_item(&mut self, idx: usize, table_keys: &[String]) {
185 for key in table_keys {
187 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(key);
188 self.params_versions
189 .bump(&data_path, "invalidate_params_tables_for_item");
190 self.eval_generation += 1;
191 }
192
193 if let Some(item_cache) = self.subform_caches.get_mut(&idx) {
195 for key in table_keys {
196 item_cache.entries.remove(key);
197 }
198 }
199 }
200
201 pub fn needs_full_evaluation(&self) -> bool {
203 self.eval_generation != self.last_evaluated_generation
204 }
205
206 pub fn mark_evaluated(&mut self) {
208 self.last_evaluated_generation = self.eval_generation;
209 }
210
211 pub(crate) fn ensure_active_item_cache(&mut self, idx: usize) {
212 self.subform_caches
213 .entry(idx)
214 .or_insert_with(SubformItemCache::new);
215 }
216
217 pub fn set_active_item(&mut self, idx: usize) {
218 self.active_item_index = Some(idx);
219 self.ensure_active_item_cache(idx);
220 }
221
222 pub fn clear_active_item(&mut self) {
223 self.active_item_index = None;
224 }
225
226 pub fn store_snapshot_and_diff_versions(&mut self, old: &Value, new: &Value) {
228 if let Some(idx) = self.active_item_index {
229 self.ensure_active_item_cache(idx);
230 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
231 diff_and_update_versions(
232 &mut sub_cache.data_versions,
233 "",
234 old,
235 new,
236 "subform store_snapshot_and_diff_versions",
237 );
238 sub_cache.item_snapshot = new.clone();
239 } else {
240 diff_and_update_versions(
241 &mut self.data_versions,
242 "",
243 old,
244 new,
245 "store_snapshot_and_diff_versions",
246 );
247 }
248 }
249
250 pub fn get_active_snapshot(&self) -> Value {
251 if let Some(idx) = self.active_item_index {
252 self.subform_caches
253 .get(&idx)
254 .map(|c| c.item_snapshot.clone())
255 .unwrap_or(Value::Null)
256 } else {
257 Value::Null
258 }
259 }
260
261 pub fn diff_active_item(
262 &mut self,
263 field_key: &str,
264 old_sub_data: &Value,
265 new_sub_data: &Value,
266 ) {
267 if let Some(idx) = self.active_item_index {
268 self.ensure_active_item_cache(idx);
269 let sub_cache = self.subform_caches.get_mut(&idx).unwrap();
270
271 let empty = Value::Null;
273 let old_item = old_sub_data.get(field_key).unwrap_or(&empty);
274 let new_item = new_sub_data.get(field_key).unwrap_or(&empty);
275
276 diff_and_update_versions(
277 &mut sub_cache.data_versions,
278 &format!("/{}", field_key),
279 old_item,
280 new_item,
281 format!("diff_active_item {}", field_key).as_str(),
282 );
283 sub_cache.item_snapshot = new_sub_data.clone();
284 }
285 }
286
287 pub fn bump_data_version(&mut self, data_path: &str) {
288 self.eval_generation += 1;
291 if let Some(idx) = self.active_item_index {
292 if let Some(cache) = self.subform_caches.get_mut(&idx) {
293 cache.data_versions.bump(data_path, "bump_data_version1");
294 }
295 } else {
296 self.data_versions.bump(data_path, "bump_data_version2");
297 }
298 }
299
300 pub fn bump_params_version(&mut self, data_path: &str) {
301 self.params_versions.bump(data_path, "bump_params_version");
302 self.eval_generation += 1;
303 }
304
305 pub fn check_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
311 if let Some(idx) = self.active_item_index {
312 if let Some(cache) = self.subform_caches.get(&idx) {
314 if let Some(hit) =
315 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
316 {
317 if crate::utils::is_debug_cache_enabled() {
318 println!("Cache HIT [T1 idx={}] {}", idx, eval_key);
319 }
320 return Some(hit);
321 }
322 }
323
324 let item_data_versions = self
330 .subform_caches
331 .get(&idx)
332 .map(|c| &c.data_versions)
333 .unwrap_or(&self.data_versions);
334
335 if let Some(entry) = self.entries.get(eval_key) {
336 let index_safe = match entry.computed_for_item {
337 None => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
342 Some(stored_idx) if stored_idx == idx => true,
343 _ => entry.dep_versions.keys().all(|p| p.starts_with("/$params")),
344 };
345 if index_safe {
346 let result =
347 self.validate_entry(eval_key, deps, &self.entries, item_data_versions);
348 if result.is_some() {
349 if crate::utils::is_debug_cache_enabled() {
350 println!(
351 "Cache HIT [T2 idx={} for={:?}] {}",
352 idx, entry.computed_for_item, eval_key
353 );
354 }
355 }
356 return result;
357 }
358 }
359
360 None
361 } else {
362 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
363 }
364 }
365
366 pub fn check_table_cache(&self, eval_key: &str, deps: &IndexSet<String>) -> Option<Value> {
378 if let Some(idx) = self.active_item_index {
379 if let Some(cache) = self.subform_caches.get(&idx) {
381 if let Some(hit) =
382 self.validate_entry(eval_key, deps, &cache.entries, &cache.data_versions)
383 {
384 if crate::utils::is_debug_cache_enabled() {
385 println!("Cache HIT [T1 table idx={}] {}", idx, eval_key);
386 }
387 return Some(hit);
388 }
389 }
390
391 let result = self.validate_entry(eval_key, deps, &self.entries, &self.data_versions);
400 if result.is_some() {
401 if crate::utils::is_debug_cache_enabled() {
402 println!("Cache HIT [T2 table idx={}] {}", idx, eval_key);
403 }
404 }
405 result
406 } else {
407 self.validate_entry(eval_key, deps, &self.entries, &self.data_versions)
408 }
409 }
410
411 fn validate_entry(
412 &self,
413 eval_key: &str,
414 deps: &IndexSet<String>,
415 entries: &HashMap<String, CacheEntry>,
416 data_versions: &VersionTracker,
417 ) -> Option<Value> {
418 let entry = entries.get(eval_key)?;
419 for dep in deps {
420 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
421
422 let current_ver = if data_dep_path.starts_with("/$params") {
423 self.params_versions.get(&data_dep_path)
424 } else {
425 data_versions.get(&data_dep_path)
426 };
427
428 if let Some(&cached_ver) = entry.dep_versions.get(data_dep_path.as_ref()) {
429 if current_ver != cached_ver {
430 if crate::utils::is_debug_cache_enabled() {
431 println!(
432 "Cache MISS {}: dep {} changed ({} -> {})",
433 eval_key, data_dep_path, cached_ver, current_ver
434 );
435 }
436 return None;
437 }
438 } else {
439 if crate::utils::is_debug_cache_enabled() {
440 println!(
441 "Cache MISS {}: dep {} missing from cache entry",
442 eval_key, data_dep_path
443 );
444 }
445 return None;
446 }
447 }
448 if crate::utils::is_debug_cache_enabled() {
449 println!("Cache HIT {}", eval_key);
450 }
451 Some(entry.result.clone())
452 }
453
454 pub fn store_cache(&mut self, eval_key: &str, deps: &IndexSet<String>, result: Value) {
462 let mut dep_versions = HashMap::with_capacity(deps.len());
466 {
467 let data_versions = if let Some(idx) = self.active_item_index {
468 self.ensure_active_item_cache(idx);
469 &self.subform_caches[&idx].data_versions
470 } else {
471 &self.data_versions
472 };
473
474 for dep in deps {
475 let data_dep_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(dep);
476 let ver = if data_dep_path.starts_with("/$params") {
477 self.params_versions.get(&data_dep_path)
478 } else {
479 data_versions.get(&data_dep_path)
480 };
481 dep_versions.insert(data_dep_path.into_owned(), ver);
482 }
483 }
484
485 let computed_for_item = self.active_item_index;
487
488 if eval_key.starts_with("#/$params") {
497 let existing_result: Option<&Value> = if let Some(idx) = self.active_item_index {
498 self.entries.get(eval_key).map(|e| &e.result).or_else(|| {
500 self.subform_caches
501 .get(&idx)
502 .and_then(|c| c.entries.get(eval_key))
503 .map(|e| &e.result)
504 })
505 } else {
506 self.entries.get(eval_key).map(|e| &e.result)
507 };
508
509 let value_changed = existing_result.map_or(true, |r| r != &result);
510
511 if value_changed {
512 let data_path = crate::jsoneval::path_utils::schema_path_to_data_pointer(eval_key);
513
514 let mut current_path = data_path.as_ref();
517 let mut slash_count = current_path.matches('/').count();
518
519 while slash_count >= 3 {
520 self.params_versions.bump(current_path, "store_cache");
521 if let Some(last_slash) = current_path.rfind('/') {
522 current_path = ¤t_path[..last_slash];
523 slash_count -= 1;
524 } else {
525 break;
526 }
527 }
528
529 self.eval_generation += 1;
530 }
531 }
532
533 let entry = CacheEntry {
534 dep_versions,
535 result,
536 computed_for_item,
537 };
538
539 if let Some(idx) = self.active_item_index {
540 self.subform_caches
542 .get_mut(&idx)
543 .unwrap()
544 .entries
545 .insert(eval_key.to_string(), entry.clone());
546
547 if eval_key.starts_with("#/$params") {
556 let t2_dep_versions: HashMap<String, u64> = entry
557 .dep_versions
558 .iter()
559 .map(|(path, &item_ver)| {
560 let parent_ver = if path.starts_with("/$params") {
561 item_ver } else {
563 self.data_versions.get(path)
565 };
566 (path.clone(), parent_ver)
567 })
568 .collect();
569
570 let t2_entry = CacheEntry {
571 dep_versions: t2_dep_versions,
572 result: entry.result.clone(),
573 computed_for_item,
574 };
575 self.entries.insert(eval_key.to_string(), t2_entry);
576 }
577 } else {
578 self.entries.insert(eval_key.to_string(), entry);
579 }
580 }
581}
582
583pub(crate) fn diff_and_update_versions(
585 tracker: &mut VersionTracker,
586 pointer: &str,
587 old: &Value,
588 new: &Value,
589 source: &str,
590) {
591 let mut pointer_buf = String::with_capacity(128);
592 pointer_buf.push_str(pointer);
593 diff_and_update_versions_internal(tracker, &mut pointer_buf, old, new, source);
594}
595
596fn diff_and_update_versions_internal(
597 tracker: &mut VersionTracker,
598 pointer: &mut String,
599 old: &Value,
600 new: &Value,
601 source: &str,
602) {
603 if old == new {
604 return;
605 }
606
607 if crate::utils::is_debug_cache_enabled() {
608 println!(
609 "[diff_and_update_versions_internal] {} pointer={}, old={:?}, new={:?}",
610 source, pointer, old, new
611 );
612 }
613
614 match (old, new) {
615 (Value::Object(a), Value::Object(b)) => {
616 let mut keys = HashSet::new();
617 for k in a.keys() {
618 keys.insert(k.as_str());
619 }
620 for k in b.keys() {
621 keys.insert(k.as_str());
622 }
623
624 for key in keys {
625 if key == "$params" {
629 continue;
630 }
631
632 let a_val = a.get(key).unwrap_or(&Value::Null);
633 let b_val = b.get(key).unwrap_or(&Value::Null);
634
635 let escaped_key = key.replace('~', "~0").replace('/', "~1");
636 let old_len = pointer.len();
637 pointer.push('/');
638 pointer.push_str(&escaped_key);
639 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
640 pointer.truncate(old_len);
641 }
642 }
643 (Value::Array(a), Value::Array(b)) => {
644 let max_len = a.len().max(b.len());
645 for i in 0..max_len {
646 let a_val = a.get(i).unwrap_or(&Value::Null);
647 let b_val = b.get(i).unwrap_or(&Value::Null);
648 let old_len = pointer.len();
649 use std::fmt::Write;
650 write!(pointer, "/{}", i).unwrap();
651 diff_and_update_versions_internal(tracker, pointer, a_val, b_val, source);
652 pointer.truncate(old_len);
653 }
654 }
655 (old_val, new_val) => {
656 if old_val != new_val {
657 if crate::utils::is_debug_cache_enabled() {
658 println!(
659 "[store_cache] Catch-all for {}: old={}, new={}",
660 pointer,
661 match old_val {
662 Value::Null => "Null",
663 Value::Bool(_) => "Bool",
664 Value::Number(_) => "Number",
665 Value::String(_) => "String",
666 Value::Array(_) => "Array",
667 Value::Object(_) => "Object",
668 },
669 match new_val {
670 Value::Null => "Null",
671 Value::Bool(_) => "Bool",
672 Value::Number(_) => "Number",
673 Value::String(_) => "String",
674 Value::Array(_) => "Array",
675 Value::Object(_) => "Object",
676 }
677 );
678 }
679 tracker.bump(pointer, "diff_and_update_versions_internal");
680
681 if old_val.is_object() || old_val.is_array() {
684 traverse_and_bump(tracker, pointer, old_val);
685 }
686 if new_val.is_object() || new_val.is_array() {
687 traverse_and_bump(tracker, pointer, new_val);
688 }
689 }
690 }
691 }
692}
693
694fn traverse_and_bump(tracker: &mut VersionTracker, pointer: &mut String, val: &Value) {
698 match val {
699 Value::Object(map) => {
700 for (key, v) in map {
701 if key == "$params" {
702 continue; }
704 let escaped_key = key.replace('~', "~0").replace('/', "~1");
705 let old_len = pointer.len();
706 pointer.push('/');
707 pointer.push_str(&escaped_key);
708 tracker.bump(pointer, "traverse_and_bump1");
709 traverse_and_bump(tracker, pointer, v);
710 pointer.truncate(old_len);
711 }
712 }
713 Value::Array(arr) => {
714 for (i, v) in arr.iter().enumerate() {
715 let old_len = pointer.len();
716 use std::fmt::Write;
717 write!(pointer, "/{}", i).unwrap();
718 tracker.bump(pointer, "traverse_and_bump2");
719 traverse_and_bump(tracker, pointer, v);
720 pointer.truncate(old_len);
721 }
722 }
723 _ => {}
724 }
725}