189 lines
4.7 KiB
Rust
189 lines
4.7 KiB
Rust
//! Cache computations and efficiently reuse them.
|
|
use std::cell::RefCell;
|
|
use std::fmt;
|
|
use std::sync::atomic::{self, AtomicU64};
|
|
|
|
/// A simple cache that stores generated values to avoid recomputation.
|
|
///
|
|
/// Keeps track of the last generated value after clearing.
|
|
pub struct Cache<T> {
|
|
group: Group,
|
|
state: RefCell<State<T>>,
|
|
}
|
|
|
|
impl<T> Cache<T> {
|
|
/// Creates a new empty [`Cache`].
|
|
pub fn new() -> Self {
|
|
Cache {
|
|
group: Group::singleton(),
|
|
state: RefCell::new(State::Empty { previous: None }),
|
|
}
|
|
}
|
|
|
|
/// Creates a new empty [`Cache`] with the given [`Group`].
|
|
///
|
|
/// Caches within the same group may reuse internal rendering storage.
|
|
///
|
|
/// You should generally group caches that are likely to change
|
|
/// together.
|
|
pub fn with_group(group: Group) -> Self {
|
|
assert!(
|
|
!group.is_singleton(),
|
|
"The group {group:?} cannot be shared!"
|
|
);
|
|
|
|
Cache {
|
|
group,
|
|
state: RefCell::new(State::Empty { previous: None }),
|
|
}
|
|
}
|
|
|
|
/// Returns the [`Group`] of the [`Cache`].
|
|
pub fn group(&self) -> Group {
|
|
self.group
|
|
}
|
|
|
|
/// Puts the given value in the [`Cache`].
|
|
///
|
|
/// Notice that, given this is a cache, a mutable reference is not
|
|
/// necessary to call this method. You can safely update the cache in
|
|
/// rendering code.
|
|
pub fn put(&self, value: T) {
|
|
*self.state.borrow_mut() = State::Filled { current: value };
|
|
}
|
|
|
|
/// Returns a reference cell to the internal [`State`] of the [`Cache`].
|
|
pub fn state(&self) -> &RefCell<State<T>> {
|
|
&self.state
|
|
}
|
|
|
|
/// Clears the [`Cache`].
|
|
pub fn clear(&self)
|
|
where
|
|
T: Clone,
|
|
{
|
|
use std::ops::Deref;
|
|
|
|
let previous = match self.state.borrow().deref() {
|
|
State::Empty { previous } => previous.clone(),
|
|
State::Filled { current } => Some(current.clone()),
|
|
};
|
|
|
|
*self.state.borrow_mut() = State::Empty { previous };
|
|
}
|
|
}
|
|
|
|
/// A cache group.
|
|
///
|
|
/// Caches that share the same group generally change together.
|
|
///
|
|
/// A cache group can be used to implement certain performance
|
|
/// optimizations during rendering, like batching or sharing atlases.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct Group {
|
|
id: u64,
|
|
is_singleton: bool,
|
|
}
|
|
|
|
impl Group {
|
|
/// Generates a new unique cache [`Group`].
|
|
pub fn unique() -> Self {
|
|
static NEXT: AtomicU64 = AtomicU64::new(0);
|
|
|
|
Self {
|
|
id: NEXT.fetch_add(1, atomic::Ordering::Relaxed),
|
|
is_singleton: false,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if the [`Group`] can only ever have a
|
|
/// single [`Cache`] in it.
|
|
///
|
|
/// This is the default kind of [`Group`] assigned when using
|
|
/// [`Cache::new`].
|
|
///
|
|
/// Knowing that a [`Group`] will never be shared may be
|
|
/// useful for rendering backends to perform additional
|
|
/// optimizations.
|
|
pub fn is_singleton(self) -> bool {
|
|
self.is_singleton
|
|
}
|
|
|
|
fn singleton() -> Self {
|
|
Self {
|
|
is_singleton: true,
|
|
..Self::unique()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> fmt::Debug for Cache<T>
|
|
where
|
|
T: fmt::Debug,
|
|
{
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
use std::ops::Deref;
|
|
|
|
let state = self.state.borrow();
|
|
|
|
match state.deref() {
|
|
State::Empty { previous } => {
|
|
write!(f, "Cache::Empty {{ previous: {previous:?} }}")
|
|
}
|
|
State::Filled { current } => {
|
|
write!(f, "Cache::Filled {{ current: {current:?} }}")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Default for Cache<T> {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// The state of a [`Cache`].
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum State<T> {
|
|
/// The [`Cache`] is empty.
|
|
Empty {
|
|
/// The previous value of the [`Cache`].
|
|
previous: Option<T>,
|
|
},
|
|
/// The [`Cache`] is filled.
|
|
Filled {
|
|
/// The current value of the [`Cache`]
|
|
current: T,
|
|
},
|
|
}
|
|
|
|
/// A piece of data that can be cached.
|
|
pub trait Cached: Sized {
|
|
/// The type of cache produced.
|
|
type Cache: Clone;
|
|
|
|
/// Loads the [`Cache`] into a proper instance.
|
|
///
|
|
/// [`Cache`]: Self::Cache
|
|
fn load(cache: &Self::Cache) -> Self;
|
|
|
|
/// Caches this value, producing its corresponding [`Cache`].
|
|
///
|
|
/// [`Cache`]: Self::Cache
|
|
fn cache(self, group: Group, previous: Option<Self::Cache>) -> Self::Cache;
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
impl Cached for () {
|
|
type Cache = ();
|
|
|
|
fn load(_cache: &Self::Cache) -> Self {}
|
|
|
|
fn cache(
|
|
self,
|
|
_group: Group,
|
|
_previous: Option<Self::Cache>,
|
|
) -> Self::Cache {
|
|
}
|
|
}
|