diff --git a/_release-content/migration-guides/load_builder.md b/_release-content/migration-guides/load_builder.md new file mode 100644 index 0000000000000..0806e5c887080 --- /dev/null +++ b/_release-content/migration-guides/load_builder.md @@ -0,0 +1,21 @@ +--- +title: Advanced AssetServer load variants are now expose through a builder pattern. +pull_requests: [] +--- + +In previous versions of Bevy, there were many different ways to load an asset: + +- `AssetServer::load` +- `AssetServer::load_acquire` +- `AssetServer::load_untyped` +- `AssetServer::load_acquire_override_with_settings` +- etc. + +All these variants have been simplified to only two variants: + +1. `AssetServer::load()`: This is just a convenience and just calls the load builder internally. +2. `AssetServer::load_builder()`: allows for constructing more complex loads like untyped loads, + loads including guards, loads with settings, etc. + +Every load variant above can be reimplemented using `load_builder`, and each one of these methods +has deprecation messages on them explaining their new equivalent. diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs index e7e5b993deb85..73dc615b5e6ab 100644 --- a/crates/bevy_asset/src/direct_access_ext.rs +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -3,7 +3,7 @@ use bevy_ecs::world::World; -use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle}; +use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle, LoadBuilder}; /// An extension trait for methods for working with assets directly from a [`World`]. pub trait DirectAssetAccessExt { @@ -13,7 +13,11 @@ pub trait DirectAssetAccessExt { /// Load an asset similarly to [`AssetServer::load`]. fn load_asset<'a, A: Asset>(&self, path: impl Into>) -> Handle; + /// Creates a new [`LoadBuilder`] similar to [`AssetServer::load_builder`]. + fn load_builder(&self) -> LoadBuilder<'_>; + /// Load an asset with settings, similarly to [`AssetServer::load_with_settings`]. + #[deprecated(note = "Use `world.load_builder().with_settings(settings).load(path)`")] fn load_asset_with_settings<'a, A: Asset, S: Settings>( &self, path: impl Into>, @@ -37,6 +41,15 @@ impl DirectAssetAccessExt for World { fn load_asset<'a, A: Asset>(&self, path: impl Into>) -> Handle { self.resource::().load(path) } + + /// Creates a new [`LoadBuilder`] similar to [`AssetServer::load_builder`]. + /// + /// # Panics + /// If `self` doesn't have an [`AssetServer`] resource initialized yet. + fn load_builder(&self) -> LoadBuilder<'_> { + self.resource::().load_builder() + } + /// Load an asset with settings, similarly to [`AssetServer::load_with_settings`]. /// /// # Panics @@ -47,6 +60,8 @@ impl DirectAssetAccessExt for World { settings: impl Fn(&mut S) + Send + Sync + 'static, ) -> Handle { self.resource::() - .load_with_settings(path, settings) + .load_builder() + .with_settings(settings) + .load(path.into()) } } diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index 9eea1dbb95b75..26171deb1c74a 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -172,8 +172,9 @@ impl GetAssetServer for AssetServer { /// This macro takes two arguments and an optional third one: /// 1. The asset source. It may be `AssetServer`, `World` or `App`. /// 2. The path to the asset to embed, as a string literal. -/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`]. -/// Consider explicitly typing the closure argument in case of type error. +/// 3. Optionally, a closure of the same type as in +/// [`LoadBuilder::with_settings`](crate::LoadBuilder::with_settings). Consider explicitly typing +/// the closure argument in case of type error. /// /// # Usage /// @@ -196,7 +197,7 @@ macro_rules! load_embedded_asset { }}; ($provider: expr, $path: literal, $settings: expr) => {{ let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider); - asset_server.load_with_settings(path, $settings) + asset_server.load_builder().with_settings($settings).load(path) }}; ($provider: expr, $path: literal) => {{ let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider); diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index c07a315bcab21..592306acfc4ab 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -277,8 +277,8 @@ pub struct AssetPlugin { pub enum UnapprovedPathMode { /// Unapproved asset loading is allowed. This is strongly discouraged. Allow, - /// Fails to load any asset that is unapproved, unless an override method is used, like - /// [`AssetServer::load_override`]. + /// Fails to load any asset that is unapproved, unless [`LoadBuilder::override_unapproved`] is + /// used. Deny, /// Fails to load any asset that is unapproved. #[default] @@ -2116,7 +2116,10 @@ mod tests { let asset_server = app.world().resource::().clone(); assert_eq!( - asset_server.load_override::("../a.cool.ron"), + asset_server + .load_builder() + .override_unapproved() + .load::("../a.cool.ron"), Handle::default() ); } @@ -2137,7 +2140,10 @@ mod tests { let mut app = unapproved_path_setup(UnapprovedPathMode::Deny); let asset_server = app.world().resource::().clone(); - let handle = asset_server.load_override::("../a.cool.ron"); + let handle = asset_server + .load_builder() + .override_unapproved() + .load::("../a.cool.ron"); assert_ne!(handle, Handle::default()); // Make sure this asset actually loads. @@ -2529,10 +2535,10 @@ mod tests { // Load the test asset twice but with different settings. fn load(asset_server: &AssetServer, path: &'static str, value: u8) -> Handle { - asset_server.load_with_settings::( - path, - move |s: &mut U8LoaderSettings| s.0 = value, - ) + asset_server + .load_builder() + .with_settings(move |s: &mut U8LoaderSettings| s.0 = value) + .load::(path) } let handle_1 = load(asset_server, "test.u8", 1); diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index c2d63a44ae740..3909a5f5df541 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -605,7 +605,7 @@ impl<'a> LoadContext<'a> { label: impl Into>, ) -> Handle { let path = self.asset_path.clone().with_label(label); - let handle = self.asset_server.get_or_create_path_handle::(path, None); + let handle = self.asset_server.get_or_create_path_handle(path, None); // `get_or_create_path_handle` always returns a Strong variant, so we are safe to unwrap. let index = (&handle).try_into().unwrap(); self.dependencies.insert(index); diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 878f87ca11c05..c58487d5d0302 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -337,11 +337,13 @@ impl NestedLoader<'_, '_, DynamicTyped, Deferred> { let handle = if self.load_context.should_load_dependencies { self.load_context .asset_server - .load_erased_with_meta_transform( + .load_with_meta_transform_erased( path, self.typing.asset_type_id, + None, self.meta_transform, (), + false, ) } else { self.load_context @@ -349,10 +351,11 @@ impl NestedLoader<'_, '_, DynamicTyped, Deferred> { .get_or_create_path_handle_erased( path, self.typing.asset_type_id, + None, self.meta_transform, ) }; - // `load_erased_with_meta_transform` and `get_or_create_path_handle_erased` always returns a + // `load_with_meta_transform_erased` and `get_or_create_path_handle_erased` always returns a // Strong variant, so we are safe to unwrap. let index = (&handle).try_into().unwrap(); self.load_context.dependencies.insert(index); @@ -370,7 +373,7 @@ impl NestedLoader<'_, '_, UnknownTyped, Deferred> { let handle = if self.load_context.should_load_dependencies { self.load_context .asset_server - .load_unknown_type_with_meta_transform(path, self.meta_transform) + .load_unknown_type_with_meta_transform(path, self.meta_transform, (), false) } else { self.load_context .asset_server diff --git a/crates/bevy_asset/src/reflect.rs b/crates/bevy_asset/src/reflect.rs index b7c569b153ce4..3269dd55f8b11 100644 --- a/crates/bevy_asset/src/reflect.rs +++ b/crates/bevy_asset/src/reflect.rs @@ -398,7 +398,7 @@ impl ReflectSerializerProcessor for HandleSerializeProcessor { pub trait LoadFromPath { /// Initiates the load for the given expected type ID, and the path. /// - /// See [`AssetServer::load_erased`] for more. + /// See [`LoadBuilder::load_erased`](crate::LoadBuilder::load_erased) for more. fn load_from_path_erased(&mut self, type_id: TypeId, path: AssetPath<'static>) -> UntypedHandle; } @@ -419,7 +419,7 @@ impl LoadFromPath for AssetServer { type_id: TypeId, path: AssetPath<'static>, ) -> UntypedHandle { - self.load_erased(type_id, path) + self.load_builder().load_erased(type_id, path) } } @@ -429,7 +429,7 @@ impl LoadFromPath for &AssetServer { type_id: TypeId, path: AssetPath<'static>, ) -> UntypedHandle { - self.load_erased(type_id, path) + self.load_builder().load_erased(type_id, path) } } @@ -625,7 +625,7 @@ mod tests { let type_registry = app.world().resource::().0.clone(); let asset_server = app.world().resource::().clone(); - let untyped = asset_server.load_untyped("def.cool.ron"); + let untyped = asset_server.load_builder().load_untyped("def.cool.ron"); run_app_until(&mut app, |_| asset_server.is_loaded(&untyped).then_some(())); let untyped = app .world() diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 4d93e0d93fe4e..a60c4c935e766 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -14,9 +14,11 @@ use bevy_ecs::world::World; use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet}; use bevy_tasks::Task; use bevy_utils::TypeIdMap; -use core::{any::TypeId, task::Waker}; +use core::{ + any::{type_name, TypeId}, + task::Waker, +}; use crossbeam_channel::Sender; -use either::Either; use thiserror::Error; use tracing::warn; @@ -123,7 +125,8 @@ impl AssetInfos { None, true, ), - Either::Left(type_name), + type_id, + Some(type_name), ) .unwrap() } @@ -168,15 +171,13 @@ impl AssetInfos { loading_mode: HandleLoadingMode, meta_transform: Option, ) -> (Handle, bool) { - let result = self.get_or_create_path_handle_internal( + let (handle, should_load) = self.get_or_create_path_handle_erased( path, - Some(TypeId::of::()), + TypeId::of::(), + Some(type_name::()), loading_mode, meta_transform, ); - // it is ok to unwrap because TypeId was specified above - let (handle, should_load) = - unwrap_with_context(result, Either::Left(core::any::type_name::())).unwrap(); (handle.typed_unchecked(), should_load) } @@ -194,12 +195,8 @@ impl AssetInfos { loading_mode, meta_transform, ); - 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") + // it is ok to unwrap because TypeId was specified above + unwrap_with_context(result, type_id, type_name).unwrap() } /// Retrieves asset tracking data, or creates it if it doesn't exist. @@ -809,17 +806,18 @@ pub(crate) enum GetOrCreateHandleInternalError { pub(crate) fn unwrap_with_context( result: Result, - type_info: Either<&str, TypeId>, + type_id: TypeId, + type_name: Option<&str>, ) -> Option { match result { Ok(value) => Some(value), Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None, - Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => match type_info { - Either::Left(type_name) => { + Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => match type_name { + Some(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) => { + None => { 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 5d09900263517..98a81b5db661b 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -33,15 +33,19 @@ use bevy_platform::{ sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; use bevy_tasks::IoTaskPool; -use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll}; +use core::{ + any::{type_name, TypeId}, + future::Future, + panic::AssertUnwindSafe, + task::Poll, +}; use crossbeam_channel::{Receiver, Sender}; -use either::Either; use futures_lite::{FutureExt, StreamExt}; use info::*; use loaders::*; use std::path::{Path, PathBuf}; use thiserror::Error; -use tracing::{error, info}; +use tracing::{error, info, warn}; /// Loads and tracks the state of [`Asset`] values from a configured [`AssetReader`](crate::io::AssetReader). /// This can be used to kick off new asset loads and retrieve their current load states. @@ -358,7 +362,12 @@ impl AssetServer { /// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`. #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load<'a, A: Asset>(&self, path: impl Into>) -> Handle { - self.load_with_meta_transform(path, None, (), false) + self.load_builder().load(path.into()) + } + + #[must_use = "the load doesn't start until LoadBuilder has been consumed"] + pub fn load_builder(&self) -> LoadBuilder<'_> { + LoadBuilder::new(self) } /// Same as [`load`](AssetServer::load), but you can load assets from unapproved paths @@ -366,18 +375,22 @@ impl AssetServer { /// is [`Deny`](UnapprovedPathMode::Deny). /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + #[deprecated( + note = "Use `asset_server.load_builder().override_unapproved().load(path)` instead" + )] pub fn load_override<'a, A: Asset>(&self, path: impl Into>) -> Handle { - self.load_with_meta_transform(path, None, (), true) + self.load_builder().override_unapproved().load(path.into()) } /// Same as [`load`](Self::load), but the type of the asset to load is specified by the runtime /// `type_id`. + #[deprecated(note = "Use `asset_server.load_builder().load_erased(type_id, path)` instead")] pub fn load_erased<'a>( &self, type_id: TypeId, path: impl Into>, ) -> UntypedHandle { - self.load_erased_with_meta_transform(path, type_id, None, ()) + self.load_builder().load_erased(type_id, path.into()) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -395,13 +408,14 @@ impl AssetServer { /// the [`Assets`] storage to see if the [`Asset`] exists yet. /// /// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`. + #[deprecated(note = "Use `asset_server.load_builder().with_guard(guard).load(path)` instead")] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load_acquire<'a, A: Asset, G: Send + Sync + 'static>( &self, path: impl Into>, guard: G, ) -> Handle { - self.load_with_meta_transform(path, None, guard, false) + self.load_builder().with_guard(guard).load(path.into()) } /// Same as [`load`](AssetServer::load_acquire), but you can load assets from unapproved paths @@ -409,29 +423,35 @@ impl AssetServer { /// is [`Deny`](UnapprovedPathMode::Deny). /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + #[deprecated( + note = "Use `asset_server.load_builder().with_guard(guard).override_unapproved().load(path)` instead" + )] pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>( &self, path: impl Into>, guard: G, ) -> Handle { - self.load_with_meta_transform(path, None, guard, true) + self.load_builder() + .with_guard(guard) + .override_unapproved() + .load(path.into()) } /// Begins loading an [`Asset`] of type `A` stored at `path`. The given `settings` function will override the asset's /// [`AssetLoader`] settings. The type `S` _must_ match the configured [`AssetLoader::Settings`] or `settings` changes /// will be ignored and an error will be printed to the log. + #[deprecated( + note = "Use `asset_server.load_builder().with_settings(settings).load(path)` instead" + )] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load_with_settings<'a, A: Asset, S: Settings>( &self, path: impl Into>, settings: impl Fn(&mut S) + Send + Sync + 'static, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - (), - false, - ) + self.load_builder() + .with_settings(settings) + .load(path.into()) } /// Same as [`load`](AssetServer::load_with_settings), but you can load assets from unapproved paths @@ -439,17 +459,18 @@ impl AssetServer { /// is [`Deny`](UnapprovedPathMode::Deny). /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + #[deprecated( + note = "Use `asset_server.load_builder().with_settings(settings).override_unapproved().load(path)` instead" + )] pub fn load_with_settings_override<'a, A: Asset, S: Settings>( &self, path: impl Into>, settings: impl Fn(&mut S) + Send + Sync + 'static, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - (), - true, - ) + self.load_builder() + .with_settings(settings) + .override_unapproved() + .load(path.into()) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -461,6 +482,9 @@ impl AssetServer { /// The given `settings` function will override the asset's /// [`AssetLoader`] settings. The type `S` _must_ match the configured [`AssetLoader::Settings`] or `settings` changes /// will be ignored and an error will be printed to the log. + #[deprecated( + note = "Use `asset_server.load_builder().with_guard(guard).with_settings(settings).load(path)` instead" + )] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub fn load_acquire_with_settings<'a, A: Asset, S: Settings, G: Send + Sync + 'static>( &self, @@ -468,12 +492,10 @@ impl AssetServer { settings: impl Fn(&mut S) + Send + Sync + 'static, guard: G, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - guard, - false, - ) + self.load_builder() + .with_guard(guard) + .with_settings(settings) + .load(path.into()) } /// Same as [`load`](AssetServer::load_acquire_with_settings), but you can load assets from unapproved paths @@ -481,6 +503,9 @@ impl AssetServer { /// is [`Deny`](UnapprovedPathMode::Deny). /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] + #[deprecated( + note = "Use `asset_server.load_builder().with_guard(guard).with_settings(settings).override_unapproved().load(path)` instead" + )] pub fn load_acquire_with_settings_override< 'a, A: Asset, @@ -492,21 +517,40 @@ impl AssetServer { settings: impl Fn(&mut S) + Send + Sync + 'static, guard: G, ) -> Handle { - self.load_with_meta_transform( + self.load_builder() + .with_guard(guard) + .with_settings(settings) + .override_unapproved() + .load(path.into()) + } + + pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>( + &self, + path: impl Into>, + meta_transform: Option, + guard: G, + override_unapproved: bool, + ) -> Handle { + self.load_with_meta_transform_erased( path, - Some(loader_settings_meta_transform(settings)), + TypeId::of::(), + Some(type_name::()), + meta_transform, guard, - true, + override_unapproved, ) + .typed_unchecked() } - pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>( + pub(crate) fn load_with_meta_transform_erased<'a, G: Send + Sync + 'static>( &self, path: impl Into>, + type_id: TypeId, + type_name: Option<&str>, meta_transform: Option, guard: G, override_unapproved: bool, - ) -> Handle { + ) -> UntypedHandle { let path = path.into().into_owned(); if path.is_unapproved() { @@ -514,38 +558,19 @@ impl AssetServer { (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {} (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => { error!("Asset path {path} is unapproved. See UnapprovedPathMode for details."); - return Handle::default(); + return UntypedHandle::Uuid { + type_id, + uuid: AssetId::<()>::DEFAULT_UUID, + }; } } } - let mut infos = self.write_infos(); - let (handle, should_load) = infos.get_or_create_path_handle::( - path.clone(), - HandleLoadingMode::Request, - meta_transform, - ); - - if should_load { - self.spawn_load_task(handle.clone().untyped(), path, infos, guard); - } - - handle - } - - 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.write_infos(); let (handle, should_load) = infos.get_or_create_path_handle_erased( path.clone(), type_id, - None, + type_name, HandleLoadingMode::Request, meta_transform, ); @@ -597,25 +622,34 @@ impl AssetServer { /// 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`]. + #[deprecated(note = "Use `asset_server.load_builder().load_untyped_async(path)` instead")] #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] pub async fn load_untyped_async<'a>( &self, path: impl Into>, ) -> Result { - self.write_infos().stats.started_load_tasks += 1; - - let path: AssetPath = path.into(); - self.load_internal(None, path, false, None) - .await - .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle")) + self.load_builder().load_untyped_async(path.into()).await } - pub(crate) fn load_unknown_type_with_meta_transform<'a>( + pub(crate) fn load_unknown_type_with_meta_transform<'a, G: Send + Sync + 'static>( &self, path: impl Into>, meta_transform: Option, + guard: G, + override_unapproved: bool, ) -> Handle { let path = path.into().into_owned(); + + if path.is_unapproved() { + match (&self.data.unapproved_path_mode, override_unapproved) { + (UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {} + (UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => { + error!("Asset path {path} is unapproved. See UnapprovedPathMode for details."); + return Handle::default(); + } + } + } + let untyped_source = AssetSourceId::Name(match path.source() { AssetSourceId::Default => CowArc::Static(UNTYPED_SOURCE_SUFFIX), AssetSourceId::Name(source) => { @@ -663,6 +697,7 @@ impl AssetServer { }); } }; + drop(guard); }); #[cfg(not(any(target_arch = "wasm32", not(feature = "multi_threaded"))))] @@ -697,9 +732,10 @@ impl AssetServer { /// /// This indirection enables a non blocking load of an untyped asset, since I/O is /// required to figure out the asset type before a handle can be created. + #[deprecated(note = "Use `asset_server.load_builder().load_untyped(path)` instead")] #[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_unknown_type_with_meta_transform(path, None) + self.load_builder().load_untyped(path.into()) } /// Performs an async asset load. @@ -765,7 +801,11 @@ impl AssetServer { HandleLoadingMode::Request, meta_transform, ); - match unwrap_with_context(result, Either::Left(loader.asset_type_name())) { + match unwrap_with_context( + result, + loader.asset_type_id(), + Some(loader.asset_type_name()), + ) { // We couldn't figure out the correct handle without its type ID (which can only // happen if we are loading a subasset). None => { @@ -1024,8 +1064,7 @@ impl AssetServer { future: impl Future> + Send + 'static, ) -> Handle { let mut infos = self.write_infos(); - let handle = - infos.create_loading_handle_untyped(TypeId::of::(), core::any::type_name::()); + let handle = infos.create_loading_handle_untyped(TypeId::of::(), type_name::()); // drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded #[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))] @@ -1083,13 +1122,11 @@ impl AssetServer { #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_folder<'a>(&self, path: impl Into>) -> Handle { let path = path.into().into_owned(); - let (handle, should_load) = self - .write_infos() - .get_or_create_path_handle::( - path.clone(), - HandleLoadingMode::Request, - None, - ); + let (handle, should_load) = self.write_infos().get_or_create_path_handle( + path.clone(), + HandleLoadingMode::Request, + None, + ); if !should_load { return handle; } @@ -1124,7 +1161,7 @@ impl AssetServer { } else { let path = child_path.to_str().expect("Path should be a valid string."); let asset_path = AssetPath::parse(path).with_source(source.clone()); - match server.load_untyped_async(asset_path).await { + match server.load_builder().load_untyped_async(asset_path).await { Ok(handle) => handles.push(handle), // skip assets that cannot be loaded Err( @@ -1435,13 +1472,13 @@ impl AssetServer { path: impl Into>, meta_transform: Option, ) -> Handle { - self.write_infos() - .get_or_create_path_handle::( - path.into().into_owned(), - HandleLoadingMode::NotLoading, - meta_transform, - ) - .0 + self.get_or_create_path_handle_erased( + path.into().into_owned(), + TypeId::of::(), + Some(type_name::()), + meta_transform, + ) + .typed_unchecked() } /// Retrieve a handle for the given path, where the asset type ID and name @@ -1452,13 +1489,14 @@ impl AssetServer { &self, path: impl Into>, type_id: TypeId, + type_name: Option<&str>, meta_transform: Option, ) -> UntypedHandle { self.write_infos() .get_or_create_path_handle_erased( path.into().into_owned(), type_id, - None, + type_name, HandleLoadingMode::NotLoading, meta_transform, ) @@ -1790,6 +1828,186 @@ impl AssetServer { } } +/// A builder for initiating a more complex load than the one provided by [`AssetServer::load`]. +pub struct LoadBuilder<'a> { + /// The asset server on which the load is invoked. + asset_server: &'a AssetServer, + /// A function to modify the meta for an asset loader. In practice, this just mutates the loader + /// settings of a load. + meta_transform: Option, + /// Whether unapproved paths are allowed to be loaded. + override_unapproved: bool, + /// A "guard" that is held until the load has fully completed. + guard: Option>, +} + +impl<'a> LoadBuilder<'a> { + /// Begins building a new load on the given `asset_server`. + #[must_use = "the load doesn't start until LoadBuilder has been consumed"] + fn new(asset_server: &'a AssetServer) -> Self { + Self { + asset_server, + meta_transform: None, + override_unapproved: false, + guard: None, + } + } + + /// Use the given `settings` function to override the asset's [`AssetLoader`] settings. + /// + /// The type `S` must match the configured [`AssetLoader::Settings`] or `settings` changes will + /// be ignored and an error will be printed to the log. + /// + /// Repeatedly calling this method will "chain" the operations (matching the order of these + /// calls). + #[must_use = "the load doesn't start until LoadBuilder has been consumed"] + pub fn with_settings( + mut self, + settings: impl Fn(&mut S) + Send + Sync + 'static, + ) -> Self { + let new_transform = loader_settings_meta_transform(settings); + if let Some(prev_transform) = self.meta_transform.take() { + self.meta_transform = Some(Box::new(move |meta| { + prev_transform(meta); + new_transform(meta); + })); + } else { + self.meta_transform = Some(new_transform); + } + self + } + + /// Loads from unapproved paths are allowed, even if + /// [`AssetPlugin::unapproved_path_mode`](crate::AssetPlugin::unapproved_path_mode) is + /// [`Deny`](crate::UnapprovedPathMode::Deny). + #[must_use = "the load doesn't start until LoadBuilder has been consumed"] + pub fn override_unapproved(mut self) -> Self { + self.override_unapproved = true; + self + } + + /// Sets the guard item that is held during the load. + /// + /// The guard item is dropped when either the asset is loaded or loading has failed. This allows + /// the [`Drop`] implementation of the guard item to notify the caller in some way. See the + /// `multi_asset_sync` example for usage. + /// + /// Only the last guard is kept. The previous guards are dropped before the load begins. + #[must_use = "the load doesn't start until LoadBuilder has been consumed"] + pub fn with_guard(mut self, guard: impl Send + Sync + 'static) -> Self { + if self.guard.is_some() { + warn!("Adding a second guard to a LoadBuilder drops the first guard! This is likely a mistake."); + } + // If guard is already a box, then we might end up double-boxing, which is sad. But this is + // almost certainly not worth caring about. + self.guard = Some(Box::new(guard)); + self + } + + /// Begins loading an [`Asset`] of type `A` stored at `path`. This will not block on the asset load. Instead, + /// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the + /// associated [`Assets`] resource. + /// + /// Note that if the asset at this path is already loaded, this function will return the existing handle, + /// and will not waste work spawning a new load task. + /// + /// This matches the behavior of [`AssetServer::load`], but supporting all other features of the + /// builder. See its docs for more details. + #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] + pub fn load<'b, A: Asset>(self, asset_path: impl Into>) -> Handle { + self.load_typed_internal(TypeId::of::(), Some(type_name::()), asset_path.into()) + .typed_unchecked() + } + + /// Same as [`load`](Self::load), but the type of the asset to load is specified by the runtime + /// `type_id`. + #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] + pub fn load_erased<'b>( + self, + type_id: TypeId, + asset_path: impl Into>, + ) -> UntypedHandle { + self.load_typed_internal(type_id, None, asset_path.into()) + } + + /// Load an asset without knowing its type. The method returns a handle to a [`LoadedUntypedAsset`]. + /// + /// Once the [`LoadedUntypedAsset`] is loaded, an untyped handle for the requested path can be + /// retrieved from it. + /// + /// ``` + /// use bevy_asset::{Assets, Handle, LoadedUntypedAsset}; + /// use bevy_ecs::system::Res; + /// use bevy_ecs::resource::Resource; + /// + /// #[derive(Resource)] + /// struct LoadingUntypedHandle(Handle); + /// + /// fn resolve_loaded_untyped_handle(loading_handle: Res, loaded_untyped_assets: Res>) { + /// if let Some(loaded_untyped_asset) = loaded_untyped_assets.get(&loading_handle.0) { + /// let handle = loaded_untyped_asset.handle.clone(); + /// // continue working with `handle` which points to the asset at the originally requested path + /// } + /// } + /// ``` + /// + /// This indirection enables a non blocking load of an untyped asset, since I/O is + /// 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 asset"] + pub fn load_untyped<'b>( + self, + asset_path: impl Into>, + ) -> Handle { + self.asset_server.load_unknown_type_with_meta_transform( + asset_path, + self.meta_transform, + self.guard, + self.override_unapproved, + ) + } + + // We intentionally don't provide a `load_async` or `load_erased_async`, since these don't + // provide any value over doing a regular deferred load + `AssetServer::wait_for_asset_id`. + // `load_untyped_async` on the other hand lets you avoid dealing with the "missing type" of the + // asset (i.e., dealing with `LoadedUntypedAsset`). + + /// 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`]. + #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] + pub async fn load_untyped_async<'b>( + self, + asset_path: impl Into>, + ) -> Result { + let path: AssetPath = asset_path.into(); + + self.asset_server.write_infos().stats.started_load_tasks += 1; + + self.asset_server + .load_internal(None, path, false, None) + .await + .map(|h| h.expect("handle must be returned, since we didn't pass in an input handle")) + } + + /// Begins a (deferred) load for an asset with the given `type_id` and `type_name`. + #[must_use = "not using the returned strong handle may result in the unexpected release of the asset"] + fn load_typed_internal( + self, + type_id: TypeId, + type_name: Option<&str>, + asset_path: AssetPath<'_>, + ) -> UntypedHandle { + self.asset_server.load_with_meta_transform_erased( + asset_path, + type_id, + type_name, + self.meta_transform, + self.guard, + self.override_unapproved, + ) + } +} + /// A system that manages internal [`AssetServer`] events, such as finalizing asset loads. pub fn handle_internal_asset_events(world: &mut World) { world.resource_scope(|world, server: Mut| { diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 112929ea73a5a..896b21f3f8c58 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -174,12 +174,12 @@ pub struct GltfLoader { /// # use bevy_asset::{AssetServer, Handle}; /// # use bevy_gltf::*; /// # let asset_server: AssetServer = panic!(); -/// let gltf_handle: Handle = asset_server.load_with_settings( -/// "my.gltf", -/// |s: &mut GltfLoaderSettings| { -/// s.load_cameras = false; -/// } -/// ); +/// let gltf_handle: Handle = asset_server.load_builder().with_settings( +/// |s: &mut GltfLoaderSettings| { +/// s.load_cameras = false; +/// } +/// ) +/// .load("my.gltf"); /// ``` #[derive(Serialize, Deserialize)] pub struct GltfLoaderSettings { @@ -672,7 +672,9 @@ impl GltfLoader { let mut named_materials = >::default(); // Only include materials in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_materials flag if !settings.load_materials.is_empty() { - // NOTE: materials must be loaded after textures because image load() calls will happen before load_with_settings, preventing is_srgb from being set properly + // NOTE: materials must be loaded after textures because image load() calls will happen + // before load_builder().with_settings().load(), preventing is_srgb from being set + // properly. for material in gltf.materials() { let (label, gltf_material) = load_material( &material, diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index 456c61406f2b0..fbe65b112dd4e 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -2555,7 +2555,7 @@ mod test { fn get_or_init_sampler_modifications() { // given some sampler let mut default_sampler = ImageSampler::Default; - // a load_with_settings call wants to customize the descriptor + // a LoadBuilder::with_settings call wants to customize the descriptor let my_sampler_in_a_loader = default_sampler .get_or_init_descriptor() .set_filter(ImageFilterMode::Linear) @@ -2572,7 +2572,7 @@ mod test { fn get_or_init_sampler_anisotropy() { // given some sampler let mut default_sampler = ImageSampler::Default; - // a load_with_settings call wants to customize the descriptor + // a LoadBuilder::with_settings call wants to customize the descriptor let my_sampler_in_a_loader = default_sampler .get_or_init_descriptor() .set_anisotropic_filter(8); diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 57cb09fb22a27..ed69dc0979acc 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -396,12 +396,12 @@ pub struct StandardMaterial { /// # use bevy_image::{Image, ImageLoaderSettings}; /// # /// fn load_normal_map(asset_server: Res) { - /// let normal_handle: Handle = asset_server.load_with_settings( - /// "textures/parallax_example/cube_normal.png", - /// // The normal map texture is in linear color space. Lighting won't look correct - /// // if `is_srgb` is `true`, which is the default. - /// |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - /// ); + /// let normal_handle: Handle = asset_server.load_builder().with_settings( + /// // The normal map texture is in linear color space. Lighting won't look correct + /// // if `is_srgb` is `true`, which is the default. + /// |settings: &mut ImageLoaderSettings| settings.is_srgb = false, + /// ) + /// .load("textures/parallax_example/cube_normal.png"); /// } /// ``` #[texture(9)] diff --git a/crates/bevy_scene2/src/scene_patch.rs b/crates/bevy_scene2/src/scene_patch.rs index bf898822d615b..ef25899e097a4 100644 --- a/crates/bevy_scene2/src/scene_patch.rs +++ b/crates/bevy_scene2/src/scene_patch.rs @@ -151,7 +151,7 @@ impl SceneListPatch { scene_list.register_dependencies(&mut dependencies); let dependencies = dependencies .iter() - .map(|dep| assets.load_erased(dep.type_id, &dep.path)) + .map(|dep| assets.load_builder().load_erased(dep.type_id, &dep.path)) .collect::>(); SceneListPatch { scene_list: Box::new(scene_list), diff --git a/examples/2d/mesh2d_repeated_texture.rs b/examples/2d/mesh2d_repeated_texture.rs index 096a1a5f99d3e..a2545b3002e6b 100644 --- a/examples/2d/mesh2d_repeated_texture.rs +++ b/examples/2d/mesh2d_repeated_texture.rs @@ -32,9 +32,9 @@ fn setup( // settings let image_with_default_sampler = asset_server.load("textures/fantasy_ui_borders/panel-border-010.png"); - let image_with_repeated_sampler = asset_server.load_with_settings( - "textures/fantasy_ui_borders/panel-border-010-repeated.png", - |s: &mut _| { + let image_with_repeated_sampler = asset_server + .load_builder() + .with_settings(|s: &mut _| { *s = ImageLoaderSettings { sampler: ImageSampler::Descriptor(ImageSamplerDescriptor { // rewriting mode to repeat image, @@ -44,8 +44,8 @@ fn setup( }), ..default() } - }, - ); + }) + .load("textures/fantasy_ui_borders/panel-border-010-repeated.png"); // central rectangle with not repeated texture commands.spawn(( diff --git a/examples/2d/tilemap_chunk.rs b/examples/2d/tilemap_chunk.rs index 4d5452e318b88..0f668bed74680 100644 --- a/examples/2d/tilemap_chunk.rs +++ b/examples/2d/tilemap_chunk.rs @@ -45,14 +45,14 @@ fn setup(mut commands: Commands, assets: Res) { TilemapChunk { chunk_size, tile_display_size, - tileset: assets.load_with_settings( - "textures/array_texture.png", - |settings: &mut ImageLoaderSettings| { + tileset: assets + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { // The tileset texture is expected to be an array of tile textures, so we tell the // `ImageLoader` that our texture is composed of 4 stacked tile images. settings.array_layout = Some(ImageArrayLayout::RowCount { rows: 4 }); - }, - ), + }) + .load("textures/array_texture.png"), ..default() }, TilemapChunkTileData(tile_data), diff --git a/examples/2d/tilemap_chunk_orientation.rs b/examples/2d/tilemap_chunk_orientation.rs index 01f49267c64ed..9aed30c8750e2 100644 --- a/examples/2d/tilemap_chunk_orientation.rs +++ b/examples/2d/tilemap_chunk_orientation.rs @@ -61,14 +61,14 @@ fn setup(mut commands: Commands, assets: Res) { TilemapChunk { chunk_size, tile_display_size, - tileset: assets.load_with_settings( - "textures/arrow.png", - |settings: &mut ImageLoaderSettings| { + tileset: assets + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { // The tileset texture is expected to be an array of tile textures, so we tell the // `ImageLoader` that our texture is composed of 2 stacked tile images. settings.array_layout = Some(ImageArrayLayout::RowCount { rows: 2 }); - }, - ), + }) + .load("textures/arrow.png"), alpha_mode: AlphaMode2d::Blend, }, TilemapChunkTileData(tile_data), diff --git a/examples/3d/atmosphere.rs b/examples/3d/atmosphere.rs index c38efee6f96e3..abe5b35fd7d92 100644 --- a/examples/3d/atmosphere.rs +++ b/examples/3d/atmosphere.rs @@ -262,38 +262,40 @@ fn spawn_water( ) { commands.spawn(( Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))), - MeshMaterial3d(water_materials.add(ExtendedMaterial { - base: StandardMaterial { - base_color: BLACK.into(), - perceptual_roughness: 0.0, - ..default() - }, - extension: Water { - normals: asset_server.load_with_settings::( - "textures/water_normals.png", - |settings| { - settings.is_srgb = false; - settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { - address_mode_u: ImageAddressMode::Repeat, - address_mode_v: ImageAddressMode::Repeat, - mag_filter: ImageFilterMode::Linear, - min_filter: ImageFilterMode::Linear, - ..default() - }); + MeshMaterial3d( + water_materials.add(ExtendedMaterial { + base: StandardMaterial { + base_color: BLACK.into(), + perceptual_roughness: 0.0, + ..default() + }, + extension: Water { + normals: asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { + settings.is_srgb = false; + settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { + address_mode_u: ImageAddressMode::Repeat, + address_mode_v: ImageAddressMode::Repeat, + mag_filter: ImageFilterMode::Linear, + min_filter: ImageFilterMode::Linear, + ..default() + }); + }) + .load("textures/water_normals.png"), + // These water settings are just random values to create some + // variety. + settings: WaterSettings { + octave_vectors: [ + vec4(0.080, 0.059, 0.073, -0.062), + vec4(0.153, 0.138, -0.149, -0.195), + ], + octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 500.0, + octave_strengths: vec4(0.16, 0.18, 0.093, 0.044) * 0.2, }, - ), - // These water settings are just random values to create some - // variety. - settings: WaterSettings { - octave_vectors: [ - vec4(0.080, 0.059, 0.073, -0.062), - vec4(0.153, 0.138, -0.149, -0.195), - ], - octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 500.0, - octave_strengths: vec4(0.16, 0.18, 0.093, 0.044) * 0.2, }, - }, - })), + }), + ), Transform::from_scale(Vec3::splat(100.0)), )); } diff --git a/examples/3d/clearcoat.rs b/examples/3d/clearcoat.rs index ef4abe793cb8e..012f143a9fa63 100644 --- a/examples/3d/clearcoat.rs +++ b/examples/3d/clearcoat.rs @@ -100,18 +100,24 @@ fn spawn_car_paint_sphere( commands .spawn(( Mesh3d(sphere.clone()), - MeshMaterial3d(materials.add(StandardMaterial { - clearcoat: 1.0, - clearcoat_perceptual_roughness: 0.1, - normal_map_texture: Some(asset_server.load_with_settings( - "textures/BlueNoise-Normal.png", - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - )), - metallic: 0.9, - perceptual_roughness: 0.5, - base_color: BLUE.into(), - ..default() - })), + MeshMaterial3d( + materials.add(StandardMaterial { + clearcoat: 1.0, + clearcoat_perceptual_roughness: 0.1, + normal_map_texture: Some( + asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { + settings.is_srgb = false; + }) + .load("textures/BlueNoise-Normal.png"), + ), + metallic: 0.9, + perceptual_roughness: 0.5, + base_color: BLUE.into(), + ..default() + }), + ), Transform::from_xyz(-1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)), )) .insert(ExampleSphere); @@ -166,18 +172,24 @@ fn spawn_scratched_gold_ball( commands .spawn(( Mesh3d(sphere.clone()), - MeshMaterial3d(materials.add(StandardMaterial { - clearcoat: 1.0, - clearcoat_perceptual_roughness: 0.3, - clearcoat_normal_texture: Some(asset_server.load_with_settings( - "textures/ScratchedGold-Normal.png", - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - )), - metallic: 0.9, - perceptual_roughness: 0.1, - base_color: GOLD.into(), - ..default() - })), + MeshMaterial3d( + materials.add(StandardMaterial { + clearcoat: 1.0, + clearcoat_perceptual_roughness: 0.3, + clearcoat_normal_texture: Some( + asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { + settings.is_srgb = false; + }) + .load("textures/ScratchedGold-Normal.png"), + ), + metallic: 0.9, + perceptual_roughness: 0.1, + base_color: GOLD.into(), + ..default() + }), + ), Transform::from_xyz(1.0, -1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)), )) .insert(ExampleSphere); diff --git a/examples/3d/clustered_decal_maps.rs b/examples/3d/clustered_decal_maps.rs index 0354b359b1f07..85c0daf9dcd56 100644 --- a/examples/3d/clustered_decal_maps.rs +++ b/examples/3d/clustered_decal_maps.rs @@ -50,14 +50,14 @@ impl FromWorld for AppTextures { let asset_server = world.resource::(); AppTextures { decal_base_color_texture: asset_server.load("branding/bevy_bird_dark.png"), - decal_normal_map_texture: asset_server.load_with_settings( - get_web_asset_url("BevyLogo-Normal.png"), - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - ), - decal_metallic_roughness_map_texture: asset_server.load_with_settings( - get_web_asset_url("BevyLogo-MetallicRoughness.png"), - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - ), + decal_normal_map_texture: asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| settings.is_srgb = false) + .load(get_web_asset_url("BevyLogo-Normal.png")), + decal_metallic_roughness_map_texture: asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| settings.is_srgb = false) + .load(get_web_asset_url("BevyLogo-MetallicRoughness.png")), decal_emissive_texture: asset_server.load(get_web_asset_url("BevyLogo-Emissive.png")), } } @@ -207,10 +207,10 @@ fn spawn_plane_mesh( // Give the plane some texture. // // Note that, as this is a normal map, we must disable sRGB when loading. - let normal_map_texture = asset_server.load_with_settings( - "textures/ScratchedGold-Normal.png", - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - ); + let normal_map_texture = asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| settings.is_srgb = false) + .load("textures/ScratchedGold-Normal.png"); // Actually spawn the plane. commands.spawn(( diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index c08dfe1ff8f50..87fea95aa39d0 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -218,12 +218,14 @@ fn setup_parallax( // The normal map. Note that to generate it in the GIMP image editor, you should // open the depth map, and do Filters → Generic → Normal Map // You should enable the "flip X" checkbox. - let normal_handle = asset_server.load_with_settings( - "textures/parallax_example/cube_normal.png", - // The normal map texture is in linear color space. Lighting won't look correct - // if `is_srgb` is `true`, which is the default. - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - ); + let normal_handle = asset_server + .load_builder() + .with_settings( + // The normal map texture is in linear color space. Lighting won't look correct + // if `is_srgb` is `true`, which is the default. + |settings: &mut ImageLoaderSettings| settings.is_srgb = false, + ) + .load("textures/parallax_example/cube_normal.png"); let mut cube = Mesh::from(Cuboid::new(0.15, 0.15, 0.15)); diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 323d691eeb770..ef7e7cb4c1fb5 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -206,12 +206,14 @@ fn setup( // The normal map. Note that to generate it in the GIMP image editor, you should // open the depth map, and do Filters → Generic → Normal Map // You should enable the "flip X" checkbox. - let normal_handle = asset_server.load_with_settings( - "textures/parallax_example/cube_normal.png", - // The normal map texture is in linear color space. Lighting won't look correct - // if `is_srgb` is `true`, which is the default. - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - ); + let normal_handle = asset_server + .load_builder() + .with_settings( + // The normal map texture is in linear color space. Lighting won't look correct + // if `is_srgb` is `true`, which is the default. + |settings: &mut ImageLoaderSettings| settings.is_srgb = false, + ) + .load("textures/parallax_example/cube_normal.png"); // Camera commands.spawn(( diff --git a/examples/3d/rotate_environment_map.rs b/examples/3d/rotate_environment_map.rs index 9bbf8092ae31a..8d137f8a6df13 100644 --- a/examples/3d/rotate_environment_map.rs +++ b/examples/3d/rotate_environment_map.rs @@ -67,18 +67,24 @@ fn spawn_sphere( ) { commands.spawn(( Mesh3d(sphere_mesh.clone()), - MeshMaterial3d(materials.add(StandardMaterial { - clearcoat: 1.0, - clearcoat_perceptual_roughness: 0.3, - clearcoat_normal_texture: Some(asset_server.load_with_settings( - "textures/ScratchedGold-Normal.png", - |settings: &mut ImageLoaderSettings| settings.is_srgb = false, - )), - metallic: 0.9, - perceptual_roughness: 0.1, - base_color: GOLD.into(), - ..default() - })), + MeshMaterial3d( + materials.add(StandardMaterial { + clearcoat: 1.0, + clearcoat_perceptual_roughness: 0.3, + clearcoat_normal_texture: Some( + asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { + settings.is_srgb = false; + }) + .load("textures/ScratchedGold-Normal.png"), + ), + metallic: 0.9, + perceptual_roughness: 0.1, + base_color: GOLD.into(), + ..default() + }), + ), Transform::from_xyz(0.0, 0.0, 0.0).with_scale(Vec3::splat(1.25)), )); } diff --git a/examples/3d/scrolling_fog.rs b/examples/3d/scrolling_fog.rs index 9dbdc050cf04e..ed291d62f12b2 100644 --- a/examples/3d/scrolling_fog.rs +++ b/examples/3d/scrolling_fog.rs @@ -89,20 +89,23 @@ fn setup( // Load a repeating 3d noise texture. Make sure to set ImageAddressMode to Repeat // so that the texture wraps around as the density texture offset is moved along. // Also set ImageFilterMode to Linear so that the fog isn't pixelated. - let noise_texture = assets.load_with_settings("volumes/fog_noise.ktx2", |settings: &mut _| { - *settings = ImageLoaderSettings { - sampler: ImageSampler::Descriptor(ImageSamplerDescriptor { - address_mode_u: ImageAddressMode::Repeat, - address_mode_v: ImageAddressMode::Repeat, - address_mode_w: ImageAddressMode::Repeat, - mag_filter: ImageFilterMode::Linear, - min_filter: ImageFilterMode::Linear, - mipmap_filter: ImageFilterMode::Linear, + let noise_texture = assets + .load_builder() + .with_settings(|settings: &mut _| { + *settings = ImageLoaderSettings { + sampler: ImageSampler::Descriptor(ImageSamplerDescriptor { + address_mode_u: ImageAddressMode::Repeat, + address_mode_v: ImageAddressMode::Repeat, + address_mode_w: ImageAddressMode::Repeat, + mag_filter: ImageFilterMode::Linear, + min_filter: ImageFilterMode::Linear, + mipmap_filter: ImageFilterMode::Linear, + ..default() + }), ..default() - }), - ..default() - } - }); + } + }) + .load("volumes/fog_noise.ktx2"); // Spawn a FogVolume and use the repeating noise texture as its density texture. commands.spawn(( diff --git a/examples/3d/solari.rs b/examples/3d/solari.rs index 03cef83045db7..c976807bd7c4b 100644 --- a/examples/3d/solari.rs +++ b/examples/3d/solari.rs @@ -240,21 +240,23 @@ fn setup_many_lights( commands .spawn(( RaytracingMesh3d(plane_mesh.clone()), - MeshMaterial3d(materials.add(StandardMaterial { - base_color_texture: Some( - asset_server.load_with_settings::( - "textures/uv_checker_bw.png", - |settings| { - settings - .sampler - .get_or_init_descriptor() - .set_address_mode(ImageAddressMode::Repeat); - }, + MeshMaterial3d( + materials.add(StandardMaterial { + base_color_texture: Some( + asset_server + .load_builder() + .with_settings::(|settings| { + settings + .sampler + .get_or_init_descriptor() + .set_address_mode(ImageAddressMode::Repeat); + }) + .load("textures/uv_checker_bw.png"), ), - ), - perceptual_roughness: 0.0, - ..default() - })), + perceptual_roughness: 0.0, + ..default() + }), + ), )) .insert_if(Mesh3d(plane_mesh), || args.pathtracer != Some(true)); diff --git a/examples/3d/ssr.rs b/examples/3d/ssr.rs index 2ed5e8d9b5856..71937f416bcff 100644 --- a/examples/3d/ssr.rs +++ b/examples/3d/ssr.rs @@ -369,38 +369,40 @@ fn spawn_water( ) { commands.spawn(( Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(1.0)))), - MeshMaterial3d(water_materials.add(ExtendedMaterial { - base: StandardMaterial { - base_color: BLACK.into(), - perceptual_roughness: 0.09, - ..default() - }, - extension: Water { - normals: asset_server.load_with_settings::( - "textures/water_normals.png", - |settings| { - settings.is_srgb = false; - settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { - address_mode_u: ImageAddressMode::Repeat, - address_mode_v: ImageAddressMode::Repeat, - mag_filter: ImageFilterMode::Linear, - min_filter: ImageFilterMode::Linear, - ..default() - }); + MeshMaterial3d( + water_materials.add(ExtendedMaterial { + base: StandardMaterial { + base_color: BLACK.into(), + perceptual_roughness: 0.09, + ..default() + }, + extension: Water { + normals: asset_server + .load_builder() + .with_settings::(|settings| { + settings.is_srgb = false; + settings.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor { + address_mode_u: ImageAddressMode::Repeat, + address_mode_v: ImageAddressMode::Repeat, + mag_filter: ImageFilterMode::Linear, + min_filter: ImageFilterMode::Linear, + ..default() + }); + }) + .load("textures/water_normals.png"), + // These water settings are just random values to create some + // variety. + settings: WaterSettings { + octave_vectors: [ + vec4(0.080, 0.059, 0.073, -0.062), + vec4(0.153, 0.138, -0.149, -0.195), + ], + octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 5.0, + octave_strengths: vec4(0.16, 0.18, 0.093, 0.044), }, - ), - // These water settings are just random values to create some - // variety. - settings: WaterSettings { - octave_vectors: [ - vec4(0.080, 0.059, 0.073, -0.062), - vec4(0.153, 0.138, -0.149, -0.195), - ], - octave_scales: vec4(1.0, 2.1, 7.9, 14.9) * 5.0, - octave_strengths: vec4(0.16, 0.18, 0.093, 0.044), }, - }, - })), + }), + ), Transform::from_scale(Vec3::splat(100.0)), WaterModel, )); diff --git a/examples/asset/alter_mesh.rs b/examples/asset/alter_mesh.rs index b96ec8ce55bd9..400944997a588 100644 --- a/examples/asset/alter_mesh.rs +++ b/examples/asset/alter_mesh.rs @@ -55,34 +55,38 @@ fn setup( // In normal use, you can call `asset_server.load`, however see below for an explanation of // `RenderAssetUsages`. - let left_shape_model = asset_server.load_with_settings( - GltfAssetLabel::Primitive { - mesh: 0, - // This field stores an index to this primitive in its parent mesh. In this case, we - // want the first one. You might also have seen the syntax: + let left_shape_model = asset_server + .load_builder() + .with_settings( + // `RenderAssetUsages::all()` is already the default, so the line below could be omitted. + // It's helpful to know it exists, however. // - // models/cube/cube.gltf#Scene0 + // `RenderAssetUsages` tell Bevy whether to keep the data around: + // - for the GPU (`RenderAssetUsages::RENDER_WORLD`), + // - for the CPU (`RenderAssetUsages::MAIN_WORLD`), + // - or both. + // `RENDER_WORLD` is necessary to render the mesh, `MAIN_WORLD` is necessary to inspect + // and modify the mesh (via `ResMut>`). // - // which accomplishes the same thing. - primitive: 0, - } - .from_asset(left_shape.get_model_path()), - // `RenderAssetUsages::all()` is already the default, so the line below could be omitted. - // It's helpful to know it exists, however. - // - // `RenderAssetUsages` tell Bevy whether to keep the data around: - // - for the GPU (`RenderAssetUsages::RENDER_WORLD`), - // - for the CPU (`RenderAssetUsages::MAIN_WORLD`), - // - or both. - // `RENDER_WORLD` is necessary to render the mesh, `MAIN_WORLD` is necessary to inspect - // and modify the mesh (via `ResMut>`). - // - // Since most games will not need to modify meshes at runtime, many developers opt to pass - // only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the mesh in - // RAM. For this example however, this would not work, as we need to inspect and modify the - // mesh at runtime. - |settings: &mut GltfLoaderSettings| settings.load_meshes = RenderAssetUsages::all(), - ); + // Since most games will not need to modify meshes at runtime, many developers opt to pass + // only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the mesh in + // RAM. For this example however, this would not work, as we need to inspect and modify the + // mesh at runtime. + |settings: &mut GltfLoaderSettings| settings.load_meshes = RenderAssetUsages::all(), + ) + .load( + GltfAssetLabel::Primitive { + mesh: 0, + // This field stores an index to this primitive in its parent mesh. In this case, we + // want the first one. You might also have seen the syntax: + // + // models/cube/cube.gltf#Scene0 + // + // which accomplishes the same thing. + primitive: 0, + } + .from_asset(left_shape.get_model_path()), + ); // Here, we rely on the default loader settings to achieve a similar result to the above. let right_shape_model = asset_server.load( diff --git a/examples/asset/alter_sprite.rs b/examples/asset/alter_sprite.rs index e489af12c85dd..e0a5922285c12 100644 --- a/examples/asset/alter_sprite.rs +++ b/examples/asset/alter_sprite.rs @@ -50,24 +50,26 @@ fn setup(mut commands: Commands, asset_server: Res) { let bird_right = Bird::Normal; commands.spawn(Camera2d); - let texture_left = asset_server.load_with_settings( - bird_left.get_texture_path(), - // `RenderAssetUsages::all()` is already the default, so the line below could be omitted. - // It's helpful to know it exists, however. - // - // `RenderAssetUsages` tell Bevy whether to keep the data around: - // - for the GPU (`RenderAssetUsages::RENDER_WORLD`), - // - for the CPU (`RenderAssetUsages::MAIN_WORLD`), - // - or both. - // `RENDER_WORLD` is necessary to render the image, `MAIN_WORLD` is necessary to inspect - // and modify the image (via `ResMut>`). - // - // Since most games will not need to modify textures at runtime, many developers opt to pass - // only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the image in - // RAM. For this example however, this would not work, as we need to inspect and modify the - // image at runtime. - |settings: &mut ImageLoaderSettings| settings.asset_usage = RenderAssetUsages::all(), - ); + let texture_left = asset_server + .load_builder() + .with_settings( + // `RenderAssetUsages::all()` is already the default, so the line below could be omitted. + // It's helpful to know it exists, however. + // + // `RenderAssetUsages` tell Bevy whether to keep the data around: + // - for the GPU (`RenderAssetUsages::RENDER_WORLD`), + // - for the CPU (`RenderAssetUsages::MAIN_WORLD`), + // - or both. + // `RENDER_WORLD` is necessary to render the image, `MAIN_WORLD` is necessary to inspect + // and modify the image (via `ResMut>`). + // + // Since most games will not need to modify textures at runtime, many developers opt to pass + // only `RENDER_WORLD`. This is more memory efficient, as we don't need to keep the image in + // RAM. For this example however, this would not work, as we need to inspect and modify the + // image at runtime. + |settings: &mut ImageLoaderSettings| settings.asset_usage = RenderAssetUsages::all(), + ) + .load(bird_left.get_texture_path()); commands.spawn(( Name::new("Bird Left"), diff --git a/examples/asset/asset_saving.rs b/examples/asset/asset_saving.rs index 6c466a9a2ab76..6f06556f899c0 100644 --- a/examples/asset/asset_saving.rs +++ b/examples/asset/asset_saving.rs @@ -104,10 +104,12 @@ F5 - Save image" .into(), )); - let handle = - asset_server.load_with_settings(ASSET_PATH, |settings: &mut ImageLoaderSettings| { + let handle = asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { settings.sampler = ImageSampler::nearest(); - }); + }) + .load(ASSET_PATH); commands.spawn(( Sprite { image: handle.clone(), diff --git a/examples/asset/asset_settings.rs b/examples/asset/asset_settings.rs index 9f4c1fe507dfe..dd0b47ec1f9f0 100644 --- a/examples/asset/asset_settings.rs +++ b/examples/asset/asset_settings.rs @@ -1,4 +1,4 @@ -//! This example demonstrates the usage of '.meta' files and [`AssetServer::load_with_settings`] to override the default settings for loading an asset +//! This example demonstrates the usage of '.meta' files and [`LoadBuilder::with_settings`](bevy::asset::LoadBuilder::with_settings) to override the default settings for loading an asset use bevy::{ image::{ImageLoaderSettings, ImageSampler}, @@ -50,7 +50,7 @@ fn setup(mut commands: Commands, asset_server: Res) { Transform::from_xyz(100.0, 0.0, 0.0), )); - // Another option is to use the AssetServers load_with_settings function. + // Another option is to use the LoadBuilder::with_settings function. // With this you can specify the same settings upon loading your asset with a // couple of differences. A big one is that you aren't required to set *every* // setting, just modify the ones that you need. It works by passing in a function @@ -62,12 +62,12 @@ fn setup(mut commands: Commands, asset_server: Res) { // same one as without a .meta file. commands.spawn(( Sprite { - image: asset_server.load_with_settings( - "bevy_pixel_dark_with_settings.png", - |settings: &mut ImageLoaderSettings| { + image: asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { settings.sampler = ImageSampler::nearest(); - }, - ), + }) + .load("bevy_pixel_dark_with_settings.png"), custom_size: Some(Vec2 { x: 160.0, y: 120.0 }), ..Default::default() }, diff --git a/examples/asset/multi_asset_sync.rs b/examples/asset/multi_asset_sync.rs index 1b87e394a5fba..1e52d0061b823 100644 --- a/examples/asset/multi_asset_sync.rs +++ b/examples/asset/multi_asset_sync.rs @@ -64,8 +64,8 @@ pub struct OneHundredThings([Handle; 100]); #[derive(Debug, Resource, Deref)] pub struct AssetBarrier(Arc); -/// This guard is to be acquired by [`AssetServer::load_acquire`] -/// and dropped once finished. +/// This guard is to be acquired by +/// [`LoadBuilder::with_guard`](bevy::asset::LoadBuilder::with_guard) and dropped once finished. #[derive(Debug, Deref)] pub struct AssetBarrierGuard(Arc); @@ -143,13 +143,16 @@ impl Drop for AssetBarrierGuard { fn setup_assets(mut commands: Commands, asset_server: Res) { let (barrier, guard) = AssetBarrier::new(); - commands.insert_resource(OneHundredThings(std::array::from_fn(|i| match i % 5 { - 0 => asset_server.load_acquire("models/GolfBall/GolfBall.glb", guard.clone()), - 1 => asset_server.load_acquire("models/AlienCake/alien.glb", guard.clone()), - 2 => asset_server.load_acquire("models/AlienCake/cakeBirthday.glb", guard.clone()), - 3 => asset_server.load_acquire("models/FlightHelmet/FlightHelmet.gltf", guard.clone()), - 4 => asset_server.load_acquire("models/torus/torus.gltf", guard.clone()), - _ => unreachable!(), + commands.insert_resource(OneHundredThings(std::array::from_fn(|i| { + let builder = asset_server.load_builder().with_guard(guard.clone()); + match i % 5 { + 0 => builder.load("models/GolfBall/GolfBall.glb"), + 1 => builder.load("models/AlienCake/alien.glb"), + 2 => builder.load("models/AlienCake/cakeBirthday.glb"), + 3 => builder.load("models/FlightHelmet/FlightHelmet.gltf"), + 4 => builder.load("models/torus/torus.gltf"), + _ => unreachable!(), + } }))); let future = barrier.wait_async(); commands.insert_resource(barrier); diff --git a/examples/asset/repeated_texture.rs b/examples/asset/repeated_texture.rs index 1f003cd083b69..55ea9abe94f08 100644 --- a/examples/asset/repeated_texture.rs +++ b/examples/asset/repeated_texture.rs @@ -36,27 +36,31 @@ fn setup( // left cube with repeated texture commands.spawn(( Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))), - MeshMaterial3d(materials.add(StandardMaterial { - base_color_texture: Some(asset_server.load_with_settings( - "textures/fantasy_ui_borders/panel-border-010-repeated.png", - |s: &mut _| { - *s = ImageLoaderSettings { - sampler: ImageSampler::Descriptor(ImageSamplerDescriptor { - // rewriting mode to repeat image, - address_mode_u: ImageAddressMode::Repeat, - address_mode_v: ImageAddressMode::Repeat, - ..default() - }), - ..default() - } - }, - )), + MeshMaterial3d( + materials.add(StandardMaterial { + base_color_texture: Some( + asset_server + .load_builder() + .with_settings(|s: &mut _| { + *s = ImageLoaderSettings { + sampler: ImageSampler::Descriptor(ImageSamplerDescriptor { + // rewriting mode to repeat image, + address_mode_u: ImageAddressMode::Repeat, + address_mode_v: ImageAddressMode::Repeat, + ..default() + }), + ..default() + } + }) + .load("textures/fantasy_ui_borders/panel-border-010-repeated.png"), + ), - // uv_transform used here for proportions only, but it is full Affine2 - // that's why you can use rotation and shift also - uv_transform: Affine2::from_scale(Vec2::new(2., 3.)), - ..default() - })), + // uv_transform used here for proportions only, but it is full Affine2 + // that's why you can use rotation and shift also + uv_transform: Affine2::from_scale(Vec2::new(2., 3.)), + ..default() + }), + ), Transform::from_xyz(-1.5, 0.0, 0.0), )); diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index e17763dde132a..e218ab90fba56 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -36,14 +36,14 @@ fn setup( asset_server: Res, ) { // Load the texture. - let array_texture = asset_server.load_with_settings( - "textures/array_texture.png", - |settings: &mut ImageLoaderSettings| { + let array_texture = asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { settings.array_layout = Some(ImageArrayLayout::RowCount { rows: TEXTURE_COUNT, }); - }, - ); + }) + .load("textures/array_texture.png"); // light commands.spawn(( diff --git a/examples/testbed/3d.rs b/examples/testbed/3d.rs index 4677833972dde..1ef53a725fa1e 100644 --- a/examples/testbed/3d.rs +++ b/examples/testbed/3d.rs @@ -450,15 +450,17 @@ mod gltf_coordinate_conversion { commands .spawn(( - WorldAssetRoot(asset_server.load_with_settings( - GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb"), - |s: &mut GltfLoaderSettings| { - s.convert_coordinates = Some(GltfConvertCoordinates { - rotate_scene_entity: true, - rotate_meshes: true, - }); - }, - )), + WorldAssetRoot( + asset_server + .load_builder() + .with_settings(|s: &mut GltfLoaderSettings| { + s.convert_coordinates = Some(GltfConvertCoordinates { + rotate_scene_entity: true, + rotate_meshes: true, + }); + }) + .load(GltfAssetLabel::Scene(0).from_asset("models/Faces/faces.glb")), + ), DespawnOnExit(CURRENT_SCENE), )) .observe(show_aabbs); diff --git a/examples/ui/images/ui_texture_slice_flip_and_tile.rs b/examples/ui/images/ui_texture_slice_flip_and_tile.rs index e530f16b3db58..88ece938b22b5 100644 --- a/examples/ui/images/ui_texture_slice_flip_and_tile.rs +++ b/examples/ui/images/ui_texture_slice_flip_and_tile.rs @@ -15,13 +15,13 @@ fn main() { } fn setup(mut commands: Commands, asset_server: Res) { - let image = asset_server.load_with_settings( - "textures/fantasy_ui_borders/numbered_slices.png", - |settings: &mut ImageLoaderSettings| { + let image = asset_server + .load_builder() + .with_settings(|settings: &mut ImageLoaderSettings| { // Need to use nearest filtering to avoid bleeding between the slices with tiling settings.sampler = ImageSampler::nearest(); - }, - ); + }) + .load("textures/fantasy_ui_borders/numbered_slices.png"); let slicer = TextureSlicer { // `numbered_slices.png` is 48 pixels square. `BorderRect::square(16.)` insets the slicing line from each edge by 16 pixels, resulting in nine slices that are each 16 pixels square.