Path: blob/main/crates/bevy_asset/src/loader_builders.rs
9367 views
//! Implementations of the builder-pattern used for loading dependent assets via1//! [`LoadContext::loader`].23use crate::{4io::Reader,5meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},6Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,7LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,8};9use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};10use core::any::TypeId;1112// Utility type for handling the sources of reader references13enum ReaderRef<'a> {14Borrowed(&'a mut dyn Reader),15Boxed(Box<dyn Reader + 'a>),16}1718impl ReaderRef<'_> {19pub fn as_mut(&mut self) -> &mut dyn Reader {20match self {21ReaderRef::Borrowed(r) => &mut **r,22ReaderRef::Boxed(b) => &mut **b,23}24}25}2627/// A builder for loading nested assets inside a [`LoadContext`].28///29/// # Loader state30///31/// The type parameters `T` and `M` determine how this will load assets:32/// - `T`: the typing of this loader. How do we know what type of asset to load?33///34/// See [`StaticTyped`] (the default), [`DynamicTyped`], and [`UnknownTyped`].35///36/// - `M`: the load mode. Do we want to load this asset right now (in which case37/// you will have to `await` the operation), or do we just want a [`Handle`],38/// and leave the actual asset loading to later?39///40/// See [`Deferred`] (the default) and [`Immediate`].41///42/// When configuring this builder, you can freely switch between these modes43/// via functions like [`deferred`] and [`immediate`].44///45/// ## Typing46///47/// To inform the loader of what type of asset to load:48/// - in [`StaticTyped`]: statically providing a type parameter `A: Asset` to49/// [`load`].50///51/// This is the simplest way to get a [`Handle<A>`] to the loaded asset, as52/// long as you know the type of `A` at compile time.53///54/// - in [`DynamicTyped`]: providing the [`TypeId`] of the asset at runtime.55///56/// If you know the type ID of the asset at runtime, but not at compile time,57/// use [`with_dynamic_type`] followed by [`load`] to start loading an asset58/// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),59/// or a [`ErasedLoadedAsset`] (via [`Immediate`]).60///61/// - in [`UnknownTyped`]: loading either a type-erased version of the asset62/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset63/// ([`LoadedUntypedAsset`]).64///65/// If you have no idea what type of asset you will be loading (not even at66/// runtime with a [`TypeId`]), use this.67///68/// ## Load mode69///70/// To inform the loader how you want to load the asset:71/// - in [`Deferred`]: when you request to load the asset, you get a [`Handle`]72/// for it, but the actual loading won't be completed until later.73///74/// Use this if you only need a [`Handle`] or [`UntypedHandle`].75///76/// - in [`Immediate`]: the load request will load the asset right then and77/// there, waiting until the asset is fully loaded and giving you access to78/// it.79///80/// Note that this requires you to `await` a future, so you must be in an81/// async context to use direct loading. In an asset loader, you will be in82/// an async context.83///84/// Use this if you need the *value* of another asset in order to load the85/// current asset. For example, if you are deriving a new asset from the86/// referenced asset, or you are building a collection of assets. This will87/// add the path of the asset as a "load dependency".88///89/// If the current loader is used in a [`Process`] "asset preprocessor",90/// such as a [`LoadTransformAndSave`] preprocessor, changing a "load91/// dependency" will result in re-processing of the asset.92///93/// # Load kickoff94///95/// If the current context is a normal [`AssetServer::load`], an actual asset96/// load will be kicked off immediately, which ensures the load happens as soon97/// as possible. "Normal loads" kicked from within a normal Bevy App will98/// generally configure the context to kick off loads immediately.99///100/// If the current context is configured to not load dependencies automatically101/// (ex: [`AssetProcessor`]), a load will not be kicked off automatically. It is102/// then the calling context's responsibility to begin a load if necessary.103///104/// # Lifetimes105///106/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference107/// - `builder`: the lifetime of the temporary builder structs108///109/// [`deferred`]: Self::deferred110/// [`immediate`]: Self::immediate111/// [`load`]: Self::load112/// [`with_dynamic_type`]: Self::with_dynamic_type113/// [`AssetServer::load`]: crate::AssetServer::load114/// [`AssetProcessor`]: crate::processor::AssetProcessor115/// [`Process`]: crate::processor::Process116/// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave117pub struct NestedLoader<'ctx, 'builder, T, M> {118load_context: &'builder mut LoadContext<'ctx>,119meta_transform: Option<MetaTransform>,120typing: T,121mode: M,122}123124mod sealed {125pub trait Typing {}126127pub trait Mode {}128}129130/// [`NestedLoader`] will be provided the type of asset as a type parameter on131/// [`load`].132///133/// [`load`]: NestedLoader::load134pub struct StaticTyped(());135136impl sealed::Typing for StaticTyped {}137138/// [`NestedLoader`] has been configured with info on what type of asset to load139/// at runtime.140pub struct DynamicTyped {141asset_type_id: TypeId,142}143144impl sealed::Typing for DynamicTyped {}145146/// [`NestedLoader`] does not know what type of asset it will be loading.147pub struct UnknownTyped(());148149impl sealed::Typing for UnknownTyped {}150151/// [`NestedLoader`] will create and return asset handles immediately, but only152/// actually load the asset later.153pub struct Deferred(());154155impl sealed::Mode for Deferred {}156157/// [`NestedLoader`] will immediately load an asset when requested.158pub struct Immediate<'builder, 'reader> {159reader: Option<&'builder mut (dyn Reader + 'reader)>,160}161162impl sealed::Mode for Immediate<'_, '_> {}163164// common to all states165166impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> {167pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self {168NestedLoader {169load_context,170meta_transform: None,171typing: StaticTyped(()),172mode: Deferred(()),173}174}175}176177impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> {178fn with_transform(179mut self,180transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static,181) -> Self {182if let Some(prev_transform) = self.meta_transform {183self.meta_transform = Some(Box::new(move |meta| {184prev_transform(meta);185transform(meta);186}));187} else {188self.meta_transform = Some(Box::new(transform));189}190self191}192193/// Configure the settings used to load the asset.194///195/// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log196/// and the asset load will fail.197#[must_use]198pub fn with_settings<S: Settings>(199self,200settings: impl Fn(&mut S) + Send + Sync + 'static,201) -> Self {202self.with_transform(move |meta| meta_transform_settings(meta, &settings))203}204205// convert between `T`s206207/// When [`load`]ing, you must pass in the asset type as a type parameter208/// statically.209///210/// If you don't know the type statically (at compile time), consider211/// [`with_dynamic_type`] or [`with_unknown_type`].212///213/// [`load`]: Self::load214/// [`with_dynamic_type`]: Self::with_dynamic_type215/// [`with_unknown_type`]: Self::with_unknown_type216#[must_use]217pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> {218NestedLoader {219load_context: self.load_context,220meta_transform: self.meta_transform,221typing: StaticTyped(()),222mode: self.mode,223}224}225226/// When [`load`]ing, the loader will attempt to load an asset with the227/// given [`TypeId`].228///229/// [`load`]: Self::load230#[must_use]231pub fn with_dynamic_type(232self,233asset_type_id: TypeId,234) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> {235NestedLoader {236load_context: self.load_context,237meta_transform: self.meta_transform,238typing: DynamicTyped { asset_type_id },239mode: self.mode,240}241}242243/// When [`load`]ing, we will infer what type of asset to load from244/// metadata.245///246/// [`load`]: Self::load247#[must_use]248pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> {249NestedLoader {250load_context: self.load_context,251meta_transform: self.meta_transform,252typing: UnknownTyped(()),253mode: self.mode,254}255}256257// convert between `M`s258259/// When [`load`]ing, create only asset handles, rather than returning the260/// actual asset.261///262/// [`load`]: Self::load263pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> {264NestedLoader {265load_context: self.load_context,266meta_transform: self.meta_transform,267typing: self.typing,268mode: Deferred(()),269}270}271272/// The [`load`] call itself will load an asset, rather than scheduling the273/// loading to happen later.274///275/// This gives you access to the loaded asset, but requires you to be in an276/// async context, and be able to `await` the resulting future.277///278/// [`load`]: Self::load279#[must_use]280pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> {281NestedLoader {282load_context: self.load_context,283meta_transform: self.meta_transform,284typing: self.typing,285mode: Immediate { reader: None },286}287}288}289290// deferred loading logic291292impl NestedLoader<'_, '_, StaticTyped, Deferred> {293/// Retrieves a handle for the asset at the given path and adds that path as294/// a dependency of this asset.295///296/// This requires you to know the type of asset statically.297/// - If you have runtime info for what type of asset you're loading (e.g. a298/// [`TypeId`]), use [`with_dynamic_type`].299/// - If you do not know at all what type of asset you're loading, use300/// [`with_unknown_type`].301///302/// [`with_dynamic_type`]: Self::with_dynamic_type303/// [`with_unknown_type`]: Self::with_unknown_type304pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {305let path = path.into().to_owned();306let handle = if self.load_context.should_load_dependencies {307self.load_context.asset_server.load_with_meta_transform(308path,309self.meta_transform,310(),311true,312)313} else {314self.load_context315.asset_server316.get_or_create_path_handle(path, self.meta_transform)317};318// `load_with_meta_transform` and `get_or_create_path_handle` always returns a Strong319// variant, so we are safe to unwrap.320let index = (&handle).try_into().unwrap();321self.load_context.dependencies.insert(index);322handle323}324}325326impl NestedLoader<'_, '_, DynamicTyped, Deferred> {327/// Retrieves a handle for the asset at the given path and adds that path as328/// a dependency of this asset.329///330/// This requires you to pass in the asset type ID into331/// [`with_dynamic_type`].332///333/// [`with_dynamic_type`]: Self::with_dynamic_type334pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {335let path = path.into().to_owned();336let handle = if self.load_context.should_load_dependencies {337self.load_context338.asset_server339.load_erased_with_meta_transform(340path,341self.typing.asset_type_id,342self.meta_transform,343(),344)345} else {346self.load_context347.asset_server348.get_or_create_path_handle_erased(349path,350self.typing.asset_type_id,351self.meta_transform,352)353};354// `load_erased_with_meta_transform` and `get_or_create_path_handle_erased` always returns a355// Strong variant, so we are safe to unwrap.356let index = (&handle).try_into().unwrap();357self.load_context.dependencies.insert(index);358handle359}360}361362impl NestedLoader<'_, '_, UnknownTyped, Deferred> {363/// Retrieves a handle for the asset at the given path and adds that path as364/// a dependency of this asset.365///366/// This will infer the asset type from metadata.367pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {368let path = path.into().to_owned();369let handle = if self.load_context.should_load_dependencies {370self.load_context371.asset_server372.load_unknown_type_with_meta_transform(path, self.meta_transform)373} else {374self.load_context375.asset_server376.get_or_create_path_handle(path, self.meta_transform)377};378// `load_unknown_type_with_meta_transform` and `get_or_create_path_handle` always returns a379// Strong variant, so we are safe to unwrap.380let index = (&handle).try_into().unwrap();381self.load_context.dependencies.insert(index);382handle383}384}385386// immediate loading logic387388impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {389/// Specify the reader to use to read the asset data.390#[must_use]391pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {392self.mode.reader = Some(reader);393self394}395396async fn load_internal(397self,398path: &AssetPath<'static>,399asset_type_id: Option<TypeId>,400) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {401if path.label().is_some() {402return Err(LoadDirectError::RequestedSubasset(path.clone()));403}404self.load_context405.asset_server406.write_infos()407.stats408.started_load_tasks += 1;409let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {410let loader = if let Some(asset_type_id) = asset_type_id {411self.load_context412.asset_server413.get_asset_loader_with_asset_type_id(asset_type_id)414.await415.map_err(|error| LoadDirectError::LoadError {416dependency: path.clone(),417error: error.into(),418})?419} else {420self.load_context421.asset_server422.get_path_asset_loader(path)423.await424.map_err(|error| LoadDirectError::LoadError {425dependency: path.clone(),426error: error.into(),427})?428};429let meta = loader.default_meta();430(meta, loader, ReaderRef::Borrowed(reader))431} else {432let (meta, loader, reader) = self433.load_context434.asset_server435.get_meta_loader_and_reader(path, asset_type_id)436.await437.map_err(|error| LoadDirectError::LoadError {438dependency: path.clone(),439error,440})?;441(meta, loader, ReaderRef::Boxed(reader))442};443444if let Some(meta_transform) = self.meta_transform {445meta_transform(&mut *meta);446}447448let asset = self449.load_context450.load_direct_internal(451path.clone(),452meta.loader_settings().expect("meta corresponds to a load"),453&*loader,454reader.as_mut(),455meta.processed_info().as_ref(),456)457.await?;458Ok((loader, asset))459}460}461462impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {463/// Attempts to load the asset at the given `path` immediately.464///465/// This requires you to know the type of asset statically.466/// - If you have runtime info for what type of asset you're loading (e.g. a467/// [`TypeId`]), use [`with_dynamic_type`].468/// - If you do not know at all what type of asset you're loading, use469/// [`with_unknown_type`].470///471/// [`with_dynamic_type`]: Self::with_dynamic_type472/// [`with_unknown_type`]: Self::with_unknown_type473pub async fn load<'p, A: Asset>(474self,475path: impl Into<AssetPath<'p>>,476) -> Result<LoadedAsset<A>, LoadDirectError> {477let path = path.into().into_owned();478self.load_internal(&path, Some(TypeId::of::<A>()))479.await480.and_then(move |(loader, untyped_asset)| {481untyped_asset482.downcast::<A>()483.map_err(|_| LoadDirectError::LoadError {484dependency: path.clone(),485error: AssetLoadError::RequestedHandleTypeMismatch {486path,487requested: TypeId::of::<A>(),488actual_asset_name: loader.asset_type_name(),489loader_name: loader.type_path(),490},491})492})493}494}495496impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {497/// Attempts to load the asset at the given `path` immediately.498///499/// This requires you to pass in the asset type ID into500/// [`with_dynamic_type`].501///502/// [`with_dynamic_type`]: Self::with_dynamic_type503pub async fn load<'p>(504self,505path: impl Into<AssetPath<'p>>,506) -> Result<ErasedLoadedAsset, LoadDirectError> {507let path = path.into().into_owned();508let asset_type_id = Some(self.typing.asset_type_id);509self.load_internal(&path, asset_type_id)510.await511.map(|(_, asset)| asset)512}513}514515impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {516/// Attempts to load the asset at the given `path` immediately.517///518/// This will infer the asset type from metadata.519pub async fn load<'p>(520self,521path: impl Into<AssetPath<'p>>,522) -> Result<ErasedLoadedAsset, LoadDirectError> {523let path = path.into().into_owned();524self.load_internal(&path, None)525.await526.map(|(_, asset)| asset)527}528}529530531