use crate::sync::MutexExt;
use std::any::{Any, TypeId};
use std::collections::{VecDeque, hash_map::DefaultHasher};
use std::hash::{Hash, Hasher};
use std::panic::Location;
use std::sync::{Arc, Mutex};
use super::CacheLimits;
pub struct SceneCache {
capacity: usize,
max_bytes: usize,
pub(super) entries: Mutex<VecDeque<CacheEntry>>,
}
impl std::fmt::Debug for SceneCache {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("SceneCache")
.field("capacity", &self.capacity)
.field("max_bytes", &self.max_bytes)
.finish_non_exhaustive()
}
}
pub(super) struct CacheEntry {
key: CacheKey,
input: Box<dyn Any + Send + Sync>,
value: Arc<dyn Any + Send + Sync>,
bytes: usize,
}
#[derive(PartialEq, Eq)]
struct CacheKey {
location: &'static Location<'static>,
value_type: TypeId,
hash: u64,
}
impl CacheEntry {
fn matches<Key: Eq + 'static>(&self, key: &CacheKey, input: &Key) -> bool {
self.key == *key && self.input.downcast_ref::<Key>() == Some(input)
}
}
impl Default for SceneCache {
fn default() -> Self {
Self::new(256)
}
}
impl SceneCache {
pub fn new(capacity: usize) -> Self {
Self::with_limits(CacheLimits {
max_entries: capacity,
max_bytes: usize::MAX,
})
}
pub fn with_limits(limits: CacheLimits) -> Self {
Self {
capacity: limits.max_entries,
max_bytes: limits.max_bytes,
entries: Mutex::new(VecDeque::new()),
}
}
pub fn max_bytes(&self) -> usize {
self.max_bytes
}
pub fn retained_bytes(&self) -> usize {
self.entries
.lock_unpoisoned()
.iter()
.map(|entry| entry.bytes)
.sum()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.entries.lock_unpoisoned().len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
let entries = std::mem::take(&mut *self.entries.lock_unpoisoned());
drop(entries);
}
#[track_caller]
pub(super) fn get<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: Key,
build: impl FnOnce() -> Value,
) -> Arc<Value> {
match self.get_cached(
key,
|| Ok::<_, std::convert::Infallible>(build()),
|_| 0,
false,
Location::caller(),
) {
Ok(value) => value,
Err(never) => match never {},
}
}
#[track_caller]
pub(super) fn try_get<
Key: Eq + Hash + Send + Sync + 'static,
Value: Send + Sync + 'static,
Error,
>(
&self,
key: Key,
build: impl FnOnce() -> Result<Value, Error>,
) -> Result<Arc<Value>, Error> {
self.get_cached(key, build, |_| 0, false, Location::caller())
}
#[track_caller]
pub fn get_with_cost<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: Key,
build: impl FnOnce() -> Value,
cost: impl FnOnce(&Value) -> usize,
) -> Arc<Value> {
match self.get_cached(
key,
|| Ok::<_, std::convert::Infallible>(build()),
cost,
true,
Location::caller(),
) {
Ok(value) => value,
Err(never) => match never {},
}
}
fn content_location() -> &'static Location<'static> {
Location::caller()
}
pub fn lookup<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: &Key,
) -> Option<Arc<Value>> {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
self.find(
&CacheKey {
location: Self::content_location(),
value_type: TypeId::of::<Value>(),
hash: hasher.finish(),
},
key,
)
}
pub fn insert_with_cost<
Key: Eq + Hash + Send + Sync + 'static,
Value: Send + Sync + 'static,
>(
&self,
key: Key,
value: Value,
cost: impl FnOnce(&Value) -> usize,
) -> Arc<Value> {
match self.get_cached(
key,
|| Ok::<_, std::convert::Infallible>(value),
cost,
true,
Self::content_location(),
) {
Ok(value) => value,
Err(never) => match never {},
}
}
#[track_caller]
fn get_cached<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static, Error>(
&self,
key: Key,
build: impl FnOnce() -> Result<Value, Error>,
cost: impl FnOnce(&Value) -> usize,
accounted: bool,
location: &'static Location<'static>,
) -> Result<Arc<Value>, Error> {
if self.capacity == 0 || (accounted && self.max_bytes == 0) {
return build().map(Arc::new);
}
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
let cache_key = CacheKey {
location,
value_type: TypeId::of::<Value>(),
hash: hasher.finish(),
};
if let Some(value) = self.find(&cache_key, &key) {
return Ok(value);
}
let value = Arc::new(build()?);
let bytes = if accounted {
cost(value.as_ref())
.max(std::mem::size_of::<Value>().saturating_add(std::mem::size_of::<Key>()))
} else {
0
};
if bytes > self.max_bytes {
return Ok(value);
}
let mut entries = self.entries.lock_unpoisoned();
if let Some(index) = entries
.iter()
.position(|entry| entry.matches(&cache_key, &key))
{
let entry = entries.remove(index).expect("located cache entry");
let stored = entry.value.clone();
entries.push_back(entry);
drop(entries);
return Ok(stored
.downcast()
.unwrap_or_else(|_| unreachable!("the cache key encodes the value type")));
}
let mut retained: usize = entries.iter().map(|entry| entry.bytes).sum();
let mut evicted = Vec::new();
while entries.len() >= self.capacity || bytes > self.max_bytes.saturating_sub(retained) {
let Some(entry) = entries.pop_front() else {
break;
};
retained -= entry.bytes;
evicted.push(entry);
}
entries.push_back(CacheEntry {
key: cache_key,
input: Box::new(key),
value: value.clone(),
bytes,
});
drop(entries);
drop(evicted);
Ok(value)
}
fn find<Key: Eq + 'static, Value: Send + Sync + 'static>(
&self,
key: &CacheKey,
input: &Key,
) -> Option<Arc<Value>> {
let mut entries = self.entries.lock_unpoisoned();
let index = entries.iter().position(|entry| entry.matches(key, input))?;
let entry = entries.remove(index).expect("located cache entry");
let value = entry.value.clone();
entries.push_back(entry);
drop(entries);
Some(
value
.downcast()
.unwrap_or_else(|_| unreachable!("the cache key encodes the value type")),
)
}
}