rari 0.13.3

rari: Runtime Accelerated Rendering Infrastructure - A high-performance React Server Components framework powered by Rust
Documentation
use crate::error::RariError;
use crate::runtime::module_loader::config::CacheStats;
use dashmap::DashMap;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
use serde_json::Value as JsonValue;
use std::collections::VecDeque;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio::time::Instant;

#[derive(Debug, Clone)]
struct CacheEntry {
    value: JsonValue,
    last_accessed: Instant,
    access_count: usize,
}

#[derive(Debug)]
struct ThreadSafeCache {
    entries: Arc<RwLock<FxHashMap<String, CacheEntry>>>,
    access_order: Arc<RwLock<VecDeque<String>>>,
    max_size: usize,
    hit_count: AtomicUsize,
    miss_count: AtomicUsize,
    eviction_count: AtomicUsize,
}

impl ThreadSafeCache {
    fn new(max_size: usize) -> Self {
        let cache = Self {
            entries: Arc::new(RwLock::new(FxHashMap::default())),
            access_order: Arc::new(RwLock::new(VecDeque::new())),
            max_size,
            hit_count: AtomicUsize::new(0),
            miss_count: AtomicUsize::new(0),
            eviction_count: AtomicUsize::new(0),
        };

        cache.start_cleanup_task();
        cache
    }

    fn start_cleanup_task(&self) {
        let entries_clone = Arc::clone(&self.entries);
        let access_order_clone = Arc::clone(&self.access_order);

        std::thread::spawn(move || {
            loop {
                std::thread::sleep(std::time::Duration::from_secs(300));

                let max_age = std::time::Duration::from_secs(3600);
                let now = Instant::now();

                let mut entries = entries_clone.write();
                let mut order = access_order_clone.write();
                {
                    let mut to_remove = Vec::new();

                    for (key, entry) in entries.iter() {
                        if now.duration_since(entry.last_accessed) > max_age {
                            to_remove.push(key.clone());
                        }
                    }

                    for key in &to_remove {
                        entries.remove(key);
                        if let Some(pos) = order.iter().position(|x| x == key) {
                            order.remove(pos);
                        }
                    }
                }
            }
        });
    }

    pub fn get(&self, key: &str) -> Option<JsonValue> {
        let now = Instant::now();

        {
            let mut entries = self.entries.write();
            if let Some(entry) = entries.get_mut(key) {
                entry.last_accessed = now;
                entry.access_count += 1;
                self.hit_count.fetch_add(1, Ordering::Relaxed);

                let mut order = self.access_order.write();
                order.retain(|k| k != key);
                order.push_back(key.to_string());

                return Some(entry.value.clone());
            }
        }

        self.miss_count.fetch_add(1, Ordering::Relaxed);
        None
    }

    fn insert(&self, key: String, value: JsonValue) -> Result<(), RariError> {
        let now = Instant::now();

        let mut entries = self.entries.write();
        let mut order = self.access_order.write();

        if entries.len() >= self.max_size && !entries.contains_key(&key) {
            self.evict_lru(&mut entries, &mut order);
        }

        let entry = CacheEntry { value, last_accessed: now, access_count: 1 };

        entries.insert(key.clone(), entry);

        order.retain(|k| k != &key);
        order.push_back(key);

        Ok(())
    }

    fn evict_lru(&self, entries: &mut FxHashMap<String, CacheEntry>, order: &mut VecDeque<String>) {
        if let Some(lru_key) = order.pop_front() {
            entries.remove(&lru_key);
            self.eviction_count.fetch_add(1, Ordering::Relaxed);
        }
    }

    fn stats(&self) -> CacheStats {
        let entries = self.entries.read();
        CacheStats {
            hits: self.hit_count.load(Ordering::Relaxed),
            misses: self.miss_count.load(Ordering::Relaxed),
            evictions: self.eviction_count.load(Ordering::Relaxed),
            size: entries.len(),
            memory_bytes: entries.len() * std::mem::size_of::<CacheEntry>(),
        }
    }

    fn clear(&self) {
        let mut entries = self.entries.write();
        let mut order = self.access_order.write();
        entries.clear();
        order.clear();
    }
}

#[derive(Debug)]
pub struct ModuleCaching {
    cache: ThreadSafeCache,
    component_source_paths: DashMap<String, String>,
}

impl ModuleCaching {
    pub fn new(cache_size: usize) -> Self {
        Self { cache: ThreadSafeCache::new(cache_size), component_source_paths: DashMap::new() }
    }

    pub fn get_cache_stats(&self) -> CacheStats {
        self.cache.stats()
    }

    pub fn get(&self, key: &str) -> Option<JsonValue> {
        self.cache.get(key)
    }

    pub fn insert(&self, key: String, value: JsonValue) -> Result<(), RariError> {
        self.cache.insert(key, value)
    }

    pub fn set_component_source_path(&self, component_id: String, path: String) {
        self.component_source_paths.insert(component_id, path);
    }

    pub fn get_component_source_path(&self, component_id: &str) -> Option<String> {
        self.component_source_paths.get(component_id).map(|entry| entry.value().clone())
    }

    pub fn remove_component_source_path(&self, component_id: &str) {
        self.component_source_paths.remove(component_id);
    }

    pub fn clear(&self) {
        self.cache.clear();
        self.component_source_paths.clear();
    }

    pub fn clear_component(&self, component_id: &str) {
        self.component_source_paths.remove(component_id);
    }
}

impl Default for ModuleCaching {
    fn default() -> Self {
        Self::new(5000)
    }
}

#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
    use super::*;

    #[test]
    fn test_cache_basic_operations() {
        let cache = ThreadSafeCache::new(10);

        cache.insert("key1".to_string(), serde_json::json!({"value": 1})).unwrap();
        let result = cache.get("key1");
        assert!(result.is_some());
        assert_eq!(result.unwrap(), serde_json::json!({"value": 1}));

        let result = cache.get("nonexistent");
        assert!(result.is_none());
    }

    #[test]
    fn test_cache_lru_eviction() {
        let cache = ThreadSafeCache::new(2);

        cache.insert("key1".to_string(), serde_json::json!(1)).unwrap();
        cache.insert("key2".to_string(), serde_json::json!(2)).unwrap();
        cache.insert("key3".to_string(), serde_json::json!(3)).unwrap();

        assert!(cache.get("key1").is_none());
        assert!(cache.get("key2").is_some());
        assert!(cache.get("key3").is_some());
    }

    #[test]
    fn test_cache_stats() {
        let cache = ThreadSafeCache::new(10);

        cache.insert("key1".to_string(), serde_json::json!(1)).unwrap();

        cache.get("key1");
        cache.get("key2");

        let stats = cache.stats();
        assert_eq!(stats.hits, 1);
        assert_eq!(stats.misses, 1);
    }

    #[test]
    fn test_module_caching() {
        let caching = ModuleCaching::new(10);

        caching.set_component_source_path("comp1".to_string(), "/path/to/comp1".to_string());
        assert_eq!(caching.get_component_source_path("comp1"), Some("/path/to/comp1".to_string()));

        caching.insert("module1".to_string(), serde_json::json!({"data": "test"})).unwrap();
        assert!(caching.get("module1").is_some());
    }
}