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
|
/// Caches the results of a computation for one frame.
|
||||
#[derive(Clone)] |
/// If it is still used next frame, it is not recomputed.
|
||||
pub struct Cache<K, V>([Option<(K, V)>; SIZE]); |
/// 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 |
where |
||||
K: Copy, |
Computer: Default, |
||||
V: Copy, |
|
||||
{ |
{ |
||||
fn default() -> Self { |
fn default() -> Self { |
||||
Self([None; SIZE]) |
Self::new(Computer::default()) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
impl<K, V> std::fmt::Debug for Cache<K, V> { |
impl<Value, Computer> FrameCache<Value, Computer> { |
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
pub fn new(computer: Computer) -> Self { |
||||
write!(f, "Cache") |
Self { |
||||
|
generation: 0, |
||||
|
computer, |
||||
|
cache: Default::default(), |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 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<K, V> Cache<K, V> |
impl<Value, Computer> FrameCache<Value, Computer> { |
||||
where |
/// Get from cache (if the same key was used last frame)
|
||||
K: std::hash::Hash + PartialEq, |
/// or recompute and store in the cache.
|
||||
|
pub fn get<Key>(&mut self, key: Key) -> Value |
||||
|
where |
||||
|
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> { |
fn update(&mut self) { |
||||
let bucket = (hash(key) % (SIZE as u64)) as usize; |
self.evice_cache() |
||||
match &self.0[bucket] { |
} |
||||
Some((k, v)) if k == key => Some(v), |
|
||||
_ => None, |
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(); |
||||
} |
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
|
impl Clone for CacheStorage { |
||||
|
fn clone(&self) -> Self { |
||||
|
// We return an empty cache that can be filled in again.
|
||||
|
Self::default() |
||||
|
} |
||||
|
} |
||||
|
|
||||
pub fn set(&mut self, key: K, value: V) { |
impl std::fmt::Debug for CacheStorage { |
||||
let bucket = (hash(&key) % (SIZE as u64)) as usize; |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
self.0[bucket] = Some((key, value)); |
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.
|
//! Miscellaneous tools used by the rest of egui.
|
||||
|
|
||||
pub(crate) mod cache; |
pub mod cache; |
||||
|
pub(crate) mod fixed_cache; |
||||
mod history; |
mod history; |
||||
pub mod undoer; |
pub mod undoer; |
||||
|
|
||||
pub(crate) use cache::Cache; |
|
||||
pub use history::History; |
pub use history::History; |
||||
|
|
||||
|
pub use epaint::util::{hash, hash_with}; |
||||
|
Loading…
Reference in new issue