#![warn(missing_docs)]
use std::collections::HashMap;
use std::hash::Hash;
#[cfg(test)]
mod test {
use std::fmt::Debug;
use super::*;
fn check_overlaps<K: Hash + Eq + Debug, V>(cache: &LruTextureCache<K, V>) {
let rects = cache.rows.iter().flat_map(|x| x.rects.iter()).enumerate();
let overlap = |a: &Rect<V>, b: &Rect<V>| {
a.x < b.x + b.width
&& b.x < a.x + a.width
&& a.y < b.y + b.height
&& b.y < a.y + a.height
};
for (x, this) in rects.clone() {
if this.x + this.width > cache.width || this.y + this.height > cache.height {
panic!("rect overflow");
}
for (y, other) in rects.clone() {
if x != y {
assert!(!overlap(this, other), "detected overlap");
}
}
}
let mut values = cache.key_to_row.values().collect::<Vec<_>>();
let len = values.len();
values.sort();
println!("{:?}", values);
values.dedup();
assert_eq!(values.len(), len, "{:?}", cache.key_to_row);
}
#[test]
fn too_big() {
let mut cache = LruTextureCache::new(100, 100);
assert!(cache.add_rect(150, 50, 0, ()) == AddRectResult::TooLarge);
assert!(cache.add_rect(50, 150, 1, ()) == AddRectResult::TooLarge);
assert!(cache.add_rect(101, 0, 0, ()) == AddRectResult::TooLarge);
}
#[test]
fn row_count() {
let mut cache = LruTextureCache::new(100, 100);
let mut counter = -1;
let mut rects = move || {
(0..10)
.map(|_| {
counter += 1;
RectEntry {
width: 10,
height: 10,
key: counter,
value: (),
entry_data: (),
}
})
.collect::<Vec<_>>()
};
for i in 0..10 {
let result = cache.cache_rects(&mut rects());
assert_eq!(result, Ok(Cached::Added(10)));
check_overlaps(&cache);
assert_eq!(cache.rows.len(), i + 1);
}
let result = cache.cache_rects(&mut rects());
assert_eq!(result, Ok(Cached::Changed(10)));
check_overlaps(&cache);
assert_eq!(cache.rows.len(), 10);
for i in 0..10 {
assert!(!cache.contains(&i));
}
for i in 10..110 {
assert!(cache.contains(&i));
}
}
#[test]
fn random_sample() {
use rand::prelude::*;
let seed = thread_rng().gen::<u64>();
println!("set seed {:016x}", seed);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut gen = || rng.gen_range(1..=10) + rng.gen_range(1..=10);
let rects: Vec<_> = (0..1000)
.map(|x| RectEntry {
width: gen(),
height: gen(),
key: x,
value: (),
entry_data: (),
})
.collect();
let mut cache = LruTextureCache::new(100, 100);
for i in 0..200 {
println!("sample number {}", i);
let size = rng.gen_range(25..100);
let mut sample = rects.iter().cloned().choose_multiple(&mut rng, size);
if cache.cache_rects(&mut sample).is_ok() {
for rect in sample.iter() {
assert!(cache.contains(&rect.key));
}
}
check_overlaps(&cache);
}
}
#[test]
#[ignore]
fn seed_sample() {
use rand::prelude::*;
let seed = 0xe850b0f0dae9bbd6;
println!("set seed {:016x}", seed);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut gen = || rng.gen_range(1..=10) + rng.gen_range(1..=10);
let rects: Vec<_> = (0..1000)
.map(|x| RectEntry {
width: gen(),
height: gen(),
key: x,
value: (),
entry_data: (),
})
.collect();
let mut cache = LruTextureCache::new(100, 100);
for i in 0..2 {
println!("sample number {}", i);
let size = rng.gen_range(25..100);
let mut sample = rects.iter().cloned().choose_multiple(&mut rng, size);
if cache.cache_rects(&mut sample).is_ok() {
for rect in sample.iter() {
assert!(cache.contains(&rect.key));
}
}
check_overlaps(&cache);
}
}
#[test]
fn multiple_size() {
let mut cache = LruTextureCache::new(100, 100);
let i = std::sync::atomic::AtomicU64::new(0);
let count = move || i.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let rects = (0..11)
.map(|_| RectEntry {
width: 15,
height: 15,
key: count(),
value: (),
entry_data: (),
})
.chain((0..3).map(|_| RectEntry {
width: 10,
height: 10,
key: count(),
value: (),
entry_data: (),
}));
let mut rects: Vec<_> = rects.collect();
cache.cache_rects(&mut rects).unwrap();
println!(
"{:?}",
cache.rows.iter().map(|x| &x.rects).collect::<Vec<_>>()
);
}
}
#[derive(Clone, Debug)]
pub struct Rect<V> {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub value: V,
}
struct Row<V> {
age: u8,
top: u32,
height: u32,
free_space: u32,
pub rects: Vec<Rect<V>>,
}
impl<V> Row<V> {
pub fn push_rect(&mut self, width: u32, height: u32, value: V) {
let y = self.top;
let x = self.rects.last().map_or(0, |x| x.x + x.width);
let rect = Rect {
x,
y,
width,
height,
value,
};
self.free_space -= rect.width;
self.rects.push(rect);
}
fn len(&self) -> usize {
self.rects.len()
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct RectEntry<K: Hash + Eq + Clone, V: Clone, D> {
pub width: u32,
pub height: u32,
pub key: K,
pub value: V,
pub entry_data: D,
}
type RowIndex = u32;
type RectIndex = u32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Cached {
Added(usize),
Changed(usize),
Cleared(usize),
}
impl Cached {
pub fn len(&self) -> usize {
match *self {
Self::Added(len) => len,
Self::Changed(len) => len,
Self::Cleared(len) => len,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum CacheErr {
RectTooLarge(usize),
DontFit(usize),
}
impl CacheErr {
pub fn len(&self) -> usize {
match *self {
Self::RectTooLarge(len) => len,
Self::DontFit(len) => len,
}
}
}
#[derive(PartialEq, Eq, Debug)]
enum AddRectResult {
Added,
ClearedRows,
TooLarge,
NoRowToFit,
}
pub struct LruTextureCache<K: Hash + Eq, V> {
width: u32,
height: u32,
rows: Vec<Row<V>>,
key_to_row: HashMap<K, (RowIndex, RectIndex)>,
free_space: u32,
}
impl<K: Hash + Eq + Copy + 'static, V: Clone> LruTextureCache<K, V> {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
rows: Default::default(),
key_to_row: Default::default(),
free_space: height,
}
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn min_height(&self) -> u32 {
self.height - self.free_space
}
pub fn len(&self) -> usize {
self.key_to_row.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.free_space = self.height;
self.rows.clear();
self.key_to_row.clear();
}
pub fn contains(&self, key: &K) -> bool {
self.key_to_row.contains_key(key)
}
fn partition_cached<D>(&mut self, rects: &mut [RectEntry<K, V, D>]) -> usize {
fn partition<T, F: FnMut(&T) -> bool>(slice: &mut [T], mut f: F) -> usize {
let mut l = 0;
for i in 0..slice.len() {
if f(&slice[i]) {
slice.swap(i, l);
l += 1;
}
}
l
}
partition(rects, |x| {
if !self.key_to_row.contains_key(&x.key) {
self.key_to_row.insert(x.key, (!0, !0));
true
} else {
false
}
})
}
#[must_use]
pub fn cache_rects<D>(&mut self, rects: &mut [RectEntry<K, V, D>]) -> Result<Cached, CacheErr> {
let s = self.partition_cached(rects);
rects[..s].sort_unstable_by_key(|x| !x.height);
for row in &mut self.rows {
row.age = row.age.saturating_add(1);
}
for rect in &rects[s..] {
let &(row, _) = self.key_to_row.get(&rect.key).unwrap();
if row == !0 {
continue;
}
self.rows[row as usize].age = 0;
}
enum Sucess {
Okay,
Change,
Fail,
}
use AddRectResult::*;
use Sucess::*;
let mut sucess = Okay;
for (r, rect) in rects[..s].iter().enumerate() {
match self.add_rect(rect.width, rect.height, rect.key, rect.value.clone()) {
Added => {}
ClearedRows => sucess = Change,
NoRowToFit => {
sucess = Fail;
break;
}
TooLarge => {
for rect in &rects[r..s] {
self.key_to_row.remove(&rect.key);
}
return Err(CacheErr::RectTooLarge(r));
}
}
}
match sucess {
Okay => Ok(Cached::Added(s)),
Change => Ok(Cached::Changed(s)),
Fail => {
self.clear();
let s = self.partition_cached(rects);
rects[..s].sort_unstable_by_key(|x| !x.height);
for (r, rect) in rects[..s].iter().enumerate() {
match self.add_rect(rect.width, rect.height, rect.key, rect.value.clone()) {
Added => {}
ClearedRows => unreachable!("there are no unused rows"),
TooLarge => unreachable!("too large rects would have failed already"),
NoRowToFit => {
for rect in &rects[r..s] {
self.key_to_row.remove(&rect.key);
}
return Err(CacheErr::DontFit(r));
}
}
}
Ok(Cached::Cleared(s))
}
}
}
pub fn get_rect(&self, key: &K) -> Option<Rect<V>> {
let (row, index) = *self.key_to_row.get(&key)?;
Some(self.rows[row as usize].rects[index as usize].clone())
}
fn add_rect(&mut self, width: u32, height: u32, key: K, value: V) -> AddRectResult {
if width > self.width || height > self.height {
return AddRectResult::TooLarge;
}
for (r, row) in self.rows.iter_mut().enumerate() {
if row.height >= height && row.free_space >= width {
let row = &mut self.rows[r];
row.age = 0;
self.key_to_row
.insert(key, (r as RowIndex, row.len() as RectIndex));
row.push_rect(width, height, value);
return AddRectResult::Added;
}
}
if self.free_space >= height {
self.free_space -= height;
let mut row = Row {
age: 0,
top: self.rows.last().map_or(0, |x| x.top + x.height),
height,
free_space: self.width,
rects: Vec::new(),
};
self.key_to_row
.insert(key, (self.rows.len() as RowIndex, 0 as RectIndex));
row.push_rect(width, height, value);
self.rows.push(row);
return AddRectResult::Added;
}
let mut possible_row = None;
let mut best = 0;
for r in 0..self.rows.len() {
let mut rows_height = 0;
let mut age = !0;
for o in r..self.rows.len() {
let row = &self.rows[o];
if row.age == 0 {
break;
}
if row.age < age {
if row.age <= best {
break;
}
age = row.age;
}
rows_height += row.height;
if rows_height >= height {
possible_row = Some((r..o + 1, rows_height));
best = age;
break;
}
}
}
if let Some((range, row_height)) = possible_row {
let top = self.rows[range.start as usize].top;
let mut new_row = Row {
age: 0,
top,
height,
free_space: self.width,
rects: Vec::new(),
};
new_row.push_rect(width, height, value);
let add_len = if row_height == height {
self.rows.splice(range.clone(), std::iter::once(new_row));
1
} else {
let split_row = Row {
age: !0,
top: top + height,
height: row_height - height,
free_space: self.width,
rects: Vec::new(),
};
self.rows
.splice(range.clone(), IntoIterator::into_iter([new_row, split_row]));
2
};
self.key_to_row.retain(|_, (row, _)| {
if (*row as usize) < range.start {
true
} else if (*row as usize) >= range.end {
if *row != !0 {
*row = *row + add_len - range.len() as u32;
}
true
} else {
false
}
});
self.key_to_row
.insert(key, (range.start as RowIndex, 0 as RectIndex));
return AddRectResult::ClearedRows;
}
AddRectResult::NoRowToFit
}
}