//! Incremental file based incremental rendering #![allow(non_snake_case)] use chrono::offset::Utc; use chrono::DateTime; use rustc_hash::FxHasher; use std::{hash::BuildHasherDefault, num::NonZeroUsize}; use super::freshness::RenderFreshness; pub(crate) struct InMemoryCache { #[allow(clippy::type_complexity)] lru: Option, Vec), BuildHasherDefault>>, invalidate_after: Option, } impl InMemoryCache { pub fn new(memory_cache_limit: usize, invalidate_after: Option) -> Self { Self { lru: NonZeroUsize::new(memory_cache_limit) .map(|limit| lru::LruCache::with_hasher(limit, Default::default())), invalidate_after, } } pub fn clear(&mut self) { if let Some(cache) = &mut self.lru { cache.clear(); } } pub fn put(&mut self, route: String, timestamp: DateTime, data: Vec) { if let Some(cache) = &mut self.lru { cache.put(route, (timestamp, data)); } } pub fn invalidate(&mut self, route: &str) { if let Some(cache) = &mut self.lru { cache.pop(route); } } pub fn try_get_or_insert<'a, F: FnOnce() -> Result<(DateTime, Vec), E>, E>( &'a mut self, route: &str, or_insert: F, ) -> Result, E> { if let Some(memory_cache) = self.lru.as_mut() { let (timestamp, _) = memory_cache.try_get_or_insert(route.to_string(), or_insert)?; let now = Utc::now(); let elapsed = timestamp.signed_duration_since(now); let age = elapsed.num_seconds(); // The cache entry is out of date, so we need to remove it. if let Some(invalidate_after) = self.invalidate_after { // If we can't convert to a std duration, the duration is negative and hasn't elapsed yet. if let Ok(std_elapsed) = elapsed.to_std() { if std_elapsed > invalidate_after { tracing::trace!("memory cache out of date"); memory_cache.pop(route); return Ok(None); } } } // We need to reborrow because we may have invalidated the lifetime if the route was removed. // We know it wasn't because we returned... but rust doesn't understand that. let (timestamp, cache_hit) = memory_cache.get(route).unwrap(); return match self.invalidate_after { Some(invalidate_after) => { tracing::trace!("memory cache hit"); let max_age = invalidate_after.as_secs(); let freshness = RenderFreshness::new(age as u64, max_age, *timestamp); Ok(Some((freshness, cache_hit))) } None => { tracing::trace!("memory cache hit"); let freshness = RenderFreshness::new_age(age as u64, *timestamp); Ok(Some((freshness, cache_hit))) } }; } Ok(None) } }