diff options
Diffstat (limited to 'src/query.rs')
| -rw-r--r-- | src/query.rs | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 00000000..97af0139 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,121 @@ +//! Query caching. + +use std::any::Any; +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::{self, Display, Formatter}; +use std::hash::Hash; + +thread_local! { + /// The thread-local query cache. + static CACHE: RefCell<Cache> = RefCell::default(); +} + +/// A map from hashes to cache entries. +type Cache = HashMap<u64, CacheEntry>; + +/// Access the cache. +fn with<F, R>(f: F) -> R +where + F: FnOnce(&mut Cache) -> R, +{ + CACHE.with(|cell| f(&mut cell.borrow_mut())) +} + +/// An entry in the query cache. +struct CacheEntry { + /// The query's results. + data: Box<dyn Any>, + /// How many evictions have passed since the entry has been last used. + age: usize, +} + +/// Execute a query. +/// +/// This hashes all inputs to the query and then either returns a cached version +/// from the thread-local query cache or executes the query and saves a copy of +/// the results in the cache. +/// +/// Note that `f` must be a pure function. +pub fn query<I, O>(input: I, f: fn(input: I) -> O) -> O +where + I: Hash, + O: Clone + 'static, +{ + query_ref(input, f, Clone::clone) +} + +/// Execute a query and then call a function with a reference to the result. +/// +/// This hashes all inputs to the query and then either call `g` with a cached +/// version from the thread-local query cache or executes the query, calls `g` +/// with the fresh version and saves the result in the cache. +/// +/// Note that `f` must be a pure function, while `g` does not need to be pure. +pub fn query_ref<I, O, G, R>(input: I, f: fn(input: I) -> O, g: G) -> R +where + I: Hash, + O: 'static, + G: Fn(&O) -> R, +{ + let hash = fxhash::hash64(&input); + let result = with(|cache| { + let entry = cache.get_mut(&hash)?; + entry.age = 0; + entry.data.downcast_ref().map(|output| g(output)) + }); + + result.unwrap_or_else(|| { + let output = f(input); + let result = g(&output); + let entry = CacheEntry { data: Box::new(output), age: 0 }; + with(|cache| cache.insert(hash, entry)); + result + }) +} + +/// Garbage-collect the thread-local query cache. +/// +/// This deletes elements which haven't been used in a while and returns details +/// about the eviction. +pub fn evict() -> Eviction { + with(|cache| { + const MAX_AGE: usize = 5; + + let before = cache.len(); + cache.retain(|_, entry| { + entry.age += 1; + entry.age <= MAX_AGE + }); + + Eviction { before, after: cache.len() } + }) +} + +/// Details about a cache eviction. +pub struct Eviction { + /// The number of items in the cache before the eviction. + pub before: usize, + /// The number of items in the cache after the eviction. + pub after: usize, +} + +impl Display for Eviction { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + writeln!(f, "Before: {}", self.before)?; + writeln!(f, "Evicted: {}", self.before - self.after)?; + writeln!(f, "After: {}", self.after) + } +} + +// These impls are temporary and incorrect. +macro_rules! skip { + ($ty:ty) => { + impl Hash for $ty { + fn hash<H: std::hash::Hasher>(&self, _: &mut H) {} + } + }; +} + +skip!(crate::font::FontStore); +skip!(crate::Context); |
