Skip to content

Commit

Permalink
Allow loading assets with custom async behavior (#13700)
Browse files Browse the repository at this point in the history
# 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`
  • Loading branch information
joseph-gio authored Jun 6, 2024
1 parent 95edd2e commit 9389de5
Showing 1 changed file with 45 additions and 0 deletions.
45 changes: 45 additions & 0 deletions crates/bevy_asset/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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`].
Expand Down

0 comments on commit 9389de5

Please sign in to comment.