motion_core/authoring/context/
cache.rs1use std::any::{Any, TypeId};
4use std::collections::{VecDeque, hash_map::DefaultHasher};
5use std::hash::{Hash, Hasher};
6use std::panic::Location;
7use std::sync::{Arc, Mutex};
8
9use super::CacheLimits;
10
11pub struct SceneCache {
22 capacity: usize,
23 max_bytes: usize,
24 pub(super) entries: Mutex<VecDeque<CacheEntry>>,
25 pub(super) empty_assets: crate::Assets,
26}
27
28impl std::fmt::Debug for SceneCache {
29 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 formatter
31 .debug_struct("SceneCache")
32 .field("capacity", &self.capacity)
33 .field("max_bytes", &self.max_bytes)
34 .finish_non_exhaustive()
35 }
36}
37
38pub(super) struct CacheEntry {
39 key: CacheKey,
40 input: Box<dyn Any + Send + Sync>,
41 value: Arc<dyn Any + Send + Sync>,
42 bytes: usize,
43}
44
45#[derive(PartialEq, Eq)]
46struct CacheKey {
47 location: &'static Location<'static>,
48 value_type: TypeId,
49 hash: u64,
50}
51
52impl CacheEntry {
53 fn matches<Key: Eq + 'static>(&self, key: &CacheKey, input: &Key) -> bool {
54 self.key == *key && self.input.downcast_ref::<Key>() == Some(input)
55 }
56}
57
58impl Default for SceneCache {
59 fn default() -> Self {
60 Self::new(256)
61 }
62}
63
64impl SceneCache {
65 pub fn new(capacity: usize) -> Self {
67 Self::with_limits(CacheLimits {
68 entries: capacity,
69 bytes: usize::MAX,
70 })
71 }
72
73 pub fn with_limits(limits: CacheLimits) -> Self {
77 Self {
78 capacity: limits.entries,
79 max_bytes: limits.bytes,
80 entries: Mutex::new(VecDeque::new()),
81 empty_assets: crate::Assets::default(),
82 }
83 }
84
85 pub fn max_bytes(&self) -> usize {
87 self.max_bytes
88 }
89
90 pub fn retained_bytes(&self) -> usize {
92 self.entries
93 .lock()
94 .expect("scene cache lock poisoned")
95 .iter()
96 .map(|entry| entry.bytes)
97 .sum()
98 }
99
100 pub fn capacity(&self) -> usize {
102 self.capacity
103 }
104
105 pub fn len(&self) -> usize {
107 self.entries
108 .lock()
109 .expect("scene cache lock poisoned")
110 .len()
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.len() == 0
116 }
117
118 pub fn clear(&self) {
120 let entries = std::mem::take(&mut *self.entries.lock().expect("scene cache lock poisoned"));
121 drop(entries);
122 }
123
124 #[track_caller]
125 pub(super) fn get<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
126 &self,
127 key: Key,
128 build: impl FnOnce() -> Value,
129 ) -> Arc<Value> {
130 self.get_cached(key, build, |_| 0, false, Location::caller())
131 }
132
133 #[track_caller]
135 pub fn get_with_cost<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
136 &self,
137 key: Key,
138 build: impl FnOnce() -> Value,
139 cost: impl FnOnce(&Value) -> usize,
140 ) -> Arc<Value> {
141 self.get_cached(key, build, cost, true, Location::caller())
142 }
143
144 fn content_location() -> &'static Location<'static> {
145 Location::caller()
146 }
147
148 pub fn lookup<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
150 &self,
151 key: &Key,
152 ) -> Option<Arc<Value>> {
153 let mut hasher = DefaultHasher::new();
154 key.hash(&mut hasher);
155 self.find(
156 &CacheKey {
157 location: Self::content_location(),
158 value_type: TypeId::of::<Value>(),
159 hash: hasher.finish(),
160 },
161 key,
162 )
163 }
164
165 pub fn insert_with_cost<
167 Key: Eq + Hash + Send + Sync + 'static,
168 Value: Send + Sync + 'static,
169 >(
170 &self,
171 key: Key,
172 value: Value,
173 cost: impl FnOnce(&Value) -> usize,
174 ) -> Arc<Value> {
175 self.get_cached(key, || value, cost, true, Self::content_location())
176 }
177
178 #[track_caller]
179 fn get_cached<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
180 &self,
181 key: Key,
182 build: impl FnOnce() -> Value,
183 cost: impl FnOnce(&Value) -> usize,
184 accounted: bool,
185 location: &'static Location<'static>,
186 ) -> Arc<Value> {
187 if self.capacity == 0 || (accounted && self.max_bytes == 0) {
188 return Arc::new(build());
189 }
190 let mut hasher = DefaultHasher::new();
191 key.hash(&mut hasher);
192 let cache_key = CacheKey {
193 location,
194 value_type: TypeId::of::<Value>(),
195 hash: hasher.finish(),
196 };
197 if let Some(value) = self.find(&cache_key, &key) {
198 return value;
199 }
200 let value = Arc::new(build());
201 let bytes = if accounted {
202 cost(value.as_ref())
203 .max(std::mem::size_of::<Value>().saturating_add(std::mem::size_of::<Key>()))
204 } else {
205 0
206 };
207 if bytes > self.max_bytes {
208 return value;
209 }
210 let mut entries = self.entries.lock().expect("scene cache lock poisoned");
211 if let Some(index) = entries
212 .iter()
213 .position(|entry| entry.matches(&cache_key, &key))
214 {
215 let entry = entries.remove(index).expect("located cache entry");
216 let stored = entry.value.clone();
217 entries.push_back(entry);
218 drop(entries);
219 return stored.downcast().unwrap_or_else(|_| unreachable!());
220 }
221 let mut retained: usize = entries.iter().map(|entry| entry.bytes).sum();
222 let mut evicted = Vec::new();
223 while entries.len() >= self.capacity || bytes > self.max_bytes.saturating_sub(retained) {
224 let Some(entry) = entries.pop_front() else {
225 break;
226 };
227 retained -= entry.bytes;
228 evicted.push(entry);
229 }
230 entries.push_back(CacheEntry {
231 key: cache_key,
232 input: Box::new(key),
233 value: value.clone(),
234 bytes,
235 });
236 drop(entries);
237 drop(evicted);
238 value
239 }
240
241 fn find<Key: Eq + 'static, Value: Send + Sync + 'static>(
242 &self,
243 key: &CacheKey,
244 input: &Key,
245 ) -> Option<Arc<Value>> {
246 let mut entries = self.entries.lock().expect("scene cache lock poisoned");
247 let index = entries.iter().position(|entry| entry.matches(key, input))?;
248 let entry = entries.remove(index).expect("located cache entry");
249 let value = entry.value.clone();
250 entries.push_back(entry);
251 drop(entries);
252 Some(value.downcast().unwrap_or_else(|_| unreachable!()))
253 }
254}