diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index d05bb498888eb..7883985e51e5e 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -36,6 +36,7 @@ async-lock = "3.0" crossbeam-channel = "0.5" downcast-rs = "1.2" disqualified = "1.0" +either = "1.13" futures-io = "0.3" futures-lite = "2.0.1" blake3 = "1.5" diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index af957b45454f4..08d08c4b1c2df 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -188,7 +188,7 @@ pub use handle::*; pub use id::*; pub use loader::*; pub use loader_builders::{ - DirectNestedLoader, NestedLoader, UntypedDirectNestedLoader, UntypedNestedLoader, + Deferred, DynamicTyped, Immediate, NestedLoader, StaticTyped, UnknownTyped, }; pub use path::*; pub use reflect::*; @@ -689,7 +689,7 @@ mod tests { for dep in ron.embedded_dependencies { let loaded = load_context .loader() - .direct() + .immediate() .load::(&dep) .await .map_err(|_| Self::Error::CannotLoadDependency { diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index aa66985631253..e1315a96e26b2 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -1,6 +1,6 @@ use crate::{ io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader}, - loader_builders::NestedLoader, + loader_builders::{Deferred, NestedLoader, StaticTyped}, meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings}, path::AssetPath, Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId, @@ -290,7 +290,11 @@ impl AssetContainer for A { } } -/// An error that occurs when attempting to call [`DirectNestedLoader::load`](crate::DirectNestedLoader::load) +/// An error that occurs when attempting to call [`NestedLoader::load`] which +/// is configured to work [immediately]. +/// +/// [`NestedLoader::load`]: crate::NestedLoader::load +/// [immediately]: crate::Immediate #[derive(Error, Debug)] #[error("Failed to load dependency {dependency:?} {error}")] pub struct LoadDirectError { @@ -550,7 +554,7 @@ impl<'a> LoadContext<'a> { /// Create a builder for loading nested assets in this context. #[must_use] - pub fn loader(&mut self) -> NestedLoader<'a, '_> { + pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> { NestedLoader::new(self) } diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 23cf722803546..681bd16cbc94d 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -5,7 +5,7 @@ use crate::{ io::Reader, meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings}, Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext, - LoadDirectError, LoadedAsset, LoadedUntypedAsset, + LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, }; use alloc::sync::Arc; use core::any::TypeId; @@ -25,28 +25,157 @@ impl ReaderRef<'_> { } } -/// A builder for loading nested assets inside a `LoadContext`. +/// A builder for loading nested assets inside a [`LoadContext`]. +/// +/// # Loader state +/// +/// The type parameters `T` and `M` determine how this will load assets: +/// - `T`: the typing of this loader. How do we know what type of asset to load? +/// +/// See [`StaticTyped`] (the default), [`DynamicTyped`], and [`UnknownTyped`]. +/// +/// - `M`: the load mode. Do we want to load this asset right now (in which case +/// you will have to `await` the operation), or do we just want a [`Handle`], +/// and leave the actual asset loading to later? +/// +/// See [`Deferred`] (the default) and [`Immediate`]. +/// +/// When configuring this builder, you can freely switch between these modes +/// via functions like [`deferred`] and [`immediate`]. +/// +/// ## Typing +/// +/// To inform the loader of what type of asset to load: +/// - in [`StaticTyped`]: statically providing a type parameter `A: Asset` to +/// [`load`]. +/// +/// This is the simplest way to get a [`Handle`] to the loaded asset, as +/// long as you know the type of `A` at compile time. +/// +/// - in [`DynamicTyped`]: providing the [`TypeId`] of the asset at runtime. +/// +/// If you know the type ID of the asset at runtime, but not at compile time, +/// use [`with_dynamic_type`] followed by [`load`] to start loading an asset +/// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]), +/// or a [`ErasedLoadedAsset`] (via [`Immediate`]). +/// +/// - in [`UnknownTyped`]: loading either a type-erased version of the asset +/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset +/// ([`LoadedUntypedAsset`]). +/// +/// If you have no idea what type of asset you will be loading (not even at +/// runtime with a [`TypeId`]), use this. +/// +/// ## Load mode +/// +/// To inform the loader how you want to load the asset: +/// - in [`Deferred`]: when you request to load the asset, you get a [`Handle`] +/// for it, but the actual loading won't be completed until later. +/// +/// Use this if you only need a [`Handle`] or [`UntypedHandle`]. +/// +/// - in [`Immediate`]: the load request will load the asset right then and +/// there, waiting until the asset is fully loaded and giving you access to +/// it. +/// +/// Note that this requires you to `await` a future, so you must be in an +/// async context to use direct loading. In an asset loader, you will be in +/// an async context. +/// +/// Use this if you need the *value* of another asset in order to load the +/// current asset. For example, if you are deriving a new asset from the +/// referenced asset, or you are building a collection of assets. This will +/// add the path of the asset as a "load dependency". +/// +/// If the current loader is used in a [`Process`] "asset preprocessor", +/// such as a [`LoadTransformAndSave`] preprocessor, changing a "load +/// dependency" will result in re-processing of the asset. +/// +/// # Load kickoff +/// +/// If the current context is a normal [`AssetServer::load`], an actual asset +/// load will be kicked off immediately, which ensures the load happens as soon +/// as possible. "Normal loads" kicked from within a normal Bevy App will +/// generally configure the context to kick off loads immediately. +/// +/// If the current context is configured to not load dependencies automatically +/// (ex: [`AssetProcessor`]), a load will not be kicked off automatically. It is +/// then the calling context's responsibility to begin a load if necessary. /// /// # Lifetimes +/// /// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference /// - `builder`: the lifetime of the temporary builder structs -pub struct NestedLoader<'ctx, 'builder> { +/// +/// [`deferred`]: Self::deferred +/// [`immediate`]: Self::immediate +/// [`load`]: Self::load +/// [`with_dynamic_type`]: Self::with_dynamic_type +/// [`AssetServer::load`]: crate::AssetServer::load +/// [`AssetProcessor`]: crate::processor::AssetProcessor +/// [`Process`]: crate::processor::Process +/// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave +pub struct NestedLoader<'ctx, 'builder, T, M> { load_context: &'builder mut LoadContext<'ctx>, meta_transform: Option, - asset_type_id: Option, + typing: T, + mode: M, +} + +mod sealed { + pub trait Typing {} + + pub trait Mode {} } -impl<'ctx, 'builder> NestedLoader<'ctx, 'builder> { - pub(crate) fn new( - load_context: &'builder mut LoadContext<'ctx>, - ) -> NestedLoader<'ctx, 'builder> { +/// [`NestedLoader`] will be provided the type of asset as a type parameter on +/// [`load`]. +/// +/// [`load`]: NestedLoader::load +pub struct StaticTyped(()); + +impl sealed::Typing for StaticTyped {} + +/// [`NestedLoader`] has been configured with info on what type of asset to load +/// at runtime. +pub struct DynamicTyped { + asset_type_id: TypeId, +} + +impl sealed::Typing for DynamicTyped {} + +/// [`NestedLoader`] does not know what type of asset it will be loading. +pub struct UnknownTyped(()); + +impl sealed::Typing for UnknownTyped {} + +/// [`NestedLoader`] will create and return asset handles immediately, but only +/// actually load the asset later. +pub struct Deferred(()); + +impl sealed::Mode for Deferred {} + +/// [`NestedLoader`] will immediately load an asset when requested. +pub struct Immediate<'builder, 'reader> { + reader: Option<&'builder mut (dyn Reader + 'reader)>, +} + +impl sealed::Mode for Immediate<'_, '_> {} + +// common to all states + +impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> { + pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self { NestedLoader { load_context, meta_transform: None, - asset_type_id: None, + typing: StaticTyped(()), + mode: Deferred(()), } } +} +impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> { fn with_transform( mut self, transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static, @@ -74,47 +203,105 @@ impl<'ctx, 'builder> NestedLoader<'ctx, 'builder> { self.with_transform(move |meta| meta_transform_settings(meta, &settings)) } - /// Specify the output asset type. + // convert between `T`s + + /// When [`load`]ing, you must pass in the asset type as a type parameter + /// statically. + /// + /// If you don't know the type statically (at compile time), consider + /// [`with_dynamic_type`] or [`with_unknown_type`]. + /// + /// [`load`]: Self::load + /// [`with_dynamic_type`]: Self::with_dynamic_type + /// [`with_unknown_type`]: Self::with_unknown_type #[must_use] - pub fn with_asset_type(mut self) -> Self { - self.asset_type_id = Some(TypeId::of::()); - self + pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> { + NestedLoader { + load_context: self.load_context, + meta_transform: self.meta_transform, + typing: StaticTyped(()), + mode: self.mode, + } } - /// Specify the output asset type. + /// When [`load`]ing, the loader will attempt to load an asset with the + /// given [`TypeId`]. + /// + /// [`load`]: Self::load #[must_use] - pub fn with_asset_type_id(mut self, asset_type_id: TypeId) -> Self { - self.asset_type_id = Some(asset_type_id); - self + pub fn with_dynamic_type( + self, + asset_type_id: TypeId, + ) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> { + NestedLoader { + load_context: self.load_context, + meta_transform: self.meta_transform, + typing: DynamicTyped { asset_type_id }, + mode: self.mode, + } } - /// Load assets directly, rather than creating handles. + /// When [`load`]ing, we will infer what type of asset to load from + /// metadata. + /// + /// [`load`]: Self::load #[must_use] - pub fn direct<'c>(self) -> DirectNestedLoader<'ctx, 'builder, 'c> { - DirectNestedLoader { - base: self, - reader: None, + pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> { + NestedLoader { + load_context: self.load_context, + meta_transform: self.meta_transform, + typing: UnknownTyped(()), + mode: self.mode, } } - /// Load assets without static type information. + // convert between `M`s + + /// When [`load`]ing, create only asset handles, rather than returning the + /// actual asset. /// - /// If you need to specify the type of asset, but cannot do it statically, - /// use `.with_asset_type_id()`. + /// [`load`]: Self::load + pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> { + NestedLoader { + load_context: self.load_context, + meta_transform: self.meta_transform, + typing: self.typing, + mode: Deferred(()), + } + } + + /// The [`load`] call itself will load an asset, rather than scheduling the + /// loading to happen later. + /// + /// This gives you access to the loaded asset, but requires you to be in an + /// async context, and be able to `await` the resulting future. + /// + /// [`load`]: Self::load #[must_use] - pub fn untyped(self) -> UntypedNestedLoader<'ctx, 'builder> { - UntypedNestedLoader { base: self } + pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> { + NestedLoader { + load_context: self.load_context, + meta_transform: self.meta_transform, + typing: self.typing, + mode: Immediate { reader: None }, + } } +} - /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset. - /// If the current context is a normal [`AssetServer::load`](crate::AssetServer::load), an actual asset - /// load will be kicked off immediately, which ensures the load happens as soon as possible. - /// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off - /// loads immediately. - /// If the current context is configured to not load dependencies automatically - /// (ex: [`AssetProcessor`](crate::processor::AssetProcessor)), - /// a load will not be kicked off automatically. It is then the calling context's responsibility to begin - /// a load if necessary. +// deferred loading logic + +impl NestedLoader<'_, '_, StaticTyped, Deferred> { + /// Retrieves a handle for the asset at the given path and adds that path as + /// a dependency of this asset. + /// + /// This requires you to know the type of asset statically. + /// - If you have runtime info for what type of asset you're loading (e.g. a + /// [`TypeId`]), use [`with_dynamic_type`]. + /// - If you do not know at all what type of asset you're loading, use + /// [`with_unknown_type`]. + /// + /// [`with_dynamic_type`]: Self::with_dynamic_type + /// [`with_unknown_type`]: Self::with_unknown_type pub fn load<'c, A: Asset>(self, path: impl Into>) -> Handle { let path = path.into().to_owned(); let handle = if self.load_context.should_load_dependencies { @@ -131,74 +318,78 @@ impl<'ctx, 'builder> NestedLoader<'ctx, 'builder> { } } -/// A builder for loading untyped nested assets inside a [`LoadContext`]. -/// -/// # Lifetimes -/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference -/// - `builder`: the lifetime of the temporary builder structs -pub struct UntypedNestedLoader<'ctx, 'builder> { - base: NestedLoader<'ctx, 'builder>, +impl NestedLoader<'_, '_, DynamicTyped, Deferred> { + /// Retrieves a handle for the asset at the given path and adds that path as + /// a dependency of this asset. + /// + /// This requires you to pass in the asset type ID into + /// [`with_dynamic_type`]. + /// + /// [`with_dynamic_type`]: Self::with_dynamic_type + pub fn load<'p>(self, path: impl Into>) -> UntypedHandle { + let path = path.into().to_owned(); + let handle = if self.load_context.should_load_dependencies { + self.load_context + .asset_server + .load_erased_with_meta_transform( + path, + self.typing.asset_type_id, + self.meta_transform, + (), + ) + } else { + self.load_context + .asset_server + .get_or_create_path_handle_erased( + path, + self.typing.asset_type_id, + self.meta_transform, + ) + }; + self.load_context.dependencies.insert(handle.id()); + handle + } } -impl<'ctx, 'builder> UntypedNestedLoader<'ctx, 'builder> { - /// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset without knowing its type. +impl NestedLoader<'_, '_, UnknownTyped, Deferred> { + /// Retrieves a handle for the asset at the given path and adds that path as + /// a dependency of this asset. + /// + /// This will infer the asset type from metadata. pub fn load<'p>(self, path: impl Into>) -> Handle { let path = path.into().to_owned(); - let handle = if self.base.load_context.should_load_dependencies { - self.base - .load_context + let handle = if self.load_context.should_load_dependencies { + self.load_context .asset_server - .load_untyped_with_meta_transform(path, self.base.meta_transform) + .load_unknown_type_with_meta_transform(path, self.meta_transform) } else { - self.base - .load_context + self.load_context .asset_server - .get_or_create_path_handle(path, self.base.meta_transform) + .get_or_create_path_handle(path, self.meta_transform) }; - self.base - .load_context - .dependencies - .insert(handle.id().untyped()); + self.load_context.dependencies.insert(handle.id().untyped()); handle } } -/// A builder for directly loading nested assets inside a `LoadContext`. -/// -/// # Lifetimes -/// - `ctx`: the lifetime of the associated [`AssetServer`][crate::AssetServer] reference -/// - `builder`: the lifetime of the temporary builder structs -/// - `reader`: the lifetime of the [`Reader`] reference used to read the asset data -pub struct DirectNestedLoader<'ctx, 'builder, 'reader> { - base: NestedLoader<'ctx, 'builder>, - reader: Option<&'builder mut (dyn Reader + 'reader)>, -} +// immediate loading logic -impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reader> { +impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> { /// Specify the reader to use to read the asset data. #[must_use] pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self { - self.reader = Some(reader); + self.mode.reader = Some(reader); self } - /// Load the asset without providing static type information. - /// - /// If you need to specify the type of asset, but cannot do it statically, - /// use `.with_asset_type_id()`. - #[must_use] - pub fn untyped(self) -> UntypedDirectNestedLoader<'ctx, 'builder, 'reader> { - UntypedDirectNestedLoader { base: self } - } - async fn load_internal( self, path: &AssetPath<'static>, + asset_type_id: Option, ) -> Result<(Arc, ErasedLoadedAsset), LoadDirectError> { - let (mut meta, loader, mut reader) = if let Some(reader) = self.reader { - let loader = if let Some(asset_type_id) = self.base.asset_type_id { - self.base - .load_context + let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader { + let loader = if let Some(asset_type_id) = asset_type_id { + self.load_context .asset_server .get_asset_loader_with_asset_type_id(asset_type_id) .await @@ -207,8 +398,7 @@ impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reade error: error.into(), })? } else { - self.base - .load_context + self.load_context .asset_server .get_path_asset_loader(path) .await @@ -221,10 +411,9 @@ impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reade (meta, loader, ReaderRef::Borrowed(reader)) } else { let (meta, loader, reader) = self - .base .load_context .asset_server - .get_meta_loader_and_reader(path, self.base.asset_type_id) + .get_meta_loader_and_reader(path, asset_type_id) .await .map_err(|error| LoadDirectError { dependency: path.clone(), @@ -233,35 +422,35 @@ impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reade (meta, loader, ReaderRef::Boxed(reader)) }; - if let Some(meta_transform) = self.base.meta_transform { + if let Some(meta_transform) = self.meta_transform { meta_transform(&mut *meta); } let asset = self - .base .load_context .load_direct_internal(path.clone(), meta, &*loader, reader.as_mut()) .await?; Ok((loader, asset)) } +} - /// Loads the asset at the given `path` directly. This is an async function that will wait until the asset is fully loaded before - /// returning. Use this if you need the _value_ of another asset in order to load the current asset. For example, if you are - /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a - /// "load dependency". +impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> { + /// Attempts to load the asset at the given `path` immediately. /// - /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor, - /// changing a "load dependency" will result in re-processing of the asset. + /// This requires you to know the type of asset statically. + /// - If you have runtime info for what type of asset you're loading (e.g. a + /// [`TypeId`]), use [`with_dynamic_type`]. + /// - If you do not know at all what type of asset you're loading, use + /// [`with_unknown_type`]. /// - /// [`Process`]: crate::processor::Process - /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave + /// [`with_dynamic_type`]: Self::with_dynamic_type + /// [`with_unknown_type`]: Self::with_unknown_type pub async fn load<'p, A: Asset>( - mut self, + self, path: impl Into>, ) -> Result, LoadDirectError> { - self.base.asset_type_id = Some(TypeId::of::()); let path = path.into().into_owned(); - self.load_internal(&path) + self.load_internal(&path, Some(TypeId::of::())) .await .and_then(move |(loader, untyped_asset)| { untyped_asset.downcast::().map_err(|_| LoadDirectError { @@ -277,32 +466,36 @@ impl<'ctx: 'reader, 'builder, 'reader> DirectNestedLoader<'ctx, 'builder, 'reade } } -/// A builder for directly loading untyped nested assets inside a `LoadContext`. -/// -/// # Lifetimes -/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference -/// - `builder`: the lifetime of the temporary builder structs -/// - `reader`: the lifetime of the [`Reader`] reference used to read the asset data -pub struct UntypedDirectNestedLoader<'ctx, 'builder, 'reader> { - base: DirectNestedLoader<'ctx, 'builder, 'reader>, +impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> { + /// Attempts to load the asset at the given `path` immediately. + /// + /// This requires you to pass in the asset type ID into + /// [`with_dynamic_type`]. + /// + /// [`with_dynamic_type`]: Self::with_dynamic_type + pub async fn load<'p>( + self, + path: impl Into>, + ) -> Result { + let path = path.into().into_owned(); + let asset_type_id = Some(self.typing.asset_type_id); + self.load_internal(&path, asset_type_id) + .await + .map(|(_, asset)| asset) + } } -impl<'ctx: 'reader, 'builder, 'reader> UntypedDirectNestedLoader<'ctx, 'builder, 'reader> { - /// Loads the asset at the given `path` directly. This is an async function that will wait until the asset is fully loaded before - /// returning. Use this if you need the _value_ of another asset in order to load the current asset. For example, if you are - /// deriving a new asset from the referenced asset, or you are building a collection of assets. This will add the `path` as a - /// "load dependency". - /// - /// If the current loader is used in a [`Process`] "asset preprocessor", such as a [`LoadTransformAndSave`] preprocessor, - /// changing a "load dependency" will result in re-processing of the asset. +impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> { + /// Attempts to load the asset at the given `path` immediately. /// - /// [`Process`]: crate::processor::Process - /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave + /// This will infer the asset type from metadata. pub async fn load<'p>( self, path: impl Into>, ) -> Result { let path = path.into().into_owned(); - self.base.load_internal(&path).await.map(|(_, asset)| asset) + self.load_internal(&path, None) + .await + .map(|(_, asset)| asset) } } diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index fdeaaff089604..85b3d02fa584a 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -10,6 +10,7 @@ use bevy_tasks::Task; use bevy_utils::{tracing::warn, Entry, HashMap, HashSet, TypeIdMap}; use core::any::TypeId; use crossbeam_channel::Sender; +use either::Either; use thiserror::Error; #[derive(Debug)] @@ -103,7 +104,7 @@ impl AssetInfos { None, true, ), - type_name, + Either::Left(type_name), ) .unwrap() } @@ -162,15 +163,15 @@ impl AssetInfos { ); // it is ok to unwrap because TypeId was specified above let (handle, should_load) = - unwrap_with_context(result, core::any::type_name::()).unwrap(); + unwrap_with_context(result, Either::Left(core::any::type_name::())).unwrap(); (handle.typed_unchecked(), should_load) } - pub(crate) fn get_or_create_path_handle_untyped( + pub(crate) fn get_or_create_path_handle_erased( &mut self, path: AssetPath<'static>, type_id: TypeId, - type_name: &'static str, + type_name: Option<&str>, loading_mode: HandleLoadingMode, meta_transform: Option, ) -> (UntypedHandle, bool) { @@ -180,8 +181,12 @@ impl AssetInfos { loading_mode, meta_transform, ); - // it is ok to unwrap because TypeId was specified above - unwrap_with_context(result, type_name).unwrap() + let type_info = match type_name { + Some(type_name) => Either::Left(type_name), + None => Either::Right(type_id), + }; + unwrap_with_context(result, type_info) + .expect("type should be correct since the `TypeId` is specified above") } /// Retrieves asset tracking data, or creates it if it doesn't exist. @@ -765,14 +770,20 @@ pub(crate) enum GetOrCreateHandleInternalError { pub(crate) fn unwrap_with_context( result: Result, - type_name: &'static str, + type_info: Either<&str, TypeId>, ) -> Option { match result { Ok(value) => Some(value), Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None, - Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => { - panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \ - Make sure you have called app.init_asset::<{type_name}>()") - } + Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => match type_info { + Either::Left(type_name) => { + panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \ + Make sure you have called `app.init_asset::<{type_name}>()`"); + } + Either::Right(type_id) => { + panic!("Cannot allocate an AssetHandle of type '{type_id:?}' because the asset type has not been initialized. \ + Make sure you have called `app.init_asset::<(actual asset type)>()`") + } + }, } } diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 9d57808ed0bac..316642a45a260 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -31,6 +31,7 @@ use core::{ panic::AssertUnwindSafe, }; use crossbeam_channel::{Receiver, Sender}; +use either::Either; use futures_lite::{FutureExt, StreamExt}; use info::*; use loaders::*; @@ -382,25 +383,62 @@ impl AssetServer { ); if should_load { - let owned_handle = Some(handle.clone().untyped()); - let server = self.clone(); - let task = IoTaskPool::get().spawn(async move { - if let Err(err) = server.load_internal(owned_handle, path, false, None).await { - error!("{}", err); - } - drop(guard); - }); + self.spawn_load_task(handle.clone().untyped(), path, &mut infos, guard); + } - #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))] - infos.pending_tasks.insert(handle.id().untyped(), task); + handle + } - #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] - task.detach(); + pub(crate) fn load_erased_with_meta_transform<'a, G: Send + Sync + 'static>( + &self, + path: impl Into>, + type_id: TypeId, + meta_transform: Option, + guard: G, + ) -> UntypedHandle { + let path = path.into().into_owned(); + let mut infos = self.data.infos.write(); + let (handle, should_load) = infos.get_or_create_path_handle_erased( + path.clone(), + type_id, + None, + HandleLoadingMode::Request, + meta_transform, + ); + + if should_load { + self.spawn_load_task(handle.clone(), path, &mut infos, guard); } handle } + pub(crate) fn spawn_load_task( + &self, + handle: UntypedHandle, + path: AssetPath<'static>, + infos: &mut AssetInfos, + guard: G, + ) { + let owned_handle = handle.clone(); + let server = self.clone(); + let task = IoTaskPool::get().spawn(async move { + if let Err(err) = server + .load_internal(Some(owned_handle), path, false, None) + .await + { + error!("{}", err); + } + drop(guard); + }); + + #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))] + infos.pending_tasks.insert(handle.id(), task); + + #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] + task.detach(); + } + /// Asynchronously load an asset that you do not know the type of statically. If you _do_ know the type of the asset, /// you should use [`AssetServer::load`]. If you don't know the type of the asset, but you can't use an async method, /// consider using [`AssetServer::load_untyped`]. @@ -413,7 +451,7 @@ impl AssetServer { self.load_internal(None, path, false, None).await } - pub(crate) fn load_untyped_with_meta_transform<'a>( + pub(crate) fn load_unknown_type_with_meta_transform<'a>( &self, path: impl Into>, meta_transform: Option, @@ -492,7 +530,7 @@ impl AssetServer { /// required to figure out the asset type before a handle can be created. #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { - self.load_untyped_with_meta_transform(path, None) + self.load_unknown_type_with_meta_transform(path, None) } /// Performs an async asset load. @@ -558,7 +596,7 @@ impl AssetServer { HandleLoadingMode::Request, meta_transform, ); - unwrap_with_context(result, loader.asset_type_name()) + unwrap_with_context(result, Either::Left(loader.asset_type_name())) } }; @@ -588,10 +626,10 @@ impl AssetServer { let (base_handle, base_path) = if path.label().is_some() { let mut infos = self.data.infos.write(); let base_path = path.without_label().into_owned(); - let (base_handle, _) = infos.get_or_create_path_handle_untyped( + let (base_handle, _) = infos.get_or_create_path_handle_erased( base_path.clone(), loader.asset_type_id(), - loader.asset_type_name(), + Some(loader.asset_type_name()), HandleLoadingMode::Force, None, ); @@ -707,10 +745,10 @@ impl AssetServer { ) -> UntypedHandle { let loaded_asset = asset.into(); let handle = if let Some(path) = path { - let (handle, _) = self.data.infos.write().get_or_create_path_handle_untyped( + let (handle, _) = self.data.infos.write().get_or_create_path_handle_erased( path, loaded_asset.asset_type_id(), - loaded_asset.asset_type_name(), + Some(loaded_asset.asset_type_name()), HandleLoadingMode::NotLoading, None, ); @@ -1131,6 +1169,28 @@ impl AssetServer { .0 } + /// Retrieve a handle for the given path, where the asset type ID and name + /// are not known statically. + /// + /// This will create a handle (and [`AssetInfo`]) if it does not exist. + pub(crate) fn get_or_create_path_handle_erased<'a>( + &self, + path: impl Into>, + type_id: TypeId, + meta_transform: Option, + ) -> UntypedHandle { + let mut infos = self.data.infos.write(); + infos + .get_or_create_path_handle_erased( + path.into().into_owned(), + type_id, + None, + HandleLoadingMode::NotLoading, + meta_transform, + ) + .0 + } + pub(crate) async fn get_meta_loader_and_reader<'a>( &'a self, asset_path: &'a AssetPath<'_>, diff --git a/examples/asset/asset_decompression.rs b/examples/asset/asset_decompression.rs index e961bc976cdaa..924943349c53b 100644 --- a/examples/asset/asset_decompression.rs +++ b/examples/asset/asset_decompression.rs @@ -73,9 +73,9 @@ impl AssetLoader for GzAssetLoader { let uncompressed = load_context .loader() - .direct() + .with_unknown_type() + .immediate() .with_reader(&mut reader) - .untyped() .load(contained_path) .await?; diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 1367f26f2440f..e7eefd3aafba0 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -148,7 +148,7 @@ impl AssetLoader for CoolTextLoader { for embedded in ron.embedded_dependencies { let loaded = load_context .loader() - .direct() + .immediate() .load::(&embedded) .await?; base_text.push_str(&loaded.get().0); @@ -159,7 +159,7 @@ impl AssetLoader for CoolTextLoader { .with_settings(move |settings| { *settings = settings_override.clone(); }) - .direct() + .immediate() .load::(&path) .await?; base_text.push_str(&loaded.get().0);