mirror of https://github.com/emilk/egui.git
Emil Ernerfeldt
3 years ago
9 changed files with 235 additions and 34 deletions
@ -1,41 +1,164 @@ |
|||
use epaint::util::hash; |
|||
//! Computing the same thing each frame can be expensive,
|
|||
//! so often you want to save the result from the previous frame and reuse it.
|
|||
//!
|
|||
//! Enter [`FrameCache`]: it caches the results of a computation for one frame.
|
|||
//! If it is still used next frame, it is not recomputed.
|
|||
//! If it is not used next frame, it is evicted from the cache to save memory.
|
|||
|
|||
const SIZE: usize = 1024; // must be small for web/WASM build (for unknown reason)
|
|||
/// Something that does an expensive computation that we want to cache
|
|||
/// to save us from recomputing it each frame.
|
|||
pub trait ComputerMut<Key, Value>: 'static + Send + Sync { |
|||
fn compute(&mut self, key: Key) -> Value; |
|||
} |
|||
|
|||
/// Very stupid/simple key-value cache. TODO: improve
|
|||
#[derive(Clone)] |
|||
pub struct Cache<K, V>([Option<(K, V)>; SIZE]); |
|||
/// Caches the results of a computation for one frame.
|
|||
/// If it is still used next frame, it is not recomputed.
|
|||
/// If it is not used next frame, it is evicted from the cache to save memory.
|
|||
pub struct FrameCache<Value, Computer> { |
|||
generation: u32, |
|||
computer: Computer, |
|||
cache: nohash_hasher::IntMap<u64, (u32, Value)>, |
|||
} |
|||
|
|||
impl<K, V> Default for Cache<K, V> |
|||
impl<Value, Computer> Default for FrameCache<Value, Computer> |
|||
where |
|||
K: Copy, |
|||
V: Copy, |
|||
Computer: Default, |
|||
{ |
|||
fn default() -> Self { |
|||
Self([None; SIZE]) |
|||
Self::new(Computer::default()) |
|||
} |
|||
} |
|||
|
|||
impl<K, V> std::fmt::Debug for Cache<K, V> { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "Cache") |
|||
impl<Value, Computer> FrameCache<Value, Computer> { |
|||
pub fn new(computer: Computer) -> Self { |
|||
Self { |
|||
generation: 0, |
|||
computer, |
|||
cache: Default::default(), |
|||
} |
|||
} |
|||
|
|||
impl<K, V> Cache<K, V> |
|||
/// Must be called once per frame to clear the [`Galley`] cache.
|
|||
pub fn evice_cache(&mut self) { |
|||
let current_generation = self.generation; |
|||
self.cache.retain(|_key, cached| { |
|||
cached.0 == current_generation // only keep those that were used this frame
|
|||
}); |
|||
self.generation = self.generation.wrapping_add(1); |
|||
} |
|||
} |
|||
|
|||
impl<Value, Computer> FrameCache<Value, Computer> { |
|||
/// Get from cache (if the same key was used last frame)
|
|||
/// or recompute and store in the cache.
|
|||
pub fn get<Key>(&mut self, key: Key) -> Value |
|||
where |
|||
K: std::hash::Hash + PartialEq, |
|||
Key: Copy + std::hash::Hash, |
|||
Value: Clone, |
|||
Computer: ComputerMut<Key, Value>, |
|||
{ |
|||
let hash = crate::util::hash(key); |
|||
|
|||
match self.cache.entry(hash) { |
|||
std::collections::hash_map::Entry::Occupied(entry) => { |
|||
let cached = entry.into_mut(); |
|||
cached.0 = self.generation; |
|||
cached.1.clone() |
|||
} |
|||
std::collections::hash_map::Entry::Vacant(entry) => { |
|||
let value = self.computer.compute(key); |
|||
entry.insert((self.generation, value.clone())); |
|||
value |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
#[allow(clippy::len_without_is_empty)] |
|||
pub trait CacheTrait: 'static + Send + Sync { |
|||
/// Call once per frame to evict cache.
|
|||
fn update(&mut self); |
|||
|
|||
/// Number of values currently in the cache.
|
|||
fn len(&self) -> usize; |
|||
|
|||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any; |
|||
} |
|||
|
|||
impl<Value: 'static + Send + Sync, Computer: 'static + Send + Sync> CacheTrait |
|||
for FrameCache<Value, Computer> |
|||
{ |
|||
pub fn get(&self, key: &K) -> Option<&V> { |
|||
let bucket = (hash(key) % (SIZE as u64)) as usize; |
|||
match &self.0[bucket] { |
|||
Some((k, v)) if k == key => Some(v), |
|||
_ => None, |
|||
fn update(&mut self) { |
|||
self.evice_cache() |
|||
} |
|||
|
|||
fn len(&self) -> usize { |
|||
self.cache.len() |
|||
} |
|||
|
|||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any { |
|||
self |
|||
} |
|||
} |
|||
|
|||
/// ```
|
|||
/// use egui::util::cache::{CacheStorage, ComputerMut, FrameCache};
|
|||
///
|
|||
/// #[derive(Default)]
|
|||
/// struct CharCounter {}
|
|||
/// impl ComputerMut<&str, usize> for CharCounter {
|
|||
/// fn compute(&mut self, s: &str) -> usize {
|
|||
/// s.chars().count()
|
|||
/// }
|
|||
/// }
|
|||
/// type CharCountCache<'a> = FrameCache<usize, CharCounter>;
|
|||
///
|
|||
/// # let mut cache_storage = CacheStorage::default();
|
|||
/// let mut cache = cache_storage.cache::<CharCountCache<'_>>();
|
|||
/// assert_eq!(cache.get("hello"), 5);
|
|||
/// ```
|
|||
#[derive(Default)] |
|||
pub struct CacheStorage { |
|||
caches: ahash::AHashMap<std::any::TypeId, Box<dyn CacheTrait>>, |
|||
} |
|||
|
|||
impl CacheStorage { |
|||
pub fn cache<FrameCache: CacheTrait + Default>(&mut self) -> &mut FrameCache { |
|||
self.caches |
|||
.entry(std::any::TypeId::of::<FrameCache>()) |
|||
.or_insert_with(|| Box::new(FrameCache::default())) |
|||
.as_any_mut() |
|||
.downcast_mut::<FrameCache>() |
|||
.unwrap() |
|||
} |
|||
|
|||
/// Total number of cached values
|
|||
fn num_values(&self) -> usize { |
|||
self.caches.values().map(|cache| cache.len()).sum() |
|||
} |
|||
|
|||
/// Call once per frame to evict cache.
|
|||
pub fn update(&mut self) { |
|||
for cache in self.caches.values_mut() { |
|||
cache.update(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
pub fn set(&mut self, key: K, value: V) { |
|||
let bucket = (hash(&key) % (SIZE as u64)) as usize; |
|||
self.0[bucket] = Some((key, value)); |
|||
impl Clone for CacheStorage { |
|||
fn clone(&self) -> Self { |
|||
// We return an empty cache that can be filled in again.
|
|||
Self::default() |
|||
} |
|||
} |
|||
|
|||
impl std::fmt::Debug for CacheStorage { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!( |
|||
f, |
|||
"FrameCacheStorage[{} caches with {} elements]", |
|||
self.caches.len(), |
|||
self.num_values() |
|||
) |
|||
} |
|||
} |
|||
|
@ -0,0 +1,41 @@ |
|||
use epaint::util::hash; |
|||
|
|||
const FIXED_CACHE_SIZE: usize = 1024; // must be small for web/WASM build (for unknown reason)
|
|||
|
|||
/// Very stupid/simple key-value cache. TODO: improve
|
|||
#[derive(Clone)] |
|||
pub(crate) struct FixedCache<K, V>([Option<(K, V)>; FIXED_CACHE_SIZE]); |
|||
|
|||
impl<K, V> Default for FixedCache<K, V> |
|||
where |
|||
K: Copy, |
|||
V: Copy, |
|||
{ |
|||
fn default() -> Self { |
|||
Self([None; FIXED_CACHE_SIZE]) |
|||
} |
|||
} |
|||
|
|||
impl<K, V> std::fmt::Debug for FixedCache<K, V> { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
write!(f, "Cache") |
|||
} |
|||
} |
|||
|
|||
impl<K, V> FixedCache<K, V> |
|||
where |
|||
K: std::hash::Hash + PartialEq, |
|||
{ |
|||
pub fn get(&self, key: &K) -> Option<&V> { |
|||
let bucket = (hash(key) % (FIXED_CACHE_SIZE as u64)) as usize; |
|||
match &self.0[bucket] { |
|||
Some((k, v)) if k == key => Some(v), |
|||
_ => None, |
|||
} |
|||
} |
|||
|
|||
pub fn set(&mut self, key: K, value: V) { |
|||
let bucket = (hash(&key) % (FIXED_CACHE_SIZE as u64)) as usize; |
|||
self.0[bucket] = Some((key, value)); |
|||
} |
|||
} |
@ -1,8 +1,10 @@ |
|||
//! Miscellaneous tools used by the rest of egui.
|
|||
|
|||
pub(crate) mod cache; |
|||
pub mod cache; |
|||
pub(crate) mod fixed_cache; |
|||
mod history; |
|||
pub mod undoer; |
|||
|
|||
pub(crate) use cache::Cache; |
|||
pub use history::History; |
|||
|
|||
pub use epaint::util::{hash, hash_with}; |
|||
|
Loading…
Reference in new issue