martin_core/resources/sprites/
cache.rs1use actix_web::web::Bytes;
2use moka::future::Cache;
3
4#[derive(Clone)]
6pub struct SpriteCache {
7 cache: Cache<SpriteCacheKey, Bytes>,
8}
9
10impl std::fmt::Debug for SpriteCache {
11 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
12 f.debug_struct("SpriteCache")
13 .field("entry_count", &self.cache.entry_count())
14 .field("weighted_size", &self.cache.weighted_size())
15 .finish()
16 }
17}
18
19impl SpriteCache {
20 #[must_use]
22 pub fn new(max_size_bytes: u64) -> Self {
23 Self {
24 cache: Cache::builder()
25 .name("sprite_cache")
26 .weigher(|key: &SpriteCacheKey, value: &Bytes| -> u32 {
27 size_of_val(key).try_into().unwrap_or(u32::MAX)
28 + value.len().try_into().unwrap_or(u32::MAX)
29 })
30 .max_capacity(max_size_bytes)
31 .build(),
32 }
33 }
34
35 async fn get(&self, key: &SpriteCacheKey) -> Option<Bytes> {
37 let result = self.cache.get(key).await;
38
39 if result.is_some() {
40 log::trace!(
41 "Sprite cache HIT for {key:?} (entries={}, size={})",
42 self.cache.entry_count(),
43 self.cache.weighted_size()
44 );
45 } else {
46 log::trace!("Sprite cache MISS for {key:?}");
47 }
48
49 result
50 }
51
52 pub async fn get_or_insert<F, Fut, E>(
54 &self,
55 ids: String,
56 as_sdf: bool,
57 as_json: bool,
58 compute: F,
59 ) -> Result<Bytes, E>
60 where
61 F: FnOnce() -> Fut,
62 Fut: Future<Output = Result<Bytes, E>>,
63 {
64 let key = SpriteCacheKey::new(ids, as_sdf, as_json);
65 if let Some(data) = self.get(&key).await {
66 return Ok(data);
67 }
68
69 let data = compute().await?;
70 self.cache.insert(key, data.clone()).await;
71 Ok(data)
72 }
73
74 pub fn invalidate_source(&self, source_id: &str) {
76 let source_id_owned = source_id.to_string();
77 self.cache
78 .invalidate_entries_if(move |key, _| key.ids.contains(&source_id_owned))
79 .expect("invalidate_entries_if predicate should not error");
80 log::info!("Invalidated sprite cache for source: {source_id}");
81 }
82
83 pub fn invalidate_all(&self) {
85 self.cache.invalidate_all();
86 log::info!("Invalidated all sprite cache entries");
87 }
88
89 #[must_use]
91 pub fn entry_count(&self) -> u64 {
92 self.cache.entry_count()
93 }
94
95 #[must_use]
97 pub fn weighted_size(&self) -> u64 {
98 self.cache.weighted_size()
99 }
100}
101
102pub type OptSpriteCache = Option<SpriteCache>;
104
105pub const NO_SPRITE_CACHE: OptSpriteCache = None;
107
108#[derive(Debug, Hash, PartialEq, Eq, Clone)]
110struct SpriteCacheKey {
111 ids: String,
112 as_sdf: bool,
113 as_json: bool,
114}
115
116impl SpriteCacheKey {
117 fn new(ids: String, as_sdf: bool, as_json: bool) -> Self {
118 Self {
119 ids,
120 as_sdf,
121 as_json,
122 }
123 }
124}