use std::fmt::Debug;
use std::hash::Hash;
use std::sync::Arc;
use crate::ds::frequency_buckets::DEFAULT_BUCKET_PREALLOC;
use crate::policy::fifo::FifoCache;
use crate::policy::heap_lfu::HeapLfuCache;
use crate::policy::lfu::LfuCache;
use crate::policy::lru::LruCore;
use crate::policy::lru_k::LrukCache;
use crate::policy::s3_fifo::S3FifoCache;
use crate::policy::two_q::TwoQCore;
use crate::traits::CoreCache;
#[derive(Debug, Clone)]
pub enum CachePolicy {
Fifo,
Lru,
LruK { k: usize },
Lfu {
bucket_hint: Option<usize>,
},
HeapLfu,
TwoQ { probation_frac: f64 },
S3Fifo {
small_ratio: f64,
ghost_ratio: f64,
},
}
pub struct Cache<K, V>
where
K: Copy + Eq + Hash + Ord,
V: Clone + Debug,
{
inner: CacheInner<K, V>,
}
enum CacheInner<K, V>
where
K: Copy + Eq + Hash + Ord,
V: Clone + Debug,
{
Fifo(FifoCache<K, V>),
Lru(LruCore<K, V>),
LruK(LrukCache<K, V>),
Lfu(LfuCache<K, V>),
HeapLfu(HeapLfuCache<K, V>),
TwoQ(TwoQCore<K, V>),
S3Fifo(S3FifoCache<K, V>),
}
impl<K, V> Cache<K, V>
where
K: Copy + Eq + Hash + Ord,
V: Clone + Debug,
{
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
match &mut self.inner {
CacheInner::Fifo(fifo) => CoreCache::insert(fifo, key, value),
CacheInner::Lru(lru) => {
let arc_value = Arc::new(value);
lru.insert(key, arc_value)
.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
},
CacheInner::LruK(lruk) => CoreCache::insert(lruk, key, value),
CacheInner::Lfu(lfu) => {
let arc_value = Arc::new(value);
lfu.insert(key, arc_value)
.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
},
CacheInner::HeapLfu(heap_lfu) => {
let arc_value = Arc::new(value);
heap_lfu
.insert(key, arc_value)
.map(|arc| Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone()))
},
CacheInner::TwoQ(twoq) => CoreCache::insert(twoq, key, value),
CacheInner::S3Fifo(s3fifo) => CoreCache::insert(s3fifo, key, value),
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
match &mut self.inner {
CacheInner::Fifo(fifo) => fifo.get(key),
CacheInner::Lru(lru) => lru.get(key).map(|arc| arc.as_ref()),
CacheInner::LruK(lruk) => lruk.get(key),
CacheInner::Lfu(lfu) => lfu.get(key).map(|arc| arc.as_ref()),
CacheInner::HeapLfu(heap_lfu) => heap_lfu.get(key).map(|arc| arc.as_ref()),
CacheInner::TwoQ(twoq) => twoq.get(key),
CacheInner::S3Fifo(s3fifo) => s3fifo.get(key),
}
}
pub fn contains(&self, key: &K) -> bool {
match &self.inner {
CacheInner::Fifo(fifo) => fifo.contains(key),
CacheInner::Lru(lru) => lru.contains(key),
CacheInner::LruK(lruk) => lruk.contains(key),
CacheInner::Lfu(lfu) => lfu.contains(key),
CacheInner::HeapLfu(heap_lfu) => heap_lfu.contains(key),
CacheInner::TwoQ(twoq) => twoq.contains(key),
CacheInner::S3Fifo(s3fifo) => s3fifo.contains(key),
}
}
pub fn len(&self) -> usize {
match &self.inner {
CacheInner::Fifo(fifo) => CoreCache::len(fifo),
CacheInner::Lru(lru) => lru.len(),
CacheInner::LruK(lruk) => CoreCache::len(lruk),
CacheInner::Lfu(lfu) => lfu.len(),
CacheInner::HeapLfu(heap_lfu) => heap_lfu.len(),
CacheInner::TwoQ(twoq) => twoq.len(),
CacheInner::S3Fifo(s3fifo) => s3fifo.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn capacity(&self) -> usize {
match &self.inner {
CacheInner::Fifo(fifo) => CoreCache::capacity(fifo),
CacheInner::Lru(lru) => lru.capacity(),
CacheInner::LruK(lruk) => CoreCache::capacity(lruk),
CacheInner::Lfu(lfu) => lfu.capacity(),
CacheInner::HeapLfu(heap_lfu) => heap_lfu.capacity(),
CacheInner::TwoQ(twoq) => twoq.capacity(),
CacheInner::S3Fifo(s3fifo) => s3fifo.capacity(),
}
}
pub fn clear(&mut self) {
match &mut self.inner {
CacheInner::Fifo(fifo) => fifo.clear(),
CacheInner::Lru(lru) => lru.clear(),
CacheInner::LruK(lruk) => lruk.clear(),
CacheInner::Lfu(lfu) => lfu.clear(),
CacheInner::HeapLfu(heap_lfu) => heap_lfu.clear(),
CacheInner::TwoQ(twoq) => twoq.clear(),
CacheInner::S3Fifo(s3fifo) => s3fifo.clear(),
}
}
}
pub struct CacheBuilder {
capacity: usize,
}
impl CacheBuilder {
pub fn new(capacity: usize) -> Self {
Self { capacity }
}
pub fn build<K, V>(self, policy: CachePolicy) -> Cache<K, V>
where
K: Copy + Eq + Hash + Ord,
V: Clone + Debug,
{
let inner = match policy {
CachePolicy::Fifo => CacheInner::Fifo(FifoCache::new(self.capacity)),
CachePolicy::Lru => CacheInner::Lru(LruCore::new(self.capacity)),
CachePolicy::LruK { k } => CacheInner::LruK(LrukCache::with_k(self.capacity, k)),
CachePolicy::Lfu { bucket_hint } => {
let hint = bucket_hint.unwrap_or(DEFAULT_BUCKET_PREALLOC);
CacheInner::Lfu(LfuCache::with_bucket_hint(self.capacity, hint))
},
CachePolicy::HeapLfu => CacheInner::HeapLfu(HeapLfuCache::new(self.capacity)),
CachePolicy::TwoQ { probation_frac } => {
CacheInner::TwoQ(TwoQCore::new(self.capacity, probation_frac))
},
CachePolicy::S3Fifo {
small_ratio,
ghost_ratio,
} => CacheInner::S3Fifo(S3FifoCache::with_ratios(
self.capacity,
small_ratio,
ghost_ratio,
)),
};
Cache { inner }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_all_policies_basic_ops() {
let policies = [
CachePolicy::Fifo,
CachePolicy::Lru,
CachePolicy::LruK { k: 2 },
CachePolicy::Lfu { bucket_hint: None },
CachePolicy::HeapLfu,
CachePolicy::TwoQ {
probation_frac: 0.25,
},
CachePolicy::S3Fifo {
small_ratio: 0.1,
ghost_ratio: 0.9,
},
];
for policy in policies {
let mut cache = CacheBuilder::new(10).build::<u64, String>(policy.clone());
assert_eq!(cache.insert(1, "one".to_string()), None);
assert_eq!(cache.insert(2, "two".to_string()), None);
assert_eq!(cache.get(&1), Some(&"one".to_string()));
assert_eq!(cache.get(&2), Some(&"two".to_string()));
assert_eq!(cache.get(&3), None);
assert!(cache.contains(&1));
assert!(!cache.contains(&99));
assert_eq!(cache.len(), 2);
assert!(!cache.is_empty());
assert_eq!(cache.insert(1, "ONE".to_string()), Some("one".to_string()));
assert_eq!(cache.get(&1), Some(&"ONE".to_string()));
cache.clear();
assert!(cache.is_empty());
}
}
#[test]
fn test_capacity_enforcement() {
let mut cache = CacheBuilder::new(2).build::<u64, String>(CachePolicy::Lru);
cache.insert(1, "one".to_string());
cache.insert(2, "two".to_string());
cache.insert(3, "three".to_string());
assert_eq!(cache.len(), 2);
assert!(!cache.contains(&1)); assert!(cache.contains(&2));
assert!(cache.contains(&3));
}
}