Krolyxon
Commit f2bd31a

Branches

Tags

Refs →

remagesrccache.rs

99 lines · 2.5 KB · Rust

1//! Decoded-image LRU with a byte budget.
2
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use crate::decode::DecodedImage;
8
9struct Slot {
10 img: Arc<DecodedImage>,
11 used: u64,
12}
13
14pub struct ImageCache {
15 map: HashMap<PathBuf, Slot>,
16 budget: usize,
17 bytes: usize,
18 clock: u64,
19}
20
21impl ImageCache {
22 pub fn new(budget_bytes: usize) -> Self {
23 Self {
24 map: HashMap::new(),
25 budget: budget_bytes,
26 bytes: 0,
27 clock: 0,
28 }
29 }
30
31 pub fn get(&mut self, path: &Path) -> Option<Arc<DecodedImage>> {
32 self.clock += 1;
33 let slot = self.map.get_mut(path)?;
34 slot.used = self.clock;
35 Some(slot.img.clone())
36 }
37
38 #[cfg(test)]
39 pub fn contains(&self, path: &Path) -> bool {
40 self.map.contains_key(path)
41 }
42
43 /// Insert, then evict least-recently-used entries over budget, sparing `keep`.
44 pub fn insert(&mut self, path: PathBuf, img: Arc<DecodedImage>, keep: &[&Path]) {
45 self.clock += 1;
46 self.bytes += img.bytes();
47 if let Some(old) = self.map.insert(
48 path,
49 Slot {
50 img,
51 used: self.clock,
52 },
53 ) {
54 self.bytes -= old.img.bytes();
55 }
56 while self.bytes > self.budget {
57 let victim = self
58 .map
59 .iter()
60 .filter(|(p, _)| !keep.contains(&p.as_path()))
61 .min_by_key(|(_, s)| s.used)
62 .map(|(p, _)| p.clone());
63 let Some(victim) = victim else { break };
64 self.remove(&victim);
65 }
66 }
67
68 pub fn remove(&mut self, path: &Path) {
69 if let Some(old) = self.map.remove(path) {
70 self.bytes -= old.img.bytes();
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 fn img(bytes: usize) -> Arc<DecodedImage> {
80 Arc::new(DecodedImage {
81 size: [1, 1],
82 pixels: vec![0; bytes],
83 format: "PNG",
84 source_size: [1, 1],
85 })
86 }
87
88 #[test]
89 fn evicts_least_recent_but_spares_kept() {
90 let mut c = ImageCache::new(250);
91 c.insert("a".into(), img(100), &[]);
92 c.insert("b".into(), img(100), &[]);
93 c.get(Path::new("a"));
94 c.insert("c".into(), img(100), &[]);
95 assert!(c.contains(Path::new("a")) && !c.contains(Path::new("b")));
96 c.insert("d".into(), img(100), &[Path::new("a")]);
97 assert!(c.contains(Path::new("a")) && !c.contains(Path::new("c")));
98 }
99}