1use std::num::NonZeroUsize;
4use std::sync::Arc;
5
6use lru::LruCache;
7use parking_lot::Mutex;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct BlockKey {
12 pub ifd_offset: u64,
19 pub kind: BlockKind,
20 pub block_index: usize,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum BlockKind {
26 Strip,
27 Tile,
28}
29
30pub struct BlockCache {
32 inner: Mutex<BlockCacheState>,
33 max_bytes: usize,
34 enabled: bool,
35}
36
37struct BlockCacheState {
38 cache: LruCache<BlockKey, Arc<Vec<u8>>>,
39 current_bytes: usize,
40}
41
42impl BlockCache {
43 pub fn new(max_bytes: usize, max_slots: usize) -> Self {
45 let slots = NonZeroUsize::new(max_slots.max(1)).unwrap();
46 Self {
47 inner: Mutex::new(BlockCacheState {
48 cache: LruCache::new(slots),
49 current_bytes: 0,
50 }),
51 max_bytes,
52 enabled: max_bytes > 0 && max_slots > 0,
53 }
54 }
55
56 pub fn get(&self, key: &BlockKey) -> Option<Arc<Vec<u8>>> {
58 if !self.enabled {
59 return None;
60 }
61 let mut state = self.inner.lock();
62 state.cache.get(key).cloned()
63 }
64
65 pub fn insert(&self, key: BlockKey, data: Vec<u8>) -> Arc<Vec<u8>> {
67 let data_len = data.len();
68 let value = Arc::new(data);
69
70 let mut state = self.inner.lock();
71 if let Some(previous) = state.cache.pop(&key) {
72 state.current_bytes = state.current_bytes.saturating_sub(previous.len());
73 }
74
75 if !self.enabled || data_len > self.max_bytes {
76 return value;
77 }
78
79 while state.current_bytes > self.max_bytes - data_len && !state.cache.is_empty() {
80 if let Some((_, evicted)) = state.cache.pop_lru() {
81 state.current_bytes = state.current_bytes.saturating_sub(evicted.len());
82 }
83 }
84
85 state.current_bytes += data_len;
86 if let Some((_, evicted)) = state.cache.push(key, value.clone()) {
87 state.current_bytes = state.current_bytes.saturating_sub(evicted.len());
88 }
89
90 value
91 }
92}
93
94impl Default for BlockCache {
95 fn default() -> Self {
96 Self::new(64 * 1024 * 1024, 257)
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::{BlockCache, BlockKey, BlockKind};
103
104 #[test]
105 fn caches_and_promotes_entries() {
106 let cache = BlockCache::new(12, 8);
107 let a = BlockKey {
108 ifd_offset: 0,
109 kind: BlockKind::Strip,
110 block_index: 0,
111 };
112 let b = BlockKey {
113 ifd_offset: 0,
114 kind: BlockKind::Strip,
115 block_index: 1,
116 };
117 let c = BlockKey {
118 ifd_offset: 0,
119 kind: BlockKind::Strip,
120 block_index: 2,
121 };
122
123 cache.insert(a, vec![0; 4]);
124 cache.insert(b, vec![0; 4]);
125 cache.insert(c, vec![0; 4]);
126
127 let promoted = BlockKey {
128 ifd_offset: 0,
129 kind: BlockKind::Strip,
130 block_index: 0,
131 };
132 assert!(cache.get(&promoted).is_some());
133
134 let d = BlockKey {
135 ifd_offset: 0,
136 kind: BlockKind::Strip,
137 block_index: 3,
138 };
139 cache.insert(d, vec![0; 4]);
140
141 let evicted = BlockKey {
142 ifd_offset: 0,
143 kind: BlockKind::Strip,
144 block_index: 1,
145 };
146 assert!(cache.get(&promoted).is_some());
147 assert!(cache.get(&evicted).is_none());
148 }
149
150 #[test]
151 fn disabled_cache_bypasses_storage() {
152 let cache = BlockCache::new(0, 4);
153 let key = BlockKey {
154 ifd_offset: 0,
155 kind: BlockKind::Tile,
156 block_index: 0,
157 };
158 cache.insert(key, vec![1, 2, 3]);
159 assert!(cache.get(&key).is_none());
160 }
161
162 #[test]
163 fn zero_slots_disable_cache_storage() {
164 let cache = BlockCache::new(1024, 0);
165 let key = BlockKey {
166 ifd_offset: 0,
167 kind: BlockKind::Tile,
168 block_index: 0,
169 };
170 cache.insert(key, vec![1, 2, 3]);
171 assert!(cache.get(&key).is_none());
172 assert_eq!(cache.inner.lock().current_bytes, 0);
173 }
174
175 #[test]
176 fn slot_eviction_updates_byte_accounting() {
177 let cache = BlockCache::new(100, 2);
178 for block_index in 0..3 {
179 cache.insert(
180 BlockKey {
181 ifd_offset: 0,
182 kind: BlockKind::Strip,
183 block_index,
184 },
185 vec![0; 4],
186 );
187 }
188
189 assert_eq!(cache.inner.lock().current_bytes, 8);
190 }
191
192 #[test]
193 fn replacing_mru_entry_preserves_other_cached_blocks() {
194 let cache = BlockCache::new(10, 8);
195 let a = BlockKey {
196 ifd_offset: 0,
197 kind: BlockKind::Tile,
198 block_index: 0,
199 };
200 let b = BlockKey {
201 ifd_offset: 0,
202 kind: BlockKind::Tile,
203 block_index: 1,
204 };
205
206 cache.insert(a, vec![0; 8]);
207 cache.insert(b, vec![0; 2]);
208 assert!(cache.get(&a).is_some());
209
210 cache.insert(a, vec![0; 7]);
211
212 assert!(cache.get(&a).is_some());
213 assert!(cache.get(&b).is_some());
214 assert_eq!(cache.inner.lock().current_bytes, 9);
215 }
216
217 #[test]
218 fn oversized_replacement_removes_stale_entry() {
219 let cache = BlockCache::new(8, 8);
220 let key = BlockKey {
221 ifd_offset: 0,
222 kind: BlockKind::Tile,
223 block_index: 0,
224 };
225
226 cache.insert(key, vec![0; 4]);
227 cache.insert(key, vec![0; 9]);
228
229 assert!(cache.get(&key).is_none());
230 assert_eq!(cache.inner.lock().current_bytes, 0);
231 }
232}