use crate::{
io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
loader_builders::{Deferred, NestedLoader, StaticTyped},
meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings},
path::AssetPath,
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
UntypedHandle,
};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use atomicow::CowArc;
use bevy_ecs::world::World;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId};
use downcast_rs::{impl_downcast, Downcast};
use ron::error::SpannedError;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
pub trait AssetLoader: Send + Sync + 'static {
type Asset: Asset;
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
fn load(
&self,
reader: &mut dyn Reader,
settings: &Self::Settings,
load_context: &mut LoadContext,
) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
fn extensions(&self) -> &[&str] {
&[]
}
}
pub trait ErasedAssetLoader: Send + Sync + 'static {
fn load<'a>(
&'a self,
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
>;
fn extensions(&self) -> &[&str];
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
fn type_name(&self) -> &'static str;
fn type_id(&self) -> TypeId;
fn asset_type_name(&self) -> &'static str;
fn asset_type_id(&self) -> TypeId;
}
impl<L> ErasedAssetLoader for L
where
L: AssetLoader + Send + Sync,
{
fn load<'a>(
&'a self,
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
mut load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
> {
Box::pin(async move {
let settings = meta
.loader_settings()
.expect("Loader settings should exist")
.downcast_ref::<L::Settings>()
.expect("AssetLoader settings should match the loader type");
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
.await
.map_err(Into::into)?;
Ok(load_context.finish(asset).into())
})
}
fn extensions(&self) -> &[&str] {
<L as AssetLoader>::extensions(self)
}
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
let meta = AssetMeta::<L, ()>::deserialize(meta)?;
Ok(Box::new(meta))
}
fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
loader: self.type_name().to_string(),
settings: L::Settings::default(),
}))
}
fn type_name(&self) -> &'static str {
core::any::type_name::<L>()
}
fn type_id(&self) -> TypeId {
TypeId::of::<L>()
}
fn asset_type_name(&self) -> &'static str {
core::any::type_name::<L::Asset>()
}
fn asset_type_id(&self) -> TypeId {
TypeId::of::<L::Asset>()
}
}
pub(crate) struct LabeledAsset {
pub(crate) asset: ErasedLoadedAsset,
pub(crate) handle: UntypedHandle,
}
pub struct LoadedAsset<A: Asset> {
pub(crate) value: A,
pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> LoadedAsset<A> {
pub fn new_with_dependencies(value: A) -> Self {
let mut dependencies = <HashSet<_>>::default();
value.visit_dependencies(&mut |id| {
dependencies.insert(id);
});
LoadedAsset {
value,
dependencies,
loader_dependencies: HashMap::default(),
labeled_assets: HashMap::default(),
}
}
pub fn take(self) -> A {
self.value
}
pub fn get(&self) -> &A {
&self.value
}
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
impl<A: Asset> From<A> for LoadedAsset<A> {
fn from(asset: A) -> Self {
LoadedAsset::new_with_dependencies(asset)
}
}
pub struct ErasedLoadedAsset {
pub(crate) value: Box<dyn AssetContainer>,
pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
fn from(asset: LoadedAsset<A>) -> Self {
ErasedLoadedAsset {
value: Box::new(asset.value),
dependencies: asset.dependencies,
loader_dependencies: asset.loader_dependencies,
labeled_assets: asset.labeled_assets,
}
}
}
impl ErasedLoadedAsset {
pub fn take<A: Asset>(self) -> Option<A> {
self.value.downcast::<A>().map(|a| *a).ok()
}
pub fn get<A: Asset>(&self) -> Option<&A> {
self.value.downcast_ref::<A>()
}
pub fn asset_type_id(&self) -> TypeId {
(*self.value).type_id()
}
pub fn asset_type_name(&self) -> &'static str {
self.value.asset_type_name()
}
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
match self.value.downcast::<A>() {
Ok(value) => Ok(LoadedAsset {
value: *value,
dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies,
labeled_assets: self.labeled_assets,
}),
Err(value) => {
self.value = value;
Err(self)
}
}
}
}
pub trait AssetContainer: Downcast + Any + Send + Sync + 'static {
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World);
fn asset_type_name(&self) -> &'static str;
}
impl_downcast!(AssetContainer);
impl<A: Asset> AssetContainer for A {
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
world.resource_mut::<Assets<A>>().insert(id.typed(), *self);
}
fn asset_type_name(&self) -> &'static str {
core::any::type_name::<A>()
}
}
#[derive(Error, Debug)]
pub enum LoadDirectError {
#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
RequestedSubasset(AssetPath<'static>),
#[error("Failed to load dependency {dependency:?} {error}")]
LoadError {
dependency: AssetPath<'static>,
error: AssetLoadError,
},
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum DeserializeMetaError {
#[error("Failed to deserialize asset meta: {0:?}")]
DeserializeSettings(#[from] SpannedError),
#[error("Failed to deserialize minimal asset meta: {0:?}")]
DeserializeMinimal(SpannedError),
}
pub struct LoadContext<'a> {
pub(crate) asset_server: &'a AssetServer,
pub(crate) should_load_dependencies: bool,
populate_hashes: bool,
asset_path: AssetPath<'static>,
pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<'a> LoadContext<'a> {
pub(crate) fn new(
asset_server: &'a AssetServer,
asset_path: AssetPath<'static>,
should_load_dependencies: bool,
populate_hashes: bool,
) -> Self {
Self {
asset_server,
asset_path,
populate_hashes,
should_load_dependencies,
dependencies: HashSet::default(),
loader_dependencies: HashMap::default(),
labeled_assets: HashMap::default(),
}
}
pub fn begin_labeled_asset(&self) -> LoadContext {
LoadContext::new(
self.asset_server,
self.asset_path.clone(),
self.should_load_dependencies,
self.populate_hashes,
)
}
pub fn labeled_asset_scope<A: Asset>(
&mut self,
label: String,
load: impl FnOnce(&mut LoadContext) -> A,
) -> Handle<A> {
let mut context = self.begin_labeled_asset();
let asset = load(&mut context);
let loaded_asset = context.finish(asset);
self.add_loaded_labeled_asset(label, loaded_asset)
}
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
self.labeled_asset_scope(label, |_| asset)
}
pub fn add_loaded_labeled_asset<A: Asset>(
&mut self,
label: impl Into<CowArc<'static, str>>,
loaded_asset: LoadedAsset<A>,
) -> Handle<A> {
let label = label.into();
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
let labeled_path = self.asset_path.clone().with_label(label.clone());
let handle = self
.asset_server
.get_or_create_path_handle(labeled_path, None);
self.labeled_assets.insert(
label,
LabeledAsset {
asset: loaded_asset,
handle: handle.clone().untyped(),
},
);
handle
}
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
let path = self.asset_path.clone().with_label(label.into());
!self.asset_server.get_handles_untyped(&path).is_empty()
}
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
LoadedAsset {
value,
dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies,
labeled_assets: self.labeled_assets,
}
}
pub fn path(&self) -> &Path {
self.asset_path.path()
}
pub fn asset_path(&self) -> &AssetPath<'static> {
&self.asset_path
}
pub async fn read_asset_bytes<'b, 'c>(
&'b mut self,
path: impl Into<AssetPath<'c>>,
) -> Result<Vec<u8>, ReadAssetBytesError> {
let path = path.into();
let source = self.asset_server.get_source(path.source())?;
let asset_reader = match self.asset_server.mode() {
AssetServerMode::Unprocessed => source.reader(),
AssetServerMode::Processed => source.processed_reader()?,
};
let mut reader = asset_reader.read(path.path()).await?;
let hash = if self.populate_hashes {
let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
.map_err(DeserializeMetaError::DeserializeMinimal)?;
let processed_info = minimal
.processed_info
.ok_or(ReadAssetBytesError::MissingAssetHash)?;
processed_info.full_hash
} else {
Default::default()
};
let mut bytes = Vec::new();
reader
.read_to_end(&mut bytes)
.await
.map_err(|source| ReadAssetBytesError::Io {
path: path.path().to_path_buf(),
source,
})?;
self.loader_dependencies.insert(path.clone_owned(), hash);
Ok(bytes)
}
pub fn get_label_handle<'b, A: Asset>(
&mut self,
label: impl Into<CowArc<'b, str>>,
) -> Handle<A> {
let path = self.asset_path.clone().with_label(label);
let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
self.dependencies.insert(handle.id().untyped());
handle
}
pub(crate) async fn load_direct_internal(
&mut self,
path: AssetPath<'static>,
meta: &dyn AssetMetaDyn,
loader: &dyn ErasedAssetLoader,
reader: &mut dyn Reader,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
let loaded_asset = self
.asset_server
.load_with_meta_loader_and_reader(
&path,
meta,
loader,
reader,
false,
self.populate_hashes,
)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error,
})?;
let info = meta.processed_info().as_ref();
let hash = info.map(|i| i.full_hash).unwrap_or_default();
self.loader_dependencies.insert(path, hash);
Ok(loaded_asset)
}
#[must_use]
pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
NestedLoader::new(self)
}
pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
self.loader().load(path)
}
}
#[derive(Error, Debug)]
pub enum ReadAssetBytesError {
#[error(transparent)]
DeserializeMetaError(#[from] DeserializeMetaError),
#[error(transparent)]
AssetReaderError(#[from] AssetReaderError),
#[error(transparent)]
MissingAssetSourceError(#[from] MissingAssetSourceError),
#[error(transparent)]
MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
#[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
Io {
path: PathBuf,
source: std::io::Error,
},
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
MissingAssetHash,
}