use std::collections::HashMap;
use log::trace;
use serde::{Deserialize, Serialize};
use crate::tile::TilePairHash;
#[derive(Debug, Serialize, Deserialize)]
pub struct TilePngs {
pub content: Vec<u8>,
pub sidebar: Vec<u8>,
}
struct TiledDocumentCache {
data: HashMap<usize, TilePngs>,
}
impl TiledDocumentCache {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
fn get(&self, idx: usize) -> Option<&TilePngs> {
self.data.get(&idx)
}
fn contains(&self, idx: usize) -> bool {
self.data.contains_key(&idx)
}
fn insert(&mut self, idx: usize, pngs: TilePngs) {
self.data.insert(idx, pngs);
}
fn evict_distant(&mut self, center: usize, keep_radius: usize) {
let to_evict: Vec<usize> = self
.data
.keys()
.filter(|&&k| (k as isize - center as isize).unsigned_abs() > keep_radius)
.copied()
.collect();
for k in to_evict {
self.data.remove(&k);
trace!("cache evict tile {}", k);
}
}
fn remove(&mut self, idx: usize) -> Option<TilePngs> {
self.data.remove(&idx)
}
fn len(&self) -> usize {
self.data.len()
}
fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn clear(&mut self) {
self.data.clear();
}
}
pub struct TileCache {
cache: TiledDocumentCache,
hashes: Vec<TilePairHash>,
}
impl Default for TileCache {
fn default() -> Self {
Self::new()
}
}
impl TileCache {
pub fn new() -> Self {
Self {
cache: TiledDocumentCache::new(),
hashes: Vec::new(),
}
}
pub fn merge_generation(&mut self, new_hashes: &[TilePairHash]) -> usize {
let mut new_cache = TiledDocumentCache::new();
for (new_idx, new_hash) in new_hashes.iter().enumerate() {
if let Some(old_idx) = self.hashes.iter().position(|h| h == new_hash)
&& let Some(pngs) = self.cache.remove(old_idx)
{
new_cache.insert(new_idx, pngs);
}
}
let recovered = new_cache.len();
self.cache = new_cache;
self.hashes = new_hashes.to_vec();
recovered
}
pub fn clear(&mut self) {
self.cache.clear();
self.hashes.clear();
}
pub fn get(&self, idx: usize) -> Option<&TilePngs> {
self.cache.get(idx)
}
pub fn contains(&self, idx: usize) -> bool {
self.cache.contains(idx)
}
pub fn insert(&mut self, idx: usize, pngs: TilePngs) {
self.cache.insert(idx, pngs);
}
pub fn evict_distant(&mut self, center: usize, keep_radius: usize) {
self.cache.evict_distant(center, keep_radius);
}
pub fn len(&self) -> usize {
self.cache.len()
}
pub fn is_empty(&self) -> bool {
self.cache.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tile::TileHash;
fn make_hash(v: u8) -> TilePairHash {
TilePairHash {
content: TileHash::new_for_test(v as u64),
sidebar: TileHash::new_for_test(v as u64),
}
}
fn make_pngs(tag: u8) -> TilePngs {
TilePngs {
content: vec![tag],
sidebar: vec![tag],
}
}
#[test]
fn merge_generation_full_match() {
let hashes = vec![make_hash(1), make_hash(2)];
let mut tc = TileCache::new();
tc.hashes = hashes.clone();
tc.cache.insert(0, make_pngs(10));
tc.cache.insert(1, make_pngs(20));
let recovered = tc.merge_generation(&hashes);
assert_eq!(recovered, 2);
assert_eq!(tc.get(0).unwrap().content, vec![10]);
assert_eq!(tc.get(1).unwrap().content, vec![20]);
}
#[test]
fn merge_generation_partial_match() {
let old_hashes = vec![make_hash(1), make_hash(2)];
let new_hashes = vec![make_hash(1), make_hash(3)];
let mut tc = TileCache::new();
tc.hashes = old_hashes;
tc.cache.insert(0, make_pngs(10));
tc.cache.insert(1, make_pngs(20));
let recovered = tc.merge_generation(&new_hashes);
assert_eq!(recovered, 1);
assert!(tc.contains(0));
assert!(!tc.contains(1));
}
#[test]
fn merge_generation_zero_match() {
let old_hashes = vec![make_hash(1), make_hash(2)];
let new_hashes = vec![make_hash(3), make_hash(4)];
let mut tc = TileCache::new();
tc.hashes = old_hashes;
tc.cache.insert(0, make_pngs(10));
tc.cache.insert(1, make_pngs(20));
let recovered = tc.merge_generation(&new_hashes);
assert_eq!(recovered, 0);
}
#[test]
fn merge_generation_evicted_not_recovered() {
let hashes = vec![make_hash(1)];
let mut tc = TileCache::new();
tc.hashes = hashes.clone();
let recovered = tc.merge_generation(&hashes);
assert_eq!(recovered, 0);
}
#[test]
fn clear_empties_both() {
let mut tc = TileCache::new();
tc.hashes = vec![make_hash(1)];
tc.cache.insert(0, make_pngs(10));
tc.clear();
assert!(tc.is_empty());
assert_eq!(tc.len(), 0);
}
}