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_index: usize,
13 pub kind: BlockKind,
14 pub block_index: usize,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum BlockKind {
20 Strip,
21 Tile,
22}
23
24pub struct BlockCache {
26 inner: Mutex<BlockCacheState>,
27 max_bytes: usize,
28}
29
30struct BlockCacheState {
31 cache: LruCache<BlockKey, Arc<Vec<u8>>>,
32 current_bytes: usize,
33}
34
35impl BlockCache {
36 pub fn new(max_bytes: usize, max_slots: usize) -> Self {
38 let slots = NonZeroUsize::new(max_slots).unwrap_or_else(|| NonZeroUsize::new(257).unwrap());
39 Self {
40 inner: Mutex::new(BlockCacheState {
41 cache: LruCache::new(slots),
42 current_bytes: 0,
43 }),
44 max_bytes,
45 }
46 }
47
48 pub fn get(&self, key: &BlockKey) -> Option<Arc<Vec<u8>>> {
50 let mut state = self.inner.lock();
51 state.cache.get(key).cloned()
52 }
53
54 pub fn insert(&self, key: BlockKey, data: Vec<u8>) -> Arc<Vec<u8>> {
56 let data_len = data.len();
57 let value = Arc::new(data);
58
59 if self.max_bytes == 0 || data_len > self.max_bytes {
60 return value;
61 }
62
63 let mut state = self.inner.lock();
64 while state.current_bytes + data_len > self.max_bytes && !state.cache.is_empty() {
65 if let Some((_, evicted)) = state.cache.pop_lru() {
66 state.current_bytes = state.current_bytes.saturating_sub(evicted.len());
67 }
68 }
69
70 state.current_bytes += data_len;
71 if let Some(previous) = state.cache.put(key, value.clone()) {
72 state.current_bytes = state.current_bytes.saturating_sub(previous.len());
73 }
74
75 value
76 }
77}
78
79impl Default for BlockCache {
80 fn default() -> Self {
81 Self::new(64 * 1024 * 1024, 257)
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::{BlockCache, BlockKey, BlockKind};
88
89 #[test]
90 fn caches_and_promotes_entries() {
91 let cache = BlockCache::new(12, 8);
92 let a = BlockKey {
93 ifd_index: 0,
94 kind: BlockKind::Strip,
95 block_index: 0,
96 };
97 let b = BlockKey {
98 ifd_index: 0,
99 kind: BlockKind::Strip,
100 block_index: 1,
101 };
102 let c = BlockKey {
103 ifd_index: 0,
104 kind: BlockKind::Strip,
105 block_index: 2,
106 };
107
108 cache.insert(a, vec![0; 4]);
109 cache.insert(b, vec![0; 4]);
110 cache.insert(c, vec![0; 4]);
111
112 let promoted = BlockKey {
113 ifd_index: 0,
114 kind: BlockKind::Strip,
115 block_index: 0,
116 };
117 assert!(cache.get(&promoted).is_some());
118
119 let d = BlockKey {
120 ifd_index: 0,
121 kind: BlockKind::Strip,
122 block_index: 3,
123 };
124 cache.insert(d, vec![0; 4]);
125
126 let evicted = BlockKey {
127 ifd_index: 0,
128 kind: BlockKind::Strip,
129 block_index: 1,
130 };
131 assert!(cache.get(&promoted).is_some());
132 assert!(cache.get(&evicted).is_none());
133 }
134
135 #[test]
136 fn disabled_cache_bypasses_storage() {
137 let cache = BlockCache::new(0, 4);
138 let key = BlockKey {
139 ifd_index: 0,
140 kind: BlockKind::Tile,
141 block_index: 0,
142 };
143 cache.insert(key, vec![1, 2, 3]);
144 assert!(cache.get(&key).is_none());
145 }
146}