Move `egui::util::cache` to `egui::cache`; add `FramePublisher` (#5426)

This moves `egui::util::cache` to `egui::cache` (the old path is
deprecated, but still works).

It also adds the `FramePublisher` helper, which can be used to publish a
value which will be retained for this frame and the next:

``` rs
pub type MyPublisher = egui::cache::FramePublisher<MyKey, MyValue>;

// Publish:
ctx.memory_mut(|mem| {
    mem.caches.cache::<MyPublisher>().set(key, value);
});

// Retrieve:
let value: Option<MyValue> = ctx.memory_mut(|mem| {
    mem.caches
        .cache::<MyPublisher>()
        .get(key)
        .clone()
})
```
This commit is contained in:
Emil Ernerfeldt 2024-12-03 14:28:12 +01:00 committed by GitHub
parent c7224aab26
commit eac7ba01fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 172 additions and 86 deletions

69
crates/egui/src/cache/cache_storage.rs vendored Normal file
View File

@ -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<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::HashMap<std::any::TypeId, Box<dyn CacheTrait>>,
}
impl CacheStorage {
pub fn cache<Cache: CacheTrait + Default>(&mut self) -> &mut Cache {
self.caches
.entry(std::any::TypeId::of::<Cache>())
.or_insert_with(|| Box::<Cache>::default())
.as_any_mut()
.downcast_mut::<Cache>()
.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()
)
}
}

11
crates/egui/src/cache/cache_trait.rs vendored Normal file
View File

@ -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;
}

View File

@ -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<Value, Computer> FrameCache<Value, Computer> {
}
}
#[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>
{
@ -100,65 +84,3 @@ impl<Value: 'static + Send + Sync, Computer: 'static + Send + Sync> 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<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::HashMap<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::<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()
}
}
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()
)
}
}

View File

@ -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<Key: Eq + Hash, Value> {
generation: u32,
cache: ahash::HashMap<Key, (u32, Value)>,
}
impl<Key: Eq + Hash, Value> Default for FramePublisher<Key, Value> {
fn default() -> Self {
Self::new()
}
}
impl<Key: Eq + Hash, Value> FramePublisher<Key, Value> {
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<Key, Value> CacheTrait for FramePublisher<Key, Value>
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
}
}

21
crates/egui/src/cache/mod.rs vendored Normal file
View File

@ -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;

View File

@ -393,6 +393,7 @@
#![allow(clippy::manual_range_contains)]
mod animation_manager;
pub mod cache;
pub mod containers;
mod context;
mod data;

View File

@ -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

View File

@ -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;

View File

@ -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<LayoutJob, Highlighter>;
type HighlightCache = egui::cache::FrameCache<LayoutJob, Highlighter>;
let font_id = style
.override_font_id