use crate::{
UntypedHandle,
entry::CacheEntry,
utils::{RandomState, RwLock, RwLockReadGuard},
};
use hashbrown::HashTable;
use std::{any::TypeId, fmt};
#[derive(Clone, Default)]
pub(crate) struct Hasher(RandomState);
impl Hasher {
#[inline]
pub fn hash_entry(&self, entry: &CacheEntry) -> u64 {
self.hash_key(&entry.as_key())
}
pub fn hash_key(&self, (type_id, id): &(TypeId, &str)) -> u64 {
use std::hash::*;
let mut hasher = self.0.build_hasher();
type_id.hash(&mut hasher);
hasher.write(id.as_bytes());
hasher.finish()
}
}
#[repr(align(64))]
struct Shard(RwLock<HashTable<CacheEntry>>);
pub(crate) struct AssetMap {
shards: Box<[Shard]>,
}
impl AssetMap {
pub fn new() -> AssetMap {
let shards = match std::thread::available_parallelism() {
Ok(n) => 4 * n.get().next_power_of_two(),
Err(err) => {
log::error!("Failed to get available parallelism: {err}");
32
}
};
let shards = (0..shards)
.map(|_| Shard(RwLock::new(HashTable::new())))
.collect();
AssetMap { shards }
}
fn get_shard(&self, hash: u64) -> &Shard {
let id = (hash as usize) & (self.shards.len() - 1);
&self.shards[id]
}
pub fn get(&self, hash: u64, key: &(TypeId, &str)) -> Option<&UntypedHandle> {
let shard = self.get_shard(hash).0.read();
let entry = shard.find(hash, |e| e.as_key() == *key)?;
unsafe { Some(entry.inner().extend_lifetime()) }
}
pub fn insert(&self, entry: CacheEntry, hasher: &Hasher) -> &UntypedHandle {
let hash = hasher.hash_entry(&entry);
let shard = &mut *self.get_shard(hash).0.write();
let key = entry.as_key();
let entry = shard
.entry(hash, |e| e.as_key() == key, |e| hasher.hash_entry(e))
.or_insert(entry)
.into_mut();
unsafe { entry.inner().extend_lifetime() }
}
pub fn iter_shards(&self) -> impl Iterator<Item = LockedShard<'_>> {
self.shards.iter().map(|s| LockedShard(s.0.read()))
}
}
impl fmt::Debug for AssetMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut map = f.debug_list();
for shard in &*self.shards {
map.entries(shard.0.read().iter());
}
map.finish()
}
}
pub(crate) struct LockedShard<'a>(RwLockReadGuard<'a, HashTable<CacheEntry>>);
impl LockedShard<'_> {
pub fn iter(&self) -> impl Iterator<Item = &UntypedHandle> {
self.0.iter().map(|e| e.inner())
}
}