Use intrusive list to track LRU order

This commit is contained in:
grovesNL 2022-05-17 10:51:21 -02:30 committed by Josh Groves
parent 81671d02b8
commit e64b4ab63b
2 changed files with 179 additions and 47 deletions

View file

@ -112,7 +112,7 @@ fn try_allocate(atlas: &mut InnerAtlas, width: usize, height: usize) -> Option<A
} }
// Try to free least recently used allocation // Try to free least recently used allocation
let (key, value) = atlas.glyph_cache.entries_least_recently_used().next()?; let (key, value) = atlas.glyph_cache.pop()?;
atlas atlas
.packer .packer
.deallocate(value.atlas_id.expect("cache corrupt")); .deallocate(value.atlas_id.expect("cache corrupt"));

View file

@ -1,60 +1,175 @@
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
collections::{hash_map::Entry, HashMap}, collections::{hash_map::Entry, HashMap},
fmt,
hash::Hash, hash::Hash,
}; };
struct RecentlyUsedItem<V> { struct RecentlyUsedItem<V> {
entry_idx: usize, node_idx: usize,
value: V, value: V,
} }
pub struct RecentlyUsedMap<K: Clone + Copy + Eq + Hash, V> { #[derive(Debug)]
keys: Vec<K>, enum Node<V> {
map: HashMap<K, RecentlyUsedItem<V>>, Value {
value: V,
prev_idx: Option<usize>,
next_idx: Option<usize>,
},
Free {
next_idx: Option<usize>,
},
} }
impl<K: Clone + Copy + Eq + Hash, V> RecentlyUsedMap<K, V> { pub struct RecentlyUsedMap<K: Clone + Copy + Eq + Hash, V> {
nodes: Vec<Node<K>>,
least_recent_idx: Option<usize>,
most_recent_idx: Option<usize>,
map: HashMap<K, RecentlyUsedItem<V>>,
free: Option<usize>,
}
impl<K: Clone + Copy + Eq + Hash + fmt::Debug, V> RecentlyUsedMap<K, V> {
pub fn new() -> Self { pub fn new() -> Self {
Self::with_capacity(0) Self::with_capacity(0)
} }
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
assert!(capacity < usize::MAX - 1, "capacity too large");
Self { Self {
keys: Vec::with_capacity(capacity), nodes: Vec::with_capacity(capacity),
least_recent_idx: None,
most_recent_idx: None,
free: None,
map: HashMap::with_capacity(capacity), map: HashMap::with_capacity(capacity),
} }
} }
pub fn insert(&mut self, key: K, value: V) { fn allocate_node(&mut self, node: Node<K>) -> usize {
let new_idx = self.keys.len(); match self.free {
match self.map.entry(key) { Some(idx) => {
Entry::Occupied(mut occupied) => { // Reuse a node from storage
let old = occupied.insert(RecentlyUsedItem { match self.nodes[idx] {
entry_idx: new_idx, Node::Free { next_idx } => {
value, self.free = next_idx;
}); }
let removed = self.keys.remove(old.entry_idx); Node::Value { .. } => unreachable!(),
self.keys.push(removed); }
self.nodes[idx] = node;
idx
} }
Entry::Vacant(vacant) => { None => {
vacant.insert(RecentlyUsedItem { // Create a new storage node
entry_idx: new_idx, self.nodes.push(node);
value, self.nodes.len() - 1
});
self.keys.push(key);
} }
} }
} }
pub fn remove<Q: ?Sized>(&mut self, key: &Q) pub fn insert(&mut self, key: K, value: V) {
let new_idx = self.allocate_node(Node::Value {
value: key,
prev_idx: self.most_recent_idx,
next_idx: None,
});
let prior_most_recent = self.most_recent_idx.map(|idx| &mut self.nodes[idx]);
match prior_most_recent {
Some(p) => {
// Update prior most recent if one exists
match p {
Node::Value { next_idx, .. } => {
*next_idx = Some(new_idx);
}
Node::Free { .. } => unreachable!(),
}
}
None => {
// Update least recent if this is the first node
self.least_recent_idx = Some(new_idx);
}
}
self.most_recent_idx = Some(new_idx);
match self.map.entry(key) {
Entry::Occupied(mut occupied) => {
let old = occupied.insert(RecentlyUsedItem {
node_idx: new_idx,
value,
});
// Remove the old occurrence of this key
self.remove_node(old.node_idx);
}
Entry::Vacant(vacant) => {
vacant.insert(RecentlyUsedItem {
node_idx: new_idx,
value,
});
}
}
}
fn remove_node(&mut self, idx: usize) {
match self.nodes[idx] {
Node::Value {
prev_idx, next_idx, ..
} => {
match prev_idx {
Some(prev) => match &mut self.nodes[prev] {
Node::Value {
next_idx: old_next_idx,
..
} => {
*old_next_idx = next_idx;
}
Node::Free { .. } => unreachable!(),
},
None => {
// This node was the least recent
self.least_recent_idx = next_idx;
}
}
match next_idx {
Some(next) => match &mut self.nodes[next] {
Node::Value {
prev_idx: old_prev_idx,
..
} => {
*old_prev_idx = prev_idx;
}
Node::Free { .. } => unreachable!(),
},
None => {
// This node was the most recent
self.most_recent_idx = prev_idx;
}
}
}
Node::Free { .. } => unreachable!(),
}
// Add to free list
self.nodes[idx] = Node::Free {
next_idx: self.free,
};
self.free = Some(idx);
}
pub fn remove<Q: ?Sized>(&mut self, key: &Q) -> Option<V>
where where
K: Borrow<Q>, K: Borrow<Q>,
Q: Hash + Eq, Q: Hash + Eq,
{ {
if let Some(entry) = self.map.remove(key.borrow()) { self.map.remove(key.borrow()).map(|entry| {
self.keys.remove(entry.entry_idx); self.remove_node(entry.node_idx);
} entry.value
})
} }
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
@ -73,8 +188,17 @@ impl<K: Clone + Copy + Eq + Hash, V> RecentlyUsedMap<K, V> {
self.map.contains_key(k) self.map.contains_key(k)
} }
pub fn entries_least_recently_used(&self) -> impl Iterator<Item = (K, &V)> + '_ { pub fn pop(&mut self) -> Option<(K, V)> {
self.keys.iter().map(|k| (*k, &self.map[k].value)) let least_recent_idx = self.least_recent_idx?;
let least_recent_node = &self.nodes[least_recent_idx];
match *least_recent_node {
Node::Value { value: key, .. } => {
let value = self.remove(&key);
value.map(|v| (key, v))
}
Node::Free { .. } => unreachable!(),
}
} }
} }
@ -87,12 +211,11 @@ mod tests {
let mut rus = RecentlyUsedMap::new(); let mut rus = RecentlyUsedMap::new();
rus.insert("a", ()); rus.insert("a", ());
rus.insert("b", ()); rus.insert("b", ());
assert_eq!( rus.insert("c", ());
rus.entries_least_recently_used() assert_eq!(rus.pop(), Some(("a", ())));
.map(|(k, _)| k) assert_eq!(rus.pop(), Some(("b", ())));
.collect::<String>(), assert_eq!(rus.pop(), Some(("c", ())));
"ab" assert_eq!(rus.pop(), None);
);
} }
#[test] #[test]
@ -102,12 +225,10 @@ mod tests {
rus.insert("b", ()); rus.insert("b", ());
rus.insert("c", ()); rus.insert("c", ());
rus.insert("a", ()); rus.insert("a", ());
assert_eq!( assert_eq!(rus.pop(), Some(("b", ())));
rus.entries_least_recently_used() assert_eq!(rus.pop(), Some(("c", ())));
.map(|(k, _)| k) assert_eq!(rus.pop(), Some(("a", ())));
.collect::<String>(), assert_eq!(rus.pop(), None);
"bca"
);
} }
#[test] #[test]
@ -117,11 +238,22 @@ mod tests {
rus.insert("b", ()); rus.insert("b", ());
rus.remove("a"); rus.remove("a");
rus.remove("c"); rus.remove("c");
assert_eq!( assert_eq!(rus.pop(), Some(("b", ())));
rus.entries_least_recently_used() assert_eq!(rus.pop(), None);
.map(|(k, _)| k) }
.collect::<String>(),
"b" #[test]
); fn reuses_free_nodes() {
let mut rus = RecentlyUsedMap::new();
rus.insert("a", ());
rus.insert("b", ());
rus.insert("c", ());
rus.remove("b");
rus.remove("c");
rus.remove("a");
rus.insert("d", ());
rus.insert("e", ());
rus.insert("f", ());
assert_eq!(rus.nodes.len(), 3);
} }
} }