motion_core/authoring/context/
cache.rs1use crate::sync::MutexExt;
4use std::any::{Any, TypeId};
5use std::collections::{VecDeque, hash_map::DefaultHasher};
6use std::hash::{Hash, Hasher};
7use std::panic::Location;
8use std::sync::{Arc, Mutex};
9
10use super::CacheLimits;
11
12pub struct SceneCache {
26 capacity: usize,
27 max_bytes: usize,
28 pub(super) entries: Mutex<VecDeque<CacheEntry>>,
29}
30
31impl std::fmt::Debug for SceneCache {
32 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 formatter
34 .debug_struct("SceneCache")
35 .field("capacity", &self.capacity)
36 .field("max_bytes", &self.max_bytes)
37 .finish_non_exhaustive()
38 }
39}
40
41pub(super) struct CacheEntry {
42 key: CacheKey,
43 input: Box<dyn Any + Send + Sync>,
44 value: Arc<dyn Any + Send + Sync>,
45 bytes: usize,
46}
47
48#[derive(PartialEq, Eq)]
49struct CacheKey {
50 location: &'static Location<'static>,
51 value_type: TypeId,
52 hash: u64,
53}
54
55impl CacheEntry {
56 fn matches<Key: Eq + 'static>(&self, key: &CacheKey, input: &Key) -> bool {
57 self.key == *key && self.input.downcast_ref::<Key>() == Some(input)
58 }
59}
60
61impl Default for SceneCache {
62 fn default() -> Self {
63 Self::new(256)
64 }
65}
66
67impl SceneCache {
68 pub fn new(capacity: usize) -> Self {
70 Self::with_limits(CacheLimits {
71 max_entries: capacity,
72 max_bytes: usize::MAX,
73 })
74 }
75
76 pub fn with_limits(limits: CacheLimits) -> Self {
80 Self {
81 capacity: limits.max_entries,
82 max_bytes: limits.max_bytes,
83 entries: Mutex::new(VecDeque::new()),
84 }
85 }
86
87 pub fn max_bytes(&self) -> usize {
89 self.max_bytes
90 }
91
92 pub fn retained_bytes(&self) -> usize {
94 self.entries
95 .lock_unpoisoned()
96 .iter()
97 .map(|entry| entry.bytes)
98 .sum()
99 }
100
101 pub fn capacity(&self) -> usize {
103 self.capacity
104 }
105
106 pub fn len(&self) -> usize {
108 self.entries.lock_unpoisoned().len()
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.len() == 0
114 }
115
116 pub fn clear(&self) {
118 let entries = std::mem::take(&mut *self.entries.lock_unpoisoned());
119 drop(entries);
120 }
121
122 #[track_caller]
123 pub(super) fn get<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
124 &self,
125 key: Key,
126 build: impl FnOnce() -> Value,
127 ) -> Arc<Value> {
128 match self.get_cached(
129 key,
130 || Ok::<_, std::convert::Infallible>(build()),
131 |_| 0,
132 false,
133 Location::caller(),
134 ) {
135 Ok(value) => value,
136 Err(never) => match never {},
137 }
138 }
139
140 #[track_caller]
142 pub(super) fn try_get<
143 Key: Eq + Hash + Send + Sync + 'static,
144 Value: Send + Sync + 'static,
145 Error,
146 >(
147 &self,
148 key: Key,
149 build: impl FnOnce() -> Result<Value, Error>,
150 ) -> Result<Arc<Value>, Error> {
151 self.get_cached(key, build, |_| 0, false, Location::caller())
152 }
153
154 #[track_caller]
156 pub fn get_with_cost<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
157 &self,
158 key: Key,
159 build: impl FnOnce() -> Value,
160 cost: impl FnOnce(&Value) -> usize,
161 ) -> Arc<Value> {
162 match self.get_cached(
163 key,
164 || Ok::<_, std::convert::Infallible>(build()),
165 cost,
166 true,
167 Location::caller(),
168 ) {
169 Ok(value) => value,
170 Err(never) => match never {},
171 }
172 }
173
174 fn content_location() -> &'static Location<'static> {
175 Location::caller()
176 }
177
178 pub fn lookup<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
180 &self,
181 key: &Key,
182 ) -> Option<Arc<Value>> {
183 let mut hasher = DefaultHasher::new();
184 key.hash(&mut hasher);
185 self.find(
186 &CacheKey {
187 location: Self::content_location(),
188 value_type: TypeId::of::<Value>(),
189 hash: hasher.finish(),
190 },
191 key,
192 )
193 }
194
195 pub fn insert_with_cost<
197 Key: Eq + Hash + Send + Sync + 'static,
198 Value: Send + Sync + 'static,
199 >(
200 &self,
201 key: Key,
202 value: Value,
203 cost: impl FnOnce(&Value) -> usize,
204 ) -> Arc<Value> {
205 match self.get_cached(
206 key,
207 || Ok::<_, std::convert::Infallible>(value),
208 cost,
209 true,
210 Self::content_location(),
211 ) {
212 Ok(value) => value,
213 Err(never) => match never {},
214 }
215 }
216
217 #[track_caller]
218 fn get_cached<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static, Error>(
219 &self,
220 key: Key,
221 build: impl FnOnce() -> Result<Value, Error>,
222 cost: impl FnOnce(&Value) -> usize,
223 accounted: bool,
224 location: &'static Location<'static>,
225 ) -> Result<Arc<Value>, Error> {
226 if self.capacity == 0 || (accounted && self.max_bytes == 0) {
227 return build().map(Arc::new);
228 }
229 let mut hasher = DefaultHasher::new();
230 key.hash(&mut hasher);
231 let cache_key = CacheKey {
232 location,
233 value_type: TypeId::of::<Value>(),
234 hash: hasher.finish(),
235 };
236 if let Some(value) = self.find(&cache_key, &key) {
237 return Ok(value);
238 }
239 let value = Arc::new(build()?);
240 let bytes = if accounted {
241 cost(value.as_ref())
242 .max(std::mem::size_of::<Value>().saturating_add(std::mem::size_of::<Key>()))
243 } else {
244 0
245 };
246 if bytes > self.max_bytes {
247 return Ok(value);
248 }
249 let mut entries = self.entries.lock_unpoisoned();
250 if let Some(index) = entries
251 .iter()
252 .position(|entry| entry.matches(&cache_key, &key))
253 {
254 let entry = entries.remove(index).expect("located cache entry");
255 let stored = entry.value.clone();
256 entries.push_back(entry);
257 drop(entries);
258 return Ok(stored
259 .downcast()
260 .unwrap_or_else(|_| unreachable!("the cache key encodes the value type")));
261 }
262 let mut retained: usize = entries.iter().map(|entry| entry.bytes).sum();
263 let mut evicted = Vec::new();
264 while entries.len() >= self.capacity || bytes > self.max_bytes.saturating_sub(retained) {
265 let Some(entry) = entries.pop_front() else {
266 break;
267 };
268 retained -= entry.bytes;
269 evicted.push(entry);
270 }
271 entries.push_back(CacheEntry {
272 key: cache_key,
273 input: Box::new(key),
274 value: value.clone(),
275 bytes,
276 });
277 drop(entries);
278 drop(evicted);
279 Ok(value)
280 }
281
282 fn find<Key: Eq + 'static, Value: Send + Sync + 'static>(
283 &self,
284 key: &CacheKey,
285 input: &Key,
286 ) -> Option<Arc<Value>> {
287 let mut entries = self.entries.lock_unpoisoned();
288 let index = entries.iter().position(|entry| entry.matches(key, input))?;
289 let entry = entries.remove(index).expect("located cache entry");
290 let value = entry.value.clone();
291 entries.push_back(entry);
292 drop(entries);
293 Some(
294 value
295 .downcast()
296 .unwrap_or_else(|_| unreachable!("the cache key encodes the value type")),
297 )
298 }
299}