use slotmap::{Key as _, SecondaryMap, SlotMap, new_key_type};
use std::collections::HashMap;
new_key_type! {
pub struct RawAssetHandle;
}
pub struct Assets<T: 'static + Send + Sync> {
storage: SlotMap<RawAssetHandle, T>,
handles: HashMap<String, RawAssetHandle>,
queue: Vec<RawAssetHandle>,
removed: Vec<RawAssetHandle>,
}
impl<T: 'static + Send + Sync> Assets<T> {
pub fn new() -> Self {
Self {
storage: SlotMap::with_key(),
handles: HashMap::new(),
queue: Vec::new(),
removed: Vec::new(),
}
}
pub fn insert(&mut self, name: &str, asset: T) -> RawAssetHandle {
if let Some(&existing) = self.handles.get(name) {
if let Some(slot) = self.storage.get_mut(existing) {
*slot = asset;
if !self.queue.contains(&existing) {
self.queue.push(existing);
}
tracing::debug!(
"Assets<{}>: replaced data for {:?} ({name}) in-place",
std::any::type_name::<T>(),
existing
);
return existing;
}
}
let handle = self.storage.insert(asset);
self.handles.insert(name.to_string(), handle);
self.queue.push(handle);
handle
}
pub fn get(&self, handle: RawAssetHandle) -> Option<&T> {
if handle.is_null() {
tracing::warn!(
"Assets<{}>: get() called with a null/default handle — \
did you forget to insert the asset and store the returned handle?",
std::any::type_name::<T>()
);
return None;
}
let result = self.storage.get(handle);
if result.is_none() {
tracing::warn!(
"Assets<{}>: get() called with a stale handle {:?} — \
the asset was likely removed or replaced since this handle was obtained",
std::any::type_name::<T>(),
handle
);
}
result
}
pub fn get_mut(&mut self, handle: RawAssetHandle) -> Option<&mut T> {
if handle.is_null() {
tracing::warn!(
"Assets<{}>: get_mut() called with a null/default handle — \
did you forget to insert the asset and store the returned handle?",
std::any::type_name::<T>()
);
return None;
}
let result = self.storage.get_mut(handle);
if result.is_none() {
tracing::warn!(
"Assets<{}>: get_mut() called with a stale handle {:?} — \
the asset was likely removed or replaced since this handle was obtained",
std::any::type_name::<T>(),
handle
);
}
result
}
pub fn get_by_name(&self, name: &str) -> Option<&T> {
let result = self.handles
.get(name)
.and_then(|&handle| self.storage.get(handle));
if result.is_none() {
tracing::debug!(
"Assets<{}>: get_by_name({:?}) found no asset — \
the name may not have been inserted yet",
std::any::type_name::<T>(),
name
);
}
result
}
pub fn get_mut_by_name(&mut self, name: &str) -> Option<&mut T> {
let handle = self.handles.get(name).copied();
if handle.is_none() {
tracing::debug!(
"Assets<{}>: get_mut_by_name({:?}) found no asset — \
the name may not have been inserted yet",
std::any::type_name::<T>(),
name
);
return None;
}
self.storage.get_mut(handle.unwrap())
}
pub fn get_handle_by_name(&self, name: &str) -> Option<RawAssetHandle> {
self.handles.get(name).copied()
}
pub fn take_dirty(&mut self) -> Vec<RawAssetHandle> {
std::mem::take(&mut self.queue)
}
pub fn remove(&mut self, handle: RawAssetHandle) -> Option<T> {
if handle.is_null() {
tracing::warn!(
"Assets<{}>: remove() called with a null/default handle — no-op",
std::any::type_name::<T>()
);
return None;
}
let value = self.storage.remove(handle)?;
self.handles.retain(|_, h| *h != handle);
self.queue.retain(|h| *h != handle);
self.removed.push(handle);
Some(value)
}
pub fn remove_by_name(&mut self, name: &str) -> Option<T> {
let handle = self.handles.remove(name)?;
self.queue.retain(|h| *h != handle);
self.removed.push(handle);
self.storage.remove(handle)
}
pub fn take_removed(&mut self) -> Vec<RawAssetHandle> {
std::mem::take(&mut self.removed)
}
pub fn dirty_is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn dirty_len(&self) -> usize {
self.queue.len()
}
pub fn requeue(&mut self, handles: Vec<RawAssetHandle>) {
self.queue.extend(handles);
}
pub fn replace(&mut self, handle: RawAssetHandle, asset: T) -> bool {
if handle.is_null() {
tracing::warn!(
"Assets<{}>: replace() called with a null/default handle — no-op",
std::any::type_name::<T>()
);
return false;
}
let Some(slot) = self.storage.get_mut(handle) else {
tracing::warn!(
"Assets<{}>: replace() called with a stale handle {:?} — no-op",
std::any::type_name::<T>(),
handle
);
return false;
};
*slot = asset;
if !self.queue.contains(&handle) {
self.queue.push(handle);
}
tracing::debug!(
"Assets<{}>: replaced data for {:?}{} via handle",
std::any::type_name::<T>(),
handle,
self.name_for_handle(handle)
.map(|n| format!(" ({n})"))
.unwrap_or_default()
);
true
}
pub fn mark_dirty(&mut self, handle: RawAssetHandle) {
if handle.is_null() {
tracing::warn!(
"Assets<{}>: mark_dirty() called with a null/default handle — no-op",
std::any::type_name::<T>()
);
return;
}
if self.storage.contains_key(handle) && !self.queue.contains(&handle) {
self.queue.push(handle);
}
}
pub fn iter(&self) -> impl Iterator<Item = (RawAssetHandle, &T)> {
self.storage.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RawAssetHandle, &mut T)> {
self.storage.iter_mut()
}
pub fn names(&self) -> impl Iterator<Item = (&str, RawAssetHandle)> {
self.handles
.iter()
.map(|(name, &handle)| (name.as_str(), handle))
}
pub fn name_for_handle(&self, handle: RawAssetHandle) -> Option<&str> {
self.handles
.iter()
.find(|(_, h)| **h == handle)
.map(|(name, _)| name.as_str())
}
}
impl<'a, T: 'static + Send + Sync> IntoIterator for &'a Assets<T> {
type Item = (RawAssetHandle, &'a T);
type IntoIter = slotmap::basic::Iter<'a, RawAssetHandle, T>;
fn into_iter(self) -> Self::IntoIter {
self.storage.iter()
}
}
impl<'a, T: 'static + Send + Sync> IntoIterator for &'a mut Assets<T> {
type Item = (RawAssetHandle, &'a mut T);
type IntoIter = slotmap::basic::IterMut<'a, RawAssetHandle, T>;
fn into_iter(self) -> Self::IntoIter {
self.storage.iter_mut()
}
}
pub struct ProcessedAssets<T: 'static + Send + Sync> {
storage: SecondaryMap<RawAssetHandle, T>,
pub(crate) names: HashMap<String, RawAssetHandle>,
}
impl<T: 'static + Send + Sync> ProcessedAssets<T> {
pub fn new() -> Self {
Self {
storage: SecondaryMap::new(),
names: HashMap::new(),
}
}
pub fn insert(&mut self, handle: RawAssetHandle, asset: T) -> Option<T> {
self.storage.insert(handle, asset)
}
pub fn get(&self, handle: RawAssetHandle) -> Option<&T> {
if handle.is_null() {
tracing::warn!(
"ProcessedAssets<{}>: get() called with a null/default handle — \
did you forget to insert the source asset and store the returned handle?",
std::any::type_name::<T>()
);
return None;
}
let result = self.storage.get(handle);
if result.is_none() {
tracing::debug!(
"ProcessedAssets<{}>: get() for handle {:?} returned nothing — \
the asset may still be pending upload or was removed",
std::any::type_name::<T>(),
handle
);
}
result
}
pub fn get_mut(&mut self, handle: RawAssetHandle) -> Option<&mut T> {
if handle.is_null() {
tracing::warn!(
"ProcessedAssets<{}>: get_mut() called with a null/default handle — \
did you forget to insert the source asset and store the returned handle?",
std::any::type_name::<T>()
);
return None;
}
let result = self.storage.get_mut(handle);
if result.is_none() {
tracing::debug!(
"ProcessedAssets<{}>: get_mut() for handle {:?} returned nothing — \
the asset may still be pending upload or was removed",
std::any::type_name::<T>(),
handle
);
}
result
}
pub fn remove(&mut self, handle: RawAssetHandle) -> Option<T> {
self.names.retain(|_, h| *h != handle);
self.storage.remove(handle)
}
pub fn contains(&self, handle: RawAssetHandle) -> bool {
self.storage.contains_key(handle)
}
pub fn iter(&self) -> impl Iterator<Item = (RawAssetHandle, &T)> {
self.storage.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (RawAssetHandle, &mut T)> {
self.storage.iter_mut()
}
pub fn get_by_name(&self, name: &str) -> Option<&T> {
let handle = self.names.get(name)?;
self.storage.get(*handle)
}
}
impl<'a, T: 'static + Send + Sync> IntoIterator for &'a ProcessedAssets<T> {
type Item = (RawAssetHandle, &'a T);
type IntoIter = slotmap::secondary::Iter<'a, RawAssetHandle, T>;
fn into_iter(self) -> Self::IntoIter {
self.storage.iter()
}
}
impl<'a, T: 'static + Send + Sync> IntoIterator for &'a mut ProcessedAssets<T> {
type Item = (RawAssetHandle, &'a mut T);
type IntoIter = slotmap::secondary::IterMut<'a, RawAssetHandle, T>;
fn into_iter(self) -> Self::IntoIter {
self.storage.iter_mut()
}
}