use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
use astrelis_core::alloc::sparse_set::{IndexSlot, SparseSet};
use crate::Asset;
use crate::error::AssetError;
use crate::handle::{Handle, HandleId, UntypedHandle};
use crate::source::AssetSource;
use crate::state::{AssetEntry, AssetState};
pub struct Assets<T: Asset> {
entries: SparseSet<AssetEntry<T>>,
source_to_handle: HashMap<String, HandleId>,
ref_counts: HashMap<u32, u32>,
}
impl<T: Asset> Default for Assets<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Asset> Assets<T> {
pub fn new() -> Self {
Self {
entries: SparseSet::new(),
source_to_handle: HashMap::new(),
ref_counts: HashMap::new(),
}
}
pub fn insert(&mut self, source: AssetSource, asset: T) -> Handle<T> {
let key_ref = source.key();
if let Some(&handle_id) = self.source_to_handle.get(key_ref) {
if let Some(entry) = self.entries.try_get_mut(handle_id.slot) {
entry.state = AssetState::Ready(Arc::new(asset));
entry.version.increment();
}
return Handle::new(handle_id);
}
let key = key_ref.to_string();
let entry = AssetEntry::with_asset(source.clone(), asset);
let slot = self.entries.push(entry);
let handle_id = HandleId::new(slot, TypeId::of::<T>());
self.source_to_handle.insert(key, handle_id);
self.ref_counts.insert(slot.index(), 1);
Handle::new(handle_id)
}
pub fn reserve(&mut self, source: AssetSource) -> Handle<T> {
let key_ref = source.key();
if let Some(&handle_id) = self.source_to_handle.get(key_ref) {
return Handle::new(handle_id);
}
let key = key_ref.to_string();
let entry = AssetEntry::new(source.clone());
let slot = self.entries.push(entry);
let handle_id = HandleId::new(slot, TypeId::of::<T>());
self.source_to_handle.insert(key, handle_id);
self.ref_counts.insert(slot.index(), 1);
Handle::new(handle_id)
}
pub fn set_loading(&mut self, handle: &Handle<T>) {
if let Some(entry) = self.entries.try_get_mut(handle.id().slot) {
entry.state = AssetState::Loading;
}
}
pub fn set_loaded(&mut self, handle: &Handle<T>, asset: T) {
if let Some(entry) = self.entries.try_get_mut(handle.id().slot) {
entry.state = AssetState::Ready(Arc::new(asset));
entry.version.increment();
}
}
pub fn set_failed(&mut self, handle: &Handle<T>, error: AssetError) {
if let Some(entry) = self.entries.try_get_mut(handle.id().slot) {
entry.state = AssetState::Failed(Arc::new(error));
}
}
pub fn get(&self, handle: &Handle<T>) -> Option<&Arc<T>> {
self.entries
.try_get(handle.id().slot)
.and_then(|entry| entry.asset())
}
pub fn get_entry(&self, handle: &Handle<T>) -> Option<&AssetEntry<T>> {
self.entries.try_get(handle.id().slot)
}
pub fn state(&self, handle: &Handle<T>) -> Option<&AssetState<T>> {
self.entries.try_get(handle.id().slot).map(|e| &e.state)
}
pub fn is_ready(&self, handle: &Handle<T>) -> bool {
self.entries
.try_get(handle.id().slot)
.map(|e| e.is_ready())
.unwrap_or(false)
}
pub fn is_loading(&self, handle: &Handle<T>) -> bool {
self.entries
.try_get(handle.id().slot)
.map(|e| e.is_loading())
.unwrap_or(false)
}
pub fn version(&self, handle: &Handle<T>) -> Option<u32> {
self.entries
.try_get(handle.id().slot)
.map(|e| e.version.get())
}
pub fn find_by_source(&self, source: &AssetSource) -> Option<Handle<T>> {
let key = source.key();
self.source_to_handle.get(key).map(|&id| Handle::new(id))
}
pub fn remove(&mut self, handle: &Handle<T>) -> Option<AssetEntry<T>> {
let slot = handle.id().slot;
if self.entries.try_get(slot).is_some() {
let entry = self.entries.remove(slot);
self.source_to_handle.remove(entry.source.key());
self.ref_counts.remove(&slot.index());
Some(entry)
} else {
None
}
}
pub fn add_ref(&mut self, handle: &Handle<T>) {
let idx = handle.id().slot.index();
if let Some(count) = self.ref_counts.get_mut(&idx) {
*count = count.saturating_add(1);
}
}
pub fn release(&mut self, handle: &Handle<T>) -> bool {
let idx = handle.id().slot.index();
if let Some(count) = self.ref_counts.get_mut(&idx) {
*count = count.saturating_sub(1);
*count == 0
} else {
false
}
}
pub fn ref_count(&self, handle: &Handle<T>) -> u32 {
let idx = handle.id().slot.index();
self.ref_counts.get(&idx).copied().unwrap_or(0)
}
pub fn iter(&self) -> impl Iterator<Item = &AssetEntry<T>> {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub trait ErasedAssets: Send + Sync {
fn asset_type_id(&self) -> TypeId;
fn is_ready_untyped(&self, slot: IndexSlot) -> bool;
fn source_for_slot(&self, slot: IndexSlot) -> Option<&AssetSource>;
fn set_loaded_erased(&mut self, slot: IndexSlot, asset: Box<dyn Any + Send + Sync>) -> bool;
fn set_failed_erased(&mut self, slot: IndexSlot, error: AssetError);
fn version_for_slot(&self, slot: IndexSlot) -> Option<u32>;
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl<T: Asset> ErasedAssets for Assets<T> {
fn asset_type_id(&self) -> TypeId {
TypeId::of::<T>()
}
fn is_ready_untyped(&self, slot: IndexSlot) -> bool {
self.entries
.try_get(slot)
.map(|e| e.is_ready())
.unwrap_or(false)
}
fn source_for_slot(&self, slot: IndexSlot) -> Option<&AssetSource> {
self.entries.try_get(slot).map(|e| &e.source)
}
fn set_loaded_erased(&mut self, slot: IndexSlot, asset: Box<dyn Any + Send + Sync>) -> bool {
let Some(typed_asset) = asset.downcast::<T>().ok() else {
tracing::error!(
"Type mismatch storing asset: expected {}, got different type",
T::type_name()
);
return false;
};
let Some(entry) = self.entries.try_get_mut(slot) else {
tracing::error!("Slot not found for asset: {:?}", slot);
return false;
};
entry.state = AssetState::Ready(Arc::new(*typed_asset));
entry.version.increment();
true
}
fn set_failed_erased(&mut self, slot: IndexSlot, error: AssetError) {
if let Some(entry) = self.entries.try_get_mut(slot) {
entry.state = AssetState::Failed(Arc::new(error));
}
}
fn version_for_slot(&self, slot: IndexSlot) -> Option<u32> {
self.entries.try_get(slot).map(|e| e.version.get())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Default)]
pub struct AssetStorages {
storages: HashMap<TypeId, Box<dyn ErasedAssets>>,
}
impl AssetStorages {
pub fn new() -> Self {
Self::default()
}
pub fn get_or_create<T: Asset>(&mut self) -> &mut Assets<T> {
let type_id = TypeId::of::<T>();
self.storages
.entry(type_id)
.or_insert_with(|| Box::new(Assets::<T>::new()));
self.storages
.get_mut(&type_id)
.and_then(|s| s.as_any_mut().downcast_mut())
.expect("type mismatch in storage registry")
}
pub fn get<T: Asset>(&self) -> Option<&Assets<T>> {
let type_id = TypeId::of::<T>();
self.storages
.get(&type_id)
.and_then(|s| s.as_any().downcast_ref())
}
pub fn get_mut<T: Asset>(&mut self) -> Option<&mut Assets<T>> {
let type_id = TypeId::of::<T>();
self.storages
.get_mut(&type_id)
.and_then(|s| s.as_any_mut().downcast_mut())
}
pub fn has<T: Asset>(&self) -> bool {
self.storages.contains_key(&TypeId::of::<T>())
}
pub fn find_source(&self, handle: &UntypedHandle) -> Option<&AssetSource> {
let storage = self.storages.get(&handle.type_id())?;
storage.source_for_slot(handle.id().slot)
}
pub fn set_loaded_erased(
&mut self,
handle: &UntypedHandle,
asset: Box<dyn Any + Send + Sync>,
) -> bool {
let Some(storage) = self.storages.get_mut(&handle.type_id()) else {
tracing::error!("No storage found for type {:?}", handle.type_id());
return false;
};
storage.set_loaded_erased(handle.id().slot, asset)
}
pub fn set_failed_erased(&mut self, handle: &UntypedHandle, error: AssetError) {
if let Some(storage) = self.storages.get_mut(&handle.type_id()) {
storage.set_failed_erased(handle.id().slot, error);
}
}
pub fn version_erased(&self, handle: &UntypedHandle) -> Option<u32> {
let storage = self.storages.get(&handle.type_id())?;
storage.version_for_slot(handle.id().slot)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_insert_and_get() {
let mut assets: Assets<String> = Assets::new();
let source = AssetSource::memory("test.txt");
let handle = assets.insert(source, "Hello, World!".to_string());
assert!(assets.is_ready(&handle));
let asset = assets.get(&handle).unwrap();
assert_eq!(**asset, "Hello, World!");
}
#[test]
fn test_reserve_and_load() {
let mut assets: Assets<String> = Assets::new();
let source = AssetSource::memory("test.txt");
let handle = assets.reserve(source);
assert!(!assets.is_ready(&handle));
assets.set_loading(&handle);
assert!(assets.is_loading(&handle));
assets.set_loaded(&handle, "Loaded!".to_string());
assert!(assets.is_ready(&handle));
let asset = assets.get(&handle).unwrap();
assert_eq!(**asset, "Loaded!");
}
#[test]
fn test_find_by_source() {
let mut assets: Assets<String> = Assets::new();
let source = AssetSource::memory("test.txt");
let handle = assets.insert(source.clone(), "Test".to_string());
let found = assets.find_by_source(&source).unwrap();
assert_eq!(found.id(), handle.id());
}
#[test]
fn test_ref_counting() {
let mut assets: Assets<String> = Assets::new();
let source = AssetSource::memory("test.txt");
let handle = assets.insert(source, "Test".to_string());
assert_eq!(assets.ref_count(&handle), 1);
assets.add_ref(&handle);
assert_eq!(assets.ref_count(&handle), 2);
let should_remove = assets.release(&handle);
assert!(!should_remove);
assert_eq!(assets.ref_count(&handle), 1);
let should_remove = assets.release(&handle);
assert!(should_remove);
assert_eq!(assets.ref_count(&handle), 0);
}
}