use alloc::boxed::Box;
use alloc::collections::VecDeque;
use core::marker::PhantomData;
use crate::nexus::row::EffectRow;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CacheStrategy {
None,
Unbounded,
LRU(usize),
TTL(u64),
FIFO(usize),
}
impl Default for CacheStrategy {
fn default() -> Self {
CacheStrategy::LRU(1000)
}
}
struct CacheEntry<V> {
value: V,
last_access: u64,
}
impl<V> CacheEntry<V> {
fn new(value: V, last_access: u64) -> Self {
CacheEntry { value, last_access }
}
}
pub struct Cache<K, V> {
entries: VecDeque<(K, CacheEntry<V>)>,
strategy: CacheStrategy,
capacity: usize,
clock: u64,
}
impl<K: Eq + Clone, V: Clone> Cache<K, V> {
pub fn new(strategy: CacheStrategy) -> Self {
let capacity = match strategy {
CacheStrategy::None => 0,
CacheStrategy::Unbounded => usize::MAX,
CacheStrategy::LRU(n) | CacheStrategy::FIFO(n) => n,
CacheStrategy::TTL(_) => 1000, };
let initial = if capacity == usize::MAX { 16 } else { capacity };
Cache {
entries: VecDeque::with_capacity(initial),
strategy,
capacity,
clock: 0,
}
}
#[inline]
fn tick(&mut self) -> u64 {
self.clock += 1;
self.clock
}
#[inline]
pub fn get(&mut self, key: &K) -> Option<V> {
if matches!(self.strategy, CacheStrategy::None) {
return None;
}
let now = self.tick();
for (k, entry) in &mut self.entries {
if k == key {
entry.last_access = now;
return Some(entry.value.clone());
}
}
None
}
#[inline]
pub fn insert(&mut self, key: K, value: V) {
if matches!(self.strategy, CacheStrategy::None) {
return;
}
let now = self.tick();
for (k, entry) in &mut self.entries {
if *k == key {
entry.value = value;
entry.last_access = now;
return;
}
}
if self.entries.len() >= self.capacity {
self.evict();
}
self.entries.push_back((key, CacheEntry::new(value, now)));
}
#[inline]
fn evict(&mut self) {
match self.strategy {
CacheStrategy::FIFO(_) => {
self.entries.pop_front();
}
CacheStrategy::LRU(_) => {
if let Some(idx) = self
.entries
.iter()
.enumerate()
.min_by_key(|(_, (_, e))| e.last_access)
.map(|(i, _)| i)
{
self.entries.swap_remove_back(idx);
}
}
_ => {
self.entries.pop_front();
}
}
}
#[inline]
pub fn clear(&mut self) {
self.entries.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub struct Memoized<K, V, F> {
func: F,
cache: Cache<K, V>,
_marker: PhantomData<(K, V)>,
}
impl<K, V, F> Memoized<K, V, F>
where
K: Eq + Clone,
V: Clone,
F: Fn(&K) -> V,
{
#[inline]
pub fn new(func: F) -> Self {
Memoized {
func,
cache: Cache::new(CacheStrategy::default()),
_marker: PhantomData,
}
}
#[inline]
pub fn with_strategy(func: F, strategy: CacheStrategy) -> Self {
Memoized {
func,
cache: Cache::new(strategy),
_marker: PhantomData,
}
}
#[inline]
pub fn call(&mut self, key: &K) -> V {
if let Some(value) = self.cache.get(key) {
return value;
}
let value = (self.func)(key);
self.cache.insert(key.clone(), value.clone());
value
}
#[inline]
pub fn clear_cache(&mut self) {
self.cache.clear();
}
#[inline]
pub fn cache_size(&self) -> usize {
self.cache.len()
}
}
#[inline]
pub fn memoize<K, V, F>(func: F) -> Memoized<K, V, F>
where
K: Eq + Clone,
V: Clone,
F: Fn(&K) -> V,
{
Memoized::new(func)
}
#[inline]
pub fn memoize_with<K, V, F>(func: F, strategy: CacheStrategy) -> Memoized<K, V, F>
where
K: Eq + Clone,
V: Clone,
F: Fn(&K) -> V,
{
Memoized::with_strategy(func, strategy)
}
pub trait MemoizeSafe: EffectRow {}
impl MemoizeSafe for crate::nexus::row::Pure {}
impl MemoizeSafe for crate::nexus::row::Row<{ crate::nexus::row::READER_BIT }> {}
pub struct MemoizedEff<R: MemoizeSafe, K, V, F> {
inner: Memoized<K, V, F>,
_effect: PhantomData<R>,
}
impl<R, K, V, F> MemoizedEff<R, K, V, F>
where
R: MemoizeSafe,
K: Eq + Clone,
V: Clone,
F: Fn(&K) -> V,
{
pub fn new(func: F) -> Self {
MemoizedEff {
inner: Memoized::new(func),
_effect: PhantomData,
}
}
#[inline]
pub fn call(&mut self, key: &K) -> V {
self.inner.call(key)
}
}
pub struct Lazy<A, F> {
compute: Option<F>,
value: Option<A>,
}
impl<A, F> Lazy<A, F>
where
F: FnOnce() -> A,
{
pub fn new(compute: F) -> Self {
Lazy {
compute: Some(compute),
value: None,
}
}
#[inline]
pub fn force(&mut self) -> &A
where
A: Clone,
{
if self.value.is_none()
&& let Some(f) = self.compute.take()
{
self.value = Some(f());
}
self.value.as_ref().expect("Lazy value should be computed")
}
#[inline]
pub fn is_computed(&self) -> bool {
self.value.is_some()
}
}
#[inline]
pub fn lazy<A, F: FnOnce() -> A>(compute: F) -> Lazy<A, F> {
Lazy::new(compute)
}
pub struct Thunk<A> {
inner: Box<dyn FnOnce() -> A>,
}
impl<A: 'static> Thunk<A> {
pub fn new<F: FnOnce() -> A + 'static>(compute: F) -> Self {
Thunk {
inner: Box::new(compute),
}
}
#[inline]
pub fn force(self) -> A {
(self.inner)()
}
}
#[inline]
pub fn thunk<A: 'static, F: FnOnce() -> A + 'static>(compute: F) -> Thunk<A> {
Thunk::new(compute)
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
#[test]
fn test_cache_insert_get() {
let mut cache: Cache<i32, String> = Cache::new(CacheStrategy::Unbounded);
cache.insert(1, "one".into());
cache.insert(2, "two".into());
assert_eq!(cache.get(&1), Some("one".into()));
assert_eq!(cache.get(&2), Some("two".into()));
assert_eq!(cache.get(&3), None);
}
#[test]
fn test_cache_lru_eviction() {
let mut cache: Cache<i32, i32> = Cache::new(CacheStrategy::LRU(2));
cache.insert(1, 10);
cache.insert(2, 20);
let _ = cache.get(&1);
cache.insert(3, 30);
assert_eq!(cache.get(&1), Some(10));
assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&3), Some(30));
}
#[test]
fn test_cache_lru_evicts_least_recent_not_least_hit() {
let mut cache: Cache<i32, i32> = Cache::new(CacheStrategy::LRU(2));
cache.insert(1, 10);
cache.insert(2, 20);
let _ = cache.get(&1);
let _ = cache.get(&1);
let _ = cache.get(&2);
cache.insert(3, 30);
assert_eq!(cache.get(&1), None, "least recently used must be evicted");
assert_eq!(cache.get(&2), Some(20), "most recently used must survive");
assert_eq!(cache.get(&3), Some(30));
}
#[test]
fn test_memoized_caching() {
let mut memo = Memoized::new(|x: &i32| *x * 2);
assert_eq!(memo.call(&5), 10);
assert_eq!(memo.call(&3), 6);
assert_eq!(memo.call(&5), 10);
assert_eq!(memo.call(&3), 6);
assert_eq!(memo.cache_size(), 2);
}
#[test]
fn test_lazy_evaluation() {
let mut lazy_val = Lazy::new(|| 42);
assert!(!lazy_val.is_computed());
let value = lazy_val.force();
assert_eq!(*value, 42);
assert!(lazy_val.is_computed());
}
#[test]
fn test_thunk() {
let t = thunk(|| 100 + 23);
assert_eq!(t.force(), 123);
}
#[test]
fn test_cache_none_strategy() {
let mut cache: Cache<i32, i32> = Cache::new(CacheStrategy::None);
cache.insert(1, 10);
assert_eq!(cache.get(&1), None); }
#[test]
fn test_memoize_helper() {
let mut memo = memoize(|x: &i32| x * x);
assert_eq!(memo.call(&4), 16);
assert_eq!(memo.call(&5), 25);
assert_eq!(memo.call(&4), 16); }
#[test]
fn test_memoize_with_strategy() {
let mut memo = memoize_with(|x: &i32| *x + 1, CacheStrategy::FIFO(2));
assert_eq!(memo.call(&1), 2);
assert_eq!(memo.call(&2), 3);
assert_eq!(memo.call(&3), 4); assert_eq!(memo.cache_size(), 2);
}
}