use crate::ResolutionResult;
use rez_next_common::RezCoreError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverCacheEntry {
pub result: ResolutionResult,
pub timestamp: u64,
pub ttl: u64,
pub access_count: u64,
pub last_access: u64,
}
impl SolverCacheEntry {
pub fn new(result: ResolutionResult, ttl: u64) -> Result<Self, RezCoreError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| RezCoreError::CacheError(format!("Failed to get current time: {}", e)))?
.as_secs();
Ok(Self {
result,
timestamp: now,
ttl,
access_count: 0,
last_access: now,
})
}
pub fn is_valid(&self) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now <= self.timestamp + self.ttl
}
pub fn age(&self) -> u64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
now.saturating_sub(self.timestamp)
}
pub fn mark_accessed(&mut self) {
self.access_count += 1;
self.last_access = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverCacheConfig {
pub max_entries: usize,
pub default_ttl: u64,
pub max_memory_bytes: u64,
pub eviction_strategy: EvictionStrategy,
pub enable_persistence: bool,
pub cache_file_path: Option<String>,
}
impl Default for SolverCacheConfig {
fn default() -> Self {
Self {
max_entries: 1000,
default_ttl: 3600, max_memory_bytes: 50 * 1024 * 1024, eviction_strategy: EvictionStrategy::LRU,
enable_persistence: false,
cache_file_path: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum EvictionStrategy {
LRU,
LFU,
FIFO,
TTL,
}
#[derive(Debug)]
pub struct SolverCache {
config: SolverCacheConfig,
entries: Arc<RwLock<HashMap<String, SolverCacheEntry>>>,
access_order: Arc<RwLock<Vec<String>>>,
stats: Arc<RwLock<SolverCacheStats>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolverCacheStats {
pub hits: u64,
pub misses: u64,
pub entries: usize,
pub memory_usage_bytes: u64,
pub evictions: u64,
pub hit_rate: f64,
}
impl Default for SolverCacheStats {
fn default() -> Self {
Self {
hits: 0,
misses: 0,
entries: 0,
memory_usage_bytes: 0,
evictions: 0,
hit_rate: 0.0,
}
}
}
impl SolverCache {
pub fn new(default_ttl: u64) -> Self {
let config = SolverCacheConfig {
default_ttl,
..Default::default()
};
Self::with_config(config)
}
pub fn with_config(config: SolverCacheConfig) -> Self {
Self {
config,
entries: Arc::new(RwLock::new(HashMap::new())),
access_order: Arc::new(RwLock::new(Vec::new())),
stats: Arc::new(RwLock::new(SolverCacheStats::default())),
}
}
pub async fn get(&self, key: &str) -> Option<ResolutionResult> {
let mut entries = self.entries.write().await;
let mut stats = self.stats.write().await;
if let Some(entry) = entries.get_mut(key) {
if entry.is_valid() {
entry.mark_accessed();
stats.hits += 1;
self.update_access_order(key).await;
self.update_hit_rate(&mut stats);
return Some(entry.result.clone());
} else {
entries.remove(key);
self.remove_from_access_order(key).await;
}
}
stats.misses += 1;
self.update_hit_rate(&mut stats);
None
}
pub async fn put(&self, key: String, result: ResolutionResult) {
let entry = match SolverCacheEntry::new(result, self.config.default_ttl) {
Ok(entry) => entry,
Err(_) => return, };
{
let mut entries = self.entries.write().await;
if entries.len() >= self.config.max_entries {
self.evict_entries(&mut entries).await;
}
entries.insert(key.clone(), entry);
}
self.update_access_order(&key).await;
{
let mut stats = self.stats.write().await;
stats.entries = {
let entries = self.entries.read().await;
entries.len()
};
stats.memory_usage_bytes = self.estimate_memory_usage().await;
}
}
pub async fn remove(&self, key: &str) -> bool {
let mut entries = self.entries.write().await;
let removed = entries.remove(key).is_some();
if removed {
self.remove_from_access_order(key).await;
let mut stats = self.stats.write().await;
stats.entries = entries.len();
}
removed
}
pub async fn clear(&self) {
{
let mut entries = self.entries.write().await;
entries.clear();
}
{
let mut access_order = self.access_order.write().await;
access_order.clear();
}
{
let mut stats = self.stats.write().await;
*stats = SolverCacheStats::default();
}
}
pub async fn get_stats(&self) -> SolverCacheStats {
let stats = self.stats.read().await;
stats.clone()
}
pub async fn cleanup(&self) {
let mut entries = self.entries.write().await;
let mut expired_keys = Vec::new();
for (key, entry) in entries.iter() {
if !entry.is_valid() {
expired_keys.push(key.clone());
}
}
for key in &expired_keys {
entries.remove(key);
self.remove_from_access_order(key).await;
}
{
let mut stats = self.stats.write().await;
stats.entries = entries.len();
stats.memory_usage_bytes = self.estimate_memory_usage().await;
}
}
async fn evict_entries(&self, entries: &mut HashMap<String, SolverCacheEntry>) {
let evict_count = (entries.len() as f64 * 0.1).max(1.0) as usize;
match self.config.eviction_strategy {
EvictionStrategy::LRU => {
self.evict_lru(entries, evict_count).await;
}
EvictionStrategy::LFU => {
self.evict_lfu(entries, evict_count).await;
}
EvictionStrategy::FIFO => {
self.evict_fifo(entries, evict_count).await;
}
EvictionStrategy::TTL => {
self.evict_expired(entries).await;
}
}
{
let mut stats = self.stats.write().await;
stats.evictions += evict_count as u64;
}
}
async fn evict_lru(&self, entries: &mut HashMap<String, SolverCacheEntry>, count: usize) {
let access_order = self.access_order.read().await;
let keys_to_evict: Vec<String> = access_order.iter().take(count).cloned().collect();
for key in &keys_to_evict {
entries.remove(key);
}
drop(access_order);
for key in &keys_to_evict {
self.remove_from_access_order(key).await;
}
}
async fn evict_lfu(&self, entries: &mut HashMap<String, SolverCacheEntry>, count: usize) {
let mut entries_by_frequency: Vec<_> = entries
.iter()
.map(|(key, entry)| (key.clone(), entry.access_count))
.collect();
entries_by_frequency.sort_by(|a, b| a.1.cmp(&b.1));
let keys_to_evict: Vec<String> = entries_by_frequency
.iter()
.take(count)
.map(|(key, _)| key.clone())
.collect();
for key in &keys_to_evict {
entries.remove(key);
self.remove_from_access_order(key).await;
}
}
async fn evict_fifo(&self, entries: &mut HashMap<String, SolverCacheEntry>, count: usize) {
let mut entries_by_age: Vec<_> = entries
.iter()
.map(|(key, entry)| (key.clone(), entry.timestamp))
.collect();
entries_by_age.sort_by(|a, b| a.1.cmp(&b.1));
let keys_to_evict: Vec<String> = entries_by_age
.iter()
.take(count)
.map(|(key, _)| key.clone())
.collect();
for key in &keys_to_evict {
entries.remove(key);
self.remove_from_access_order(key).await;
}
}
async fn evict_expired(&self, entries: &mut HashMap<String, SolverCacheEntry>) {
let expired_keys: Vec<String> = entries
.iter()
.filter(|(_, entry)| !entry.is_valid())
.map(|(key, _)| key.clone())
.collect();
for key in &expired_keys {
entries.remove(key);
self.remove_from_access_order(key).await;
}
}
async fn update_access_order(&self, key: &str) {
let mut access_order = self.access_order.write().await;
access_order.retain(|k| k != key);
access_order.push(key.to_string());
}
async fn remove_from_access_order(&self, key: &str) {
let mut access_order = self.access_order.write().await;
access_order.retain(|k| k != key);
}
async fn estimate_memory_usage(&self) -> u64 {
let entries = self.entries.read().await;
let mut total_size = 0u64;
for (key, entry) in entries.iter() {
total_size += key.len() as u64;
total_size += entry.result.packages.len() as u64 * 1024; total_size += 128; }
total_size
}
fn update_hit_rate(&self, stats: &mut SolverCacheStats) {
let total_requests = stats.hits + stats.misses;
if total_requests > 0 {
stats.hit_rate = stats.hits as f64 / total_requests as f64;
}
}
}
impl Default for SolverCache {
fn default() -> Self {
Self::new(3600) }
}