use std::io::{Error as IoError, Read};
use std::sync::{Arc, Mutex, RwLock};
use crate::*;
type CacheHashMap<V> = std::collections::HashMap<String, V, ahash::RandomState>;
#[derive(Debug, derive_builder::Builder)]
pub struct AssetCacheConfig {
pub max_bytes_cost: u64,
pub max_decoded_cost: u64,
pub max_single_object_bytes_cost: u64,
pub max_single_object_decoded_cost: u64,
}
pub struct AssetCache<VfsImpl: Vfs, DecoderImpl: Decoder> {
config: AssetCacheConfig,
pinned_entries: RwLock<CacheHashMap<Arc<DecoderImpl::Output>>>,
bytes_cache: Mutex<CostBasedLru<str, Vec<u8>>>,
decoded_cache: Mutex<CostBasedLru<str, DecoderImpl::Output>>,
decoding_guards: Mutex<CacheHashMap<Arc<Mutex<()>>>>,
weak_refs: RwLock<CacheHashMap<std::sync::Weak<DecoderImpl::Output>>>,
vfs: VfsImpl,
decoder: DecoderImpl,
}
#[derive(Debug, thiserror::Error)]
pub enum AssetCacheError<DecoderError> {
Vfs(IoError),
Decoder(DecoderError),
}
impl<VfsImpl: Vfs, DecoderImpl: Decoder> AssetCache<VfsImpl, DecoderImpl> {
pub fn new(
vfs: VfsImpl,
decoder: DecoderImpl,
config: AssetCacheConfig,
) -> AssetCache<VfsImpl, DecoderImpl> {
AssetCache {
decoder,
vfs,
bytes_cache: Mutex::new(CostBasedLru::new(config.max_bytes_cost)),
decoded_cache: Mutex::new(CostBasedLru::new(config.max_decoded_cost)),
decoding_guards: Default::default(),
pinned_entries: RwLock::new(Default::default()),
weak_refs: RwLock::new(Default::default()),
config,
}
}
fn search_for_item(&self, key: &str) -> Option<Arc<DecoderImpl::Output>> {
{
let guard = self.pinned_entries.read().unwrap();
if let Some(x) = guard.get(key) {
return Some((*x).clone());
}
}
{
let mut guard = self.decoded_cache.lock().unwrap();
if let Some(x) = guard.get(key) {
return Some(x);
}
}
self.weak_refs
.read()
.unwrap()
.get(key)
.and_then(|x| x.upgrade())
}
fn find_or_decode_postchecked(
&self,
key: &str,
) -> Result<Arc<DecoderImpl::Output>, AssetCacheError<DecoderImpl::Error>> {
if let Some(x) = self.search_for_item(key) {
return Ok(x);
}
let mut bytes_reader = self.vfs.open(key).map_err(AssetCacheError::Vfs)?;
let size = bytes_reader.get_size().map_err(AssetCacheError::Vfs)?;
let decoded = if size <= self.config.max_single_object_bytes_cost {
let maybe_cached_bytes = self.bytes_cache.lock().unwrap().get(key);
if let Some(x) = maybe_cached_bytes {
self.decoder
.decode(&mut &x[..])
.map_err(AssetCacheError::Decoder)?
} else {
let mut dest = vec![];
bytes_reader
.read_to_end(&mut dest)
.map_err(AssetCacheError::Vfs)?;
let will_use = {
let mut guard = self.bytes_cache.lock().unwrap();
guard.insert(key.to_string().into(), dest, size);
guard.get(key).expect("We just inserted this")
};
self.decoder
.decode(&mut &will_use[..])
.map_err(AssetCacheError::Decoder)?
}
} else {
self.decoder
.decode(bytes_reader)
.map_err(AssetCacheError::Decoder)?
};
let cost = self
.decoder
.estimate_cost(&decoded)
.map_err(AssetCacheError::Decoder)?;
let res = if cost <= self.config.max_single_object_decoded_cost {
let mut guard = self.decoded_cache.lock().unwrap();
guard.insert(key.to_string().into(), decoded, cost);
guard.get(key).expect("Just inserted")
} else {
Arc::new(decoded)
};
let weak = Arc::downgrade(&res);
self.weak_refs
.write()
.unwrap()
.insert(key.to_string(), weak);
Ok(res)
}
fn find_or_decode(
&self,
key: &str,
) -> Result<Arc<DecoderImpl::Output>, AssetCacheError<DecoderImpl::Error>> {
if let Some(x) = self.search_for_item(key) {
return Ok(x);
}
let mutex = {
let mut guard_inner = self.decoding_guards.lock().unwrap();
let tmp = guard_inner
.entry(key.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())));
(*tmp).clone()
};
let _guard: std::sync::MutexGuard<()> = mutex.lock().unwrap();
self.find_or_decode_postchecked(key)
}
pub fn get(
&self,
key: &str,
) -> Result<Arc<DecoderImpl::Output>, AssetCacheError<DecoderImpl::Error>> {
self.find_or_decode(key)
}
pub fn cache_always(&self, key: String, value: Arc<DecoderImpl::Output>) {
let weak = Arc::downgrade(&value);
self.pinned_entries
.write()
.unwrap()
.insert(key.clone(), value);
self.weak_refs.write().unwrap().insert(key, weak);
}
pub fn remove(&self, key: &str) {
self.pinned_entries.write().unwrap().remove(key);
self.bytes_cache.lock().unwrap().remove(key);
self.decoding_guards.lock().unwrap().remove(key);
self.decoded_cache.lock().unwrap().remove(key);
self.weak_refs.write().unwrap().remove(key);
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
struct HashMapVfs(Mutex<HashMap<String, Vec<u8>>>);
impl Vfs for HashMapVfs {
type Reader = std::io::Cursor<Vec<u8>>;
fn open(&self, key: &str) -> Result<Self::Reader, IoError> {
let ret = self
.0
.lock()
.unwrap()
.get(key)
.ok_or_else(|| {
IoError::new(std::io::ErrorKind::NotFound, "Entry not found".to_string())
})?
.clone();
Ok(std::io::Cursor::new(ret))
}
}
impl VfsReader for std::io::Cursor<Vec<u8>> {
fn get_size(&self) -> Result<u64, IoError> {
Ok(self.get_ref().len() as u64)
}
}
impl HashMapVfs {
fn new() -> HashMapVfs {
HashMapVfs(Mutex::new(Default::default()))
}
pub fn insert(&self, key: &str, value: Vec<u8>) -> Option<Vec<u8>> {
self.0.lock().unwrap().insert(key.to_string(), value)
}
}
struct HashMapDecoder;
impl Decoder for HashMapDecoder {
type Error = IoError;
type Output = String;
fn decode<R: Read>(&self, mut reader: R) -> Result<String, IoError> {
let mut out = String::new();
reader.read_to_string(&mut out)?;
Ok(out)
}
fn estimate_cost(&self, item: &String) -> Result<u64, IoError> {
Ok(item.len() as u64)
}
}
fn build_cache() -> (Arc<HashMapVfs>, AssetCache<Arc<HashMapVfs>, HashMapDecoder>) {
let cfg = AssetCacheConfigBuilder::default()
.max_bytes_cost(50)
.max_single_object_bytes_cost(10)
.max_decoded_cost(60)
.max_single_object_decoded_cost(12)
.build()
.expect("Should build");
let vfs = Arc::new(HashMapVfs::new());
(vfs.clone(), AssetCache::new(vfs, HashMapDecoder, cfg))
}
#[test]
fn basic_ops() {
let (vfs, cache) = build_cache();
vfs.insert("a", "abc".into());
vfs.insert("b", "def".into());
assert_eq!(&*cache.get("a").unwrap(), "abc");
assert_eq!(&*cache.get("b").unwrap(), "def");
cache.search_for_item("a").expect("Should find the key");
cache.search_for_item("b").expect("Should find the item");
cache.remove("b");
assert!(cache.search_for_item("b").is_none());
cache.search_for_item("a").expect("Key should be found");
}
#[test]
fn test_single_object_limits() {
let (vfs, cache) = build_cache();
const SMALL: &str = "small";
const NO_CACHE_BYTES: &str = "no_cache_bytes";
const MAX_BYTES: &str = "max_bytes";
const MAX_DECODED: &str = "max_decoded";
const NO_CACHE: &str = "no_cache";
vfs.insert(SMALL, "abc".into());
vfs.insert(MAX_BYTES, "abcdefghij".into());
vfs.insert(NO_CACHE_BYTES, "abcdefghijk".into());
vfs.insert(MAX_DECODED, "abcdefghijkl".into());
vfs.insert(NO_CACHE, "abcdefghijklm".into());
for i in &[SMALL, MAX_BYTES, MAX_DECODED, NO_CACHE, NO_CACHE_BYTES] {
cache.get(i).expect("Should decode fine");
}
assert_eq!(&*cache.search_for_item(SMALL).unwrap(), "abc");
assert_eq!(&*cache.search_for_item(MAX_BYTES).unwrap(), "abcdefghij");
assert_eq!(
&*cache.search_for_item(MAX_DECODED).unwrap(),
"abcdefghijkl"
);
assert_eq!(
&*cache.search_for_item(NO_CACHE_BYTES).unwrap(),
"abcdefghijk"
);
assert!(cache.search_for_item(NO_CACHE).is_none());
}
#[test]
fn test_weak_recovery() {
let (vfs, cache) = build_cache();
let mut arcs = vec![];
for i in 0..100 {
let key = format!("{}", i);
let val = format!("{}", i);
vfs.insert(&key, val.into());
arcs.push(cache.get(&key).unwrap());
}
assert!(cache.bytes_cache.lock().unwrap().get("1").is_none());
assert!(cache.decoded_cache.lock().unwrap().get("1").is_none());
assert!(cache.weak_refs.read().unwrap().get("1").is_some());
assert_eq!(&*cache.get("1").unwrap(), "1");
arcs.clear();
assert!(cache.search_for_item("1").is_none());
vfs.insert("big", "abcdefghijklmnopqrstuvwxyz".into());
let sref = cache.get("big");
assert!(cache.bytes_cache.lock().unwrap().get("big").is_none());
assert!(cache.decoded_cache.lock().unwrap().get("big").is_none());
assert_eq!(&*cache.get("big").unwrap(), "abcdefghijklmnopqrstuvwxyz");
std::mem::drop(sref);
assert!(cache.search_for_item("big").is_none());
}
}