1use std::any::Any;
8use std::fmt;
9use std::num::NonZeroUsize;
10
11use lru::LruCache;
12use tenferro_tensor::CacheStats;
13
14pub const DEFAULT_EXTENSION_CACHE_CAPACITY: usize = 256;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub struct ExtensionCacheKey {
24 pub family_id: &'static str,
26 pub cache_name: &'static str,
28 pub discriminator: u64,
30}
31
32impl ExtensionCacheKey {
33 pub const fn new(
44 family_id: &'static str,
45 cache_name: &'static str,
46 discriminator: u64,
47 ) -> Self {
48 Self {
49 family_id,
50 cache_name,
51 discriminator,
52 }
53 }
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58pub enum ExtensionCacheSelector {
59 All,
61 Family { family_id: &'static str },
63 Cache {
65 family_id: &'static str,
66 cache_name: &'static str,
67 },
68}
69
70impl ExtensionCacheSelector {
71 pub fn matches(&self, key: &ExtensionCacheKey) -> bool {
84 match *self {
85 Self::All => true,
86 Self::Family { family_id } => key.family_id == family_id,
87 Self::Cache {
88 family_id,
89 cache_name,
90 } => key.family_id == family_id && key.cache_name == cache_name,
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct ExtensionCacheLimits {
98 max_entries: NonZeroUsize,
99}
100
101impl ExtensionCacheLimits {
102 pub const fn new(max_entries: NonZeroUsize) -> Self {
114 Self { max_entries }
115 }
116
117 pub const fn max_entries(self) -> NonZeroUsize {
119 self.max_entries
120 }
121}
122
123impl Default for ExtensionCacheLimits {
124 fn default() -> Self {
125 Self {
126 max_entries: NonZeroUsize::new(DEFAULT_EXTENSION_CACHE_CAPACITY)
127 .unwrap_or(NonZeroUsize::MIN),
128 }
129 }
130}
131
132trait ExtensionCacheValue: Send + Sync {
133 fn as_any(&self) -> &(dyn Any + Send + Sync);
134 fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync);
135 fn retained_bytes(&self) -> usize;
136}
137
138struct FixedRetainedBytes<T> {
139 value: T,
140 retained_bytes: usize,
141}
142
143impl<T> ExtensionCacheValue for FixedRetainedBytes<T>
144where
145 T: Any + Send + Sync + 'static,
146{
147 fn as_any(&self) -> &(dyn Any + Send + Sync) {
148 &self.value
149 }
150
151 fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) {
152 &mut self.value
153 }
154
155 fn retained_bytes(&self) -> usize {
156 self.retained_bytes
157 }
158}
159
160struct DynamicRetainedBytes<T, F> {
161 value: T,
162 retained_bytes: F,
163}
164
165impl<T, F> ExtensionCacheValue for DynamicRetainedBytes<T, F>
166where
167 T: Any + Send + Sync + 'static,
168 F: Fn(&T) -> usize + Send + Sync + 'static,
169{
170 fn as_any(&self) -> &(dyn Any + Send + Sync) {
171 &self.value
172 }
173
174 fn as_any_mut(&mut self) -> &mut (dyn Any + Send + Sync) {
175 &mut self.value
176 }
177
178 fn retained_bytes(&self) -> usize {
179 (self.retained_bytes)(&self.value)
180 }
181}
182
183struct ExtensionCacheEntry {
184 value: Box<dyn ExtensionCacheValue>,
185}
186
187pub struct ExtensionCacheStore {
189 limits: ExtensionCacheLimits,
190 entries: LruCache<ExtensionCacheKey, ExtensionCacheEntry>,
191}
192
193impl fmt::Debug for ExtensionCacheStore {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 f.debug_struct("ExtensionCacheStore")
196 .field("limits", &self.limits)
197 .field("stats", &self.stats(ExtensionCacheSelector::All))
198 .finish_non_exhaustive()
199 }
200}
201
202impl ExtensionCacheStore {
203 pub fn new() -> Self {
214 Self::with_limits(ExtensionCacheLimits::default())
215 }
216
217 pub fn with_limits(limits: ExtensionCacheLimits) -> Self {
219 Self {
220 entries: LruCache::new(limits.max_entries()),
221 limits,
222 }
223 }
224
225 pub const fn limits(&self) -> ExtensionCacheLimits {
227 self.limits
228 }
229
230 pub fn set_limits(&mut self, limits: ExtensionCacheLimits) {
232 self.entries.resize(limits.max_entries());
233 self.limits = limits;
234 }
235
236 pub fn len(&self) -> usize {
238 self.entries.len()
239 }
240
241 pub fn is_empty(&self) -> bool {
243 self.entries.is_empty()
244 }
245
246 pub fn put<T>(&mut self, key: ExtensionCacheKey, value: T, retained_bytes: usize)
248 where
249 T: Any + Send + Sync + 'static,
250 {
251 self.entries.put(
252 key,
253 ExtensionCacheEntry {
254 value: Box::new(FixedRetainedBytes {
255 value,
256 retained_bytes,
257 }),
258 },
259 );
260 }
261
262 pub fn put_with_retained_bytes<T, F>(
288 &mut self,
289 key: ExtensionCacheKey,
290 value: T,
291 retained_bytes: F,
292 ) where
293 T: Any + Send + Sync + 'static,
294 F: Fn(&T) -> usize + Send + Sync + 'static,
295 {
296 self.entries.put(
297 key,
298 ExtensionCacheEntry {
299 value: Box::new(DynamicRetainedBytes {
300 value,
301 retained_bytes,
302 }),
303 },
304 );
305 }
306
307 pub fn get<T>(&mut self, key: &ExtensionCacheKey) -> Option<&T>
309 where
310 T: Any + Send + Sync + 'static,
311 {
312 self.entries
313 .get(key)
314 .and_then(|entry| entry.value.as_any().downcast_ref::<T>())
315 }
316
317 pub fn get_mut<T>(&mut self, key: &ExtensionCacheKey) -> Option<&mut T>
319 where
320 T: Any + Send + Sync + 'static,
321 {
322 self.entries
323 .get_mut(key)
324 .and_then(|entry| entry.value.as_any_mut().downcast_mut::<T>())
325 }
326
327 pub fn clear_selected(&mut self, selector: ExtensionCacheSelector) {
329 if selector == ExtensionCacheSelector::All {
330 self.entries.clear();
331 return;
332 }
333
334 let keys: Vec<_> = self
335 .entries
336 .iter()
337 .map(|(key, _)| *key)
338 .filter(|key| selector.matches(key))
339 .collect();
340 for key in keys {
341 self.entries.pop(&key);
342 }
343 }
344
345 pub fn clear(&mut self) {
347 self.entries.clear();
348 }
349
350 pub fn stats(&self, selector: ExtensionCacheSelector) -> CacheStats {
352 CacheStats {
353 entries: self
354 .entries
355 .iter()
356 .filter(|(key, _)| selector.matches(key))
357 .count(),
358 retained_bytes: self
359 .entries
360 .iter()
361 .filter(|(key, _)| selector.matches(key))
362 .map(|(_, entry)| entry.value.retained_bytes())
363 .fold(0usize, usize::saturating_add),
364 }
365 }
366}
367
368impl Default for ExtensionCacheStore {
369 fn default() -> Self {
370 Self::new()
371 }
372}
373
374#[cfg(test)]
375mod tests;