0
0
Fork 0
esp32-c_its-companion/src/cache.rs

258 lines
6.5 KiB
Rust

//! Data Cache
//!
//! Store items for some time
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::ops::Sub;
pub trait Cachable<K>
where
K: core::cmp::PartialEq,
{
fn key(&self) -> K;
}
pub trait Initable<T, K>
where
T: Cachable<K>,
K: core::cmp::PartialEq,
{
fn init(key: K) -> T;
}
struct CacheItem<K, T>
where
T: Cachable<K>,
K: core::cmp::PartialEq,
{
timestamp: chrono::NaiveDateTime,
data: Rc<core::cell::RefCell<T>>,
phantom: core::marker::PhantomData<K>, // rust wants this
}
impl<K, T> core::fmt::Display for CacheItem<K, T>
where
T: Cachable<K>,
K: core::cmp::PartialEq + core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"CacheItem({}, {:?})",
self.data.borrow().key(),
self.timestamp
)
}
}
impl<K, T> CacheItem<K, T>
where
T: Cachable<K>,
K: core::cmp::PartialEq,
{
fn new(time_now: chrono::NaiveDateTime, data: T) -> Self {
Self {
timestamp: time_now,
data: Rc::new(core::cell::RefCell::new(data)),
phantom: core::marker::PhantomData,
}
}
}
#[derive(Debug, Clone)]
pub enum CacheError<K>
where
K: core::fmt::Display,
{
KeyNotFound(K),
InsertKeyAlreadyPresent(K),
}
impl<K> core::fmt::Display for CacheError<K>
where
K: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CacheError::KeyNotFound(key) => write!(f, "Key '{key}' doesn't exist in cache"),
CacheError::InsertKeyAlreadyPresent(key) => {
write!(f, "Insert failed b/c key '{key}' already exists in cache")
}
}
}
}
/// Item cache
///
/// Stores items for a certain time.
/// Items need to implement `Cachable` so that an itentifier (key) can be retreived from them.
pub struct Cache<K, T>
where
T: Cachable<K>,
K: core::cmp::PartialEq,
{
lifetime: chrono::Duration,
items: Vec<CacheItem<K, T>>,
}
impl<K, T> core::fmt::Display for Cache<K, T>
where
T: Cachable<K>,
K: core::cmp::PartialEq + core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Cache({}, {} items)", self.lifetime, self.items.len())
}
}
#[allow(unused)]
impl<K, T> Cache<K, T>
where
T: Cachable<K> + Clone,
K: core::cmp::PartialEq + core::fmt::Display,
{
/// Creates a new cache with a certain item lifetime
pub const fn new(lifetime: chrono::Duration) -> Self {
Self {
lifetime,
items: alloc::vec![],
}
}
/// Determines if a key is in the cache
pub fn contains(&mut self, key: &K) -> bool {
self.items
.iter()
.find(|i| i.data.borrow().key() == *key)
.is_some()
}
/// Determines the index of a certain key
fn find(&mut self, key: &K) -> Option<usize> {
self.items
.iter()
.position(|i| i.data.borrow().key() == *key)
}
/// Retrieves all known keys
pub fn keys(&self) -> Vec<K> {
self.items.iter().map(|i| i.data.borrow().key()).collect()
}
/// Adds a new item to the cache
///
/// Call [`Self::update`] to update the contents of the cached item
/// or just to bump the timestamp.
///
/// # Errors
/// Fails if the specified key already exist
pub fn insert(
&mut self,
time_now: chrono::NaiveDateTime,
data: T,
) -> Result<(), CacheError<K>> {
let key = data.key();
if self.contains(&key) {
Err(CacheError::InsertKeyAlreadyPresent(key))
} else {
self.items.push(CacheItem::new(time_now, data));
Ok(())
}
}
/// Retrieves an item from the cache
///
/// Returns `None` if no item exists for this key
pub fn get(&mut self, key: &K) -> Option<Rc<core::cell::RefCell<T>>> {
self.items.iter().find_map(|i| {
if i.data.borrow().key() == *key {
Some(i.data.clone())
} else {
None
}
})
}
/// Retrieves items from the cache which match the predicate
pub fn get_filtered<P>(&self, predicate: P) -> Vec<Rc<core::cell::RefCell<T>>>
where
P: Fn(&T) -> bool,
{
self.items
.iter()
.filter_map(|i| {
let data = i.data.borrow();
if predicate(&data) {
Some(i.data.clone())
} else {
None
}
})
.collect()
}
/// Updates an item in the cache using the provided function
///
/// This can also be used to just bump the timestamp of the cached item by providing an empty closure.
///
/// # Errors
/// Fails if the specified key does not exist in the cache
pub fn update<F>(
&mut self,
time_now: chrono::NaiveDateTime,
key: K,
func: F,
) -> Result<(), CacheError<K>>
where
F: Fn(&mut T),
K: core::fmt::Display,
{
// find item
let Some(pos) = self.find(&key) else {
return Err(CacheError::KeyNotFound(key));
};
// unwrap is fine since we fetched the position before
let item = self.items.get_mut(pos).unwrap();
let mut data = item.data.borrow_mut();
func(&mut data);
item.timestamp = time_now;
Ok(())
}
/// Updates an item in the cache using the provided function or create a new item with default values
pub fn update_or_init<F>(&mut self, time_now: chrono::NaiveDateTime, key: K, mut func: F)
where
F: FnMut(&mut T),
K: core::fmt::Display,
T: Initable<T, K>,
{
if let Some(pos) = self.find(&key) {
// update existsting item
// unwrap is fine since we fetched the position before
let item = self.items.get_mut(pos).unwrap();
let mut data = item.data.borrow_mut();
func(&mut data);
item.timestamp = time_now;
} else {
// insert new item
self.items.push(CacheItem::new(time_now, T::init(key)));
}
}
/// Cleans items which timed out from the cache
///
/// Call this regularly to prune old items.
pub fn prune(&mut self, time_now: chrono::NaiveDateTime) {
self.items.retain(|i| {
let age = time_now.sub(i.timestamp);
age < self.lifetime
});
}
}