-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Use async-fn in traits rather than BoxedFuture #12550
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent work! It's a "small" change being able to use async fn
directly for implementing these traits, but every little bit helps in an area already as hairy as async Rust.
#[cfg(not(target_arch = "wasm32"))] | ||
pub type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; | ||
mod conditional_send { | ||
/// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. WASM), | ||
/// futures aren't Send. | ||
pub trait ConditionalSend: Send {} | ||
impl<T: Send> ConditionalSend for T {} | ||
} | ||
|
||
#[allow(missing_docs)] | ||
#[cfg(target_arch = "wasm32")] | ||
pub type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>; | ||
#[allow(missing_docs)] | ||
mod conditional_send { | ||
pub trait ConditionalSend {} | ||
impl<T> ConditionalSend for T {} | ||
} | ||
|
||
pub use conditional_send::*; | ||
|
||
/// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. WASM), | ||
/// futures aren't Send. | ||
pub trait ConditionalSendFuture: std::future::Future + ConditionalSend {} | ||
impl<T: std::future::Future + ConditionalSend> ConditionalSendFuture for T {} | ||
|
||
/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection. | ||
pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in the previous PR, this is a clean solution to the WASM problem, whilst leaving itself available for other uses as required.
async fn load<'a>( | ||
&'a self, | ||
reader: &'a mut Reader, | ||
reader: &'a mut Reader<'_>, | ||
_settings: &'a (), | ||
_load_context: &'a mut LoadContext, | ||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { | ||
Box::pin(async move { | ||
let mut bytes = Vec::new(); | ||
reader.read_to_end(&mut bytes).await?; | ||
let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?; | ||
Ok(custom_asset) | ||
}) | ||
_load_context: &'a mut LoadContext<'_>, | ||
) -> Result<Self::Asset, Self::Error> { | ||
let mut bytes = Vec::new(); | ||
reader.read_to_end(&mut bytes).await?; | ||
let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?; | ||
Ok(custom_asset) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change alone almost makes the whole PR worth it. A much cleaner option in an area we do expect end-users to work in at some point.
|
||
/// Equivalent to an [`AssetWriter`] but using boxed futures, necessary eg. when using a `dyn AssetWriter`, | ||
/// as [`AssetWriter`] isn't currently object safe. | ||
pub trait ErasedAssetWriter: Send + Sync + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be lovely if (somehow) we could provide a hint like "AssetWriter is not object safe; use ErasedAssetWriter instead." if that particular compiler error is throw. I don't know if that's possible, let alone how, sadly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah good idea! Though I'm also not sure how you'd do that :/ I've at least added a note to AssetReader/AssetWriter about it.
It looks like your PR is a breaking change, but you didn't provide a migration guide. Could you add some context on what users should update when this change get released in a new version of Bevy? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. For other reviewers, this PR looks big but the overwhelming vast majority of it is indentation changes from dropping the Box::pin
context. Strong preference from me to expedite since a large change like this tends to accumulate merge conflicts quickly.
…async functions) (#12638) # Objective get_asset_paths tries to check whether a folder is empty, and if so delete it. However rather than checking whether any subfolder contains files it checks whether _all_ subfolders have files. Also cleanup various BoxedFutures in async recursive functions like these, rust 1.77 now allows recursive async functions (albeit still by boxing), hurray! This is a followup to #12550 (sorta). More BoxedFuture stuff can be removed now that rust 1.77 is out, which can use async recursive functions! This is mainly just cleaner code wise - the recursion still boxes the future so not much to win there. PR is mainly whitespace changes so do disable whitespace diffs for easier review.
# Objective Fixes #15541 A bunch of lifetimes were added during the Assets V2 rework, but after moving to async traits in #12550 they can be elided. That PR mentions that this might be the case, but apparently it wasn't followed up on at the time. ~~I ended up grepping for `<'a` and finding a similar case in `bevy_reflect` which I also fixed.~~ (edit: that one was needed apparently) Note that elided lifetimes are unstable in `impl Trait`. If that gets stabilized then we can elide even more. ## Solution Remove the extra lifetimes. ## Testing Everything still compiles. If I have messed something up there is a small risk that some user code stops compiling, but all the examples still work at least. --- ## Migration Guide The traits `AssetLoader`, `AssetSaver` and `Process` traits from `bevy_asset` now use elided lifetimes. If you implement these then remove the named lifetime.
# Objective Fixes bevyengine#15541 A bunch of lifetimes were added during the Assets V2 rework, but after moving to async traits in bevyengine#12550 they can be elided. That PR mentions that this might be the case, but apparently it wasn't followed up on at the time. ~~I ended up grepping for `<'a` and finding a similar case in `bevy_reflect` which I also fixed.~~ (edit: that one was needed apparently) Note that elided lifetimes are unstable in `impl Trait`. If that gets stabilized then we can elide even more. ## Solution Remove the extra lifetimes. ## Testing Everything still compiles. If I have messed something up there is a small risk that some user code stops compiling, but all the examples still work at least. --- ## Migration Guide The traits `AssetLoader`, `AssetSaver` and `Process` traits from `bevy_asset` now use elided lifetimes. If you implement these then remove the named lifetime.
Objective
Simplify implementing some asset traits without Box::pin(async move{}) shenanigans.
Fixes (in part) #11308
Solution
Use async-fn in traits when possible in all traits. Traits with return position impl trait are not object safe however, and as AssetReader and AssetWriter are both used with dynamic dispatch, you need a Boxed version of these futures anyway.
In the future, Rust is adding proc macros to generate these traits automatically, and at some point in the future dyn traits should 'just work'. Until then.... this seemed liked the right approach given more ErasedXXX already exist, but, no clue if there's plans here! Especially since these are public now, it's a bit of an unfortunate API, and means this is a breaking change.
In theory this saves some performance when these traits are used with static dispatch, but, seems like most code paths go through dynamic dispatch, which boxes anyway.
I also suspect a bunch of the lifetime annotations on these function could be simplified now as the BoxedFuture was often the only thing returned which needed a lifetime annotation, but I'm not touching that for now as traits + lifetimes can be so tricky.
This is a revival of pull/11362 after a spectacular merge f*ckup, with updates to the latest Bevy. Just to recap some discussion:
ConditionalSend
was the preferred name for the trait that might be Send, to deal with wasm platforms.Changelog
Migration Guide