Remove labeled_assets from LoadedAsset and ErasedLoadedAsset (#15481)

# Objective

Fixes #15417.

## Solution

- Remove the `labeled_assets` fields from `LoadedAsset` and
`ErasedLoadedAsset`.
- Created new structs `CompleteLoadedAsset` and
`CompleteErasedLoadedAsset` to hold the `labeled_subassets`.
- When a subasset is `LoadContext::finish`ed, it produces a
`CompleteLoadedAsset`.
- When a `CompleteLoadedAsset` is added to a `LoadContext` (as a
subasset), their `labeled_assets` are merged, reporting any overlaps.

One important detail to note: nested subassets with overlapping names
could in theory have been used in the past for the purposes of asset
preprocessing. Even though there was no way to access these "shadowed"
nested subassets, asset preprocessing does get access to these nested
subassets. This does not seem like a case we should support though. It
is confusing at best.

## Testing

- This is just a refactor.

---

## Migration Guide

- Most uses of `LoadedAsset` and `ErasedLoadedAsset` should be replaced
with `CompleteLoadedAsset` and `CompleteErasedLoadedAsset` respectively.
This commit is contained in:
andriyDev 2025-02-10 13:06:37 -08:00 committed by GitHub
parent 232824c009
commit f17644879d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 173 additions and 199 deletions

View File

@ -22,6 +22,7 @@ trace = []
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" } bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
"uuid", "uuid",
] } ] }
@ -69,9 +70,6 @@ uuid = { version = "1.13.1", default-features = false, features = ["js"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
notify-debouncer-full = { version = "0.5.0", optional = true } notify-debouncer-full = { version = "0.5.0", optional = true }
[dev-dependencies]
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
[lints] [lints]
workspace = true workspace = true

View File

@ -725,7 +725,7 @@ mod tests {
.map_err(|_| Self::Error::CannotLoadDependency { .map_err(|_| Self::Error::CannotLoadDependency {
dependency: dep.into(), dependency: dep.into(),
})?; })?;
let cool = loaded.get(); let cool = loaded.get_asset().get();
embedded.push_str(&cool.text); embedded.push_str(&cool.text);
} }
Ok(CoolText { Ok(CoolText {

View File

@ -13,6 +13,7 @@ use alloc::{
}; };
use atomicow::CowArc; use atomicow::CowArc;
use bevy_ecs::world::World; use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_platform_support::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId}; use core::any::{Any, TypeId};
@ -60,7 +61,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
load_context: LoadContext<'a>, load_context: LoadContext<'a>,
) -> BoxedFuture< ) -> BoxedFuture<
'a, 'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>, Result<CompleteErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
>; >;
/// Returns a list of extensions supported by this asset loader, without the preceding dot. /// Returns a list of extensions supported by this asset loader, without the preceding dot.
@ -91,7 +92,7 @@ where
mut load_context: LoadContext<'a>, mut load_context: LoadContext<'a>,
) -> BoxedFuture< ) -> BoxedFuture<
'a, 'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>, Result<CompleteErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
> { > {
Box::pin(async move { Box::pin(async move {
let settings = meta let settings = meta
@ -152,7 +153,6 @@ pub struct LoadedAsset<A: Asset> {
pub(crate) value: A, pub(crate) value: A,
pub(crate) dependencies: HashSet<UntypedAssetId>, pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>, pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
} }
impl<A: Asset> LoadedAsset<A> { impl<A: Asset> LoadedAsset<A> {
@ -166,7 +166,6 @@ impl<A: Asset> LoadedAsset<A> {
value, value,
dependencies, dependencies,
loader_dependencies: HashMap::default(), loader_dependencies: HashMap::default(),
labeled_assets: HashMap::default(),
} }
} }
@ -179,19 +178,6 @@ impl<A: Asset> LoadedAsset<A> {
pub fn get(&self) -> &A { pub fn get(&self) -> &A {
&self.value &self.value
} }
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
} }
impl<A: Asset> From<A> for LoadedAsset<A> { impl<A: Asset> From<A> for LoadedAsset<A> {
@ -205,7 +191,6 @@ pub struct ErasedLoadedAsset {
pub(crate) value: Box<dyn AssetContainer>, pub(crate) value: Box<dyn AssetContainer>,
pub(crate) dependencies: HashSet<UntypedAssetId>, pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>, pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
} }
impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset { impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
@ -214,7 +199,6 @@ impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
value: Box::new(asset.value), value: Box::new(asset.value),
dependencies: asset.dependencies, dependencies: asset.dependencies,
loader_dependencies: asset.loader_dependencies, loader_dependencies: asset.loader_dependencies,
labeled_assets: asset.labeled_assets,
} }
} }
} }
@ -241,19 +225,6 @@ impl ErasedLoadedAsset {
self.value.asset_type_name() self.value.asset_type_name()
} }
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
/// Cast this loaded asset as the given type. If the type does not match, /// Cast this loaded asset as the given type. If the type does not match,
/// the original type-erased asset is returned. /// the original type-erased asset is returned.
pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> { pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
@ -262,7 +233,6 @@ impl ErasedLoadedAsset {
value: *value, value: *value,
dependencies: self.dependencies, dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies, loader_dependencies: self.loader_dependencies,
labeled_assets: self.labeled_assets,
}), }),
Err(value) => { Err(value) => {
self.value = value; self.value = value;
@ -290,6 +260,100 @@ impl<A: Asset> AssetContainer for A {
} }
} }
/// A loaded asset and all its loaded subassets.
pub struct CompleteLoadedAsset<A: Asset> {
/// The loaded asset.
pub(crate) asset: LoadedAsset<A>,
/// The subassets by their label.
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> CompleteLoadedAsset<A> {
/// Take ownership of the stored [`Asset`] value.
pub fn take(self) -> A {
self.asset.value
}
/// Returns the stored asset.
pub fn get_asset(&self) -> &LoadedAsset<A> {
&self.asset
}
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
/// A "type erased / boxed" counterpart to [`CompleteLoadedAsset`]. This is used in places where the
/// loaded type is not statically known.
pub struct CompleteErasedLoadedAsset {
/// The loaded asset.
pub(crate) asset: ErasedLoadedAsset,
/// The subassets by their label.
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl CompleteErasedLoadedAsset {
/// Cast (and take ownership) of the [`Asset`] value of the given type. This will return
/// [`Some`] if the stored type matches `A` and [`None`] if it does not.
pub fn take<A: Asset>(self) -> Option<A> {
self.asset.take()
}
/// Returns the stored asset.
pub fn get_asset(&self) -> &ErasedLoadedAsset {
&self.asset
}
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
/// Cast this loaded asset as the given type. If the type does not match,
/// the original type-erased asset is returned.
pub fn downcast<A: Asset>(
mut self,
) -> Result<CompleteLoadedAsset<A>, CompleteErasedLoadedAsset> {
match self.asset.downcast::<A>() {
Ok(asset) => Ok(CompleteLoadedAsset {
asset,
labeled_assets: self.labeled_assets,
}),
Err(asset) => {
self.asset = asset;
Err(self)
}
}
}
}
impl<A: Asset> From<CompleteLoadedAsset<A>> for CompleteErasedLoadedAsset {
fn from(value: CompleteLoadedAsset<A>) -> Self {
Self {
asset: value.asset.into(),
labeled_assets: value.labeled_assets,
}
}
}
/// An error that occurs when attempting to call [`NestedLoader::load`] which /// An error that occurs when attempting to call [`NestedLoader::load`] which
/// is configured to work [immediately]. /// is configured to work [immediately].
/// ///
@ -397,8 +461,8 @@ impl<'a> LoadContext<'a> {
) -> Handle<A> { ) -> Handle<A> {
let mut context = self.begin_labeled_asset(); let mut context = self.begin_labeled_asset();
let asset = load(&mut context); let asset = load(&mut context);
let loaded_asset = context.finish(asset); let complete_asset = context.finish(asset);
self.add_loaded_labeled_asset(label, loaded_asset) self.add_loaded_labeled_asset(label, complete_asset)
} }
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label. /// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
@ -423,10 +487,14 @@ impl<'a> LoadContext<'a> {
pub fn add_loaded_labeled_asset<A: Asset>( pub fn add_loaded_labeled_asset<A: Asset>(
&mut self, &mut self,
label: impl Into<CowArc<'static, str>>, label: impl Into<CowArc<'static, str>>,
loaded_asset: LoadedAsset<A>, loaded_asset: CompleteLoadedAsset<A>,
) -> Handle<A> { ) -> Handle<A> {
let label = label.into(); let label = label.into();
let loaded_asset: ErasedLoadedAsset = loaded_asset.into(); let CompleteLoadedAsset {
asset,
labeled_assets,
} = loaded_asset;
let loaded_asset: ErasedLoadedAsset = asset.into();
let labeled_path = self.asset_path.clone().with_label(label.clone()); let labeled_path = self.asset_path.clone().with_label(label.clone());
let handle = self let handle = self
.asset_server .asset_server
@ -438,6 +506,11 @@ impl<'a> LoadContext<'a> {
handle: handle.clone().untyped(), handle: handle.clone().untyped(),
}, },
); );
for (label, asset) in labeled_assets {
if self.labeled_assets.insert(label.clone(), asset).is_some() {
warn!("A labeled asset with the label \"{label}\" already exists. Replacing with the new asset.");
}
}
handle handle
} }
@ -450,11 +523,13 @@ impl<'a> LoadContext<'a> {
} }
/// "Finishes" this context by populating the final [`Asset`] value. /// "Finishes" this context by populating the final [`Asset`] value.
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> { pub fn finish<A: Asset>(self, value: A) -> CompleteLoadedAsset<A> {
LoadedAsset { CompleteLoadedAsset {
asset: LoadedAsset {
value, value,
dependencies: self.dependencies, dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies, loader_dependencies: self.loader_dependencies,
},
labeled_assets: self.labeled_assets, labeled_assets: self.labeled_assets,
} }
} }
@ -525,8 +600,8 @@ impl<'a> LoadContext<'a> {
meta: &dyn AssetMetaDyn, meta: &dyn AssetMetaDyn,
loader: &dyn ErasedAssetLoader, loader: &dyn ErasedAssetLoader,
reader: &mut dyn Reader, reader: &mut dyn Reader,
) -> Result<ErasedLoadedAsset, LoadDirectError> { ) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
let loaded_asset = self let complete_asset = self
.asset_server .asset_server
.load_with_meta_loader_and_reader( .load_with_meta_loader_and_reader(
&path, &path,
@ -544,7 +619,7 @@ impl<'a> LoadContext<'a> {
let info = meta.processed_info().as_ref(); let info = meta.processed_info().as_ref();
let hash = info.map(|i| i.full_hash).unwrap_or_default(); let hash = info.map(|i| i.full_hash).unwrap_or_default();
self.loader_dependencies.insert(path, hash); self.loader_dependencies.insert(path, hash);
Ok(loaded_asset) Ok(complete_asset)
} }
/// Create a builder for loading nested assets in this context. /// Create a builder for loading nested assets in this context.

View File

@ -4,8 +4,8 @@
use crate::{ use crate::{
io::Reader, io::Reader,
meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings}, meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},
Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext, Asset, AssetLoadError, AssetPath, CompleteErasedLoadedAsset, CompleteLoadedAsset,
LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle, ErasedAssetLoader, Handle, LoadContext, LoadDirectError, LoadedUntypedAsset, UntypedHandle,
}; };
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc}; use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
use core::any::TypeId; use core::any::TypeId;
@ -57,11 +57,11 @@ impl ReaderRef<'_> {
/// If you know the type ID of the asset at runtime, but not at compile time, /// If you know the type ID of the asset at runtime, but not at compile time,
/// use [`with_dynamic_type`] followed by [`load`] to start loading an asset /// use [`with_dynamic_type`] followed by [`load`] to start loading an asset
/// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]), /// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),
/// or a [`ErasedLoadedAsset`] (via [`Immediate`]). /// or a [`CompleteErasedLoadedAsset`] (via [`Immediate`]).
/// ///
/// - in [`UnknownTyped`]: loading either a type-erased version of the asset /// - in [`UnknownTyped`]: loading either a type-erased version of the asset
/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset /// ([`CompleteErasedLoadedAsset`]), or a handle *to a handle* of the actual
/// ([`LoadedUntypedAsset`]). /// asset ([`LoadedUntypedAsset`]).
/// ///
/// If you have no idea what type of asset you will be loading (not even at /// If you have no idea what type of asset you will be loading (not even at
/// runtime with a [`TypeId`]), use this. /// runtime with a [`TypeId`]), use this.
@ -386,7 +386,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
self, self,
path: &AssetPath<'static>, path: &AssetPath<'static>,
asset_type_id: Option<TypeId>, asset_type_id: Option<TypeId>,
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> { ) -> Result<(Arc<dyn ErasedAssetLoader>, CompleteErasedLoadedAsset), LoadDirectError> {
let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader { let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
let loader = if let Some(asset_type_id) = asset_type_id { let loader = if let Some(asset_type_id) = asset_type_id {
self.load_context self.load_context
@ -448,7 +448,7 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
pub async fn load<'p, A: Asset>( pub async fn load<'p, A: Asset>(
self, self,
path: impl Into<AssetPath<'p>>, path: impl Into<AssetPath<'p>>,
) -> Result<LoadedAsset<A>, LoadDirectError> { ) -> Result<CompleteLoadedAsset<A>, LoadDirectError> {
let path = path.into().into_owned(); let path = path.into().into_owned();
self.load_internal(&path, Some(TypeId::of::<A>())) self.load_internal(&path, Some(TypeId::of::<A>()))
.await .await
@ -476,7 +476,7 @@ impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
pub async fn load<'p>( pub async fn load<'p>(
self, self,
path: impl Into<AssetPath<'p>>, path: impl Into<AssetPath<'p>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> { ) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
let path = path.into().into_owned(); let path = path.into().into_owned();
let asset_type_id = Some(self.typing.asset_type_id); let asset_type_id = Some(self.typing.asset_type_id);
self.load_internal(&path, asset_type_id) self.load_internal(&path, asset_type_id)
@ -492,7 +492,7 @@ impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
pub async fn load<'p>( pub async fn load<'p>(
self, self,
path: impl Into<AssetPath<'p>>, path: impl Into<AssetPath<'p>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> { ) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
let path = path.into().into_owned(); let path = path.into().into_owned();
self.load_internal(&path, None) self.load_internal(&path, None)
.await .await

View File

@ -7,7 +7,7 @@ use crate::{
processor::AssetProcessor, processor::AssetProcessor,
saver::{AssetSaver, SavedAsset}, saver::{AssetSaver, SavedAsset},
transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset}, transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset},
AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, AssetLoadError, AssetLoader, AssetPath, CompleteErasedLoadedAsset, DeserializeMetaError,
MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError, MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
}; };
use alloc::{ use alloc::{
@ -305,15 +305,15 @@ impl<'a> ProcessContext<'a> {
pub async fn load_source_asset<L: AssetLoader>( pub async fn load_source_asset<L: AssetLoader>(
&mut self, &mut self,
meta: AssetMeta<L, ()>, meta: AssetMeta<L, ()>,
) -> Result<ErasedLoadedAsset, AssetLoadError> { ) -> Result<CompleteErasedLoadedAsset, AssetLoadError> {
let server = &self.processor.server; let server = &self.processor.server;
let loader_name = core::any::type_name::<L>(); let loader_name = core::any::type_name::<L>();
let loader = server.get_asset_loader_with_type_name(loader_name).await?; let loader = server.get_asset_loader_with_type_name(loader_name).await?;
let mut reader = SliceReader::new(self.asset_bytes); let mut reader = SliceReader::new(self.asset_bytes);
let loaded_asset = server let complete_asset = server
.load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true) .load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true)
.await?; .await?;
for (path, full_hash) in &loaded_asset.loader_dependencies { for (path, full_hash) in &complete_asset.asset.loader_dependencies {
self.new_processed_info self.new_processed_info
.process_dependencies .process_dependencies
.push(ProcessDependencyInfo { .push(ProcessDependencyInfo {
@ -321,7 +321,7 @@ impl<'a> ProcessContext<'a> {
path: path.to_owned(), path: path.to_owned(),
}); });
} }
Ok(loaded_asset) Ok(complete_asset)
} }
/// The path of the asset being processed. /// The path of the asset being processed.

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader, io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader,
ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
}; };
use alloc::boxed::Box; use alloc::boxed::Box;
use atomicow::CowArc; use atomicow::CowArc;
@ -44,7 +44,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static {
fn save<'a>( fn save<'a>(
&'a self, &'a self,
writer: &'a mut Writer, writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset, complete_asset: &'a CompleteErasedLoadedAsset,
settings: &'a dyn Settings, settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>>; ) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>>;
@ -56,14 +56,14 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
fn save<'a>( fn save<'a>(
&'a self, &'a self,
writer: &'a mut Writer, writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset, complete_asset: &'a CompleteErasedLoadedAsset,
settings: &'a dyn Settings, settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>> { ) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>> {
Box::pin(async move { Box::pin(async move {
let settings = settings let settings = settings
.downcast_ref::<S::Settings>() .downcast_ref::<S::Settings>()
.expect("AssetLoader settings should match the loader type"); .expect("AssetLoader settings should match the loader type");
let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap(); let saved_asset = SavedAsset::<S::Asset>::from_loaded(complete_asset).unwrap();
if let Err(err) = self.save(writer, saved_asset, settings).await { if let Err(err) = self.save(writer, saved_asset, settings).await {
return Err(err.into()); return Err(err.into());
} }
@ -91,11 +91,11 @@ impl<'a, A: Asset> Deref for SavedAsset<'a, A> {
impl<'a, A: Asset> SavedAsset<'a, A> { impl<'a, A: Asset> SavedAsset<'a, A> {
/// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`. /// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: &'a ErasedLoadedAsset) -> Option<Self> { pub fn from_loaded(complete_asset: &'a CompleteErasedLoadedAsset) -> Option<Self> {
let value = asset.value.downcast_ref::<A>()?; let value = complete_asset.asset.value.downcast_ref::<A>()?;
Some(SavedAsset { Some(SavedAsset {
value, value,
labeled_assets: &asset.labeled_assets, labeled_assets: &complete_asset.labeled_assets,
}) })
} }
@ -114,17 +114,13 @@ impl<'a, A: Asset> SavedAsset<'a, A> {
} }
/// Returns the labeled asset, if it exists and matches this type. /// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<SavedAsset<B>> pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<&B>
where where
CowArc<'static, str>: Borrow<Q>, CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq, Q: ?Sized + Hash + Eq,
{ {
let labeled = self.labeled_assets.get(label)?; let labeled = self.labeled_assets.get(label)?;
let value = labeled.asset.value.downcast_ref::<B>()?; labeled.asset.value.downcast_ref::<B>()
Some(SavedAsset {
value,
labeled_assets: &labeled.asset.labeled_assets,
})
} }
/// Returns the type-erased labeled asset, if it exists and matches this type. /// Returns the type-erased labeled asset, if it exists and matches this type.

View File

@ -14,8 +14,8 @@ use crate::{
}, },
path::AssetPath, path::AssetPath,
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets, Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UntypedAssetId, CompleteErasedLoadedAsset, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset,
UntypedAssetLoadFailedEvent, UntypedHandle, UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle,
}; };
use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec}; use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
use alloc::{ use alloc::{
@ -697,12 +697,18 @@ impl AssetServer {
/// Sends a load event for the given `loaded_asset` and does the same recursively for all /// Sends a load event for the given `loaded_asset` and does the same recursively for all
/// labeled assets. /// labeled assets.
fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) { fn send_loaded_asset(&self, id: UntypedAssetId, mut complete_asset: CompleteErasedLoadedAsset) {
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() { for (_, labeled_asset) in complete_asset.labeled_assets.drain() {
self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset); self.send_asset_event(InternalAssetEvent::Loaded {
id: labeled_asset.handle.id(),
loaded_asset: labeled_asset.asset,
});
} }
self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset }); self.send_asset_event(InternalAssetEvent::Loaded {
id,
loaded_asset: complete_asset.asset,
});
} }
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded. /// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
@ -1326,7 +1332,7 @@ impl AssetServer {
reader: &mut dyn Reader, reader: &mut dyn Reader,
load_dependencies: bool, load_dependencies: bool,
populate_hashes: bool, populate_hashes: bool,
) -> Result<ErasedLoadedAsset, AssetLoadError> { ) -> Result<CompleteErasedLoadedAsset, AssetLoadError> {
// TODO: experiment with this // TODO: experiment with this
let asset_path = asset_path.clone_owned(); let asset_path = asset_path.clone_owned();
let load_context = let load_context =

View File

@ -1,4 +1,7 @@
use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; use crate::{
meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset,
UntypedHandle,
};
use alloc::boxed::Box; use alloc::boxed::Box;
use atomicow::CowArc; use atomicow::CowArc;
use bevy_platform_support::collections::HashMap; use bevy_platform_support::collections::HashMap;
@ -56,11 +59,11 @@ impl<A: Asset> DerefMut for TransformedAsset<A> {
impl<A: Asset> TransformedAsset<A> { impl<A: Asset> TransformedAsset<A> {
/// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`. /// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: ErasedLoadedAsset) -> Option<Self> { pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option<Self> {
if let Ok(value) = asset.value.downcast::<A>() { if let Ok(value) = complete_asset.asset.value.downcast::<A>() {
return Some(TransformedAsset { return Some(TransformedAsset {
value: *value, value: *value,
labeled_assets: asset.labeled_assets, labeled_assets: complete_asset.labeled_assets,
}); });
} }
None None
@ -87,117 +90,13 @@ impl<A: Asset> TransformedAsset<A> {
&mut self.value &mut self.value
} }
/// Returns the labeled asset, if it exists and matches this type. /// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>> pub fn get_labeled<B: Asset, Q>(&mut self, label: &'_ Q) -> Option<&mut B>
where where
CowArc<'static, str>: Borrow<Q>, CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq, Q: ?Sized + Hash + Eq,
{ {
let labeled = self.labeled_assets.get_mut(label)?; let labeled = self.labeled_assets.get_mut(label)?;
let value = labeled.asset.value.downcast_mut::<B>()?; labeled.asset.value.downcast_mut::<B>()
Some(TransformedSubAsset {
value,
labeled_assets: &mut labeled.asset.labeled_assets,
})
}
/// Returns the type-erased labeled asset, if it exists and matches this type.
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(&labeled.asset)
}
/// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(labeled.handle.clone())
}
/// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
return Some(handle);
}
None
}
/// Adds `asset` as a labeled sub asset using `label` and `handle`
pub fn insert_labeled(
&mut self,
label: impl Into<CowArc<'static, str>>,
handle: impl Into<UntypedHandle>,
asset: impl Into<ErasedLoadedAsset>,
) {
let labeled = LabeledAsset {
asset: asset.into(),
handle: handle.into(),
};
self.labeled_assets.insert(label.into(), labeled);
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
/// A labeled sub-asset of [`TransformedAsset`]
pub struct TransformedSubAsset<'a, A: Asset> {
value: &'a mut A,
labeled_assets: &'a mut HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> {
type Target = A;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}
impl<'a, A: Asset> TransformedSubAsset<'a, A> {
/// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option<Self> {
let value = asset.value.downcast_mut::<A>()?;
Some(TransformedSubAsset {
value,
labeled_assets: &mut asset.labeled_assets,
})
}
/// Retrieves the value of this asset.
#[inline]
pub fn get(&self) -> &A {
self.value
}
/// Mutably retrieves the value of this asset.
#[inline]
pub fn get_mut(&mut self) -> &mut A {
self.value
}
/// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get_mut(label)?;
let value = labeled.asset.value.downcast_mut::<B>()?;
Some(TransformedSubAsset {
value,
labeled_assets: &mut labeled.asset.labeled_assets,
})
} }
/// Returns the type-erased labeled asset, if it exists and matches this type. /// Returns the type-erased labeled asset, if it exists and matches this type.
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset> pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>

View File

@ -3,7 +3,7 @@
use bevy::{ use bevy::{
asset::{ asset::{
io::{Reader, VecReader}, io::{Reader, VecReader},
AssetLoader, ErasedLoadedAsset, LoadContext, LoadDirectError, AssetLoader, CompleteErasedLoadedAsset, LoadContext, LoadDirectError,
}, },
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
@ -14,7 +14,7 @@ use thiserror::Error;
#[derive(Asset, TypePath)] #[derive(Asset, TypePath)]
struct GzAsset { struct GzAsset {
uncompressed: ErasedLoadedAsset, uncompressed: CompleteErasedLoadedAsset,
} }
#[derive(Default)] #[derive(Default)]

View File

@ -149,15 +149,15 @@ impl AssetLoader for CoolTextLoader {
let ron: CoolTextRon = ron::de::from_bytes(&bytes)?; let ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let mut base_text = ron.text; let mut base_text = ron.text;
for embedded in ron.embedded_dependencies { for embedded in ron.embedded_dependencies {
let loaded = load_context let complete_loaded = load_context
.loader() .loader()
.immediate() .immediate()
.load::<Text>(&embedded) .load::<Text>(&embedded)
.await?; .await?;
base_text.push_str(&loaded.get().0); base_text.push_str(&complete_loaded.get_asset().get().0);
} }
for (path, settings_override) in ron.dependencies_with_settings { for (path, settings_override) in ron.dependencies_with_settings {
let loaded = load_context let complete_loaded = load_context
.loader() .loader()
.with_settings(move |settings| { .with_settings(move |settings| {
*settings = settings_override.clone(); *settings = settings_override.clone();
@ -165,7 +165,7 @@ impl AssetLoader for CoolTextLoader {
.immediate() .immediate()
.load::<Text>(&path) .load::<Text>(&path)
.await?; .await?;
base_text.push_str(&loaded.get().0); base_text.push_str(&complete_loaded.get_asset().get().0);
} }
Ok(CoolText { Ok(CoolText {
text: base_text, text: base_text,