use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug, Clone)]
pub struct ScriptBuffer {
pub data: Arc<[u8]>,
pub len: usize,
pub hash: CacheKey,
}
#[derive(Debug, Clone, Default)]
pub struct L1Stats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub memory_usage_bytes: usize,
pub hot_scripts: usize,
}
impl L1Stats {
pub fn hit_rate(&self) -> f64 {
let total: _ = self.hits + self.misses;
if total > 0 {
self.hits as f64 / total as f64
} else {
0.0
}
}
pub fn memory_usage_mb(&self) -> f64 {
self.memory_usage_bytes as f64 / (1024.0 * 1024.0)
}
}
pub struct L1ZeroCopyCache {
hot_scripts: Arc<RwLock<HashMap<CacheKey, ScriptBuffer>>>,
buffer_pool: Arc<RwLock<Vec<ScriptBuffer>>>,
stats: Arc<RwLock<L1Stats>>,
max_size_bytes: usize,
max_hot_scripts: usize,
}
impl L1ZeroCopyCache {
pub fn new() -> Self {
Self {
hot_scripts: Arc::new(Mutex::new(HashMap::new())),
buffer_pool: Arc::new(Mutex::new(Vec::new())),
stats: Arc::new(Mutex::new(L1Stats::default())),
max_size_bytes: 256 * 1024 * 1024, max_hot_scripts: 1024,
}
}
pub async fn put(&self, key: CacheKey, data: &[u8]) {
let script_buffer: _ = ScriptBuffer {
data: Arc::from(data.to_vec()),
len: data.len(),
hash: key,
};
{
let mut hot_scripts = self.hot_scripts.write().unwrap();
if hot_scripts.len() >= self.max_hot_scripts {
if let Some(oldest_key) = hot_scripts.keys().next().cloned() {
hot_scripts.remove(&oldest_key);
let mut stats = self.stats.write().unwrap();
stats.evictions += 1;
}
}
hot_scripts.insert(key, script_buffer.clone());
}
let mut stats = self.stats.write().unwrap();
stats.memory_usage_bytes += data.len();
stats.hot_scripts = self.hot_scripts.read().unwrap().len();
if stats.memory_usage_bytes > self.max_size_bytes {
self.evict_oldest().await;
}
}
pub async fn get(&self, key: CacheKey) -> Option<Vec<u8>> {
let mut stats = self.stats.write().unwrap();
if let Some(script_buffer) = self.hot_scripts.read().unwrap().get(&key).cloned() {
stats.hits += 1;
Some(script_buffer.data.to_vec())
} else {
stats.misses += 1;
None
}
}
pub async fn invalidate(&self, key: CacheKey) {
if let Some(script_buffer) = self.hot_scripts.write().unwrap().remove(&key) {
let mut stats = self.stats.write().unwrap();
stats.memory_usage_bytes -= script_buffer.len;
stats.hot_scripts = self.hot_scripts.read().unwrap().len();
}
}
pub async fn gc(&self) {
let mut stats = self.stats.write().unwrap();
stats.memory_usage_bytes = 0;
stats.hot_scripts = 0;
let hot_scripts: _ = self.hot_scripts.read().unwrap();
for script_buffer in hot_scripts.values() {
stats.memory_usage_bytes += script_buffer.len;
stats.hot_scripts += 1;
}
}
pub async fn get_stats(&self) -> L1Stats {
self.stats.read().unwrap().clone()
}
async fn evict_oldest(&self) {
let mut hot_scripts = self.hot_scripts.write().unwrap();
let keys_to_remove: Vec<CacheKey> = hot_scripts.keys().cloned().collect();
let half: _ = keys_to_remove.len() / 2;
let mut stats = self.stats.write().unwrap();
for key in keys_to_remove.into_iter().take(half) {
if let Some(script_buffer) = hot_scripts.remove(&key) {
stats.memory_usage_bytes -= script_buffer.len;
stats.evictions += 1;
}
}
stats.hot_scripts = hot_scripts.len();
}
pub fn size(&self) -> usize {
self.hot_scripts.read().unwrap().len()
}
pub fn is_empty(&self) -> bool {
self.hot_scripts.read().unwrap().is_empty()
}
}
impl Default for L1ZeroCopyCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#[tokio::test]
async fn test_l1_basic_operations() {
let cache: _ = L1ZeroCopyCache::new();
cache.put(123, b"console.log('test');").await;
let result: _ = cache.get(123).await;
assert!(result.is_some());
assert_eq!(result.unwrap(), b"console.log('test');");
}
#[tokio::test]
async fn test_l1_hit_rate() {
let cache: _ = L1ZeroCopyCache::new();
cache.put(1, b"script1").await;
cache.get(1).await;
cache.get(1).await;
let stats: _ = cache.get_stats().await;
assert!(stats.hits > 0);
assert!(stats.hit_rate() > 0.5);
}
#[tokio::test]
async fn test_l1_invalidation() {
let cache: _ = L1ZeroCopyCache::new();
cache.put(1, b"test").await;
assert!(cache.get(1).await.is_some());
cache.invalidate(1).await;
assert!(cache.get(1).await.is_none());
}
#[tokio::test]
async fn test_l1_memory_tracking() {
let cache: _ = L1ZeroCopyCache::new();
cache.put(1, b"hello world").await;
let stats: _ = cache.get_stats().await;
assert_eq!(stats.memory_usage_bytes, 11);
assert!(stats.memory_usage_mb() > 0.0);
}
}