use crate::database::{
AssetDatabase, AssetDatabaseCommandsSender, AssetReferenceCounter, handle::AssetHandle,
path::AssetPathStatic,
};
use anput::{component::Component, entity::Entity, query::TypedLookupFetch};
use serde::{Deserialize, Serialize};
use std::{
error::Error,
ops::{Deref, DerefMut},
sync::RwLock,
};
#[derive(Debug, Serialize, Deserialize)]
#[serde(from = "AssetPathStatic", into = "AssetPathStatic")]
pub struct AssetRef {
path: AssetPathStatic,
#[serde(skip)]
handle: RwLock<Option<AssetHandle>>,
}
impl Default for AssetRef {
fn default() -> Self {
Self::new("")
}
}
impl AssetRef {
pub fn new(path: impl Into<AssetPathStatic>) -> Self {
Self {
path: path.into(),
handle: RwLock::new(None),
}
}
pub fn new_resolved(path: impl Into<AssetPathStatic>, handle: AssetHandle) -> Self {
Self {
path: path.into(),
handle: RwLock::new(Some(handle)),
}
}
pub fn invalidate(&self) -> Result<(), Box<dyn Error>> {
*self.handle.write().map_err(|error| format!("{error}"))? = None;
Ok(())
}
pub fn path(&self) -> &AssetPathStatic {
&self.path
}
pub fn handle(&self) -> Result<AssetHandle, Box<dyn Error>> {
self.handle
.read()
.map_err(|error| format!("{error}"))?
.ok_or_else(|| format!("Asset with `{}` path is not yet resolved!", self.path).into())
}
pub fn resolve<'a>(
&'a self,
database: &'a AssetDatabase,
) -> Result<AssetResolved<'a>, Box<dyn Error>> {
let mut handle = self.handle.write().map_err(|error| format!("{error}"))?;
if let Some(result) = handle.as_ref() {
Ok(AssetResolved::new(*result, database))
} else {
let result = database
.find(self.path.clone())
.ok_or_else(|| format!("Asset with `{}` path not found in database!", self.path))?;
*handle = Some(result);
Ok(AssetResolved::new(result, database))
}
}
pub fn ensure<'a>(
&'a self,
database: &'a mut AssetDatabase,
) -> Result<AssetResolved<'a>, Box<dyn Error>> {
let mut handle = self.handle.write().map_err(|error| format!("{error}"))?;
if let Some(result) = handle.as_ref() {
Ok(AssetResolved::new(*result, database))
} else {
let result = database.ensure(self.path.clone())?;
*handle = Some(result);
Ok(AssetResolved::new(result, database))
}
}
}
impl Clone for AssetRef {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
handle: RwLock::new(
self.handle
.try_read()
.ok()
.map(|handle| *handle)
.unwrap_or_default(),
),
}
}
}
impl PartialEq for AssetRef {
fn eq(&self, other: &Self) -> bool {
self.path.eq(&other.path)
}
}
impl Eq for AssetRef {}
impl From<AssetPathStatic> for AssetRef {
fn from(path: AssetPathStatic) -> Self {
Self::new(path)
}
}
impl From<AssetRef> for AssetPathStatic {
fn from(value: AssetRef) -> Self {
value.path
}
}
pub struct AssetResolved<'a> {
handle: AssetHandle,
database: &'a AssetDatabase,
}
impl<'a> AssetResolved<'a> {
pub fn new(handle: AssetHandle, database: &'a AssetDatabase) -> Self {
Self { handle, database }
}
pub fn entity(&self) -> Entity {
self.handle.entity()
}
pub fn does_exists(&self) -> bool {
self.handle.does_exists(self.database)
}
pub fn has<T: Component>(&self) -> bool {
self.handle.has::<T>(self.database)
}
pub fn is_ready_to_use(&self) -> bool {
self.handle.is_ready_to_use(self.database)
}
pub async fn wait_for_ready_to_use(&self) {
self.handle.wait_for_ready_to_use(self.database).await
}
pub fn access_checked<'b, Fetch: TypedLookupFetch<'b, true>>(&'b self) -> Option<Fetch::Value> {
self.handle.access_checked::<Fetch>(self.database)
}
pub fn access<'b, Fetch: TypedLookupFetch<'b, true>>(&'b self) -> Fetch::Value {
self.handle.access::<Fetch>(self.database)
}
pub fn dependencies(&self) -> impl Iterator<Item = AssetRef> + '_ {
self.handle
.dependencies(self.database)
.filter_map(|handle| {
Some(AssetRef::new_resolved(
handle
.access_checked::<&AssetPathStatic>(self.database)?
.clone(),
handle,
))
})
}
pub fn dependent(&self) -> impl Iterator<Item = AssetRef> + '_ {
self.handle.dependent(self.database).filter_map(|handle| {
Some(AssetRef::new_resolved(
handle
.access_checked::<&AssetPathStatic>(self.database)?
.clone(),
handle,
))
})
}
pub fn traverse_dependencies(&self) -> impl Iterator<Item = AssetRef> + '_ {
self.handle
.traverse_dependencies(self.database)
.filter_map(|handle| {
Some(AssetRef::new_resolved(
handle
.access_checked::<&AssetPathStatic>(self.database)?
.clone(),
handle,
))
})
}
}
pub struct SmartAssetRef {
inner: AssetRef,
sender: AssetDatabaseCommandsSender,
}
impl Drop for SmartAssetRef {
fn drop(&mut self) {
if let Ok(handle) = self.inner.handle() {
self.sender.send(Box::new(move |storage| {
if let Ok(mut counter) =
storage.component_mut::<true, AssetReferenceCounter>(handle.entity())
{
counter.decrement();
storage.update::<AssetReferenceCounter>(handle.entity());
}
}));
}
}
}
impl SmartAssetRef {
pub fn new(
path: impl Into<AssetPathStatic>,
database: &mut AssetDatabase,
) -> Result<Self, Box<dyn Error>> {
Self::from_ref(AssetRef::new(path), database)
}
pub fn from_ref(inner: AssetRef, database: &mut AssetDatabase) -> Result<Self, Box<dyn Error>> {
let sender = database.commands_sender();
let handle = inner.ensure(database)?.handle;
handle
.ensure::<AssetReferenceCounter>(database)?
.increment();
database
.storage
.update::<AssetReferenceCounter>(handle.entity());
Ok(Self { inner, sender })
}
pub fn into_ref(self) -> AssetRef {
self.inner.clone()
}
}
impl Clone for SmartAssetRef {
fn clone(&self) -> Self {
if let Ok(handle) = self.inner.handle() {
self.sender.send(Box::new(move |storage| {
if let Ok(mut counter) =
storage.component_mut::<true, AssetReferenceCounter>(handle.entity())
{
counter.increment();
storage.update::<AssetReferenceCounter>(handle.entity());
}
}));
}
Self {
inner: self.inner.clone(),
sender: self.sender.clone(),
}
}
}
impl Deref for SmartAssetRef {
type Target = AssetRef;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for SmartAssetRef {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}