use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use std::time::SystemTime;
use std::hash::{Hash, Hasher, DefaultHasher};
#[derive(Debug, Clone)]
pub struct CacheEntry {
pub script: Vec<u8>,
pub created_at: Instant,
pub last_accessed: Instant,
pub access_count: usize,
#[allow(dead_code)]
pub file_modified: Option<std::time::SystemTime>,
}
pub struct BytecodeCache {
entries: Arc<Mutex<HashMap<String, CacheEntry>>>,
config: CacheConfig,
stats: Arc<Mutex<CacheStats>>,
}
#[derive(Debug, Clone)]
pub struct CacheConfig {
#[allow(dead_code)]
pub max_entries: usize,
#[allow(dead_code)]
pub max_age: Duration,
#[allow(dead_code)]
pub min_access_count: usize,
#[allow(dead_code)]
pub cleanup_interval: Duration,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
max_entries: 1000,
max_age: Duration::from_secs(3600), min_access_count: 3,
cleanup_interval: Duration::from_secs(300), }
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
#[allow(dead_code)]
pub hits: usize,
#[allow(dead_code)]
pub misses: usize,
#[allow(dead_code)]
pub evictions: usize,
#[allow(dead_code)]
pub total_cached: usize,
}
#[allow(dead_code)]
impl BytecodeCache {
pub fn new(config: CacheConfig) -> Self {
Self {
entries: Arc::new(Mutex::new(HashMap::new())),
config,
stats: Arc::new(Mutex::new(CacheStats::default())),
}
}
fn calculate_code_hash(code: &str) -> u64 {
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
hasher.finish()
}
fn get_cache_key(code: &str, source: Option<&str>) -> String {
if let Some(source_path) = source {
format!("file:{}", source_path)
} else {
format!("hash:{:016x}", Self::calculate_code_hash(code))
}
}
pub fn get(&self, code: &str, source: Option<&str>) -> Option<Vec<u8>> {
let key: _ = Self::get_cache_key(code, source);
let mut entries = self.entries.lock().unwrap();
let mut stats = self.stats.lock().unwrap();
if let Some(entry) = entries.get_mut(&key) {
entry.last_accessed = Instant::now();
entry.access_count += 1;
stats.hits += 1;
println!(
"[CACHE HIT] Key: {}, Access count: {}",
key, entry.access_count
);
Some(entry.script.clone())
} else {
stats.misses += 1;
println!("[CACHE MISS] Key: {}", key);
None
}
}
pub fn put(&self, code: &str, source: Option<&str>, script: Vec<u8>) {
let key: _ = Self::get_cache_key(code, source);
let now: _ = Instant::now();
let mut entries = self.entries.lock().unwrap();
let mut stats = self.stats.lock().unwrap();
entries.insert(
key.clone(),
CacheEntry {
script,
created_at: now,
last_accessed: now,
access_count: 1,
file_modified: None,
},
);
stats.total_cached += 1;
println!(
"[CACHE STORE] Key: {}, Total cached: {}",
key, stats.total_cached
);
if entries.len() > self.config.max_entries {
self.evict_old_entries(&mut entries, &mut stats);
}
}
fn evict_old_entries(&self, entries: &mut HashMap<String, CacheEntry>, stats: &mut CacheStats) {
let now: _ = Instant::now();
let max_age: _ = self.config.max_age;
let min_access: _ = self.config.min_access_count;
let keys_to_remove: Vec<String> = entries
.iter()
.filter_map(|(key, entry)| {
let is_old: _ = now.duration_since(entry.created_at) > max_age;
let is_rarely_used: _ = entry.access_count < min_access;
if is_old || is_rarely_used {
Some(key.clone())
} else {
None
}
})
.collect();
for key in keys_to_remove {
entries.remove(&key);
stats.evictions += 1;
stats.total_cached = stats.total_cached.saturating_sub(1);
}
if stats.evictions > 0 {
println!(
"[CACHE CLEANUP] Evicted {} entries, {} remaining",
stats.evictions,
entries.len()
);
}
}
pub fn get_stats(&self) -> CacheStats {
self.stats.lock().unwrap().clone()
}
pub fn cleanup(&self) {
let mut entries = self.entries.lock().unwrap();
let mut stats = self.stats.lock().unwrap();
self.evict_old_entries(&mut entries, &mut stats);
}
pub fn clear(&self) {
let mut entries = self.entries.lock().unwrap();
let mut stats = self.stats.lock().unwrap();
let count: _ = entries.len();
entries.clear();
stats.total_cached = 0;
println!("[CACHE CLEAR] Cleared {} entries", count);
}
pub fn usage_ratio(&self) -> f64 {
let entries: _ = self.entries.lock().unwrap();
entries.len() as f64 / self.config.max_entries as f64
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_cache_basic_operations() {
let cache: _ = BytecodeCache::new(CacheConfig::default());
let script_data: _ = vec![1, 2, 3, 4, 5];
cache.put("test code", None, script_data.clone());
let retrieved: _ = cache.get("test code", None);
assert_eq!(retrieved, Some(script_data));
let stats: _ = cache.get_stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 0);
}
#[test]
fn test_cache_miss() {
let cache: _ = BytecodeCache::new(CacheConfig::default());
let retrieved: _ = cache.get("nonexistent code", None);
assert_eq!(retrieved, None);
let stats: _ = cache.get_stats();
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 1);
}
#[test]
fn test_cache_key_generation() {
let _cache: _ = BytecodeCache::new(CacheConfig::default());
let key1: _ = BytecodeCache::get_cache_key("const x = 1;", None);
let key2: _ = BytecodeCache::get_cache_key("const x = 1;", None);
assert_eq!(key1, key2);
let key3: _ = BytecodeCache::get_cache_key("const x = 2;", None);
assert_ne!(key1, key3);
let file_key: _ = BytecodeCache::get_cache_key("const x = 1;", Some("/path/to/file.js"));
assert!(file_key.starts_with("file:"));
}
}