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>>,
pub(super) empty_assets: crate::Assets,
}
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 {
entries: capacity,
bytes: usize::MAX,
})
}
pub fn with_limits(limits: CacheLimits) -> Self {
Self {
capacity: limits.entries,
max_bytes: limits.bytes,
entries: Mutex::new(VecDeque::new()),
empty_assets: crate::Assets::default(),
}
}
pub fn max_bytes(&self) -> usize {
self.max_bytes
}
pub fn retained_bytes(&self) -> usize {
self.entries
.lock()
.expect("scene cache lock poisoned")
.iter()
.map(|entry| entry.bytes)
.sum()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.entries
.lock()
.expect("scene cache lock poisoned")
.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&self) {
let entries = std::mem::take(&mut *self.entries.lock().expect("scene cache lock poisoned"));
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> {
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> {
self.get_cached(key, build, cost, true, Location::caller())
}
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> {
self.get_cached(key, || value, cost, true, Self::content_location())
}
#[track_caller]
fn get_cached<Key: Eq + Hash + Send + Sync + 'static, Value: Send + Sync + 'static>(
&self,
key: Key,
build: impl FnOnce() -> Value,
cost: impl FnOnce(&Value) -> usize,
accounted: bool,
location: &'static Location<'static>,
) -> Arc<Value> {
if self.capacity == 0 || (accounted && self.max_bytes == 0) {
return Arc::new(build());
}
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 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 value;
}
let mut entries = self.entries.lock().expect("scene cache lock poisoned");
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 stored.downcast().unwrap_or_else(|_| unreachable!());
}
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);
value
}
fn find<Key: Eq + 'static, Value: Send + Sync + 'static>(
&self,
key: &CacheKey,
input: &Key,
) -> Option<Arc<Value>> {
let mut entries = self.entries.lock().expect("scene cache lock poisoned");
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!()))
}
}