Allow loading assets with custom async behavior (#13700)

# Objective

Currently, bevy supports custom asset loading via `AssetServer:;add`,
which allows you to add arbitrary assets to the asset system and returns
a handle to it. However this only works for assets that have already
been fully loaded. If your loading logic involves any async, you need to
wait until the asset is done loading before adding it to the server.
This is problematic, as the `Handle` does not get allocated until the
very end, which makes it very difficult to use and defeats the value of
having handles for asynchronously-loaded assets.

## Solution

Add the method `AssetServer::add_async`. This has the same behavior as
`AssetServer::add`, only it accepts a future instead of a fully loaded
asset.

## Testing

I added an identical method to my company's fork of bevy, which works in
our app. I'm not quite sure how to go about adding an actual unit test
for asset loading behvior, but I will note that `AssetServer::add` also
does not appear to have any tests.

---

## Changelog

+ Added `AssetServer::add_async`, which allows adding assets with custom
asynchronous loading behavior to the `AssetServer`
This commit is contained in:
Joseph 2024-06-05 17:21:59 -07:00 committed by GitHub
parent 95edd2ea71
commit 9389de5c71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -26,6 +26,7 @@ use futures_lite::StreamExt;
use info::*;
use loaders::*;
use parking_lot::RwLock;
use std::future::Future;
use std::{any::Any, path::PathBuf};
use std::{any::TypeId, path::Path, sync::Arc};
use thiserror::Error;
@ -712,6 +713,50 @@ impl AssetServer {
handle
}
/// Queues a new asset to be tracked by the [`AssetServer`] and returns a [`Handle`] to it. This can be used to track
/// dependencies of assets created at runtime.
///
/// After the asset has been fully loaded, it will show up in the relevant [`Assets`] storage.
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn add_async<A: Asset>(
&self,
future: impl Future<Output = Result<A, AssetLoadError>> + Send + 'static,
) -> Handle<A> {
let handle = self
.data
.infos
.write()
.create_loading_handle_untyped(std::any::TypeId::of::<A>(), std::any::type_name::<A>());
let id = handle.id();
let event_sender = self.data.asset_event_sender.clone();
IoTaskPool::get()
.spawn(async move {
match future.await {
Ok(asset) => {
let loaded_asset = LoadedAsset::new_with_dependencies(asset, None).into();
event_sender
.send(InternalAssetEvent::Loaded { id, loaded_asset })
.unwrap();
}
Err(error) => {
error!("{error}");
event_sender
.send(InternalAssetEvent::Failed {
id,
path: Default::default(),
error,
})
.unwrap();
}
}
})
.detach();
handle.typed_debug_checked()
}
/// Loads all assets from the specified folder recursively. The [`LoadedFolder`] asset (when it loads) will
/// contain handles to all assets in the folder. You can wait for all assets to load by checking the [`LoadedFolder`]'s
/// [`RecursiveDependencyLoadState`].