cachewell 0.1.0

LruCache with pinning and detachment support
Documentation
use lru::LruCache;
use std::{hash::Hash, num::NonZeroUsize};

enum Value<V> {
    Detached,
    Pinned(V),
    Regular(V),
}

impl<V> Value<V> {
    fn get(&self) -> &V {
        match self {
            Value::Detached => panic!("Value is detached"),
            Value::Pinned(v) => v,
            Value::Regular(v) => v,
        }
    }

    fn get_mut(&mut self) -> &mut V {
        match self {
            Value::Detached => panic!("Value is detached"),
            Value::Pinned(v) => v,
            Value::Regular(v) => v,
        }
    }

    fn out(self, pin_count: &mut usize) -> V {
        match self {
            Value::Detached => panic!("Value is detached"),
            Value::Pinned(v) => {
                *pin_count -= 1;
                v
            }
            Value::Regular(v) => v,
        }
    }
}

pub struct Cache<K: Clone + PartialEq + Eq + Hash, V> {
    entries: LruCache<K, Value<V>>,
    detach_count: usize,
    pin_count: usize,
}

impl<K: Clone + PartialEq + Eq + Hash, V> Cache<K, V> {
    pub fn attach(&mut self, key: &K, val: V) {
        let (key, d_val) = self
            .entries
            .pop_entry(key)
            .expect("Cannot attach: Requested entry is not cached");
        assert!(
            matches!(d_val, Value::Detached),
            "Only detached entries can be attached"
        );
        self.entries.push(key, Value::Regular(val));
        self.detach_count -= 1;
    }

    // NOTE: This will also detach pinned entries
    pub fn detach(&mut self, key: &K) -> V {
        let (key, val) = self
            .entries
            .pop_entry(key)
            .expect("Cannot detach: Requested entry is not cached");
        self.entries.push(key, Value::Detached);
        self.detach_count += 1;
        val.out(&mut self.pin_count)
    }

    // NOTE: This will also evict pinned entries
    pub fn empty(&mut self) -> Vec<(K, V)> {
        let mut entries = Vec::new();
        while let Some((key, val)) = self.entries.pop_lru() {
            entries.push((key, val.out(&mut self.pin_count)))
        }
        entries
    }

    pub fn get(&mut self, key: &K) -> &V {
        self.entries
            .get(key)
            .expect("Cannot get: Requested entry is not cached")
            .get()
    }

    pub fn get_mut(&mut self, key: &K) -> &mut V {
        self.entries
            .get_mut(key)
            .expect("Cannot get_mut: Requested entry is not cached")
            .get_mut()
    }

    pub fn is_cached(&self, key: &K) -> bool {
        self.entries.contains(key)
    }

    pub fn len(&self) -> usize {
        self.entries.len()
    }

    pub fn margin(&self) -> usize {
        self.entries.cap().get() - self.detach_count - self.pin_count
    }

    pub fn new(cap: NonZeroUsize) -> Self {
        Self {
            entries: LruCache::new(cap),
            detach_count: 0,
            pin_count: 0,
        }
    }

    pub fn pin(&mut self, key: &K) {
        let (key, val) = self
            .entries
            .pop_entry(key)
            .expect("Cannot pin: Requested entry is not cached");
        self.entries
            .push(key, Value::Pinned(val.out(&mut self.pin_count)));
        self.pin_count += 1;
    }

    pub fn pop(&mut self, key: &K) -> V {
        self.entries
            .pop(key)
            .expect("Cannot pop: Requested entry is not cached")
            .out(&mut self.pin_count)
    }

    pub fn pop_lru(&mut self) -> Option<(K, V)> {
        self.refresh();
        self.entries.pop_lru().map(|(k, v)| (k, v.out(&mut 0)))
    }

    pub fn push(&mut self, key: K, val: V) -> Option<(K, V)> {
        self.refresh();
        self.entries
            .push(key, Value::Regular(val))
            .map(|(k, v)| (k, v.out(&mut 0)))
    }

    fn refresh(&mut self) {
        if self.entries.len() < self.entries.cap().get() {
            return;
        }
        assert!(
            self.margin() > 0,
            "Cache Overflow -- Capacity: {}, Detached: {}, Pinned: {}",
            self.entries.cap().get(),
            self.detach_count,
            self.pin_count
        );
        loop {
            let key = match self.entries.peek_lru() {
                Some((key, val)) if !matches!(val, Value::Regular(_)) => key.clone(),
                _ => break,
            };
            self.entries.promote(&key);
        }
    }

    pub fn unpin(&mut self, key: &K) {
        let (key, val) = self
            .entries
            .pop_entry(key)
            .expect("Cannot unpin: Requested entry is not cached");
        self.entries
            .push(key, Value::Regular(val.out(&mut self.pin_count)));
    }
}