diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 9fa8eb4381485..0a962a373c575 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -387,10 +387,7 @@ impl Assets { pub fn add(&mut self, asset: impl Into) -> Handle { let index = self.dense_storage.allocator.reserve(); self.insert_with_index(index, asset.into()).unwrap(); - Handle::Strong( - self.handle_provider - .get_handle(index.into(), false, None, None), - ) + Handle::Strong(self.handle_provider.get_handle(index.into(), false, None)) } /// Upgrade an `AssetId` into a strong `Handle` that will prevent asset drop. @@ -408,7 +405,7 @@ impl Assets { AssetId::Uuid { uuid } => uuid.into(), }; Some(Handle::Strong( - self.handle_provider.get_handle(index, false, None, None), + self.handle_provider.get_handle(index, false, None), )) } diff --git a/crates/bevy_asset/src/direct_access_ext.rs b/crates/bevy_asset/src/direct_access_ext.rs index 792d523a30063..d29df80b0bfdc 100644 --- a/crates/bevy_asset/src/direct_access_ext.rs +++ b/crates/bevy_asset/src/direct_access_ext.rs @@ -14,10 +14,10 @@ pub trait DirectAssetAccessExt { fn load_asset<'a, A: Asset>(&self, path: impl Into>) -> Handle; /// Load an asset with settings, similarly to [`AssetServer::load_with_settings`]. - fn load_asset_with_settings<'a, A: Asset, S: Settings>( + fn load_asset_with_settings<'a, A: Asset, S: Settings + serde::Serialize + Default>( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, ) -> Handle; } impl DirectAssetAccessExt for World { @@ -40,10 +40,10 @@ impl DirectAssetAccessExt for World { /// /// # Panics /// If `self` doesn't have an [`AssetServer`] resource initialized yet. - fn load_asset_with_settings<'a, A: Asset, S: Settings>( + fn load_asset_with_settings<'a, A: Asset, S: Settings + serde::Serialize + Default>( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, ) -> Handle { self.resource::() .load_with_settings(path, settings) diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index a78c43ffdde30..a8f2158e49e3e 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -1,7 +1,4 @@ -use crate::{ - meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId, - UntypedAssetId, -}; +use crate::{Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId, UntypedAssetId}; use alloc::sync::Arc; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; use core::{ @@ -44,7 +41,7 @@ impl AssetHandleProvider { /// [`UntypedHandle`] will match the [`Asset`] [`TypeId`] assigned to this [`AssetHandleProvider`]. pub fn reserve_handle(&self) -> UntypedHandle { let index = self.allocator.reserve(); - UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None)) + UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None)) } pub(crate) fn get_handle( @@ -52,12 +49,10 @@ impl AssetHandleProvider { id: InternalAssetId, asset_server_managed: bool, path: Option>, - meta_transform: Option, ) -> Arc { Arc::new(StrongHandle { id: id.untyped(self.type_id), drop_sender: self.drop_sender.clone(), - meta_transform, path, asset_server_managed, }) @@ -67,15 +62,9 @@ impl AssetHandleProvider { &self, asset_server_managed: bool, path: Option>, - meta_transform: Option, ) -> Arc { let index = self.allocator.reserve(); - self.get_handle( - InternalAssetId::Index(index), - asset_server_managed, - path, - meta_transform, - ) + self.get_handle(InternalAssetId::Index(index), asset_server_managed, path) } } @@ -86,10 +75,6 @@ pub struct StrongHandle { pub(crate) id: UntypedAssetId, pub(crate) asset_server_managed: bool, pub(crate) path: Option>, - /// Modifies asset meta. This is stored on the handle because it is: - /// 1. configuration tied to the lifetime of a specific asset load - /// 2. configuration that must be repeatable when the asset is hot-reloaded - pub(crate) meta_transform: Option, pub(crate) drop_sender: Sender, } @@ -381,16 +366,6 @@ impl UntypedHandle { pub fn try_typed(self) -> Result, UntypedAssetConversionError> { Handle::try_from(self) } - - /// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform - /// associated with it. - #[inline] - pub fn meta_transform(&self) -> Option<&MetaTransform> { - match self { - UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(), - UntypedHandle::Weak(_) => None, - } - } } impl PartialEq for UntypedHandle { diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 8f4863b885c68..0a1cfccabe40a 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -32,7 +32,7 @@ pub trait AssetLoader: Send + Sync + 'static { /// The top level [`Asset`] loaded by this [`AssetLoader`]. type Asset: Asset; /// The settings type used by this [`AssetLoader`]. - type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; + type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a> + Clone; /// The type of [error](`std::error::Error`) which could be encountered by this loader. type Error: Into>; /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`]. @@ -432,9 +432,7 @@ impl<'a> LoadContext<'a> { let label = label.into(); let loaded_asset: ErasedLoadedAsset = loaded_asset.into(); let labeled_path = self.asset_path.clone().with_label(label.clone()); - let handle = self - .asset_server - .get_or_create_path_handle(labeled_path, None); + let handle = self.asset_server.get_or_create_path_handle(labeled_path); self.labeled_assets.insert( label, LabeledAsset { @@ -518,7 +516,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); self.dependencies.insert(handle.id().untyped()); handle } diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 13bea2b71dc2a..160463b3c280c 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -2,10 +2,8 @@ //! [`LoadContext::loader`]. use crate::{ - io::Reader, - meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings}, - Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext, - LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, + io::Reader, Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, + LoadContext, LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, }; use alloc::{borrow::ToOwned, boxed::Box, sync::Arc}; use core::any::TypeId; @@ -117,7 +115,6 @@ impl ReaderRef<'_> { /// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave pub struct NestedLoader<'ctx, 'builder, T, M> { load_context: &'builder mut LoadContext<'ctx>, - meta_transform: Option, typing: T, mode: M, } @@ -168,7 +165,6 @@ impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> { pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self { NestedLoader { load_context, - meta_transform: None, typing: StaticTyped(()), mode: Deferred(()), } @@ -176,33 +172,6 @@ impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> { } impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> { - fn with_transform( - mut self, - transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static, - ) -> Self { - if let Some(prev_transform) = self.meta_transform { - self.meta_transform = Some(Box::new(move |meta| { - prev_transform(meta); - transform(meta); - })); - } else { - self.meta_transform = Some(Box::new(transform)); - } - self - } - - /// Configure the settings used to load the asset. - /// - /// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log - /// and the asset load will fail. - #[must_use] - pub fn with_settings( - self, - settings: impl Fn(&mut S) + Send + Sync + 'static, - ) -> Self { - self.with_transform(move |meta| meta_transform_settings(meta, &settings)) - } - // convert between `T`s /// When [`load`]ing, you must pass in the asset type as a type parameter @@ -218,7 +187,6 @@ impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'bui pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> { NestedLoader { load_context: self.load_context, - meta_transform: self.meta_transform, typing: StaticTyped(()), mode: self.mode, } @@ -235,7 +203,6 @@ impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'bui ) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> { NestedLoader { load_context: self.load_context, - meta_transform: self.meta_transform, typing: DynamicTyped { asset_type_id }, mode: self.mode, } @@ -249,7 +216,6 @@ impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'bui pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> { NestedLoader { load_context: self.load_context, - meta_transform: self.meta_transform, typing: UnknownTyped(()), mode: self.mode, } @@ -264,7 +230,6 @@ impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'bui pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> { NestedLoader { load_context: self.load_context, - meta_transform: self.meta_transform, typing: self.typing, mode: Deferred(()), } @@ -281,7 +246,6 @@ impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'bui pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> { NestedLoader { load_context: self.load_context, - meta_transform: self.meta_transform, typing: self.typing, mode: Immediate { reader: None }, } @@ -305,16 +269,13 @@ impl NestedLoader<'_, '_, StaticTyped, Deferred> { pub fn load<'c, A: Asset>(self, path: impl Into>) -> Handle { let path = path.into().to_owned(); let handle = if self.load_context.should_load_dependencies { - self.load_context.asset_server.load_with_meta_transform( - path, - self.meta_transform, - (), - true, - ) + self.load_context + .asset_server + .load_with_meta_transform(path, (), true) } else { self.load_context .asset_server - .get_or_create_path_handle(path, None) + .get_or_create_path_handle(path) }; self.load_context.dependencies.insert(handle.id().untyped()); handle @@ -334,20 +295,11 @@ impl NestedLoader<'_, '_, DynamicTyped, Deferred> { let handle = if self.load_context.should_load_dependencies { self.load_context .asset_server - .load_erased_with_meta_transform( - path, - self.typing.asset_type_id, - self.meta_transform, - (), - ) + .load_erased_with_meta_transform(path, self.typing.asset_type_id, ()) } else { self.load_context .asset_server - .get_or_create_path_handle_erased( - path, - self.typing.asset_type_id, - self.meta_transform, - ) + .get_or_create_path_handle_erased(path, self.typing.asset_type_id) }; self.load_context.dependencies.insert(handle.id()); handle @@ -364,11 +316,11 @@ 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) } else { self.load_context .asset_server - .get_or_create_path_handle(path, self.meta_transform) + .get_or_create_path_handle(path) }; self.load_context.dependencies.insert(handle.id().untyped()); handle @@ -428,8 +380,8 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> (meta, loader, ReaderRef::Boxed(reader)) }; - if let Some(meta_transform) = self.meta_transform { - meta_transform(&mut *meta); + if let Some(settings) = path.settings() { + meta.apply_settings(settings.value()); } let asset = self diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs index 0e972261198cc..a8e1bc258f213 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -1,5 +1,4 @@ use alloc::{ - boxed::Box, string::{String, ToString}, vec::Vec, }; @@ -11,10 +10,8 @@ use crate::{ use downcast_rs::{impl_downcast, Downcast}; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; -use tracing::error; pub const META_FORMAT_VERSION: &str = "1.0"; -pub type MetaTransform = Box; /// Asset metadata that informs how an [`Asset`] should be handled by the asset system. /// @@ -125,8 +122,8 @@ pub struct ProcessedInfoMinimal { pub trait AssetMetaDyn: Downcast + Send + Sync { /// Returns a reference to the [`AssetLoader`] settings, if they exist. fn loader_settings(&self) -> Option<&dyn Settings>; - /// Returns a mutable reference to the [`AssetLoader`] settings, if they exist. - fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings>; + /// XXX TODO: Document. + fn apply_settings(&mut self, settings: &dyn Settings); /// Serializes the internal [`AssetMeta`]. fn serialize(&self) -> Vec; /// Returns a reference to the [`ProcessedInfo`] if it exists. @@ -143,11 +140,18 @@ impl AssetMetaDyn for AssetMeta { None } } - fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings> { + fn apply_settings(&mut self, new_settings: &dyn Settings) { if let AssetAction::Load { settings, .. } = &mut self.asset { - Some(settings) - } else { - None + if let Some(new_settings) = new_settings.downcast_ref::<::Settings>() + { + *settings = new_settings.clone(); + } else { + tracing::error!( + "Configured settings type {} does not match AssetLoader settings type {}", + new_settings.debug_type_name(), + core::any::type_name::<::Settings>(), + ); + } } } fn serialize(&self) -> Vec { @@ -168,9 +172,19 @@ impl_downcast!(AssetMetaDyn); /// Settings used by the asset system, such as by [`AssetLoader`], [`Process`], and [`AssetSaver`] /// /// [`AssetSaver`]: crate::saver::AssetSaver -pub trait Settings: Downcast + Send + Sync + 'static {} +pub trait Settings: Downcast + Send + Sync + 'static { + // XXX TODO: Better solution. Currently used by apply_settings to print error. + fn debug_type_name(&self) -> &'static str; +} -impl Settings for T where T: Send + Sync {} +impl Settings for T +where + T: Send + Sync, +{ + fn debug_type_name(&self) -> &'static str { + core::any::type_name::() + } +} impl_downcast!(Settings); @@ -216,28 +230,6 @@ impl AssetLoader for () { } } -pub(crate) fn meta_transform_settings( - meta: &mut dyn AssetMetaDyn, - settings: &(impl Fn(&mut S) + Send + Sync + 'static), -) { - if let Some(loader_settings) = meta.loader_settings_mut() { - if let Some(loader_settings) = loader_settings.downcast_mut::() { - settings(loader_settings); - } else { - error!( - "Configured settings type {} does not match AssetLoader settings type", - core::any::type_name::(), - ); - } - } -} - -pub(crate) fn loader_settings_meta_transform( - settings: impl Fn(&mut S) + Send + Sync + 'static, -) -> MetaTransform { - Box::new(move |meta| meta_transform_settings(meta, &settings)) -} - pub type AssetHash = [u8; 32]; /// NOTE: changing the hashing logic here is a _breaking change_ that requires a [`META_FORMAT_VERSION`] bump. diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index ad127812dcfcf..854ab202c28b2 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -1,19 +1,101 @@ -use crate::io::AssetSourceId; +use crate::{io::AssetSourceId, meta::Settings}; use alloc::{ borrow::ToOwned, + boxed::Box, string::{String, ToString}, + sync::Arc, }; use atomicow::CowArc; +use bevy_platform::hash::FixedHasher; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use core::{ + any::TypeId, fmt::{Debug, Display}, - hash::Hash, + hash::{BuildHasher, Hash, Hasher}, ops::Deref, }; use serde::{de::Visitor, Deserialize, Serialize}; use std::path::{Path, PathBuf}; use thiserror::Error; +/// Identifies an erased settings value. This is used to compare and hash values +/// without having to read the underlying value. +/// +/// XXX TODO: Reconsider auto-deriving Hash. This currently means we hash our +/// own hash + type id. Could be avoided if hash includes the type id. This +/// also starts to look suspiciously like `bevy_platform::Hashed`. +#[derive(Eq, PartialEq, Hash, Copy, Clone)] +pub struct ErasedSettingsId { + // XXX TODO: Should we store the type id separately or just include it in the + // hash? Separately might be nicer for debugging. + type_id: TypeId, + hash: u64, +} + +impl Display for ErasedSettingsId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + // XXX TODO: Reconsider formatting. Also we're using Debug for type_id. + write!(f, "{:?}/{}", self.type_id, self.hash) + } +} + +/// An erased settings value and its id. +pub struct ErasedSettings { + value: Box, + id: ErasedSettingsId, +} + +impl ErasedSettings { + pub fn new(settings: S) -> ErasedSettings { + // Hash by serializing to RON. This means settings are not required to + // implement Hash. + // XXX TODO: Hashing via RON serialization is very debatable. + // XXX TODO: Allocating a string is bad. Should consider a small vec or + // hashing directly through the serializer. + let hash = FixedHasher.hash_one(ron::ser::to_string(&settings).expect("XXX TODO?")); + + ErasedSettings { + value: Box::new(settings), + id: ErasedSettingsId { + type_id: TypeId::of::(), + hash, + }, + } + } +} + +impl<'a> ErasedSettings { + pub fn value(&'a self) -> &'a dyn Settings { + // The `deref` means we're returning the underlying type's implementation + // of Settings - not Box's wrapper. This is required so that the value + // can be downcast by `AssetMeta::apply_settings`. + // + // XXX TODO: Maybe this should all be done in `AssetMeta::apply_settings`? + // Then everything's in one place. + self.value.deref() + } +} + +impl Hash for ErasedSettings { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for ErasedSettings { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for ErasedSettings {} + +impl Display for ErasedSettings { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.id.fmt(f) + } +} + /// Represents a path to an asset in a "virtual filesystem". /// /// Asset paths consist of three main parts: @@ -58,6 +140,10 @@ pub struct AssetPath<'a> { source: AssetSourceId<'a>, path: CowArc<'a, Path>, label: Option>, + // XXX TODO: This is an Arc for now to simplify the implementation. Should + // consider changing to CowArc, although I'm not sure if that actually has + // any benefits. + settings: Option>, } impl<'a> Debug for AssetPath<'a> { @@ -75,6 +161,11 @@ impl<'a> Display for AssetPath<'a> { if let Some(label) = &self.label { write!(f, "#{label}")?; } + // XXX TODO: This might need a rethink as I'm not sure if the output + // needs to be parseable. Also see comments on ErasedSettingsId::fmt. + if let Some(settings) = &self.settings { + write!(f, " (settings: {settings})")?; + } Ok(()) } } @@ -131,6 +222,7 @@ impl<'a> AssetPath<'a> { }, path: CowArc::Borrowed(path), label: label.map(CowArc::Borrowed), + settings: None, // XXX TODO: Do we need to document this behavior? }) } @@ -230,6 +322,7 @@ impl<'a> AssetPath<'a> { path: CowArc::Borrowed(path), source: AssetSourceId::Default, label: None, + settings: None, } } @@ -258,6 +351,12 @@ impl<'a> AssetPath<'a> { self.path.deref() } + /// XXX TODO: Docs. + #[inline] + pub fn settings(&self) -> Option<&ErasedSettings> { + self.settings.as_deref() + } + /// Gets the path to the asset in the "virtual filesystem" without a label (if a label is currently set). #[inline] pub fn without_label(&self) -> AssetPath<'_> { @@ -265,6 +364,7 @@ impl<'a> AssetPath<'a> { source: self.source.clone(), path: self.path.clone(), label: None, + settings: self.settings.clone(), } } @@ -288,6 +388,7 @@ impl<'a> AssetPath<'a> { source: self.source, path: self.path, label: Some(label.into()), + settings: self.settings, } } @@ -299,6 +400,32 @@ impl<'a> AssetPath<'a> { source: source.into(), path: self.path, label: self.label, + settings: self.settings, + } + } + + #[inline] + pub fn with_settings(self, settings: S) -> AssetPath<'a> { + AssetPath { + source: self.source, + path: self.path, + label: self.label, + settings: Some(Arc::new(ErasedSettings::new(settings))), + } + } + + #[inline] + pub fn with_settings_fn( + self, + settings_fn: impl FnOnce(&mut S) + Send + Sync + 'static, + ) -> AssetPath<'a> { + let mut settings = S::default(); + settings_fn(&mut settings); + AssetPath { + source: self.source, + path: self.path, + label: self.label, + settings: Some(Arc::new(ErasedSettings::new(settings))), } } @@ -313,6 +440,7 @@ impl<'a> AssetPath<'a> { source: self.source.clone(), label: None, path, + settings: self.settings.clone(), // XXX TODO: Reconsider `Arc` behavior. }) } @@ -326,6 +454,7 @@ impl<'a> AssetPath<'a> { source: self.source.into_owned(), path: self.path.into_owned(), label: self.label.map(CowArc::into_owned), + settings: self.settings.clone(), // XXX TODO: Reconsider `Arc` behavior. } } @@ -447,6 +576,7 @@ impl<'a> AssetPath<'a> { }, path: CowArc::Owned(result_path.into()), label: rlabel.map(|l| CowArc::Owned(l.into())), + settings: self.settings.clone(), // XXX TODO: Reconsider `Arc` behavior. }) } } @@ -533,16 +663,19 @@ impl AssetPath<'static> { source, path, label, + settings, } = self; let source = source.as_static(); let path = path.as_static(); let label = label.map(CowArc::as_static); + let settings = settings.clone(); Self { source, path, label, + settings, } } @@ -562,6 +695,7 @@ impl<'a> From<&'a str> for AssetPath<'a> { source: source.into(), path: CowArc::Borrowed(path), label: label.map(CowArc::Borrowed), + settings: None, } } } @@ -587,6 +721,7 @@ impl<'a> From<&'a Path> for AssetPath<'a> { source: AssetSourceId::Default, path: CowArc::Borrowed(path), label: None, + settings: None, } } } @@ -598,6 +733,7 @@ impl From for AssetPath<'static> { source: AssetSourceId::Default, path: path.into(), label: None, + settings: None, } } } diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 1b3bb3cb65d68..967fb5048f497 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -1,8 +1,7 @@ use crate::{ - meta::{AssetHash, MetaTransform}, - Asset, AssetHandleProvider, AssetLoadError, AssetPath, DependencyLoadState, ErasedLoadedAsset, - Handle, InternalAssetEvent, LoadState, RecursiveDependencyLoadState, StrongHandle, - UntypedAssetId, UntypedHandle, + meta::AssetHash, Asset, AssetHandleProvider, AssetLoadError, AssetPath, DependencyLoadState, + ErasedLoadedAsset, Handle, InternalAssetEvent, LoadState, RecursiveDependencyLoadState, + StrongHandle, UntypedAssetId, UntypedHandle, }; use alloc::{ borrow::ToOwned, @@ -111,7 +110,6 @@ impl AssetInfos { self.watching_for_changes, type_id, None, - None, true, ), Either::Left(type_name), @@ -126,7 +124,6 @@ impl AssetInfos { watching_for_changes: bool, type_id: TypeId, path: Option>, - meta_transform: Option, loading: bool, ) -> Result { let provider = handle_providers @@ -143,7 +140,7 @@ impl AssetInfos { } } - let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform); + let handle = provider.reserve_handle_internal(true, path.clone()); let mut info = AssetInfo::new(Arc::downgrade(&handle), path); if loading { info.load_state = LoadState::Loading; @@ -159,14 +156,9 @@ impl AssetInfos { &mut self, path: AssetPath<'static>, loading_mode: HandleLoadingMode, - meta_transform: Option, ) -> (Handle, bool) { - let result = self.get_or_create_path_handle_internal( - path, - Some(TypeId::of::()), - loading_mode, - meta_transform, - ); + let result = + self.get_or_create_path_handle_internal(path, Some(TypeId::of::()), loading_mode); // 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(); @@ -179,14 +171,8 @@ impl AssetInfos { type_id: TypeId, type_name: Option<&str>, loading_mode: HandleLoadingMode, - meta_transform: Option, ) -> (UntypedHandle, bool) { - let result = self.get_or_create_path_handle_internal( - path, - Some(type_id), - loading_mode, - meta_transform, - ); + let result = self.get_or_create_path_handle_internal(path, Some(type_id), loading_mode); let type_info = match type_name { Some(type_name) => Either::Left(type_name), None => Either::Right(type_id), @@ -202,7 +188,6 @@ impl AssetInfos { path: AssetPath<'static>, type_id: Option, loading_mode: HandleLoadingMode, - meta_transform: Option, ) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> { let handles = self.path_to_id.entry(path.clone()).or_default(); @@ -251,8 +236,7 @@ impl AssetInfos { .handle_providers .get(&type_id) .ok_or(MissingHandleProviderError(type_id))?; - let handle = - provider.get_handle(id.internal(), true, Some(path), meta_transform); + let handle = provider.get_handle(id.internal(), true, Some(path)); info.weak_handle = Arc::downgrade(&handle); Ok((UntypedHandle::Strong(handle), should_load)) } @@ -270,7 +254,6 @@ impl AssetInfos { self.watching_for_changes, type_id, Some(path), - meta_transform, should_load, )?; entry.insert(handle.id()); diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index ff5800474d8b7..f4f944de2d3ba 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -9,10 +9,7 @@ use crate::{ MissingProcessedAssetReaderError, Reader, }, loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset}, - meta::{ - loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal, - MetaTransform, Settings, - }, + meta::{AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal, Settings}, path::AssetPath, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode, @@ -322,7 +319,7 @@ 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_with_meta_transform(path, (), false) } /// Same as [`load`](AssetServer::load), but you can load assets from unaproved paths @@ -331,7 +328,7 @@ impl AssetServer { /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] pub fn load_override<'a, A: Asset>(&self, path: impl Into>) -> Handle { - self.load_with_meta_transform(path, None, (), true) + self.load_with_meta_transform(path, (), true) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -355,7 +352,7 @@ impl AssetServer { path: impl Into>, guard: G, ) -> Handle { - self.load_with_meta_transform(path, None, guard, false) + self.load_with_meta_transform(path, guard, false) } /// Same as [`load`](AssetServer::load_acquire), but you can load assets from unaproved paths @@ -368,24 +365,19 @@ impl AssetServer { path: impl Into>, guard: G, ) -> Handle { - self.load_with_meta_transform(path, None, guard, true) + self.load_with_meta_transform(path, guard, true) } /// 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. #[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>( + pub fn load_with_settings<'a, A: Asset, S: Settings + serde::Serialize + Default>( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - (), - false, - ) + self.load_with_meta_transform(path.into().with_settings_fn(settings), (), false) } /// Same as [`load`](AssetServer::load_with_settings), but you can load assets from unaproved paths @@ -393,17 +385,12 @@ impl AssetServer { /// is [`Deny`](UnapprovedPathMode::Deny). /// /// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`] - pub fn load_with_settings_override<'a, A: Asset, S: Settings>( + pub fn load_with_settings_override<'a, A: Asset, S: Settings + serde::Serialize + Default>( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - (), - true, - ) + self.load_with_meta_transform(path.into().with_settings_fn(settings), (), true) } /// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item. @@ -416,18 +403,18 @@ impl AssetServer { /// [`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. #[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>( + pub fn load_acquire_with_settings< + 'a, + A: Asset, + S: Settings + serde::Serialize + Default, + G: Send + Sync + 'static, + >( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, guard: G, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - guard, - false, - ) + self.load_with_meta_transform(path.into().with_settings_fn(settings), guard, false) } /// Same as [`load`](AssetServer::load_acquire_with_settings), but you can load assets from unaproved paths @@ -438,26 +425,23 @@ impl AssetServer { pub fn load_acquire_with_settings_override< 'a, A: Asset, - S: Settings, + S: Settings + serde::Serialize + Default, G: Send + Sync + 'static, >( &self, path: impl Into>, - settings: impl Fn(&mut S) + Send + Sync + 'static, + settings: impl FnOnce(&mut S) + Send + Sync + 'static, guard: G, ) -> Handle { - self.load_with_meta_transform( - path, - Some(loader_settings_meta_transform(settings)), - guard, - true, - ) + self.load_with_meta_transform(path.into().with_settings_fn(settings), guard, true) } + // XXX TODO: This should probably be renamed if `MetaTransform` is removed, + // but I'm not sure what would be a good name. `load_internal` is taken. + // Maybe `load_with_params`? 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 { @@ -474,11 +458,8 @@ impl AssetServer { } let mut infos = self.data.infos.write(); - let (handle, should_load) = infos.get_or_create_path_handle::( - path.clone(), - HandleLoadingMode::Request, - meta_transform, - ); + let (handle, should_load) = + infos.get_or_create_path_handle::(path.clone(), HandleLoadingMode::Request); if should_load { self.spawn_load_task(handle.clone().untyped(), path, infos, guard); @@ -491,7 +472,6 @@ impl AssetServer { &self, path: impl Into>, type_id: TypeId, - meta_transform: Option, guard: G, ) -> UntypedHandle { let path = path.into().into_owned(); @@ -501,7 +481,6 @@ impl AssetServer { type_id, None, HandleLoadingMode::Request, - meta_transform, ); if should_load { @@ -525,10 +504,7 @@ impl AssetServer { let owned_handle = handle.clone(); let server = self.clone(); let task = IoTaskPool::get().spawn(async move { - if let Err(err) = server - .load_internal(Some(owned_handle), path, false, None) - .await - { + if let Err(err) = server.load_internal(Some(owned_handle), path, false).await { error!("{}", err); } drop(guard); @@ -553,13 +529,12 @@ impl AssetServer { path: impl Into>, ) -> Result { let path: AssetPath = path.into(); - self.load_internal(None, path, false, None).await + self.load_internal(None, path, false).await } pub(crate) fn load_unknown_type_with_meta_transform<'a>( &self, path: impl Into>, - meta_transform: Option, ) -> Handle { let path = path.into().into_owned(); let untyped_source = AssetSourceId::Name(match path.source() { @@ -572,7 +547,6 @@ impl AssetServer { let (handle, should_load) = infos.get_or_create_path_handle::( path.clone().with_source(untyped_source), HandleLoadingMode::Request, - meta_transform, ); // drop the lock on `AssetInfos` before spawning a task that may block on it in single-threaded @@ -638,7 +612,7 @@ impl AssetServer { /// required to figure out the asset type before a handle can be created. #[must_use = "not using the returned strong handle may result in the unexpected release of the assets"] pub fn load_untyped<'a>(&self, path: impl Into>) -> Handle { - self.load_unknown_type_with_meta_transform(path, None) + self.load_unknown_type_with_meta_transform(path) } /// Performs an async asset load. @@ -650,7 +624,6 @@ impl AssetServer { mut input_handle: Option, path: AssetPath<'a>, force: bool, - meta_transform: Option, ) -> Result { let asset_type_id = input_handle.as_ref().map(UntypedHandle::type_id); @@ -671,8 +644,8 @@ impl AssetServer { } })?; - if let Some(meta_transform) = input_handle.as_ref().and_then(|h| h.meta_transform()) { - (*meta_transform)(&mut *meta); + if let Some(settings) = path.settings() { + meta.apply_settings(settings.value()); } // downgrade the input handle so we don't keep the asset alive just because we're loading it // note we can't just pass a weak handle in, as only strong handles contain the asset meta transform @@ -702,7 +675,6 @@ impl AssetServer { path.clone(), path.label().is_none().then(|| loader.asset_type_id()), HandleLoadingMode::Request, - meta_transform, ); unwrap_with_context(result, Either::Left(loader.asset_type_name())) } @@ -739,7 +711,6 @@ impl AssetServer { loader.asset_type_id(), Some(loader.asset_type_name()), HandleLoadingMode::Force, - None, ); (base_handle, base_path) } else { @@ -817,7 +788,7 @@ impl AssetServer { .infos .read() .get_path_handles(&path) - .map(|handle| server.load_internal(Some(handle), path.clone(), true, None)) + .map(|handle| server.load_internal(Some(handle), path.clone(), true)) .collect::>(); for result in requests { @@ -828,7 +799,7 @@ impl AssetServer { } if !reloaded && server.data.infos.read().should_reload(&path) { - if let Err(err) = server.load_internal(None, path, true, None).await { + if let Err(err) = server.load_internal(None, path, true).await { error!("{}", err); } } @@ -865,7 +836,6 @@ impl AssetServer { loaded_asset.asset_type_id(), Some(loaded_asset.asset_type_name()), HandleLoadingMode::NotLoading, - None, ); handle } else { @@ -950,11 +920,7 @@ impl AssetServer { .data .infos .write() - .get_or_create_path_handle::( - path.clone(), - HandleLoadingMode::Request, - None, - ); + .get_or_create_path_handle::(path.clone(), HandleLoadingMode::Request); if !should_load { return handle; } @@ -1276,15 +1242,10 @@ impl AssetServer { pub(crate) fn get_or_create_path_handle<'a, A: Asset>( &self, path: impl Into>, - meta_transform: Option, ) -> Handle { let mut infos = self.data.infos.write(); infos - .get_or_create_path_handle::( - path.into().into_owned(), - HandleLoadingMode::NotLoading, - meta_transform, - ) + .get_or_create_path_handle::(path.into().into_owned(), HandleLoadingMode::NotLoading) .0 } @@ -1296,7 +1257,6 @@ impl AssetServer { &self, path: impl Into>, type_id: TypeId, - meta_transform: Option, ) -> UntypedHandle { let mut infos = self.data.infos.write(); infos @@ -1305,7 +1265,6 @@ impl AssetServer { type_id, None, HandleLoadingMode::NotLoading, - meta_transform, ) .0 } diff --git a/crates/bevy_gltf/src/loader/extensions/khr_materials_anisotropy.rs b/crates/bevy_gltf/src/loader/extensions/khr_materials_anisotropy.rs index f859cfba84052..9b3ed91fb6c9f 100644 --- a/crates/bevy_gltf/src/loader/extensions/khr_materials_anisotropy.rs +++ b/crates/bevy_gltf/src/loader/extensions/khr_materials_anisotropy.rs @@ -4,6 +4,8 @@ use gltf::{Document, Material}; use serde_json::Value; +use crate::loader::LoadedTexture; + #[cfg(feature = "pbr_anisotropy_texture")] use { crate::loader::gltf_ext::{material::uv_channel, texture::texture_handle_from_info}, @@ -39,6 +41,7 @@ impl AnisotropyExtension { )] pub(crate) fn parse( load_context: &mut LoadContext, + loaded_textures: &[LoadedTexture], document: &Document, material: &Material, ) -> Option { @@ -54,7 +57,7 @@ impl AnisotropyExtension { .map(|json_info| { ( uv_channel(material, "anisotropy", json_info.tex_coord), - texture_handle_from_info(&json_info, document, load_context), + texture_handle_from_info(&json_info, document, load_context, loaded_textures), ) }) .unzip(); diff --git a/crates/bevy_gltf/src/loader/extensions/khr_materials_clearcoat.rs b/crates/bevy_gltf/src/loader/extensions/khr_materials_clearcoat.rs index 5128487ca4445..45fdbbc28598d 100644 --- a/crates/bevy_gltf/src/loader/extensions/khr_materials_clearcoat.rs +++ b/crates/bevy_gltf/src/loader/extensions/khr_materials_clearcoat.rs @@ -4,6 +4,8 @@ use gltf::{Document, Material}; use serde_json::Value; +use crate::loader::LoadedTexture; + #[cfg(feature = "pbr_multi_layer_material_textures")] use { crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle, @@ -43,6 +45,7 @@ impl ClearcoatExtension { )] pub(crate) fn parse( load_context: &mut LoadContext, + loaded_textures: &[LoadedTexture], document: &Document, material: &Material, ) -> Option { @@ -55,6 +58,7 @@ impl ClearcoatExtension { let (clearcoat_channel, clearcoat_texture) = parse_material_extension_texture( material, load_context, + loaded_textures, document, extension, "clearcoatTexture", @@ -66,6 +70,7 @@ impl ClearcoatExtension { parse_material_extension_texture( material, load_context, + loaded_textures, document, extension, "clearcoatRoughnessTexture", @@ -76,6 +81,7 @@ impl ClearcoatExtension { let (clearcoat_normal_channel, clearcoat_normal_texture) = parse_material_extension_texture( material, load_context, + loaded_textures, document, extension, "clearcoatNormalTexture", diff --git a/crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs b/crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs index f0adcc4940b10..dc7571d95ebdd 100644 --- a/crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs +++ b/crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs @@ -4,6 +4,8 @@ use gltf::{Document, Material}; use serde_json::Value; +use crate::loader::LoadedTexture; + #[cfg(feature = "pbr_specular_textures")] use { crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle, @@ -43,6 +45,7 @@ pub(crate) struct SpecularExtension { impl SpecularExtension { pub(crate) fn parse( _load_context: &mut LoadContext, + _loaded_textures: &[LoadedTexture], _document: &Document, material: &Material, ) -> Option { @@ -55,6 +58,7 @@ impl SpecularExtension { let (_specular_channel, _specular_texture) = parse_material_extension_texture( material, _load_context, + _loaded_textures, _document, extension, "specularTexture", @@ -65,6 +69,7 @@ impl SpecularExtension { let (_specular_color_channel, _specular_color_texture) = parse_material_extension_texture( material, _load_context, + _loaded_textures, _document, extension, "specularColorTexture", diff --git a/crates/bevy_gltf/src/loader/gltf_ext/material.rs b/crates/bevy_gltf/src/loader/gltf_ext/material.rs index 9d8b7c5745910..47df3539ba2b5 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/material.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/material.rs @@ -16,6 +16,7 @@ use super::texture::texture_transform_to_affine2; ))] use { super::texture::texture_handle_from_info, + crate::loader::LoadedTexture, bevy_asset::{Handle, LoadContext}, bevy_image::Image, gltf::Document, @@ -31,6 +32,7 @@ use { pub(crate) fn parse_material_extension_texture( material: &Material, load_context: &mut LoadContext, + loaded_textures: &[LoadedTexture], document: &Document, extension: &Map, texture_name: &str, @@ -42,7 +44,12 @@ pub(crate) fn parse_material_extension_texture( { Some(json_info) => ( uv_channel(material, texture_kind, json_info.tex_coord), - Some(texture_handle_from_info(&json_info, document, load_context)), + Some(texture_handle_from_info( + &json_info, + document, + load_context, + loaded_textures, + )), ), None => (UvChannel::default(), None), } diff --git a/crates/bevy_gltf/src/loader/gltf_ext/texture.rs b/crates/bevy_gltf/src/loader/gltf_ext/texture.rs index f666752479bb6..f5677d54f1d05 100644 --- a/crates/bevy_gltf/src/loader/gltf_ext/texture.rs +++ b/crates/bevy_gltf/src/loader/gltf_ext/texture.rs @@ -2,10 +2,7 @@ use bevy_asset::{Handle, LoadContext}; use bevy_image::{Image, ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor}; use bevy_math::Affine2; -use gltf::{ - image::Source, - texture::{MagFilter, MinFilter, Texture, TextureTransform, WrappingMode}, -}; +use gltf::texture::{MagFilter, MinFilter, Texture, TextureTransform, WrappingMode}; #[cfg(any( feature = "pbr_anisotropy_texture", @@ -14,28 +11,25 @@ use gltf::{ ))] use gltf::{json::texture::Info, Document}; -use crate::{loader::DataUri, GltfAssetLabel}; +use crate::loader::{LabelOrAssetPath, LoadedTexture}; +// XXX TODO: Documentation. pub(crate) fn texture_handle( texture: &Texture<'_>, load_context: &mut LoadContext, + loaded_textures: &[LoadedTexture], ) -> Handle { - match texture.source().source() { - Source::View { .. } => load_context.get_label_handle(texture_label(texture).to_string()), - Source::Uri { uri, .. } => { - let uri = percent_encoding::percent_decode_str(uri) - .decode_utf8() - .unwrap(); - let uri = uri.as_ref(); - if let Ok(_data_uri) = DataUri::parse(uri) { - load_context.get_label_handle(texture_label(texture).to_string()) - } else { - let parent = load_context.path().parent().unwrap(); - let image_path = parent.join(uri); - load_context.load(image_path) - } - } - } + let loaded_texture = &loaded_textures[texture.index()]; // XXX TODO: Guard against invalid indices? + + let handle = match &loaded_texture.path { + LabelOrAssetPath::Label(label) => load_context.get_label_handle(label.to_string()), + LabelOrAssetPath::AssetPath(path) => load_context.load(path), + }; + + // XXX TODO: Document and decide on error handling. + assert_eq!(handle, loaded_texture.handle); + + handle } /// Extracts the texture sampler data from the glTF [`Texture`]. @@ -83,10 +77,6 @@ pub(crate) fn texture_sampler( sampler } -pub(crate) fn texture_label(texture: &Texture<'_>) -> GltfAssetLabel { - GltfAssetLabel::Texture(texture.index()) -} - pub(crate) fn address_mode(wrapping_mode: &WrappingMode) -> ImageAddressMode { match wrapping_mode { WrappingMode::ClampToEdge => ImageAddressMode::ClampToEdge, @@ -117,10 +107,11 @@ pub(crate) fn texture_handle_from_info( info: &Info, document: &Document, load_context: &mut LoadContext, + loaded_textures: &[LoadedTexture], ) -> Handle { let texture = document .textures() .nth(info.index.value()) .expect("Texture info references a nonexistent texture"); - texture_handle(&texture, load_context) + texture_handle(&texture, load_context, loaded_textures) } diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index f85a739b2e01f..0b668c9dba52c 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -11,7 +11,7 @@ use std::{ #[cfg(feature = "bevy_animation")] use bevy_animation::{prelude::*, AnimationTarget, AnimationTargetId}; use bevy_asset::{ - io::Reader, AssetLoadError, AssetLoader, Handle, LoadContext, ReadAssetBytesError, + io::Reader, AssetLoadError, AssetLoader, AssetPath, Handle, LoadContext, ReadAssetBytesError, RenderAssetUsages, }; use bevy_color::{Color, LinearRgba}; @@ -169,7 +169,7 @@ pub struct GltfLoader { /// } /// ); /// ``` -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct GltfLoaderSettings { /// If empty, the gltf mesh nodes will be skipped. /// @@ -528,7 +528,9 @@ async fn load_gltf<'a, 'b, 'c>( // In theory we could store a mapping between texture.index() and handle to use // later in the loader when looking up handles for materials. However this would mean // that the material's load context would no longer track those images as dependencies. - let mut _texture_handles = Vec::new(); + // + // XXX TODO: Update comment. + let mut loaded_textures: Vec = Vec::new(); if gltf.textures().len() == 1 || cfg!(target_arch = "wasm32") { for texture in gltf.textures() { let parent_path = load_context.path().parent().unwrap(); @@ -542,7 +544,7 @@ async fn load_gltf<'a, 'b, 'c>( settings, ) .await?; - image.process_loaded_texture(load_context, &mut _texture_handles); + image.process_loaded_texture(load_context, &mut loaded_textures); } } else { #[cfg(not(target_arch = "wasm32"))] @@ -569,7 +571,7 @@ async fn load_gltf<'a, 'b, 'c>( .into_iter() .for_each(|result| match result { Ok(image) => { - image.process_loaded_texture(load_context, &mut _texture_handles); + image.process_loaded_texture(load_context, &mut loaded_textures); } Err(err) => { warn!("Error loading glTF texture: {}", err); @@ -583,7 +585,13 @@ async fn load_gltf<'a, 'b, 'c>( 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 for material in gltf.materials() { - let handle = load_material(&material, load_context, &gltf.document, false); + let handle = load_material( + &material, + load_context, + &gltf.document, + false, + &loaded_textures, + ); if let Some(name) = material.name() { named_materials.insert(name.into(), handle.clone()); } @@ -885,6 +893,7 @@ async fn load_gltf<'a, 'b, 'c>( #[cfg(feature = "bevy_animation")] None, &gltf.document, + &loaded_textures, ); if result.is_err() { err = Some(result); @@ -1041,6 +1050,7 @@ fn load_material( load_context: &mut LoadContext, document: &Document, is_scale_inverted: bool, + loaded_textures: &[LoadedTexture], ) -> Handle { let material_label = material_label(material, is_scale_inverted); load_context.labeled_asset_scope(material_label.to_string(), |load_context| { @@ -1054,7 +1064,7 @@ fn load_material( .unwrap_or_default(); let base_color_texture = pbr .base_color_texture() - .map(|info| texture_handle(&info.texture(), load_context)); + .map(|info| texture_handle(&info.texture(), load_context, loaded_textures)); let uv_transform = pbr .base_color_texture() @@ -1068,7 +1078,7 @@ fn load_material( let normal_map_texture: Option> = material.normal_texture().map(|normal_texture| { // TODO: handle normal_texture.scale - texture_handle(&normal_texture.texture(), load_context) + texture_handle(&normal_texture.texture(), load_context, loaded_textures) }); let metallic_roughness_channel = pbr @@ -1082,7 +1092,7 @@ fn load_material( uv_transform, "metallic/roughness", ); - texture_handle(&info.texture(), load_context) + texture_handle(&info.texture(), load_context, loaded_textures) }); let occlusion_channel = material @@ -1091,7 +1101,7 @@ fn load_material( .unwrap_or_default(); let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| { // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) - texture_handle(&occlusion_texture.texture(), load_context) + texture_handle(&occlusion_texture.texture(), load_context, loaded_textures) }); let emissive = material.emissive_factor(); @@ -1102,7 +1112,7 @@ fn load_material( let emissive_texture = material.emissive_texture().map(|info| { // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive"); - texture_handle(&info.texture(), load_context) + texture_handle(&info.texture(), load_context, loaded_textures) }); #[cfg(feature = "pbr_transmission_textures")] @@ -1117,7 +1127,11 @@ fn load_material( let transmission_texture: Option> = transmission .transmission_texture() .map(|transmission_texture| { - texture_handle(&transmission_texture.texture(), load_context) + texture_handle( + &transmission_texture.texture(), + load_context, + loaded_textures, + ) }); ( @@ -1148,7 +1162,7 @@ fn load_material( .unwrap_or_default(); let thickness_texture: Option> = volume.thickness_texture().map(|thickness_texture| { - texture_handle(&thickness_texture.texture(), load_context) + texture_handle(&thickness_texture.texture(), load_context, loaded_textures) }); ( @@ -1177,20 +1191,24 @@ fn load_material( // Parse the `KHR_materials_clearcoat` extension data if necessary. let clearcoat = - ClearcoatExtension::parse(load_context, document, material).unwrap_or_default(); + ClearcoatExtension::parse(load_context, loaded_textures, document, material) + .unwrap_or_default(); // Parse the `KHR_materials_anisotropy` extension data if necessary. let anisotropy = - AnisotropyExtension::parse(load_context, document, material).unwrap_or_default(); + AnisotropyExtension::parse(load_context, loaded_textures, document, material) + .unwrap_or_default(); // Parse the `KHR_materials_specular` extension data if necessary. - let specular = - SpecularExtension::parse(load_context, document, material).unwrap_or_default(); + let specular = SpecularExtension::parse(load_context, loaded_textures, document, material) + .unwrap_or_default(); // We need to operate in the Linear color space and be willing to exceed 1.0 in our channels let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]); let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0); + // XXX TODO: Need some way to dump the load_context dependencies and confirm they're right. + StandardMaterial { base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]), base_color_channel, @@ -1296,6 +1314,7 @@ fn load_node( #[cfg(feature = "bevy_animation")] animation_roots: &HashSet, #[cfg(feature = "bevy_animation")] mut animation_context: Option, document: &Document, + loaded_textures: &[LoadedTexture], ) -> Result<(), GltfError> { let mut gltf_error = None; let transform = node_transform(gltf_node); @@ -1404,7 +1423,13 @@ fn load_node( if !root_load_context.has_labeled_asset(&material_label) && !load_context.has_labeled_asset(&material_label) { - load_material(&material, load_context, document, is_scale_inverted); + load_material( + &material, + load_context, + document, + is_scale_inverted, + loaded_textures, + ); } let primitive_label = GltfAssetLabel::Primitive { @@ -1562,6 +1587,7 @@ fn load_node( #[cfg(feature = "bevy_animation")] animation_context.clone(), document, + loaded_textures, ) { gltf_error = Some(err); return; @@ -1688,28 +1714,49 @@ impl ImageOrPath { fn process_loaded_texture( self, load_context: &mut LoadContext, - handles: &mut Vec>, + loaded: &mut Vec, ) { - let handle = match self { + loaded.push(match self { ImageOrPath::Image { label, image } => { - load_context.add_labeled_asset(label.to_string(), image) + let handle = load_context.add_labeled_asset(label.to_string(), image); + LoadedTexture { + handle, + path: LabelOrAssetPath::Label(label), + } } ImageOrPath::Path { path, is_srgb, sampler_descriptor, - } => load_context - .loader() - .with_settings(move |settings: &mut ImageLoaderSettings| { - settings.is_srgb = is_srgb; - settings.sampler = ImageSampler::Descriptor(sampler_descriptor.clone()); - }) - .load(path), - }; - handles.push(handle); + } => { + let path = AssetPath::from(path).with_settings(ImageLoaderSettings { + is_srgb, + sampler: ImageSampler::Descriptor(sampler_descriptor.clone()), + ..Default::default() + }); + let handle = load_context.loader().load(path.clone()); + LoadedTexture { + handle, + path: LabelOrAssetPath::AssetPath(path), + } + } + }); } } +// XXX TODO: Document. +enum LabelOrAssetPath<'a> { + Label(GltfAssetLabel), + AssetPath(AssetPath<'a>), +} + +// XXX TODO: Document. +struct LoadedTexture<'a> { + handle: Handle, + // XXX TODO: Reconsider variable name? + path: LabelOrAssetPath<'a>, +} + struct PrimitiveMorphAttributesIter<'s>( pub ( Option>, diff --git a/crates/bevy_image/src/exr_texture_loader.rs b/crates/bevy_image/src/exr_texture_loader.rs index d9b89dd21e152..931ffee965c82 100644 --- a/crates/bevy_image/src/exr_texture_loader.rs +++ b/crates/bevy_image/src/exr_texture_loader.rs @@ -10,7 +10,7 @@ use wgpu_types::{Extent3d, TextureDimension, TextureFormat}; #[cfg(feature = "exr")] pub struct ExrTextureLoader; -#[derive(Serialize, Deserialize, Default, Debug)] +#[derive(Serialize, Deserialize, Default, Debug, Clone)] #[cfg(feature = "exr")] pub struct ExrTextureLoaderSettings { pub asset_usage: RenderAssetUsages, diff --git a/crates/bevy_image/src/hdr_texture_loader.rs b/crates/bevy_image/src/hdr_texture_loader.rs index 3177e94865809..d21917fdb048a 100644 --- a/crates/bevy_image/src/hdr_texture_loader.rs +++ b/crates/bevy_image/src/hdr_texture_loader.rs @@ -10,7 +10,7 @@ use wgpu_types::{Extent3d, TextureDimension, TextureFormat}; #[derive(Clone, Default)] pub struct HdrTextureLoader; -#[derive(Serialize, Deserialize, Default, Debug)] +#[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct HdrTextureLoaderSettings { pub asset_usage: RenderAssetUsages, } diff --git a/examples/asset/alter_mesh.rs b/examples/asset/alter_mesh.rs index 7462c47ae1f85..722b67d5ab2c4 100644 --- a/examples/asset/alter_mesh.rs +++ b/examples/asset/alter_mesh.rs @@ -47,6 +47,28 @@ impl Shape { #[derive(Component, Debug)] struct Left; +// A function that can be passed to `AssetServer::load_with_settings` and sets +// our desired `RenderAssetUsages`. +// +// `RenderAssetUsages::all()` is already the default, so this function could have been +// omitted and `load_with_settings` replaced with a simple `load`. It's helpful to know it +// exists, however. +// +// `RenderAssetUsages` tells 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. +fn settings(settings: &mut GltfLoaderSettings) { + settings.load_meshes = RenderAssetUsages::all(); +} + fn setup( mut commands: Commands, asset_server: Res, @@ -55,7 +77,7 @@ fn setup( let left_shape = Shape::Cube; let right_shape = Shape::Cube; - // In normal use, you can call `asset_server.load`, however see below for an explanation of + // In normal use, you can call `asset_server.load`, however see above for an explanation of // `RenderAssetUsages`. let left_shape_model = asset_server.load_with_settings( GltfAssetLabel::Primitive { @@ -69,30 +91,16 @@ fn setup( 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(), + settings, ); - // Here, we rely on the default loader settings to achieve a similar result to the above. - let right_shape_model = asset_server.load( + let right_shape_model = asset_server.load_with_settings( GltfAssetLabel::Primitive { mesh: 0, primitive: 0, } .from_asset(right_shape.get_model_path()), + settings, ); // Add a material asset directly to the materials storage @@ -161,12 +169,13 @@ fn alter_handle( // Modify the handle associated with the Shape on the right side. Note that we will only // have to load the same path from storage media once: repeated attempts will re-use the // asset. - mesh.0 = asset_server.load( + mesh.0 = asset_server.load_with_settings( GltfAssetLabel::Primitive { mesh: 0, primitive: 0, } .from_asset(shape.get_model_path()), + settings, ); } diff --git a/examples/asset/alter_sprite.rs b/examples/asset/alter_sprite.rs index d47c303921521..ce9056e426231 100644 --- a/examples/asset/alter_sprite.rs +++ b/examples/asset/alter_sprite.rs @@ -45,29 +45,34 @@ impl Bird { #[derive(Component, Debug)] struct Left; +// A function that can be passed to `AssetServer::load_with_settings` and sets +// our desired `RenderAssetUsages`. +// +// `RenderAssetUsages::all()` is already the default, so this function could have been +// omitted and `load_with_settings` replaced with a simple `load`. It's helpful to know it +// exists, however. +// +// `RenderAssetUsages` tells 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. +fn settings(settings: &mut ImageLoaderSettings) { + settings.asset_usage = RenderAssetUsages::all(); +} + fn setup(mut commands: Commands, asset_server: Res) { let bird_left = Bird::Normal; 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_with_settings(bird_left.get_texture_path(), settings); commands.spawn(( Name::new("Bird Left"), @@ -81,8 +86,9 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(( Name::new("Bird Right"), - // In contrast to the above, here we rely on the default `RenderAssetUsages` loader setting - Sprite::from_image(asset_server.load(bird_right.get_texture_path())), + Sprite::from_image( + asset_server.load_with_settings(bird_right.get_texture_path(), settings), + ), Transform::from_xyz(200.0, 0.0, 0.0), bird_right, )); @@ -118,7 +124,7 @@ fn alter_handle( // Modify the handle associated with the Bird on the right side. Note that we will only // have to load the same path from storage media once: repeated attempts will re-use the // asset. - sprite.image = asset_server.load(bird.get_texture_path()); + sprite.image = asset_server.load_with_settings(bird.get_texture_path(), settings); } fn alter_asset(mut images: ResMut>, left_bird: Single<&Sprite, With>) { diff --git a/examples/asset/asset_decompression.rs b/examples/asset/asset_decompression.rs index e514924ca9d0a..8cafe76563f16 100644 --- a/examples/asset/asset_decompression.rs +++ b/examples/asset/asset_decompression.rs @@ -23,6 +23,7 @@ struct GzAssetLoader; /// Possible errors that can be produced by [`GzAssetLoader`] #[non_exhaustive] #[derive(Debug, Error)] +#[expect(clippy::large_enum_variant, reason = "XXX TODO: Is this ok?")] enum GzAssetLoaderError { /// An [IO](std::io) Error #[error("Could not load asset: {0}")] diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 47a12fc6bb202..a7bb770c8f1f2 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -1,6 +1,7 @@ //! This example illustrates how to define custom `AssetLoader`s, `AssetTransformer`s, and `AssetSaver`s, how to configure them, and how to register asset processors. use bevy::{ + asset::AssetPath, asset::{ embedded_asset, io::{Reader, Writer}, @@ -159,11 +160,10 @@ impl AssetLoader for CoolTextLoader { for (path, settings_override) in ron.dependencies_with_settings { let loaded = load_context .loader() - .with_settings(move |settings| { - *settings = settings_override.clone(); - }) .immediate() - .load::(&path) + .load::(AssetPath::from(path).with_settings_fn(move |settings| { + *settings = settings_override.clone(); + })) .await?; base_text.push_str(&loaded.get().0); }