Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions _release-content/migration-guides/assets_not_loaded_by_type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: AssetLoaders are no longer chosen by the asset type.
pull_requests: []
---

Previously, when picking which asset loader to use, the first step was looking up the asset loader
by the requested type. So if you loaded `asset_server.load::<Image>("blah.mp4")`, it would attempt
to load this `mp4` file with the `ImageLoader` (despite the fact that `mp4` is not a valid image
loader extension). This also lead to very complicated internal heuristics to deal with the fact that
a single asset file could be loaded as multiple different asset types at once.

Now, the asset loader selection can only use the file extension. This means for any file path there
is an unambiguous default loader.

This however breaks some uses. The most common is using a generic extension and then providing
asset loaders for those particular asset types. So for example, you may have files:

```
level1.ron
monster_snake.ron
```

Previously, if you had a `RonLoader<LevelDefinition>` and `RonLoader<Monster>`, these could be
loaded with `asset_server.load::<LevelDefinition>("level1.ron")` and
`asset_server.load::<Monster>("monster_snake.ron")` respectively, since the type of the `load` call
tells the asset system which loader to use.

Now, these two would conflict (and we'd use whichever loader was registered last). To resolve this,
one thing to do is to give a unique extension. A good pattern is to add the type name as the
extension, for example:

```
level1.LevelDefinition.ron
monster_snake.Monster.ron
```

(don't forget to update the `extensions` method in your `AssetLoader`)

Another approach is to use meta files. Meta files allow you to explicitly say which loader to use.
For example, we could write the following meta file at `level1.ron.meta`:

```
(
meta_format_version: "1.0",
asset: Load(
loader: "RonLoader<LevelDefinition>",
settings: (),
),
)
```

If you truly need to load one file with two loaders, come chat with us so we can better understand
your use-case!
72 changes: 15 additions & 57 deletions crates/bevy_asset/src/loader_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,28 +150,14 @@ impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
.await
}

/// Loads the provided path as the given type, returning the loaded data.
/// Loads the provided path, returning the loaded data.
///
/// This load is async and therefore needs to be awaited before returning the loaded data.
pub async fn load_erased_value<'a>(
self,
type_id: TypeId,
path: impl Into<AssetPath<'a>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
self.load_value_internal(Some(type_id), &path.into().into_owned(), None)
.await
.map(|(_, asset)| asset)
}

/// Loads the provided path with an unknown type (which is guessed based on the path or meta
/// file), returning the loaded data.
///
/// This load is async and therefore needs to be awaited before returning the loaded data.
pub async fn load_untyped_value<'a>(
self,
path: impl Into<AssetPath<'a>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
self.load_value_internal(None, &path.into().into_owned(), None)
self.load_value_internal(&path.into().into_owned(), None)
.await
.map(|(_, asset)| asset)
}
Expand All @@ -190,34 +176,17 @@ impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
.await
}

/// Loads the given type from the given `reader`, returning the loaded data.
/// Loads from the given `reader`, returning the loaded data.
///
/// This load is async and therefore needs to be awaited before returning the loaded data. The
/// provided path determines the path used for handles of subassets, as well as any relative
/// paths of assets used by the nested loader.
pub async fn load_erased_value_from_reader<'a>(
self,
type_id: TypeId,
path: impl Into<AssetPath<'a>>,
reader: &'builder mut dyn Reader,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
self.load_value_internal(Some(type_id), &path.into().into_owned(), Some(reader))
.await
.map(|(_, asset)| asset)
}

/// Loads an asset from the given `reader` with an unknown type (which is guessed based on the
/// path or meta file), returning the loaded data.
///
/// This load is async and therefore needs to be awaited before returning the loaded data. The
/// provided path determines the path used for handles of subassets, as well as any relative
/// paths of assets used by the nested loader.
pub async fn load_untyped_value_from_reader<'a>(
self,
path: impl Into<AssetPath<'a>>,
reader: &'builder mut dyn Reader,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
self.load_value_internal(None, &path.into().into_owned(), Some(reader))
self.load_value_internal(&path.into().into_owned(), Some(reader))
.await
.map(|(_, asset)| asset)
}
Expand Down Expand Up @@ -265,7 +234,6 @@ impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
/// `path`.
async fn load_value_internal(
self,
type_id: Option<TypeId>,
path: &AssetPath<'static>,
reader: Option<&'builder mut dyn Reader>,
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
Expand All @@ -282,32 +250,22 @@ impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
.stats
.started_load_tasks += 1;
let (mut meta, loader, mut reader) = if let Some(reader) = reader {
let loader = if let Some(type_id) = type_id {
self.load_context
.asset_server
.get_asset_loader_with_asset_type_id(type_id)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error: Box::new(error.into()),
})?
} else {
self.load_context
.asset_server
.get_path_asset_loader(path)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error: Box::new(error.into()),
})?
};
let loader = self
.load_context
.asset_server
.get_path_asset_loader(path)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error: Box::new(error.into()),
})?;
let meta = loader.default_meta();
(meta, loader, ReaderRef::Borrowed(reader))
} else {
let (meta, loader, reader) = self
.load_context
.asset_server
.get_meta_loader_and_reader(path, type_id)
.get_meta_loader_and_reader(path)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
Expand Down Expand Up @@ -340,7 +298,7 @@ impl<'ctx, 'builder> NestedLoadBuilder<'ctx, 'builder> {
path: AssetPath<'static>,
reader: Option<&'builder mut dyn Reader>,
) -> Result<LoadedAsset<A>, LoadDirectError> {
self.load_value_internal(Some(TypeId::of::<A>()), &path, reader)
self.load_value_internal(&path, reader)
.await
.and_then(move |(loader, untyped_asset)| {
untyped_asset
Expand Down
Loading
Loading