use super::types::AdaptiveCacheConfig;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub(crate) struct AdaptiveCacheEntry<V> {
pub value: V,
pub created_at: Instant,
pub last_accessed: Instant,
pub ttl_seconds: AtomicU64,
pub access_count: AtomicUsize,
pub access_window: VecDeque<Instant>,
}
impl<V> AdaptiveCacheEntry<V> {
pub(crate) fn new(value: V, default_ttl: Duration) -> Self {
let now = Instant::now();
Self {
value,
created_at: now,
last_accessed: now,
ttl_seconds: AtomicU64::new(default_ttl.as_secs()),
access_count: AtomicUsize::new(0),
access_window: VecDeque::new(),
}
}
pub(crate) fn record_access(&mut self, now: Instant, config: &AdaptiveCacheConfig) {
self.last_accessed = now;
self.access_window.push_back(now);
if self.access_window.len() > config.window_size {
self.access_window.pop_front();
}
let count = self.access_count.fetch_add(1, Ordering::SeqCst) + 1;
let current_ttl = self.ttl_seconds.load(Ordering::SeqCst);
let new_ttl = self.calculate_adaptive_ttl(count, current_ttl, config);
self.ttl_seconds.store(new_ttl, Ordering::SeqCst);
}
fn calculate_adaptive_ttl(
&self,
access_count: usize,
current_ttl: u64,
config: &AdaptiveCacheConfig,
) -> u64 {
let mut new_ttl = current_ttl as f64;
let adaptation = config.adaptation_rate;
if access_count >= config.hot_threshold {
new_ttl *= 1.0 + adaptation;
} else if access_count <= config.cold_threshold {
new_ttl *= 1.0 - adaptation;
}
let min_secs = config.min_ttl.as_secs() as f64;
let max_secs = config.max_ttl.as_secs() as f64;
new_ttl = new_ttl.clamp(min_secs, max_secs);
new_ttl as u64
}
pub(crate) fn is_expired(&self, now: Instant) -> bool {
let ttl_seconds = self.ttl_seconds.load(Ordering::SeqCst);
if ttl_seconds == 0 {
return false; }
let expires_at = self.created_at + Duration::from_secs(ttl_seconds);
now >= expires_at
}
pub(crate) fn ttl(&self) -> Duration {
Duration::from_secs(self.ttl_seconds.load(Ordering::SeqCst))
}
pub(crate) fn access_count(&self) -> usize {
self.access_count.load(Ordering::SeqCst)
}
pub(crate) fn is_hot(&self, hot_threshold: usize) -> bool {
self.access_count.load(Ordering::SeqCst) >= hot_threshold
}
pub(crate) fn is_cold(&self, cold_threshold: usize) -> bool {
self.access_count.load(Ordering::SeqCst) <= cold_threshold
}
}
impl<V: Clone> Clone for AdaptiveCacheEntry<V> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
created_at: self.created_at,
last_accessed: self.last_accessed,
ttl_seconds: AtomicU64::new(self.ttl_seconds.load(Ordering::SeqCst)),
access_count: AtomicUsize::new(self.access_count.load(Ordering::SeqCst)),
access_window: self.access_window.clone(),
}
}
}