Skip to main content

tiff_reader/
cache.rs

1//! LRU cache for decompressed strips and tiles.
2
3use std::num::NonZeroUsize;
4use std::sync::Arc;
5
6use lru::LruCache;
7use parking_lot::Mutex;
8
9/// Cache key for a decoded strip or tile.
10#[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/// Whether the cached block came from a strip- or tile-backed image.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum BlockKind {
20    Strip,
21    Tile,
22}
23
24/// Thread-safe LRU cache for decoded block payloads.
25pub 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    /// Create a new cache with byte and slot limits.
37    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    /// Return a cached block and promote it in LRU order.
49    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    /// Insert a decoded block into the cache.
55    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}