diff --git a/crates/egui/src/cache/cache_storage.rs b/crates/egui/src/cache/cache_storage.rs new file mode 100644 index 00000000..d4c3c9ae --- /dev/null +++ b/crates/egui/src/cache/cache_storage.rs @@ -0,0 +1,69 @@ +use super::CacheTrait; + +/// A typemap of many caches, all implemented with [`CacheTrait`]. +/// +/// You can access egui's caches via [`crate::Memory::caches`], +/// found with [`crate::Context::memory_mut`]. +/// +/// ``` +/// use egui::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; +/// +/// # let mut cache_storage = CacheStorage::default(); +/// let mut cache = cache_storage.cache::>(); +/// assert_eq!(cache.get("hello"), 5); +/// ``` +#[derive(Default)] +pub struct CacheStorage { + caches: ahash::HashMap>, +} + +impl CacheStorage { + pub fn cache(&mut self) -> &mut Cache { + self.caches + .entry(std::any::TypeId::of::()) + .or_insert_with(|| Box::::default()) + .as_any_mut() + .downcast_mut::() + .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) { + self.caches.retain(|_, cache| { + cache.update(); + cache.len() > 0 + }); + } +} + +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() + ) + } +} diff --git a/crates/egui/src/cache/cache_trait.rs b/crates/egui/src/cache/cache_trait.rs new file mode 100644 index 00000000..73cb61f3 --- /dev/null +++ b/crates/egui/src/cache/cache_trait.rs @@ -0,0 +1,11 @@ +/// A cache, storing some value for some length of time. +#[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; +} diff --git a/crates/egui/src/util/cache.rs b/crates/egui/src/cache/frame_cache.rs similarity index 51% rename from crates/egui/src/util/cache.rs rename to crates/egui/src/cache/frame_cache.rs index 14ec9a7b..6c74c58d 100644 --- a/crates/egui/src/util/cache.rs +++ b/crates/egui/src/cache/frame_cache.rs @@ -1,9 +1,4 @@ -//! 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. +use super::CacheTrait; /// Something that does an expensive computation that we want to cache /// to save us from recomputing it each frame. @@ -74,17 +69,6 @@ impl FrameCache { } } -#[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 CacheTrait for FrameCache { @@ -100,65 +84,3 @@ impl CacheTrait 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; -/// -/// # let mut cache_storage = CacheStorage::default(); -/// let mut cache = cache_storage.cache::>(); -/// assert_eq!(cache.get("hello"), 5); -/// ``` -#[derive(Default)] -pub struct CacheStorage { - caches: ahash::HashMap>, -} - -impl CacheStorage { - pub fn cache(&mut self) -> &mut FrameCache { - self.caches - .entry(std::any::TypeId::of::()) - .or_insert_with(|| Box::::default()) - .as_any_mut() - .downcast_mut::() - .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() - } -} - -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() - ) - } -} diff --git a/crates/egui/src/cache/frame_publisher.rs b/crates/egui/src/cache/frame_publisher.rs new file mode 100644 index 00000000..0c2bc81d --- /dev/null +++ b/crates/egui/src/cache/frame_publisher.rs @@ -0,0 +1,61 @@ +use std::hash::Hash; + +use super::CacheTrait; + +/// Stores a key:value pair for the duration of this frame and the next. +pub struct FramePublisher { + generation: u32, + cache: ahash::HashMap, +} + +impl Default for FramePublisher { + fn default() -> Self { + Self::new() + } +} + +impl FramePublisher { + pub fn new() -> Self { + Self { + generation: 0, + cache: Default::default(), + } + } + + /// Publish the value. It will be available for the duration of this and the next frame. + pub fn set(&mut self, key: Key, value: Value) { + self.cache.insert(key, (self.generation, value)); + } + + /// Retrieve a value if it was published this or the previous frame. + pub fn get(&self, key: &Key) -> Option<&Value> { + self.cache.get(key).map(|(_, value)| value) + } + + /// Must be called once per frame to clear the cache. + pub fn evict_cache(&mut self) { + let current_generation = self.generation; + self.cache.retain(|_key, cached| { + cached.0 == current_generation // only keep those that were published this frame + }); + self.generation = self.generation.wrapping_add(1); + } +} + +impl CacheTrait for FramePublisher +where + Key: 'static + Eq + Hash + Send + Sync, + Value: 'static + Send + Sync, +{ + fn update(&mut self) { + self.evict_cache(); + } + + fn len(&self) -> usize { + self.cache.len() + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} diff --git a/crates/egui/src/cache/mod.rs b/crates/egui/src/cache/mod.rs new file mode 100644 index 00000000..68469ef3 --- /dev/null +++ b/crates/egui/src/cache/mod.rs @@ -0,0 +1,21 @@ +//! Caches for preventing the same value from being recomputed every frame. +//! +//! 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. +//! +//! You can access egui's caches via [`crate::Memory::caches`], +//! found with [`crate::Context::memory_mut`]. + +mod cache_storage; +mod cache_trait; +mod frame_cache; +mod frame_publisher; + +pub use cache_storage::CacheStorage; +pub use cache_trait::CacheTrait; +pub use frame_cache::{ComputerMut, FrameCache}; +pub use frame_publisher::FramePublisher; diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 18b99c69..6d8c6a34 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -393,6 +393,7 @@ #![allow(clippy::manual_range_contains)] mod animation_manager; +pub mod cache; pub mod containers; mod context; mod data; diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index f49f1342..5e94e6c0 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -54,7 +54,7 @@ pub struct Memory { /// so as not to lock the UI thread. /// /// ``` - /// use egui::util::cache::{ComputerMut, FrameCache}; + /// use egui::cache::{ComputerMut, FrameCache}; /// /// #[derive(Default)] /// struct CharCounter {} @@ -72,7 +72,7 @@ pub struct Memory { /// }); /// ``` #[cfg_attr(feature = "persistence", serde(skip))] - pub caches: crate::util::cache::CacheStorage, + pub caches: crate::cache::CacheStorage, // ------------------------------------------ /// new fonts that will be applied at the start of the next frame diff --git a/crates/egui/src/util/mod.rs b/crates/egui/src/util/mod.rs index 55e93eb0..de62b961 100644 --- a/crates/egui/src/util/mod.rs +++ b/crates/egui/src/util/mod.rs @@ -1,6 +1,5 @@ //! Miscellaneous tools used by the rest of egui. -pub mod cache; pub(crate) mod fixed_cache; pub mod id_type_map; pub mod undoer; @@ -9,3 +8,7 @@ pub use id_type_map::IdTypeMap; pub use epaint::emath::History; pub use epaint::util::{hash, hash_with}; + +/// Deprecated alias for [`crate::cache`]. +#[deprecated = "Use egui::cache instead"] +pub use crate::cache; diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 293fbde0..9275d345 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -33,9 +33,7 @@ pub fn highlight( // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available // (ui.ctx(), ui.style()) can be used - impl egui::util::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> - for Highlighter - { + impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter { fn compute( &mut self, (font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str), @@ -44,7 +42,7 @@ pub fn highlight( } } - type HighlightCache = egui::util::cache::FrameCache; + type HighlightCache = egui::cache::FrameCache; let font_id = style .override_font_id