1use std::{
4 any::Any,
5 collections::HashMap,
6 sync::{
7 Arc, Mutex,
8 atomic::{AtomicU32, Ordering},
9 },
10 time::Duration,
11};
12use tracing::debug;
13
14use crate::platform::{DEFAULT_MAX_CACHE_SIZE, DEFAULT_UNUSED_THRESHOLD};
15
16#[cfg(not(target_family = "wasm"))]
18use std::time::Instant;
19#[cfg(target_family = "wasm")]
20use web_time::Instant;
21
22#[derive(Clone, PartialEq)]
24pub enum AsyncState<T, E> {
25 Loading,
27 Success(T),
29 Error(E),
31}
32
33impl<T, E> AsyncState<T, E> {
34 pub fn is_loading(&self) -> bool {
36 matches!(self, AsyncState::Loading)
37 }
38
39 pub fn is_success(&self) -> bool {
41 matches!(self, AsyncState::Success(_))
42 }
43
44 pub fn is_error(&self) -> bool {
46 matches!(self, AsyncState::Error(_))
47 }
48
49 pub fn data(&self) -> Option<&T> {
51 match self {
52 AsyncState::Success(data) => Some(data),
53 _ => None,
54 }
55 }
56
57 pub fn error(&self) -> Option<&E> {
59 match self {
60 AsyncState::Error(error) => Some(error),
61 _ => None,
62 }
63 }
64}
65
66#[derive(Clone)]
68pub struct CacheEntry {
69 data: Arc<dyn Any + Send + Sync>,
70 cached_at: Instant,
71 reference_count: Arc<AtomicU32>,
72 last_accessed: Arc<Mutex<Instant>>,
73 access_count: Arc<AtomicU32>,
74}
75
76impl CacheEntry {
77 pub fn new<T: Clone + Send + Sync + 'static>(data: T) -> Self {
78 let now = Instant::now();
79 Self {
80 data: Arc::new(data),
81 cached_at: now,
82 reference_count: Arc::new(AtomicU32::new(0)),
83 last_accessed: Arc::new(Mutex::new(now)),
84 access_count: Arc::new(AtomicU32::new(0)),
85 }
86 }
87
88 pub fn get<T: Clone + Send + Sync + 'static>(&self) -> Option<T> {
89 if let Ok(mut last_accessed) = self.last_accessed.lock() {
91 *last_accessed = Instant::now();
92 }
93 self.access_count.fetch_add(1, Ordering::SeqCst);
94 self.data.downcast_ref::<T>().cloned()
95 }
96
97 pub fn is_expired(&self, expiration: Duration) -> bool {
98 self.cached_at.elapsed() > expiration
99 }
100
101 pub fn is_stale(&self, stale_time: Duration) -> bool {
102 self.cached_at.elapsed() > stale_time
103 }
104
105 pub fn add_reference(&self) {
107 self.reference_count.fetch_add(1, Ordering::SeqCst);
108 }
109
110 pub fn remove_reference(&self) {
112 self.reference_count.fetch_sub(1, Ordering::SeqCst);
113 }
114
115 pub fn reference_count(&self) -> u32 {
117 self.reference_count.load(Ordering::SeqCst)
118 }
119
120 pub fn access_count(&self) -> u32 {
122 self.access_count.load(Ordering::SeqCst)
123 }
124
125 pub fn is_unused_for(&self, duration: Duration) -> bool {
127 if let Ok(last_accessed) = self.last_accessed.lock() {
128 last_accessed.elapsed() > duration
129 } else {
130 false
131 }
132 }
133
134 pub fn time_since_last_access(&self) -> Duration {
136 if let Ok(last_accessed) = self.last_accessed.lock() {
137 last_accessed.elapsed()
138 } else {
139 Duration::from_secs(0)
140 }
141 }
142
143 pub fn age(&self) -> Duration {
145 self.cached_at.elapsed()
146 }
147}
148
149#[derive(Clone, Default)]
151pub struct ProviderCache {
152 pub cache: Arc<Mutex<HashMap<String, CacheEntry>>>,
153}
154
155impl ProviderCache {
156 pub fn new() -> Self {
158 Self::default()
159 }
160
161 pub fn get<T: Clone + Send + Sync + 'static>(&self, key: &str) -> Option<T> {
163 self.cache.lock().ok()?.get(key)?.get::<T>()
164 }
165
166 pub fn get_with_expiration<T: Clone + Send + Sync + 'static>(
168 &self,
169 key: &str,
170 expiration: Option<Duration>,
171 ) -> Option<T> {
172 let is_expired = {
174 let cache_guard = self.cache.lock().ok()?;
175 let entry = cache_guard.get(key)?;
176
177 if let Some(exp_duration) = expiration {
178 entry.is_expired(exp_duration)
179 } else {
180 false
181 }
182 };
183
184 if is_expired {
186 if let Ok(mut cache) = self.cache.lock() {
187 cache.remove(key);
188 debug!(
189 "๐๏ธ [CACHE EXPIRATION] Removing expired cache entry for key: {}",
190 key
191 );
192 }
193 return None;
194 }
195
196 let cache_guard = self.cache.lock().ok()?;
198 let entry = cache_guard.get(key)?;
199 entry.get::<T>()
200 }
201
202 pub fn get_with_staleness<T: Clone + Send + Sync + 'static>(
204 &self,
205 key: &str,
206 stale_time: Option<Duration>,
207 expiration: Option<Duration>,
208 ) -> Option<(T, bool)> {
209 let cache_guard = self.cache.lock().ok()?;
210 let entry = cache_guard.get(key)?;
211
212 if let Some(exp_duration) = expiration {
214 if entry.is_expired(exp_duration) {
215 return None;
216 }
217 }
218
219 let data = entry.get::<T>()?;
221
222 let is_stale = if let Some(stale_duration) = stale_time {
224 entry.is_stale(stale_duration)
225 } else {
226 false
227 };
228
229 Some((data, is_stale))
230 }
231
232 pub fn set<T: Clone + Send + Sync + 'static>(&self, key: String, value: T) {
234 if let Ok(mut cache) = self.cache.lock() {
235 cache.insert(key.clone(), CacheEntry::new(value));
236 debug!("๐ [CACHE-STORE] Stored data for key: {}", key);
237 }
238 }
239
240 pub fn remove(&self, key: &str) -> bool {
242 if let Ok(mut cache) = self.cache.lock() {
243 cache.remove(key).is_some()
244 } else {
245 false
246 }
247 }
248
249 pub fn invalidate(&self, key: &str) {
251 self.remove(key);
252 debug!(
253 "๐๏ธ [CACHE-INVALIDATE] Invalidated cache entry for key: {}",
254 key
255 );
256 }
257
258 pub fn clear(&self) {
260 if let Ok(mut cache) = self.cache.lock() {
261 let count = cache.len();
262 cache.clear();
263 debug!("๐๏ธ [CACHE-CLEAR] Cleared {} cache entries", count);
264 }
265 }
266
267 pub fn size(&self) -> usize {
269 self.cache.lock().map(|cache| cache.len()).unwrap_or(0)
270 }
271
272 pub fn cleanup_unused_entries(&self, unused_threshold: Duration) -> usize {
274 if let Ok(mut cache) = self.cache.lock() {
275 let initial_size = cache.len();
276 cache.retain(|key, entry| {
277 let should_keep =
278 !entry.is_unused_for(unused_threshold) || entry.reference_count() > 0;
279 if !should_keep {
280 debug!("๐งน [CACHE-CLEANUP] Removing unused entry: {}", key);
281 }
282 should_keep
283 });
284 let removed = initial_size - cache.len();
285 if removed > 0 {
286 debug!("๐งน [CACHE-CLEANUP] Removed {} unused entries", removed);
287 }
288 removed
289 } else {
290 0
291 }
292 }
293
294 pub fn evict_lru_entries(&self, max_size: usize) -> usize {
296 if let Ok(mut cache) = self.cache.lock() {
297 if cache.len() <= max_size {
298 return 0;
299 }
300
301 let mut entries: Vec<_> = cache.drain().collect();
303
304 entries.sort_by(|(_, a), (_, b)| {
306 a.time_since_last_access().cmp(&b.time_since_last_access())
307 });
308
309 let to_keep = entries.split_off(entries.len().saturating_sub(max_size));
311 let evicted = entries.len();
312
313 cache.extend(to_keep);
315
316 if evicted > 0 {
317 debug!(
318 "๐๏ธ [LRU-EVICT] Evicted {} entries due to cache size limit",
319 evicted
320 );
321 }
322 evicted
323 } else {
324 0
325 }
326 }
327
328 pub fn maintain(&self) -> CacheMaintenanceStats {
330 CacheMaintenanceStats {
331 unused_removed: self.cleanup_unused_entries(DEFAULT_UNUSED_THRESHOLD),
332 lru_evicted: self.evict_lru_entries(DEFAULT_MAX_CACHE_SIZE),
333 final_size: self.size(),
334 }
335 }
336
337 pub fn stats(&self) -> CacheStats {
339 if let Ok(cache) = self.cache.lock() {
340 let mut total_age = Duration::ZERO;
341 let mut total_accesses = 0;
342 let mut total_references = 0;
343
344 for entry in cache.values() {
345 total_age += entry.age();
346 total_accesses += entry.access_count();
347 total_references += entry.reference_count();
348 }
349
350 let entry_count = cache.len();
351 let avg_age = if entry_count > 0 {
352 total_age / entry_count as u32
353 } else {
354 Duration::ZERO
355 };
356
357 CacheStats {
358 entry_count,
359 total_accesses,
360 total_references,
361 avg_age,
362 total_size_bytes: entry_count * 1024, }
364 } else {
365 CacheStats::default()
366 }
367 }
368}
369
370#[derive(Debug, Clone, Default)]
372pub struct CacheMaintenanceStats {
373 pub unused_removed: usize,
374 pub lru_evicted: usize,
375 pub final_size: usize,
376}
377
378#[derive(Debug, Clone, Default)]
380pub struct CacheStats {
381 pub entry_count: usize,
382 pub total_accesses: u32,
383 pub total_references: u32,
384 pub avg_age: Duration,
385 pub total_size_bytes: usize,
386}
387
388impl CacheStats {
389 pub fn avg_accesses_per_entry(&self) -> f64 {
390 if self.entry_count > 0 {
391 self.total_accesses as f64 / self.entry_count as f64
392 } else {
393 0.0
394 }
395 }
396
397 pub fn avg_references_per_entry(&self) -> f64 {
398 if self.entry_count > 0 {
399 self.total_references as f64 / self.entry_count as f64
400 } else {
401 0.0
402 }
403 }
404}