Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/validation/cache.rs

9.3 KiB, 57 runs

created by r1870400018:8618, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! A validator that remembers its answers.
2//!
3//! Three LRU caches, one each for CalClock, CalendarDate and ClockTime, keyed
4//! on the fields that decide the outcome.
5//!
6//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
7//! Anthropic Claude
8
9use crate::{
10 calendar::CalendarDate,
11 clock::ClockTime,
12 time::CalClock,
13 validation::{CalClockValidator, ValidationResult},
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17
18use std::{
19 collections::HashMap,
20 hash::Hash,
21};
22
23/// # Examples
24///
25/// ```ignore
26/// use oxedyne_fe2o3_datime::validation::{CachedValidator, CalClockValidator};
27///
28/// let base_validator = CalClockValidator::new();
29/// let mut cached_validator = CachedValidator::new(base_validator);
30///
31/// // First validation - computed and cached
32/// let result1 = cached_validator.validate_calclock(&some_calclock);
33///
34/// // Second validation of same CalClock - returned from cache
35/// let result2 = cached_validator.validate_calclock(&some_calclock);
36/// ```
37#[derive(Debug)]
38pub struct CachedValidator {
39 validator: CalClockValidator,
40 calclock_cache: ValidationCache<CalClockKey, ValidationResult>,
41 date_cache: ValidationCache<DateKey, ValidationResult>,
42 time_cache: ValidationCache<TimeKey, ValidationResult>,
43 cache_hits: u64,
44 cache_misses: u64,
45}
46
47impl CachedValidator {
48 pub fn new(validator: CalClockValidator) -> Self {
49 Self {
50 validator,
51 calclock_cache: ValidationCache::new(1000),
52 date_cache: ValidationCache::new(1000),
53 time_cache: ValidationCache::new(1000),
54 cache_hits: 0,
55 cache_misses: 0,
56 }
57 }
58
59 pub fn with_cache_sizes(
60 validator: CalClockValidator,
61 calclock_cache_size: usize,
62 date_cache_size: usize,
63 time_cache_size: usize,
64 ) -> Self {
65 Self {
66 validator,
67 calclock_cache: ValidationCache::new(calclock_cache_size),
68 date_cache: ValidationCache::new(date_cache_size),
69 time_cache: ValidationCache::new(time_cache_size),
70 cache_hits: 0,
71 cache_misses: 0,
72 }
73 }
74
75 pub fn validate_calclock(&mut self, calclock: &CalClock) -> ValidationResult {
76 let key = CalClockKey::from_calclock(calclock);
77
78 if let Some(cached_result) = self.calclock_cache.get(&key) {
79 self.cache_hits += 1;
80 return cached_result.clone();
81 }
82
83 self.cache_misses += 1;
84 let result = self.validator.validate_calclock(calclock);
85 self.calclock_cache.insert(key, result.clone());
86 result
87 }
88
89 pub fn validate_date(&mut self, date: &CalendarDate) -> ValidationResult {
90 let key = DateKey::from_date(date);
91
92 if let Some(cached_result) = self.date_cache.get(&key) {
93 self.cache_hits += 1;
94 return cached_result.clone();
95 }
96
97 self.cache_misses += 1;
98 let result = self.validator.validate_date(date);
99 self.date_cache.insert(key, result.clone());
100 result
101 }
102
103 pub fn validate_time(&mut self, time: &ClockTime) -> ValidationResult {
104 let key = TimeKey::from_time(time);
105
106 if let Some(cached_result) = self.time_cache.get(&key) {
107 self.cache_hits += 1;
108 return cached_result.clone();
109 }
110
111 self.cache_misses += 1;
112 let result = self.validator.validate_time(time);
113 self.time_cache.insert(key, result.clone());
114 result
115 }
116
117 pub fn is_valid_calclock(&mut self, calclock: &CalClock) -> bool {
118 self.validate_calclock(calclock).is_ok()
119 }
120
121 pub fn is_valid_date(&mut self, date: &CalendarDate) -> bool {
122 self.validate_date(date).is_ok()
123 }
124
125 pub fn is_valid_time(&mut self, time: &ClockTime) -> bool {
126 self.validate_time(time).is_ok()
127 }
128
129 pub fn cache_stats(&self) -> CacheStats {
130 CacheStats {
131 hits: self.cache_hits,
132 misses: self.cache_misses,
133 calclock_cache_size: self.calclock_cache.len(),
134 date_cache_size: self.date_cache.len(),
135 time_cache_size: self.time_cache.len(),
136 calclock_cache_capacity: self.calclock_cache.capacity(),
137 date_cache_capacity: self.date_cache.capacity(),
138 time_cache_capacity: self.time_cache.capacity(),
139 }
140 }
141
142 /// A fraction between zero and one, not a percentage.
143 pub fn hit_rate(&self) -> f64 {
144 let total = self.cache_hits + self.cache_misses;
145 if total == 0 {
146 0.0
147 } else {
148 self.cache_hits as f64 / total as f64
149 }
150 }
151
152 pub fn clear_cache(&mut self) {
153 self.calclock_cache.clear();
154 self.date_cache.clear();
155 self.time_cache.clear();
156 self.cache_hits = 0;
157 self.cache_misses = 0;
158 }
159
160 pub fn validator(&self) -> &CalClockValidator {
161 &self.validator
162 }
163
164 pub fn validator_mut(&mut self) -> &mut CalClockValidator {
165 // Clear cache when validator is modified
166 self.clear_cache();
167 &mut self.validator
168 }
169}
170
171#[derive(Debug)]
172pub struct ValidationCache<K, V> {
173 data: HashMap<K, CacheEntry<V>>,
174 capacity: usize,
175 access_counter: u64,
176}
177
178impl<K: Hash + Eq + Clone, V: Clone> ValidationCache<K, V> {
179 pub fn new(capacity: usize) -> Self {
180 Self {
181 data: HashMap::new(),
182 capacity,
183 access_counter: 0,
184 }
185 }
186
187 pub fn get(&mut self, key: &K) -> Option<V> {
188 if let Some(entry) = self.data.get_mut(key) {
189 entry.last_accessed = self.access_counter;
190 self.access_counter += 1;
191 Some(entry.value.clone())
192 } else {
193 None
194 }
195 }
196
197 /// Evicts the least recently used entry when the cache is full.
198 pub fn insert(&mut self, key: K, value: V) {
199 if self.data.len() >= self.capacity && !self.data.contains_key(&key) {
200 self.evict_lru();
201 }
202
203 let entry = CacheEntry {
204 value,
205 last_accessed: self.access_counter,
206 };
207
208 self.data.insert(key, entry);
209 self.access_counter += 1;
210 }
211
212 fn evict_lru(&mut self) {
213 if let Some((key_to_remove, _)) = self.data
214 .iter()
215 .min_by_key(|(_, entry)| entry.last_accessed)
216 .map(|(k, v)| (k.clone(), v.last_accessed))
217 {
218 self.data.remove(&key_to_remove);
219 }
220 }
221
222 pub fn len(&self) -> usize {
223 self.data.len()
224 }
225
226 pub fn is_empty(&self) -> bool {
227 self.data.is_empty()
228 }
229
230 pub fn capacity(&self) -> usize {
231 self.capacity
232 }
233
234 pub fn clear(&mut self) {
235 self.data.clear();
236 self.access_counter = 0;
237 }
238}
239
240#[derive(Debug, Clone)]
241struct CacheEntry<V> {
242 value: V,
243 last_accessed: u64,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247struct CalClockKey {
248 year: i32,
249 month: u8,
250 day: u8,
251 hour: u8,
252 minute: u8,
253 second: u8,
254 nanosecond: u32,
255 // Note: We don't include timezone in the key for simplicity
256 // In a production system, you'd want to include timezone info
257}
258
259impl CalClockKey {
260 fn from_calclock(calclock: &CalClock) -> Self {
261 Self {
262 year: calclock.year(),
263 month: calclock.month(),
264 day: calclock.day(),
265 hour: calclock.hour(),
266 minute: calclock.minute(),
267 second: calclock.second(),
268 nanosecond: calclock.nanosecond(),
269 }
270 }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Hash)]
274struct DateKey {
275 year: i32,
276 month: u8,
277 day: u8,
278}
279
280impl DateKey {
281 fn from_date(date: &CalendarDate) -> Self {
282 Self {
283 year: date.year(),
284 month: date.month(),
285 day: date.day(),
286 }
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Hash)]
291struct TimeKey {
292 hour: u8,
293 minute: u8,
294 second: u8,
295 nanosecond: u32,
296}
297
298impl TimeKey {
299 fn from_time(time: &ClockTime) -> Self {
300 Self {
301 hour: time.hour().of(),
302 minute: time.minute().of(),
303 second: time.second().of(),
304 nanosecond: time.nanosecond().of(),
305 }
306 }
307}
308
309#[derive(Debug, Clone)]
310pub struct CacheStats {
311 pub hits: u64,
312 pub misses: u64,
313 pub calclock_cache_size: usize,
314 pub date_cache_size: usize,
315 pub time_cache_size: usize,
316 pub calclock_cache_capacity: usize,
317 pub date_cache_capacity: usize,
318 pub time_cache_capacity: usize,
319}
320
321impl CacheStats {
322 /// A fraction between zero and one, not a percentage.
323 pub fn hit_rate(&self) -> f64 {
324 let total = self.hits + self.misses;
325 if total == 0 {
326 0.0
327 } else {
328 self.hits as f64 / total as f64
329 }
330 }
331
332 pub fn total_operations(&self) -> u64 {
333 self.hits + self.misses
334 }
335
336 pub fn format(&self) -> String {
337 format!(
338 "Cache Stats: {:.1}% hit rate ({} hits, {} misses)\n\
339 CalClock cache: {}/{} entries\n\
340 Date cache: {}/{} entries\n\
341 Time cache: {}/{} entries",
342 self.hit_rate() * 100.0,
343 self.hits,
344 self.misses,
345 self.calclock_cache_size,
346 self.calclock_cache_capacity,
347 self.date_cache_size,
348 self.date_cache_capacity,
349 self.time_cache_size,
350 self.time_cache_capacity
351 )
352 }
353}