Skip to main content

iced_graphics/
cache.rs

1//! Cache computations and efficiently reuse them.
2use std::cell::RefCell;
3use std::fmt;
4use std::mem;
5use std::sync::atomic::{self, AtomicU64};
6
7/// A simple cache that stores generated values to avoid recomputation.
8///
9/// Keeps track of the last generated value after clearing.
10pub struct Cache<T> {
11    group: Group,
12    state: RefCell<State<T>>,
13    version: RefCell<u64>,
14}
15
16static VERSION: AtomicU64 = AtomicU64::new(0);
17
18impl<T> Cache<T> {
19    /// Creates a new empty [`Cache`].
20    pub fn new() -> Self {
21        Cache {
22            group: Group::singleton(),
23            state: RefCell::new(State::Empty { previous: None }),
24            version: RefCell::new(VERSION.load(atomic::Ordering::Relaxed)),
25        }
26    }
27
28    /// Creates a new empty [`Cache`] with the given [`Group`].
29    ///
30    /// Caches within the same group may reuse internal rendering storage.
31    ///
32    /// You should generally group caches that are likely to change
33    /// together.
34    pub fn with_group(group: Group) -> Self {
35        assert!(
36            !group.is_singleton(),
37            "The group {group:?} cannot be shared!"
38        );
39
40        Cache {
41            group,
42            state: RefCell::new(State::Empty { previous: None }),
43            version: RefCell::new(VERSION.load(atomic::Ordering::Relaxed)),
44        }
45    }
46
47    /// Returns the [`Group`] of the [`Cache`].
48    pub fn group(&self) -> Group {
49        self.group
50    }
51
52    /// Puts the given value in the [`Cache`].
53    ///
54    /// Notice that, given this is a cache, a mutable reference is not
55    /// necessary to call this method. You can safely update the cache in
56    /// rendering code.
57    pub fn put(&self, value: T) {
58        *self.state.borrow_mut() = State::Filled { current: value };
59    }
60
61    /// Returns a reference cell to the internal [`State`] of the [`Cache`].
62    pub fn state(&self) -> &RefCell<State<T>> {
63        let version = VERSION.load(atomic::Ordering::Relaxed);
64
65        if *self.version.borrow() != version {
66            *self.state.borrow_mut() = State::Empty { previous: None };
67            let _ = self.version.replace(version);
68        }
69
70        &self.state
71    }
72
73    /// Clears the [`Cache`].
74    pub fn clear(&self) {
75        let mut state = self.state.borrow_mut();
76
77        let previous = mem::replace(&mut *state, State::Empty { previous: None });
78
79        let previous = match previous {
80            State::Empty { previous } => previous,
81            State::Filled { current } => Some(current),
82        };
83
84        *state = State::Empty { previous };
85    }
86}
87
88/// A cache group.
89///
90/// Caches that share the same group generally change together.
91///
92/// A cache group can be used to implement certain performance
93/// optimizations during rendering, like batching or sharing atlases.
94#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
95pub struct Group {
96    id: u64,
97    is_singleton: bool,
98}
99
100impl Group {
101    /// Generates a new unique cache [`Group`].
102    pub fn unique() -> Self {
103        static NEXT: AtomicU64 = AtomicU64::new(0);
104
105        Self {
106            id: NEXT.fetch_add(1, atomic::Ordering::Relaxed),
107            is_singleton: false,
108        }
109    }
110
111    /// Returns `true` if the [`Group`] can only ever have a
112    /// single [`Cache`] in it.
113    ///
114    /// This is the default kind of [`Group`] assigned when using
115    /// [`Cache::new`].
116    ///
117    /// Knowing that a [`Group`] will never be shared may be
118    /// useful for rendering backends to perform additional
119    /// optimizations.
120    pub fn is_singleton(self) -> bool {
121        self.is_singleton
122    }
123
124    fn singleton() -> Self {
125        Self {
126            is_singleton: true,
127            ..Self::unique()
128        }
129    }
130}
131
132impl<T> fmt::Debug for Cache<T>
133where
134    T: fmt::Debug,
135{
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        use std::ops::Deref;
138
139        let state = self.state.borrow();
140
141        match state.deref() {
142            State::Empty { previous } => {
143                write!(f, "Cache::Empty {{ previous: {previous:?} }}")
144            }
145            State::Filled { current } => {
146                write!(f, "Cache::Filled {{ current: {current:?} }}")
147            }
148        }
149    }
150}
151
152impl<T> Default for Cache<T> {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158/// The state of a [`Cache`].
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum State<T> {
161    /// The [`Cache`] is empty.
162    Empty {
163        /// The previous value of the [`Cache`].
164        previous: Option<T>,
165    },
166    /// The [`Cache`] is filled.
167    Filled {
168        /// The current value of the [`Cache`]
169        current: T,
170    },
171}
172
173/// A piece of data that can be cached.
174pub trait Cached: Sized {
175    /// The type of cache produced.
176    type Cache: Clone;
177
178    /// Loads the [`Cache`] into a proper instance.
179    ///
180    /// [`Cache`]: Self::Cache
181    fn load(cache: &Self::Cache) -> Self;
182
183    /// Caches this value, producing its corresponding [`Cache`].
184    ///
185    /// [`Cache`]: Self::Cache
186    fn cache(self, group: Group, previous: Option<Self::Cache>) -> Self::Cache;
187}
188
189#[cfg(debug_assertions)]
190impl Cached for () {
191    type Cache = ();
192
193    fn load(_cache: &Self::Cache) -> Self {}
194
195    fn cache(self, _group: Group, _previous: Option<Self::Cache>) -> Self::Cache {}
196}
197
198/// Invalidates all the existing [`Cache`]; clearing their contents.
199pub fn invalidate_all() {
200    let _ = VERSION.fetch_add(1, atomic::Ordering::Relaxed);
201}