bevy/crates/bevy_asset/src/transformer.rs
andriyDev f17644879d
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.
2025-02-10 21:06:37 +00:00

183 lines
6.4 KiB
Rust

use crate::{
meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset,
UntypedHandle,
};
use alloc::boxed::Box;
use atomicow::CowArc;
use bevy_platform_support::collections::HashMap;
use bevy_tasks::ConditionalSendFuture;
use core::{
borrow::Borrow,
convert::Infallible,
hash::Hash,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use serde::{Deserialize, Serialize};
/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type.
///
/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows.
pub trait AssetTransformer: Send + Sync + 'static {
/// The [`Asset`] type which this [`AssetTransformer`] takes as and input.
type AssetInput: Asset;
/// The [`Asset`] type which this [`AssetTransformer`] outputs.
type AssetOutput: Asset;
/// The settings type used by this [`AssetTransformer`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this transformer.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
/// Transforms the given [`TransformedAsset`] to [`AssetTransformer::AssetOutput`].
/// The [`TransformedAsset`]'s `labeled_assets` can be altered to add new Labeled Sub-Assets
/// The passed in `settings` can influence how the `asset` is transformed
fn transform<'a>(
&'a self,
asset: TransformedAsset<Self::AssetInput>,
settings: &'a Self::Settings,
) -> impl ConditionalSendFuture<Output = Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
}
/// An [`Asset`] (and any "sub assets") intended to be transformed
pub struct TransformedAsset<A: Asset> {
pub(crate) value: A,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> Deref for TransformedAsset<A> {
type Target = A;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<A: Asset> DerefMut for TransformedAsset<A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<A: Asset> TransformedAsset<A> {
/// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option<Self> {
if let Ok(value) = complete_asset.asset.value.downcast::<A>() {
return Some(TransformedAsset {
value: *value,
labeled_assets: complete_asset.labeled_assets,
});
}
None
}
/// Creates a new [`TransformedAsset`] from `asset`, transferring the `labeled_assets` from this [`TransformedAsset`] to the new one
pub fn replace_asset<B: Asset>(self, asset: B) -> TransformedAsset<B> {
TransformedAsset {
value: asset,
labeled_assets: self.labeled_assets,
}
}
/// Takes the labeled assets from `labeled_source` and places them in this [`TransformedAsset`]
pub fn take_labeled_assets<B: Asset>(&mut self, labeled_source: TransformedAsset<B>) {
self.labeled_assets = labeled_source.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 {
&mut 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<&mut B>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get_mut(label)?;
labeled.asset.value.downcast_mut::<B>()
}
/// 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)
}
}
/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.]
pub struct IdentityAssetTransformer<A: Asset> {
_phantom: PhantomData<fn(A) -> A>,
}
impl<A: Asset> IdentityAssetTransformer<A> {
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<A: Asset> Default for IdentityAssetTransformer<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Asset> AssetTransformer for IdentityAssetTransformer<A> {
type AssetInput = A;
type AssetOutput = A;
type Settings = ();
type Error = Infallible;
async fn transform<'a>(
&'a self,
asset: TransformedAsset<Self::AssetInput>,
_settings: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
Ok(asset)
}
}