#![allow(clippy::cast_precision_loss)]
use indexmap::IndexMap;
use parking_lot::RwLock;
use std::hash::Hash;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
}
impl CacheStats {
#[must_use]
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
}
pub struct LruCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
capacity: usize,
inner: RwLock<IndexMap<K, V>>,
hits: AtomicU64,
misses: AtomicU64,
evictions: AtomicU64,
}
impl<K, V> LruCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
#[must_use]
pub fn new(capacity: usize) -> Self {
Self {
capacity,
inner: RwLock::new(IndexMap::with_capacity(capacity)),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
evictions: AtomicU64::new(0),
}
}
#[must_use]
pub fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.read().is_empty()
}
pub fn insert(&self, key: K, value: V) {
let mut inner = self.inner.write();
if inner.shift_remove(&key).is_some() {
inner.insert(key, value);
return;
}
if inner.len() >= self.capacity {
if inner.shift_remove_index(0).is_some() {
self.evictions.fetch_add(1, Ordering::Relaxed);
}
}
inner.insert(key, value);
}
#[must_use]
pub fn get(&self, key: &K) -> Option<V> {
let mut inner = self.inner.write();
if let Some((_idx, owned_key, value)) = inner.shift_remove_full(key) {
let cloned = value.clone();
inner.insert(owned_key, value);
drop(inner);
self.hits.fetch_add(1, Ordering::Relaxed);
Some(cloned)
} else {
drop(inner);
self.misses.fetch_add(1, Ordering::Relaxed);
None
}
}
#[must_use]
pub fn peek(&self, key: &K) -> Option<V> {
let inner = self.inner.read();
inner.get(key).cloned()
}
pub fn remove(&self, key: &K) {
let mut inner = self.inner.write();
inner.swap_remove(key);
}
pub fn clear(&self) {
let mut inner = self.inner.write();
inner.clear();
}
#[must_use]
pub fn stats(&self) -> CacheStats {
CacheStats {
hits: self.hits.load(Ordering::Relaxed),
misses: self.misses.load(Ordering::Relaxed),
evictions: self.evictions.load(Ordering::Relaxed),
}
}
}
impl<K, V> Default for LruCache<K, V>
where
K: Hash + Eq + Clone,
V: Clone,
{
fn default() -> Self {
Self::new(10_000) }
}