Path: blob/main/crates/bevy_asset/src/loader_builders.rs
6598 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};318self.load_context.dependencies.insert(handle.id().untyped());319handle320}321}322323impl NestedLoader<'_, '_, DynamicTyped, Deferred> {324/// Retrieves a handle for the asset at the given path and adds that path as325/// a dependency of this asset.326///327/// This requires you to pass in the asset type ID into328/// [`with_dynamic_type`].329///330/// [`with_dynamic_type`]: Self::with_dynamic_type331pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {332let path = path.into().to_owned();333let handle = if self.load_context.should_load_dependencies {334self.load_context335.asset_server336.load_erased_with_meta_transform(337path,338self.typing.asset_type_id,339self.meta_transform,340(),341)342} else {343self.load_context344.asset_server345.get_or_create_path_handle_erased(346path,347self.typing.asset_type_id,348self.meta_transform,349)350};351self.load_context.dependencies.insert(handle.id());352handle353}354}355356impl NestedLoader<'_, '_, UnknownTyped, Deferred> {357/// Retrieves a handle for the asset at the given path and adds that path as358/// a dependency of this asset.359///360/// This will infer the asset type from metadata.361pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {362let path = path.into().to_owned();363let handle = if self.load_context.should_load_dependencies {364self.load_context365.asset_server366.load_unknown_type_with_meta_transform(path, self.meta_transform)367} else {368self.load_context369.asset_server370.get_or_create_path_handle(path, self.meta_transform)371};372self.load_context.dependencies.insert(handle.id().untyped());373handle374}375}376377// immediate loading logic378379impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {380/// Specify the reader to use to read the asset data.381#[must_use]382pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {383self.mode.reader = Some(reader);384self385}386387async fn load_internal(388self,389path: &AssetPath<'static>,390asset_type_id: Option<TypeId>,391) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {392if path.label().is_some() {393return Err(LoadDirectError::RequestedSubasset(path.clone()));394}395let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {396let loader = if let Some(asset_type_id) = asset_type_id {397self.load_context398.asset_server399.get_asset_loader_with_asset_type_id(asset_type_id)400.await401.map_err(|error| LoadDirectError::LoadError {402dependency: path.clone(),403error: error.into(),404})?405} else {406self.load_context407.asset_server408.get_path_asset_loader(path)409.await410.map_err(|error| LoadDirectError::LoadError {411dependency: path.clone(),412error: error.into(),413})?414};415let meta = loader.default_meta();416(meta, loader, ReaderRef::Borrowed(reader))417} else {418let (meta, loader, reader) = self419.load_context420.asset_server421.get_meta_loader_and_reader(path, asset_type_id)422.await423.map_err(|error| LoadDirectError::LoadError {424dependency: path.clone(),425error,426})?;427(meta, loader, ReaderRef::Boxed(reader))428};429430if let Some(meta_transform) = self.meta_transform {431meta_transform(&mut *meta);432}433434let asset = self435.load_context436.load_direct_internal(path.clone(), meta.as_ref(), &*loader, reader.as_mut())437.await?;438Ok((loader, asset))439}440}441442impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {443/// Attempts to load the asset at the given `path` immediately.444///445/// This requires you to know the type of asset statically.446/// - If you have runtime info for what type of asset you're loading (e.g. a447/// [`TypeId`]), use [`with_dynamic_type`].448/// - If you do not know at all what type of asset you're loading, use449/// [`with_unknown_type`].450///451/// [`with_dynamic_type`]: Self::with_dynamic_type452/// [`with_unknown_type`]: Self::with_unknown_type453pub async fn load<'p, A: Asset>(454self,455path: impl Into<AssetPath<'p>>,456) -> Result<LoadedAsset<A>, LoadDirectError> {457let path = path.into().into_owned();458self.load_internal(&path, Some(TypeId::of::<A>()))459.await460.and_then(move |(loader, untyped_asset)| {461untyped_asset462.downcast::<A>()463.map_err(|_| LoadDirectError::LoadError {464dependency: path.clone(),465error: AssetLoadError::RequestedHandleTypeMismatch {466path,467requested: TypeId::of::<A>(),468actual_asset_name: loader.asset_type_name(),469loader_name: loader.type_name(),470},471})472})473}474}475476impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {477/// Attempts to load the asset at the given `path` immediately.478///479/// This requires you to pass in the asset type ID into480/// [`with_dynamic_type`].481///482/// [`with_dynamic_type`]: Self::with_dynamic_type483pub async fn load<'p>(484self,485path: impl Into<AssetPath<'p>>,486) -> Result<ErasedLoadedAsset, LoadDirectError> {487let path = path.into().into_owned();488let asset_type_id = Some(self.typing.asset_type_id);489self.load_internal(&path, asset_type_id)490.await491.map(|(_, asset)| asset)492}493}494495impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {496/// Attempts to load the asset at the given `path` immediately.497///498/// This will infer the asset type from metadata.499pub async fn load<'p>(500self,501path: impl Into<AssetPath<'p>>,502) -> Result<ErasedLoadedAsset, LoadDirectError> {503let path = path.into().into_owned();504self.load_internal(&path, None)505.await506.map(|(_, asset)| asset)507}508}509510511