Skip to content

Add a method for asynchronously waiting for an asset to load #14431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from

Conversation

joseph-gio
Copy link
Member

Objective

Currently, is is very painful to wait for an asset to load from the context of an async task. While bevy's AssetServer is asynchronous at its core, the public API is mainly focused on being used from synchronous contexts such as bevy systems. Currently, the best way of waiting for an asset handle to finish loading is to have a system that runs every frame, and either listens for AssetEvents or manually polls the asset server. While this is an acceptable interface for bevy systems, it is extremely awkward to do this in a way that integrates well with the async task system. At my work we had to create our own (inefficient) abstraction that encapsulated the boilerplate of checking an asset's load status and waking up a task when it's done.

Solution

Add the method AssetServer::wait_for_asset, which returns a future that suspends until the asset associated with a given Handle either finishes loading or fails to load.

Testing

TODO

@joseph-gio joseph-gio added A-Assets Load files from disk to use for things like images, models, and sounds D-Async Deals with asynchronous abstractions labels Jul 22, 2024
@alice-i-cecile alice-i-cecile added this to the 0.15 milestone Jul 22, 2024
@alice-i-cecile alice-i-cecile added the C-Usability A targeted quality-of-life change that makes Bevy easier to use label Jul 22, 2024
@mintlu8
Copy link
Contributor

mintlu8 commented Jul 22, 2024

This method already exists called load_acquire.

Does not return a future but is arguably more flexible.

@alice-i-cecile alice-i-cecile added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jul 22, 2024
@joseph-gio
Copy link
Member Author

joseph-gio commented Jul 22, 2024

load_acquire is less flexible serves a different purpose than wait_for_asset. load_acquire can only be used with an asset path, but wait_for_asset can be used with any handle, whether it was created by you or someone else.

And of course, the main advantage of wait_for_asset is that it does return a future. This API integrates well with the async task system, while load_acquire would require you to hack together a custom future that returns a value triggered by a guard type's drop impl.

@mintlu8
Copy link
Contributor

mintlu8 commented Jul 22, 2024

And of course, the main advantage of wait_for_asset is that it does return a future. This API integrates well with the async task system, while load_acquire would require you to hack together a custom future that returns a value triggered by a guard type's drop impl.

The reason load_acquire was implemented like this was for awaiting multiple assets, i.e. example multi_asset_sync, by passing in barrier guards. That being said I can see the benefit of turning arbitrary handles into futures.

///
/// This will return an error if the asset or any of its dependencies fail to load,
/// or if the asset has not been queued up to be loaded.
pub async fn wait_for_asset_id(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more ergonomic to return a 'static future, maybe based on event_listener::Event?

Can be simplified to event.listen().await

Copy link
Member Author

@joseph-gio joseph-gio Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally used a 'static future for wait_for_asset_id, but I changed it to non-static for consistency with the wait_for_asset{_untyped}, and to avoid unnecessarily cloning the AssestServer when _id() is used to implement the other methods. I'm neutral on if we keep it this way or not

@@ -37,6 +38,8 @@ pub(crate) struct AssetInfo {
/// The number of handle drops to skip for this asset.
/// See usage (and comments) in `get_or_create_path_handle` for context.
handle_drops_to_skip: usize,
/// List of tasks waiting for this asset to complete loading
pub(crate) waiting_tasks: Vec<Waker>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe more ergonomic to use event_listener::Event?

@alice-i-cecile
Copy link
Member

@JoJoJet, I'd like to get this into 0.15. Can you resolve merge conflicts?

Copy link
Contributor

@bushrat011899 bushrat011899 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great; I love seeing our asynchronous APIs get proper integration with Rust's async. I approve of this PR, but obviously you'll need to resolve the merge conflicts (switch to core instead of std, derive Display for WaitForAssetError, etc.)

@bushrat011899 bushrat011899 added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Oct 13, 2024
@alice-i-cecile alice-i-cecile added S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Oct 14, 2024
@alice-i-cecile
Copy link
Member

Closing as adopted.

github-merge-queue bot pushed a commit that referenced this pull request Oct 15, 2024
#15913)

# Objective

Currently, is is very painful to wait for an asset to load from the
context of an `async` task. While bevy's `AssetServer` is asynchronous
at its core, the public API is mainly focused on being used from
synchronous contexts such as bevy systems. Currently, the best way of
waiting for an asset handle to finish loading is to have a system that
runs every frame, and either listens for `AssetEvents` or manually polls
the asset server. While this is an acceptable interface for bevy
systems, it is extremely awkward to do this in a way that integrates
well with the `async` task system. At my work we had to create our own
(inefficient) abstraction that encapsulated the boilerplate of checking
an asset's load status and waking up a task when it's done.

## Solution

Add the method `AssetServer::wait_for_asset`, which returns a future
that suspends until the asset associated with a given `Handle` either
finishes loading or fails to load.

## Testing

- CI

## Notes

This is an adoption of #14431, the above description is directly from
that original PR.

---------

Co-authored-by: Joseph <21144246+JoJoJet@users.noreply.github.com>
Co-authored-by: andriyDev <andriydzikh@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Assets Load files from disk to use for things like images, models, and sounds C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Async Deals with asynchronous abstractions S-Adopt-Me The original PR author has no intent to complete this work. Pick me up!
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants