From ac49dce4caec320cd65868ef12a8fa520b5686bc Mon Sep 17 00:00:00 2001 From: Arthur Brussee Date: Mon, 18 Mar 2024 17:56:57 +0000 Subject: [PATCH] Use async-fn in traits rather than BoxedFuture (#12550) # Objective Simplify implementing some asset traits without Box::pin(async move{}) shenanigans. Fixes (in part) https://github.com/bevyengine/bevy/issues/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 ](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html)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](https://github.com/bevyengine/bevy/pull/11362) after a spectacular merge f*ckup, with updates to the latest Bevy. Just to recap some discussion: - Overall this seems like a win for code quality, especially when implementing these traits, but a loss for having to deal with ErasedXXX variants. - `ConditionalSend` was the preferred name for the trait that might be Send, to deal with wasm platforms. - When reviewing be sure to disable whitespace difference, as that's 95% of the PR. ## Changelog - AssetReader, AssetWriter, AssetLoader, AssetSaver and Process now use async-fn in traits rather than boxed futures. ## Migration Guide - Custom implementations of AssetReader, AssetWriter, AssetLoader, AssetSaver and Process should switch to async fn rather than returning a bevy_utils::BoxedFuture. - Simultaniously, to use dynamic dispatch on these traits you should instead use dyn ErasedXXX. --- crates/bevy_animation/src/graph.rs | 58 ++-- crates/bevy_asset/src/io/android.rs | 69 ++--- crates/bevy_asset/src/io/file/file_asset.rs | 286 ++++++++--------- .../bevy_asset/src/io/file/sync_file_asset.rs | 288 ++++++++---------- crates/bevy_asset/src/io/gated.rs | 35 +-- crates/bevy_asset/src/io/memory.rs | 83 +++-- crates/bevy_asset/src/io/mod.rs | 232 ++++++++++++-- crates/bevy_asset/src/io/processor_gated.rs | 131 ++++---- crates/bevy_asset/src/io/source.rs | 53 ++-- crates/bevy_asset/src/io/wasm.rs | 35 +-- crates/bevy_asset/src/lib.rs | 99 +++--- crates/bevy_asset/src/loader.rs | 4 +- crates/bevy_asset/src/meta.rs | 14 +- crates/bevy_asset/src/processor/mod.rs | 13 +- crates/bevy_asset/src/processor/process.rs | 99 +++--- crates/bevy_asset/src/saver.rs | 6 +- crates/bevy_asset/src/server/loaders.rs | 20 +- crates/bevy_asset/src/server/mod.rs | 10 +- crates/bevy_asset/src/transformer.rs | 4 +- crates/bevy_audio/src/audio_source.rs | 19 +- crates/bevy_gltf/src/loader.rs | 16 +- .../bevy_render/src/render_resource/shader.rs | 66 ++-- .../src/texture/compressed_image_saver.rs | 70 ++--- .../src/texture/exr_texture_loader.rs | 65 ++-- .../src/texture/hdr_texture_loader.rs | 70 +++-- .../bevy_render/src/texture/image_loader.rs | 54 ++-- crates/bevy_scene/src/scene_loader.rs | 29 +- crates/bevy_text/src/font_loader.rs | 16 +- crates/bevy_utils/src/lib.rs | 27 +- examples/asset/asset_decompression.rs | 53 ++-- examples/asset/custom_asset.rs | 37 +-- examples/asset/custom_asset_reader.rs | 35 +-- examples/asset/processing/asset_processing.rs | 89 +++--- 33 files changed, 1090 insertions(+), 1095 deletions(-) diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index aba53f1d7a..01a270ed53 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -6,7 +6,6 @@ use std::ops::{Index, IndexMut}; use bevy_asset::io::Reader; use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, AsyncReadExt as _, Handle, LoadContext}; use bevy_reflect::{Reflect, ReflectSerialize}; -use bevy_utils::BoxedFuture; use petgraph::graph::{DiGraph, NodeIndex}; use ron::de::SpannedError; use serde::{Deserialize, Serialize}; @@ -336,40 +335,37 @@ impl AssetLoader for AnimationGraphAssetLoader { type Error = AnimationGraphLoadError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _: &'a Self::Settings, - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; - // Deserialize a `SerializedAnimationGraph` directly, so that we can - // get the list of the animation clips it refers to and load them. - let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; - let serialized_animation_graph = - SerializedAnimationGraph::deserialize(&mut deserializer) - .map_err(|err| deserializer.span_error(err))?; + // Deserialize a `SerializedAnimationGraph` directly, so that we can + // get the list of the animation clips it refers to and load them. + let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; + let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer) + .map_err(|err| deserializer.span_error(err))?; - // Load all `AssetPath`s to convert from a - // `SerializedAnimationGraph` to a real `AnimationGraph`. - Ok(AnimationGraph { - graph: serialized_animation_graph.graph.map( - |_, serialized_node| AnimationGraphNode { - clip: serialized_node.clip.as_ref().map(|clip| match clip { - SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id), - SerializedAnimationClip::AssetPath(asset_path) => { - load_context.load(asset_path) - } - }), - weight: serialized_node.weight, - }, - |_, _| (), - ), - root: serialized_animation_graph.root, - }) + // Load all `AssetPath`s to convert from a + // `SerializedAnimationGraph` to a real `AnimationGraph`. + Ok(AnimationGraph { + graph: serialized_animation_graph.graph.map( + |_, serialized_node| AnimationGraphNode { + clip: serialized_node.clip.as_ref().map(|clip| match clip { + SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id), + SerializedAnimationClip::AssetPath(asset_path) => { + load_context.load(asset_path) + } + }), + weight: serialized_node.weight, + }, + |_, _| (), + ), + root: serialized_animation_graph.root, }) } diff --git a/crates/bevy_asset/src/io/android.rs b/crates/bevy_asset/src/io/android.rs index 38791b35e0..18daac5949 100644 --- a/crates/bevy_asset/src/io/android.rs +++ b/crates/bevy_asset/src/io/android.rs @@ -2,7 +2,6 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader, }; use bevy_utils::tracing::error; -use bevy_utils::BoxedFuture; use std::{ffi::CString, path::Path}; /// [`AssetReader`] implementation for Android devices, built on top of Android's [`AssetManager`]. @@ -17,57 +16,47 @@ use std::{ffi::CString, path::Path}; pub struct AndroidAssetReader; impl AssetReader for AndroidAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let asset_manager = bevy_winit::ANDROID_APP - .get() - .expect("Bevy must be setup with the #[bevy_main] macro on Android") - .asset_manager(); - let mut opened_asset = asset_manager - .open(&CString::new(path.to_str().unwrap()).unwrap()) - .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?; - let bytes = opened_asset.buffer()?; - let reader: Box = Box::new(VecReader::new(bytes.to_vec())); - Ok(reader) - }) + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let asset_manager = bevy_winit::ANDROID_APP + .get() + .expect("Bevy must be setup with the #[bevy_main] macro on Android") + .asset_manager(); + let mut opened_asset = asset_manager + .open(&CString::new(path.to_str().unwrap()).unwrap()) + .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?; + let bytes = opened_asset.buffer()?; + let reader: Box = Box::new(VecReader::new(bytes.to_vec())); + Ok(reader) } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let meta_path = get_meta_path(path); - let asset_manager = bevy_winit::ANDROID_APP - .get() - .expect("Bevy must be setup with the #[bevy_main] macro on Android") - .asset_manager(); - let mut opened_asset = asset_manager - .open(&CString::new(meta_path.to_str().unwrap()).unwrap()) - .ok_or(AssetReaderError::NotFound(meta_path))?; - let bytes = opened_asset.buffer()?; - let reader: Box = Box::new(VecReader::new(bytes.to_vec())); - Ok(reader) - }) + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let meta_path = get_meta_path(path); + let asset_manager = bevy_winit::ANDROID_APP + .get() + .expect("Bevy must be setup with the #[bevy_main] macro on Android") + .asset_manager(); + let mut opened_asset = asset_manager + .open(&CString::new(meta_path.to_str().unwrap()).unwrap()) + .ok_or(AssetReaderError::NotFound(meta_path))?; + let bytes = opened_asset.buffer()?; + let reader: Box = Box::new(VecReader::new(bytes.to_vec())); + Ok(reader) } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, _path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { + ) -> Result, AssetReaderError> { let stream: Box = Box::new(EmptyPathStream); error!("Reading directories is not supported with the AndroidAssetReader"); - Box::pin(async move { Ok(stream) }) + Ok(stream) } - fn is_directory<'a>( + async fn is_directory<'a>( &'a self, _path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result> { + ) -> std::result::Result { error!("Reading directories is not supported with the AndroidAssetReader"); - Box::pin(async move { Ok(false) }) + Ok(false) } } diff --git a/crates/bevy_asset/src/io/file/file_asset.rs b/crates/bevy_asset/src/io/file/file_asset.rs index aa20913140..5826fe097d 100644 --- a/crates/bevy_asset/src/io/file/file_asset.rs +++ b/crates/bevy_asset/src/io/file/file_asset.rs @@ -3,7 +3,6 @@ use crate::io::{ Reader, Writer, }; use async_fs::{read_dir, File}; -use bevy_utils::BoxedFuture; use futures_lite::StreamExt; use std::path::Path; @@ -11,215 +10,168 @@ use std::path::Path; use super::{FileAssetReader, FileAssetWriter}; impl AssetReader for FileAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - match File::open(&full_path).await { - Ok(file) => { - let reader: Box = Box::new(file); - Ok(reader) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let full_path = self.root_path.join(path); + match File::open(&full_path).await { + Ok(file) => { + let reader: Box = Box::new(file); + Ok(reader) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { let meta_path = get_meta_path(path); - Box::pin(async move { - let full_path = self.root_path.join(meta_path); - match File::open(&full_path).await { - Ok(file) => { - let reader: Box = Box::new(file); - Ok(reader) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + let full_path = self.root_path.join(meta_path); + match File::open(&full_path).await { + Ok(file) => { + let reader: Box = Box::new(file); + Ok(reader) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - match read_dir(&full_path).await { - Ok(read_dir) => { - let root_path = self.root_path.clone(); - let mapped_stream = read_dir.filter_map(move |f| { - f.ok().and_then(|dir_entry| { - let path = dir_entry.path(); - // filter out meta files as they are not considered assets - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if ext.eq_ignore_ascii_case("meta") { - return None; - } + ) -> Result, AssetReaderError> { + let full_path = self.root_path.join(path); + match read_dir(&full_path).await { + Ok(read_dir) => { + let root_path = self.root_path.clone(); + let mapped_stream = read_dir.filter_map(move |f| { + f.ok().and_then(|dir_entry| { + let path = dir_entry.path(); + // filter out meta files as they are not considered assets + if let Some(ext) = path.extension().and_then(|e| e.to_str()) { + if ext.eq_ignore_ascii_case("meta") { + return None; } - let relative_path = path.strip_prefix(&root_path).unwrap(); - Some(relative_path.to_owned()) - }) - }); - let read_dir: Box = Box::new(mapped_stream); - Ok(read_dir) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + } + let relative_path = path.strip_prefix(&root_path).unwrap(); + Some(relative_path.to_owned()) + }) + }); + let read_dir: Box = Box::new(mapped_stream); + Ok(read_dir) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let full_path = self.root_path.join(path); - let metadata = full_path - .metadata() - .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?; - Ok(metadata.file_type().is_dir()) - }) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + let full_path = self.root_path.join(path); + let metadata = full_path + .metadata() + .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?; + Ok(metadata.file_type().is_dir()) } } impl AssetWriter for FileAssetWriter { - fn write<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - if let Some(parent) = full_path.parent() { - async_fs::create_dir_all(parent).await?; - } - let file = File::create(&full_path).await?; - let writer: Box = Box::new(file); - Ok(writer) - }) + async fn write<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> { + let full_path = self.root_path.join(path); + if let Some(parent) = full_path.parent() { + async_fs::create_dir_all(parent).await?; + } + let file = File::create(&full_path).await?; + let writer: Box = Box::new(file); + Ok(writer) } - fn write_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>> { - Box::pin(async move { - let meta_path = get_meta_path(path); - let full_path = self.root_path.join(meta_path); - if let Some(parent) = full_path.parent() { - async_fs::create_dir_all(parent).await?; - } - let file = File::create(&full_path).await?; - let writer: Box = Box::new(file); - Ok(writer) - }) + async fn write_meta<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> { + let meta_path = get_meta_path(path); + let full_path = self.root_path.join(meta_path); + if let Some(parent) = full_path.parent() { + async_fs::create_dir_all(parent).await?; + } + let file = File::create(&full_path).await?; + let writer: Box = Box::new(file); + Ok(writer) } - fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - async_fs::remove_file(full_path).await?; - Ok(()) - }) + async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + async_fs::remove_file(full_path).await?; + Ok(()) } - fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let meta_path = get_meta_path(path); - let full_path = self.root_path.join(meta_path); - async_fs::remove_file(full_path).await?; - Ok(()) - }) + async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> { + let meta_path = get_meta_path(path); + let full_path = self.root_path.join(meta_path); + async_fs::remove_file(full_path).await?; + Ok(()) } - fn rename<'a>( + async fn rename<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let full_old_path = self.root_path.join(old_path); - let full_new_path = self.root_path.join(new_path); - if let Some(parent) = full_new_path.parent() { - async_fs::create_dir_all(parent).await?; - } - async_fs::rename(full_old_path, full_new_path).await?; - Ok(()) - }) + ) -> Result<(), AssetWriterError> { + let full_old_path = self.root_path.join(old_path); + let full_new_path = self.root_path.join(new_path); + if let Some(parent) = full_new_path.parent() { + async_fs::create_dir_all(parent).await?; + } + async_fs::rename(full_old_path, full_new_path).await?; + Ok(()) } - fn rename_meta<'a>( + async fn rename_meta<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let old_meta_path = get_meta_path(old_path); - let new_meta_path = get_meta_path(new_path); - let full_old_path = self.root_path.join(old_meta_path); - let full_new_path = self.root_path.join(new_meta_path); - if let Some(parent) = full_new_path.parent() { - async_fs::create_dir_all(parent).await?; - } - async_fs::rename(full_old_path, full_new_path).await?; - Ok(()) - }) + ) -> Result<(), AssetWriterError> { + let old_meta_path = get_meta_path(old_path); + let new_meta_path = get_meta_path(new_path); + let full_old_path = self.root_path.join(old_meta_path); + let full_new_path = self.root_path.join(new_meta_path); + if let Some(parent) = full_new_path.parent() { + async_fs::create_dir_all(parent).await?; + } + async_fs::rename(full_old_path, full_new_path).await?; + Ok(()) } - fn remove_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - async_fs::remove_dir_all(full_path).await?; - Ok(()) - }) + async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + async_fs::remove_dir_all(full_path).await?; + Ok(()) } - fn remove_empty_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - async_fs::remove_dir(full_path).await?; - Ok(()) - }) + async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + async_fs::remove_dir(full_path).await?; + Ok(()) } - fn remove_assets_in_directory<'a>( + async fn remove_assets_in_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - async_fs::remove_dir_all(&full_path).await?; - async_fs::create_dir_all(&full_path).await?; - Ok(()) - }) + ) -> Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + async_fs::remove_dir_all(&full_path).await?; + async_fs::create_dir_all(&full_path).await?; + Ok(()) } } diff --git a/crates/bevy_asset/src/io/file/sync_file_asset.rs b/crates/bevy_asset/src/io/file/sync_file_asset.rs index a8bf573a7a..4264721501 100644 --- a/crates/bevy_asset/src/io/file/sync_file_asset.rs +++ b/crates/bevy_asset/src/io/file/sync_file_asset.rs @@ -5,7 +5,6 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream, Reader, Writer, }; -use bevy_utils::BoxedFuture; use std::{ fs::{read_dir, File}, @@ -76,221 +75,180 @@ impl Stream for DirReader { } impl AssetReader for FileAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - match File::open(&full_path) { - Ok(file) => { - let reader: Box = Box::new(FileReader(file)); - Ok(reader) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let full_path = self.root_path.join(path); + match File::open(&full_path) { + Ok(file) => { + let reader: Box = Box::new(FileReader(file)); + Ok(reader) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { let meta_path = get_meta_path(path); - Box::pin(async move { - let full_path = self.root_path.join(meta_path); - match File::open(&full_path) { - Ok(file) => { - let reader: Box = Box::new(FileReader(file)); - Ok(reader) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + let full_path = self.root_path.join(meta_path); + match File::open(&full_path) { + Ok(file) => { + let reader: Box = Box::new(FileReader(file)); + Ok(reader) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - match read_dir(&full_path) { - Ok(read_dir) => { - let root_path = self.root_path.clone(); - let mapped_stream = read_dir.filter_map(move |f| { - f.ok().and_then(|dir_entry| { - let path = dir_entry.path(); - // filter out meta files as they are not considered assets - if let Some(ext) = path.extension().and_then(|e| e.to_str()) { - if ext.eq_ignore_ascii_case("meta") { - return None; - } + ) -> Result, AssetReaderError> { + let full_path = self.root_path.join(path); + match read_dir(&full_path) { + Ok(read_dir) => { + let root_path = self.root_path.clone(); + let mapped_stream = read_dir.filter_map(move |f| { + f.ok().and_then(|dir_entry| { + let path = dir_entry.path(); + // filter out meta files as they are not considered assets + if let Some(ext) = path.extension().and_then(|e| e.to_str()) { + if ext.eq_ignore_ascii_case("meta") { + return None; } - let relative_path = path.strip_prefix(&root_path).unwrap(); - Some(relative_path.to_owned()) - }) - }); - let read_dir: Box = Box::new(DirReader(mapped_stream.collect())); - Ok(read_dir) - } - Err(e) => { - if e.kind() == std::io::ErrorKind::NotFound { - Err(AssetReaderError::NotFound(full_path)) - } else { - Err(e.into()) - } + } + let relative_path = path.strip_prefix(&root_path).unwrap(); + Some(relative_path.to_owned()) + }) + }); + let read_dir: Box = Box::new(DirReader(mapped_stream.collect())); + Ok(read_dir) + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + Err(AssetReaderError::NotFound(full_path)) + } else { + Err(e.into()) } } - }) + } } - fn is_directory<'a>( + async fn is_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result> { - Box::pin(async move { - let full_path = self.root_path.join(path); - let metadata = full_path - .metadata() - .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?; - Ok(metadata.file_type().is_dir()) - }) + ) -> std::result::Result { + let full_path = self.root_path.join(path); + let metadata = full_path + .metadata() + .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?; + Ok(metadata.file_type().is_dir()) } } impl AssetWriter for FileAssetWriter { - fn write<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - if let Some(parent) = full_path.parent() { - std::fs::create_dir_all(parent)?; - } - let file = File::create(&full_path)?; - let writer: Box = Box::new(FileWriter(file)); - Ok(writer) - }) + async fn write<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> { + let full_path = self.root_path.join(path); + if let Some(parent) = full_path.parent() { + std::fs::create_dir_all(parent)?; + } + let file = File::create(&full_path)?; + let writer: Box = Box::new(FileWriter(file)); + Ok(writer) } - fn write_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>> { - Box::pin(async move { - let meta_path = get_meta_path(path); - let full_path = self.root_path.join(meta_path); - if let Some(parent) = full_path.parent() { - std::fs::create_dir_all(parent)?; - } - let file = File::create(&full_path)?; - let writer: Box = Box::new(FileWriter(file)); - Ok(writer) - }) + async fn write_meta<'a>(&'a self, path: &'a Path) -> Result, AssetWriterError> { + let meta_path = get_meta_path(path); + let full_path = self.root_path.join(meta_path); + if let Some(parent) = full_path.parent() { + std::fs::create_dir_all(parent)?; + } + let file = File::create(&full_path)?; + let writer: Box = Box::new(FileWriter(file)); + Ok(writer) } - fn remove<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - std::fs::remove_file(full_path)?; - Ok(()) - }) + async fn remove<'a>(&'a self, path: &'a Path) -> std::result::Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + std::fs::remove_file(full_path)?; + Ok(()) } - fn remove_meta<'a>( + async fn remove_meta<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let meta_path = get_meta_path(path); - let full_path = self.root_path.join(meta_path); - std::fs::remove_file(full_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let meta_path = get_meta_path(path); + let full_path = self.root_path.join(meta_path); + std::fs::remove_file(full_path)?; + Ok(()) } - fn remove_directory<'a>( + async fn remove_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - std::fs::remove_dir_all(full_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + std::fs::remove_dir_all(full_path)?; + Ok(()) } - fn remove_empty_directory<'a>( + async fn remove_empty_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - std::fs::remove_dir(full_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + std::fs::remove_dir(full_path)?; + Ok(()) } - fn remove_assets_in_directory<'a>( + async fn remove_assets_in_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let full_path = self.root_path.join(path); - std::fs::remove_dir_all(&full_path)?; - std::fs::create_dir_all(&full_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let full_path = self.root_path.join(path); + std::fs::remove_dir_all(&full_path)?; + std::fs::create_dir_all(&full_path)?; + Ok(()) } - fn rename<'a>( + async fn rename<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let full_old_path = self.root_path.join(old_path); - let full_new_path = self.root_path.join(new_path); - if let Some(parent) = full_new_path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::rename(full_old_path, full_new_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let full_old_path = self.root_path.join(old_path); + let full_new_path = self.root_path.join(new_path); + if let Some(parent) = full_new_path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::rename(full_old_path, full_new_path)?; + Ok(()) } - fn rename_meta<'a>( + async fn rename_meta<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { - Box::pin(async move { - let old_meta_path = get_meta_path(old_path); - let new_meta_path = get_meta_path(new_path); - let full_old_path = self.root_path.join(old_meta_path); - let full_new_path = self.root_path.join(new_meta_path); - if let Some(parent) = full_new_path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::rename(full_old_path, full_new_path)?; - Ok(()) - }) + ) -> std::result::Result<(), AssetWriterError> { + let old_meta_path = get_meta_path(old_path); + let new_meta_path = get_meta_path(new_path); + let full_old_path = self.root_path.join(old_meta_path); + let full_new_path = self.root_path.join(new_meta_path); + if let Some(parent) = full_new_path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::rename(full_old_path, full_new_path)?; + Ok(()) } } diff --git a/crates/bevy_asset/src/io/gated.rs b/crates/bevy_asset/src/io/gated.rs index 76f531a04c..d3d2b35f1f 100644 --- a/crates/bevy_asset/src/io/gated.rs +++ b/crates/bevy_asset/src/io/gated.rs @@ -1,5 +1,5 @@ use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; -use bevy_utils::{BoxedFuture, HashMap}; +use bevy_utils::HashMap; use crossbeam_channel::{Receiver, Sender}; use parking_lot::RwLock; use std::{path::Path, sync::Arc}; @@ -55,10 +55,7 @@ impl GatedReader { } impl AssetReader for GatedReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { let receiver = { let mut gates = self.gates.write(); let gates = gates @@ -66,31 +63,23 @@ impl AssetReader for GatedReader { .or_insert_with(crossbeam_channel::unbounded); gates.1.clone() }; - Box::pin(async move { - receiver.recv().unwrap(); - let result = self.reader.read(path).await?; - Ok(result) - }) + receiver.recv().unwrap(); + let result = self.reader.read(path).await?; + Ok(result) } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - self.reader.read_meta(path) + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + self.reader.read_meta(path).await } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - self.reader.read_directory(path) + ) -> Result, AssetReaderError> { + self.reader.read_directory(path).await } - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - self.reader.is_directory(path) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + self.reader.is_directory(path).await } } diff --git a/crates/bevy_asset/src/io/memory.rs b/crates/bevy_asset/src/io/memory.rs index cc13d04820..563086f7b0 100644 --- a/crates/bevy_asset/src/io/memory.rs +++ b/crates/bevy_asset/src/io/memory.rs @@ -1,5 +1,5 @@ use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; -use bevy_utils::{BoxedFuture, HashMap}; +use bevy_utils::HashMap; use futures_io::AsyncRead; use futures_lite::{ready, Stream}; use parking_lot::RwLock; @@ -237,62 +237,47 @@ impl AsyncRead for DataReader { } impl AssetReader for MemoryAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - self.root - .get_asset(path) - .map(|data| { - let reader: Box = Box::new(DataReader { - data, - bytes_read: 0, - }); - reader - }) - .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) - }) + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + self.root + .get_asset(path) + .map(|data| { + let reader: Box = Box::new(DataReader { + data, + bytes_read: 0, + }); + reader + }) + .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - self.root - .get_metadata(path) - .map(|data| { - let reader: Box = Box::new(DataReader { - data, - bytes_read: 0, - }); - reader - }) - .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) - }) + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + self.root + .get_metadata(path) + .map(|data| { + let reader: Box = Box::new(DataReader { + data, + bytes_read: 0, + }); + reader + }) + .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - Box::pin(async move { - self.root - .get_dir(path) - .map(|dir| { - let stream: Box = Box::new(DirStream::new(dir)); - stream - }) - .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) - }) + ) -> Result, AssetReaderError> { + self.root + .get_dir(path) + .map(|dir| { + let stream: Box = Box::new(DirStream::new(dir)); + stream + }) + .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf())) } - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { Ok(self.root.get_dir(path).is_some()) }) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + Ok(self.root.get_dir(path).is_some()) } } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index 9b8f83b0ee..766e536455 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -21,7 +21,7 @@ mod source; pub use futures_lite::{AsyncReadExt, AsyncWriteExt}; pub use source::*; -use bevy_utils::BoxedFuture; +use bevy_utils::{BoxedFuture, ConditionalSendFuture}; use futures_io::{AsyncRead, AsyncWrite}; use futures_lite::{ready, Stream}; use std::{ @@ -59,7 +59,7 @@ pub type Reader<'a> = dyn AsyncRead + Unpin + Send + Sync + 'a; /// Performs read operations on an asset storage. [`AssetReader`] exposes a "virtual filesystem" /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given -/// `path`. +/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead. /// /// Also see [`AssetWriter`]. pub trait AssetReader: Send + Sync + 'static { @@ -67,35 +67,90 @@ pub trait AssetReader: Send + Sync + 'static { fn read<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>>; + ) -> impl ConditionalSendFuture>, AssetReaderError>>; /// Returns a future to load the full file data at the provided path. fn read_meta<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>>; + ) -> impl ConditionalSendFuture>, AssetReaderError>>; /// Returns an iterator of directory entry names at the provided path. fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>>; - /// Returns true if the provided path points to a directory. + ) -> impl ConditionalSendFuture, AssetReaderError>>; + /// Returns an iterator of directory entry names at the provided path. fn is_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result>; - + ) -> impl ConditionalSendFuture>; /// Reads asset metadata bytes at the given `path` into a [`Vec`]. This is a convenience /// function that wraps [`AssetReader::read_meta`] by default. fn read_meta_bytes<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - Box::pin(async move { + ) -> impl ConditionalSendFuture, AssetReaderError>> { + async { let mut meta_reader = self.read_meta(path).await?; let mut meta_bytes = Vec::new(); meta_reader.read_to_end(&mut meta_bytes).await?; Ok(meta_bytes) - }) + } + } +} + +/// Equivalent to an [`AssetReader`] but using boxed futures, necessary eg. when using a `dyn AssetReader`, +/// as [`AssetReader`] isn't currently object safe. +pub trait ErasedAssetReader: Send + Sync + 'static { + /// Returns a future to load the full file data at the provided path. + fn read<'a>(&'a self, path: &'a Path) + -> BoxedFuture>, AssetReaderError>>; + /// Returns a future to load the full file data at the provided path. + fn read_meta<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture>, AssetReaderError>>; + /// Returns an iterator of directory entry names at the provided path. + fn read_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetReaderError>>; + /// Returns true if the provided path points to a directory. + fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture>; + /// Reads asset metadata bytes at the given `path` into a [`Vec`]. This is a convenience + /// function that wraps [`ErasedAssetReader::read_meta`] by default. + fn read_meta_bytes<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetReaderError>>; +} + +impl ErasedAssetReader for T { + fn read<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture>, AssetReaderError>> { + Box::pin(Self::read(self, path)) + } + fn read_meta<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture>, AssetReaderError>> { + Box::pin(Self::read_meta(self, path)) + } + fn read_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetReaderError>> { + Box::pin(Self::read_directory(self, path)) + } + fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture> { + Box::pin(Self::is_directory(self, path)) + } + fn read_meta_bytes<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetReaderError>> { + Box::pin(Self::read_meta_bytes(self, path)) } } @@ -113,7 +168,7 @@ pub enum AssetWriterError { /// Preforms write operations on an asset storage. [`AssetWriter`] exposes a "virtual filesystem" /// API, where asset bytes and asset metadata bytes are both stored and accessible for a given -/// `path`. +/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead. /// /// Also see [`AssetReader`]. pub trait AssetWriter: Send + Sync + 'static { @@ -121,72 +176,195 @@ pub trait AssetWriter: Send + Sync + 'static { fn write<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>>; + ) -> impl ConditionalSendFuture, AssetWriterError>>; /// Writes the full asset meta bytes at the provided path. /// This _should not_ include storage specific extensions like `.meta`. fn write_meta<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetWriterError>>; + ) -> impl ConditionalSendFuture, AssetWriterError>>; /// Removes the asset stored at the given path. - fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + fn remove<'a>( + &'a self, + path: &'a Path, + ) -> impl ConditionalSendFuture>; /// Removes the asset meta stored at the given path. /// This _should not_ include storage specific extensions like `.meta`. - fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + fn remove_meta<'a>( + &'a self, + path: &'a Path, + ) -> impl ConditionalSendFuture>; /// Renames the asset at `old_path` to `new_path` fn rename<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + ) -> impl ConditionalSendFuture>; /// Renames the asset meta for the asset at `old_path` to `new_path`. /// This _should not_ include storage specific extensions like `.meta`. fn rename_meta<'a>( &'a self, old_path: &'a Path, new_path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + ) -> impl ConditionalSendFuture>; /// Removes the directory at the given path, including all assets _and_ directories in that directory. fn remove_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + ) -> impl ConditionalSendFuture>; /// Removes the directory at the given path, but only if it is completely empty. This will return an error if the /// directory is not empty. fn remove_empty_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + ) -> impl ConditionalSendFuture>; /// Removes all assets (and directories) in this directory, resulting in an empty directory. fn remove_assets_in_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result<(), AssetWriterError>>; + ) -> impl ConditionalSendFuture>; /// Writes the asset `bytes` to the given `path`. fn write_bytes<'a>( &'a self, path: &'a Path, bytes: &'a [u8], - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { + ) -> impl ConditionalSendFuture> { + async { let mut writer = self.write(path).await?; writer.write_all(bytes).await?; writer.flush().await?; Ok(()) - }) + } } /// Writes the asset meta `bytes` to the given `path`. fn write_meta_bytes<'a>( &'a self, path: &'a Path, bytes: &'a [u8], - ) -> BoxedFuture<'a, Result<(), AssetWriterError>> { - Box::pin(async move { + ) -> impl ConditionalSendFuture> { + async { let mut meta_writer = self.write_meta(path).await?; meta_writer.write_all(bytes).await?; meta_writer.flush().await?; Ok(()) - }) + } + } +} + +/// 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 { + /// Writes the full asset bytes at the provided path. + fn write<'a>(&'a self, path: &'a Path) -> BoxedFuture, AssetWriterError>>; + /// Writes the full asset meta bytes at the provided path. + /// This _should not_ include storage specific extensions like `.meta`. + fn write_meta<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetWriterError>>; + /// Removes the asset stored at the given path. + fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture>; + /// Removes the asset meta stored at the given path. + /// This _should not_ include storage specific extensions like `.meta`. + fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture>; + /// Renames the asset at `old_path` to `new_path` + fn rename<'a>( + &'a self, + old_path: &'a Path, + new_path: &'a Path, + ) -> BoxedFuture>; + /// Renames the asset meta for the asset at `old_path` to `new_path`. + /// This _should not_ include storage specific extensions like `.meta`. + fn rename_meta<'a>( + &'a self, + old_path: &'a Path, + new_path: &'a Path, + ) -> BoxedFuture>; + /// Removes the directory at the given path, including all assets _and_ directories in that directory. + fn remove_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture>; + /// Removes the directory at the given path, but only if it is completely empty. This will return an error if the + /// directory is not empty. + fn remove_empty_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture>; + /// Removes all assets (and directories) in this directory, resulting in an empty directory. + fn remove_assets_in_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture>; + /// Writes the asset `bytes` to the given `path`. + fn write_bytes<'a>( + &'a self, + path: &'a Path, + bytes: &'a [u8], + ) -> BoxedFuture>; + /// Writes the asset meta `bytes` to the given `path`. + fn write_meta_bytes<'a>( + &'a self, + path: &'a Path, + bytes: &'a [u8], + ) -> BoxedFuture>; +} + +impl ErasedAssetWriter for T { + fn write<'a>(&'a self, path: &'a Path) -> BoxedFuture, AssetWriterError>> { + Box::pin(Self::write(self, path)) + } + fn write_meta<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture, AssetWriterError>> { + Box::pin(Self::write_meta(self, path)) + } + fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture> { + Box::pin(Self::remove(self, path)) + } + fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture> { + Box::pin(Self::remove_meta(self, path)) + } + fn rename<'a>( + &'a self, + old_path: &'a Path, + new_path: &'a Path, + ) -> BoxedFuture> { + Box::pin(Self::rename(self, old_path, new_path)) + } + fn rename_meta<'a>( + &'a self, + old_path: &'a Path, + new_path: &'a Path, + ) -> BoxedFuture> { + Box::pin(Self::rename_meta(self, old_path, new_path)) + } + fn remove_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture> { + Box::pin(Self::remove_directory(self, path)) + } + fn remove_empty_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture> { + Box::pin(Self::remove_empty_directory(self, path)) + } + fn remove_assets_in_directory<'a>( + &'a self, + path: &'a Path, + ) -> BoxedFuture> { + Box::pin(Self::remove_assets_in_directory(self, path)) + } + fn write_bytes<'a>( + &'a self, + path: &'a Path, + bytes: &'a [u8], + ) -> BoxedFuture> { + Box::pin(Self::write_bytes(self, path, bytes)) + } + fn write_meta_bytes<'a>( + &'a self, + path: &'a Path, + bytes: &'a [u8], + ) -> BoxedFuture> { + Box::pin(Self::write_meta_bytes(self, path, bytes)) } } diff --git a/crates/bevy_asset/src/io/processor_gated.rs b/crates/bevy_asset/src/io/processor_gated.rs index b86460f04b..5f3bfe1467 100644 --- a/crates/bevy_asset/src/io/processor_gated.rs +++ b/crates/bevy_asset/src/io/processor_gated.rs @@ -5,16 +5,17 @@ use crate::{ }; use async_lock::RwLockReadGuardArc; use bevy_utils::tracing::trace; -use bevy_utils::BoxedFuture; use futures_io::AsyncRead; use std::{path::Path, pin::Pin, sync::Arc}; +use super::ErasedAssetReader; + /// An [`AssetReader`] that will prevent asset (and asset metadata) read futures from returning for a /// given path until that path has been processed by [`AssetProcessor`]. /// /// [`AssetProcessor`]: crate::processor::AssetProcessor pub struct ProcessorGatedReader { - reader: Box, + reader: Box, source: AssetSourceId<'static>, processor_data: Arc, } @@ -23,7 +24,7 @@ impl ProcessorGatedReader { /// Creates a new [`ProcessorGatedReader`]. pub fn new( source: AssetSourceId<'static>, - reader: Box, + reader: Box, processor_data: Arc, ) -> Self { Self { @@ -48,87 +49,69 @@ impl ProcessorGatedReader { } impl AssetReader for ProcessorGatedReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); - trace!("Waiting for processing to finish before reading {asset_path}"); - let process_result = self - .processor_data - .wait_until_processed(asset_path.clone()) - .await; - match process_result { - ProcessStatus::Processed => {} - ProcessStatus::Failed | ProcessStatus::NonExistent => { - return Err(AssetReaderError::NotFound(path.to_owned())); - } + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); + trace!("Waiting for processing to finish before reading {asset_path}"); + let process_result = self + .processor_data + .wait_until_processed(asset_path.clone()) + .await; + match process_result { + ProcessStatus::Processed => {} + ProcessStatus::Failed | ProcessStatus::NonExistent => { + return Err(AssetReaderError::NotFound(path.to_owned())); } - trace!("Processing finished with {asset_path}, reading {process_result:?}",); - let lock = self.get_transaction_lock(&asset_path).await?; - let asset_reader = self.reader.read(path).await?; - let reader: Box> = - Box::new(TransactionLockedReader::new(asset_reader, lock)); - Ok(reader) - }) + } + trace!("Processing finished with {asset_path}, reading {process_result:?}",); + let lock = self.get_transaction_lock(&asset_path).await?; + let asset_reader = self.reader.read(path).await?; + let reader: Box> = Box::new(TransactionLockedReader::new(asset_reader, lock)); + Ok(reader) } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); - trace!("Waiting for processing to finish before reading meta for {asset_path}",); - let process_result = self - .processor_data - .wait_until_processed(asset_path.clone()) - .await; - match process_result { - ProcessStatus::Processed => {} - ProcessStatus::Failed | ProcessStatus::NonExistent => { - return Err(AssetReaderError::NotFound(path.to_owned())); - } + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); + trace!("Waiting for processing to finish before reading meta for {asset_path}",); + let process_result = self + .processor_data + .wait_until_processed(asset_path.clone()) + .await; + match process_result { + ProcessStatus::Processed => {} + ProcessStatus::Failed | ProcessStatus::NonExistent => { + return Err(AssetReaderError::NotFound(path.to_owned())); } - trace!("Processing finished with {process_result:?}, reading meta for {asset_path}",); - let lock = self.get_transaction_lock(&asset_path).await?; - let meta_reader = self.reader.read_meta(path).await?; - let reader: Box> = Box::new(TransactionLockedReader::new(meta_reader, lock)); - Ok(reader) - }) + } + trace!("Processing finished with {process_result:?}, reading meta for {asset_path}",); + let lock = self.get_transaction_lock(&asset_path).await?; + let meta_reader = self.reader.read_meta(path).await?; + let reader: Box> = Box::new(TransactionLockedReader::new(meta_reader, lock)); + Ok(reader) } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - Box::pin(async move { - trace!( - "Waiting for processing to finish before reading directory {:?}", - path - ); - self.processor_data.wait_until_finished().await; - trace!("Processing finished, reading directory {:?}", path); - let result = self.reader.read_directory(path).await?; - Ok(result) - }) + ) -> Result, AssetReaderError> { + trace!( + "Waiting for processing to finish before reading directory {:?}", + path + ); + self.processor_data.wait_until_finished().await; + trace!("Processing finished, reading directory {:?}", path); + let result = self.reader.read_directory(path).await?; + Ok(result) } - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - trace!( - "Waiting for processing to finish before reading directory {:?}", - path - ); - self.processor_data.wait_until_finished().await; - trace!("Processing finished, getting directory status {:?}", path); - let result = self.reader.is_directory(path).await?; - Ok(result) - }) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + trace!( + "Waiting for processing to finish before reading directory {:?}", + path + ); + self.processor_data.wait_until_finished().await; + trace!("Processing finished, getting directory status {:?}", path); + let result = self.reader.is_directory(path).await?; + Ok(result) } } diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index 7293a73bea..21bd29294e 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -1,8 +1,5 @@ use crate::{ - io::{ - processor_gated::ProcessorGatedReader, AssetReader, AssetSourceEvent, AssetWatcher, - AssetWriter, - }, + io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher}, processor::AssetProcessorData, }; use bevy_ecs::system::Resource; @@ -11,6 +8,12 @@ use bevy_utils::{CowArc, Duration, HashMap}; use std::{fmt::Display, hash::Hash, sync::Arc}; use thiserror::Error; +use super::{ErasedAssetReader, ErasedAssetWriter}; + +// Needed for doc strings. +#[allow(unused_imports)] +use crate::io::{AssetReader, AssetWriter}; + /// A reference to an "asset source", which maps to an [`AssetReader`] and/or [`AssetWriter`]. /// /// * [`AssetSourceId::Default`] corresponds to "default asset paths" that don't specify a source: `/path/to/asset.png` @@ -110,8 +113,8 @@ impl<'a> PartialEq for AssetSourceId<'a> { /// and whether or not the source is processed. #[derive(Default)] pub struct AssetSourceBuilder { - pub reader: Option Box + Send + Sync>>, - pub writer: Option Option> + Send + Sync>>, + pub reader: Option Box + Send + Sync>>, + pub writer: Option Option> + Send + Sync>>, pub watcher: Option< Box< dyn FnMut(crossbeam_channel::Sender) -> Option> @@ -119,9 +122,9 @@ pub struct AssetSourceBuilder { + Sync, >, >, - pub processed_reader: Option Box + Send + Sync>>, + pub processed_reader: Option Box + Send + Sync>>, pub processed_writer: - Option Option> + Send + Sync>>, + Option Option> + Send + Sync>>, pub processed_watcher: Option< Box< dyn FnMut(crossbeam_channel::Sender) -> Option> @@ -192,7 +195,7 @@ impl AssetSourceBuilder { /// Will use the given `reader` function to construct unprocessed [`AssetReader`] instances. pub fn with_reader( mut self, - reader: impl FnMut() -> Box + Send + Sync + 'static, + reader: impl FnMut() -> Box + Send + Sync + 'static, ) -> Self { self.reader = Some(Box::new(reader)); self @@ -201,7 +204,7 @@ impl AssetSourceBuilder { /// Will use the given `writer` function to construct unprocessed [`AssetWriter`] instances. pub fn with_writer( mut self, - writer: impl FnMut(bool) -> Option> + Send + Sync + 'static, + writer: impl FnMut(bool) -> Option> + Send + Sync + 'static, ) -> Self { self.writer = Some(Box::new(writer)); self @@ -222,7 +225,7 @@ impl AssetSourceBuilder { /// Will use the given `reader` function to construct processed [`AssetReader`] instances. pub fn with_processed_reader( mut self, - reader: impl FnMut() -> Box + Send + Sync + 'static, + reader: impl FnMut() -> Box + Send + Sync + 'static, ) -> Self { self.processed_reader = Some(Box::new(reader)); self @@ -231,7 +234,7 @@ impl AssetSourceBuilder { /// Will use the given `writer` function to construct processed [`AssetWriter`] instances. pub fn with_processed_writer( mut self, - writer: impl FnMut(bool) -> Option> + Send + Sync + 'static, + writer: impl FnMut(bool) -> Option> + Send + Sync + 'static, ) -> Self { self.processed_writer = Some(Box::new(writer)); self @@ -355,10 +358,10 @@ impl AssetSourceBuilders { /// for a specific asset source, identified by an [`AssetSourceId`]. pub struct AssetSource { id: AssetSourceId<'static>, - reader: Box, - writer: Option>, - processed_reader: Option>, - processed_writer: Option>, + reader: Box, + writer: Option>, + processed_reader: Option>, + processed_writer: Option>, watcher: Option>, processed_watcher: Option>, event_receiver: Option>, @@ -379,13 +382,13 @@ impl AssetSource { /// Return's this source's unprocessed [`AssetReader`]. #[inline] - pub fn reader(&self) -> &dyn AssetReader { + pub fn reader(&self) -> &dyn ErasedAssetReader { &*self.reader } /// Return's this source's unprocessed [`AssetWriter`], if it exists. #[inline] - pub fn writer(&self) -> Result<&dyn AssetWriter, MissingAssetWriterError> { + pub fn writer(&self) -> Result<&dyn ErasedAssetWriter, MissingAssetWriterError> { self.writer .as_deref() .ok_or_else(|| MissingAssetWriterError(self.id.clone_owned())) @@ -393,7 +396,9 @@ impl AssetSource { /// Return's this source's processed [`AssetReader`], if it exists. #[inline] - pub fn processed_reader(&self) -> Result<&dyn AssetReader, MissingProcessedAssetReaderError> { + pub fn processed_reader( + &self, + ) -> Result<&dyn ErasedAssetReader, MissingProcessedAssetReaderError> { self.processed_reader .as_deref() .ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned())) @@ -401,7 +406,9 @@ impl AssetSource { /// Return's this source's processed [`AssetWriter`], if it exists. #[inline] - pub fn processed_writer(&self) -> Result<&dyn AssetWriter, MissingProcessedAssetWriterError> { + pub fn processed_writer( + &self, + ) -> Result<&dyn ErasedAssetWriter, MissingProcessedAssetWriterError> { self.processed_writer .as_deref() .ok_or_else(|| MissingProcessedAssetWriterError(self.id.clone_owned())) @@ -429,7 +436,9 @@ impl AssetSource { /// Returns a builder function for this platform's default [`AssetReader`]. `path` is the relative path to /// the asset root. - pub fn get_default_reader(_path: String) -> impl FnMut() -> Box + Send + Sync { + pub fn get_default_reader( + _path: String, + ) -> impl FnMut() -> Box + Send + Sync { move || { #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] return Box::new(super::file::FileAssetReader::new(&_path)); @@ -444,7 +453,7 @@ impl AssetSource { /// the asset root. This will return [`None`] if this platform does not support writing assets by default. pub fn get_default_writer( _path: String, - ) -> impl FnMut(bool) -> Option> + Send + Sync { + ) -> impl FnMut(bool) -> Option> + Send + Sync { move |_create_root: bool| { #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] return Some(Box::new(super::file::FileAssetWriter::new( diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index aab497ddfa..95f4905508 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -2,7 +2,6 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader, }; use bevy_utils::tracing::error; -use bevy_utils::BoxedFuture; use js_sys::{Uint8Array, JSON}; use std::path::{Path, PathBuf}; use wasm_bindgen::{JsCast, JsValue}; @@ -59,40 +58,30 @@ impl HttpWasmAssetReader { } impl AssetReader for HttpWasmAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let path = self.root_path.join(path); - self.fetch_bytes(path).await - }) + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let path = self.root_path.join(path); + self.fetch_bytes(path).await } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - Box::pin(async move { - let meta_path = get_meta_path(&self.root_path.join(path)); - Ok(self.fetch_bytes(meta_path).await?) - }) + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + let meta_path = get_meta_path(&self.root_path.join(path)); + Ok(self.fetch_bytes(meta_path).await?) } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, _path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { + ) -> Result, AssetReaderError> { let stream: Box = Box::new(EmptyPathStream); error!("Reading directories is not supported with the HttpWasmAssetReader"); - Box::pin(async move { Ok(stream) }) + Ok(stream) } - fn is_directory<'a>( + async fn is_directory<'a>( &'a self, _path: &'a Path, - ) -> BoxedFuture<'a, std::result::Result> { + ) -> std::result::Result { error!("Reading directories is not supported with the HttpWasmAssetReader"); - Box::pin(async move { Ok(false) }) + Ok(false) } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 4e579a04f2..f15797f619 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -40,8 +40,6 @@ pub use path::*; pub use reflect::*; pub use server::*; -pub use bevy_utils::BoxedFuture; - /// Rusty Object Notation, a crate used to serialize and deserialize bevy assets. pub use ron; @@ -448,7 +446,7 @@ mod tests { }; use bevy_log::LogPlugin; use bevy_reflect::TypePath; - use bevy_utils::{BoxedFuture, Duration, HashMap}; + use bevy_utils::{Duration, HashMap}; use futures_lite::AsyncReadExt; use serde::{Deserialize, Serialize}; use std::{path::Path, sync::Arc}; @@ -497,40 +495,38 @@ mod tests { type Error = CoolTextLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a Self::Settings, - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?; - let mut embedded = String::new(); - for dep in ron.embedded_dependencies { - let loaded = load_context.load_direct(&dep).await.map_err(|_| { - Self::Error::CannotLoadDependency { - dependency: dep.into(), - } - })?; - let cool = loaded.get::().unwrap(); - embedded.push_str(&cool.text); - } - Ok(CoolText { - text: ron.text, - embedded, - dependencies: ron - .dependencies - .iter() - .map(|p| load_context.load(p)) - .collect(), - sub_texts: ron - .sub_texts - .drain(..) - .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text })) - .collect(), - }) + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?; + let mut embedded = String::new(); + for dep in ron.embedded_dependencies { + let loaded = load_context.load_direct(&dep).await.map_err(|_| { + Self::Error::CannotLoadDependency { + dependency: dep.into(), + } + })?; + let cool = loaded.get::().unwrap(); + embedded.push_str(&cool.text); + } + Ok(CoolText { + text: ron.text, + embedded, + dependencies: ron + .dependencies + .iter() + .map(|p| load_context.load(p)) + .collect(), + sub_texts: ron + .sub_texts + .drain(..) + .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text })) + .collect(), }) } @@ -560,31 +556,25 @@ mod tests { } impl AssetReader for UnstableMemoryAssetReader { - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - self.memory_reader.is_directory(path) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + self.memory_reader.is_directory(path).await } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - self.memory_reader.read_directory(path) + ) -> Result, AssetReaderError> { + self.memory_reader.read_directory(path).await } - fn read_meta<'a>( + async fn read_meta<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - self.memory_reader.read_meta(path) + ) -> Result>, AssetReaderError> { + self.memory_reader.read_meta(path).await } - fn read<'a>( + async fn read<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture< - 'a, - Result>, bevy_asset::io::AssetReaderError>, - > { + ) -> Result>, bevy_asset::io::AssetReaderError> { let attempt_number = { let mut attempt_counters = self.attempt_counters.lock().unwrap(); if let Some(existing) = attempt_counters.get_mut(path) { @@ -605,13 +595,14 @@ mod tests { ), ); let wait = self.load_delay; - return Box::pin(async move { + return async move { std::thread::sleep(wait); Err(AssetReaderError::Io(io_error.into())) - }); + } + .await; } - self.memory_reader.read(path) + self.memory_reader.read(path).await } } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 627b286482..ae3f08511a 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -9,7 +9,7 @@ use crate::{ UntypedAssetId, UntypedHandle, }; use bevy_ecs::world::World; -use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet}; +use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap, HashSet}; use downcast_rs::{impl_downcast, Downcast}; use futures_lite::AsyncReadExt; use ron::error::SpannedError; @@ -35,7 +35,7 @@ pub trait AssetLoader: Send + Sync + 'static { reader: &'a mut Reader, settings: &'a Self::Settings, load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result>; + ) -> impl ConditionalSendFuture>; /// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot. /// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension. diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs index 2b082e9550..ccc1df9b72 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -171,12 +171,12 @@ impl Process for () { type Settings = (); type OutputLoader = (); - fn process<'a>( + async fn process<'a>( &'a self, - _context: &'a mut bevy_asset::processor::ProcessContext, + _context: &'a mut bevy_asset::processor::ProcessContext<'_>, _meta: AssetMeta<(), Self>, _writer: &'a mut bevy_asset::io::Writer, - ) -> bevy_utils::BoxedFuture<'a, Result<(), bevy_asset::processor::ProcessError>> { + ) -> Result<(), bevy_asset::processor::ProcessError> { unreachable!() } } @@ -194,12 +194,12 @@ impl AssetLoader for () { type Asset = (); type Settings = (); type Error = std::io::Error; - fn load<'a>( + async fn load<'a>( &'a self, - _reader: &'a mut crate::io::Reader, + _reader: &'a mut crate::io::Reader<'_>, _settings: &'a Self::Settings, - _load_context: &'a mut crate::LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { + _load_context: &'a mut crate::LoadContext<'_>, + ) -> Result { unreachable!(); } diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index ace12c8f73..a507ea6547 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -6,8 +6,9 @@ pub use process::*; use crate::{ io::{ - AssetReader, AssetReaderError, AssetSource, AssetSourceBuilders, AssetSourceEvent, - AssetSourceId, AssetSources, AssetWriter, AssetWriterError, MissingAssetSourceError, + AssetReaderError, AssetSource, AssetSourceBuilders, AssetSourceEvent, AssetSourceId, + AssetSources, AssetWriterError, ErasedAssetReader, ErasedAssetWriter, + MissingAssetSourceError, }, meta::{ get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta, @@ -30,6 +31,10 @@ use std::{ }; use thiserror::Error; +// Needed for doc strings +#[allow(unused_imports)] +use crate::io::{AssetReader, AssetWriter}; + /// A "background" asset processor that reads asset values from a source [`AssetSource`] (which corresponds to an [`AssetReader`] / [`AssetWriter`] pair), /// processes them in some way, and writes them to a destination [`AssetSource`]. /// @@ -510,8 +515,8 @@ impl AssetProcessor { /// Retrieves asset paths recursively. If `clean_empty_folders_writer` is Some, it will be used to clean up empty /// folders when they are discovered. fn get_asset_paths<'a>( - reader: &'a dyn AssetReader, - clean_empty_folders_writer: Option<&'a dyn AssetWriter>, + reader: &'a dyn ErasedAssetReader, + clean_empty_folders_writer: Option<&'a dyn ErasedAssetWriter>, path: PathBuf, paths: &'a mut Vec, ) -> BoxedFuture<'a, Result> { diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index 75b10acfa2..b2fca01c12 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -10,7 +10,7 @@ use crate::{ AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError, }; -use bevy_utils::BoxedFuture; +use bevy_utils::{BoxedFuture, ConditionalSendFuture}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; use thiserror::Error; @@ -32,7 +32,9 @@ pub trait Process: Send + Sync + Sized + 'static { context: &'a mut ProcessContext, meta: AssetMeta<(), Self>, writer: &'a mut Writer, - ) -> BoxedFuture<'a, Result<::Settings, ProcessError>>; + ) -> impl ConditionalSendFuture< + Output = Result<::Settings, ProcessError>, + >; } /// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms @@ -173,41 +175,38 @@ impl< type Settings = LoadTransformAndSaveSettings; type OutputLoader = Saver::OutputLoader; - fn process<'a>( + async fn process<'a>( &'a self, - context: &'a mut ProcessContext, + context: &'a mut ProcessContext<'_>, meta: AssetMeta<(), Self>, writer: &'a mut Writer, - ) -> BoxedFuture<'a, Result<::Settings, ProcessError>> { - Box::pin(async move { - let AssetAction::Process { settings, .. } = meta.asset else { - return Err(ProcessError::WrongMetaType); - }; - let loader_meta = AssetMeta::::new(AssetAction::Load { - loader: std::any::type_name::().to_string(), - settings: settings.loader_settings, - }); - let pre_transformed_asset = TransformedAsset::::from_loaded( - context.load_source_asset(loader_meta).await?, - ) - .unwrap(); + ) -> Result<::Settings, ProcessError> { + let AssetAction::Process { settings, .. } = meta.asset else { + return Err(ProcessError::WrongMetaType); + }; + let loader_meta = AssetMeta::::new(AssetAction::Load { + loader: std::any::type_name::().to_string(), + settings: settings.loader_settings, + }); + let pre_transformed_asset = TransformedAsset::::from_loaded( + context.load_source_asset(loader_meta).await?, + ) + .unwrap(); - let post_transformed_asset = self - .transformer - .transform(pre_transformed_asset, &settings.transformer_settings) - .await - .map_err(|err| ProcessError::AssetTransformError(err.into()))?; + let post_transformed_asset = self + .transformer + .transform(pre_transformed_asset, &settings.transformer_settings) + .await + .map_err(|err| ProcessError::AssetTransformError(err.into()))?; - let saved_asset = - SavedAsset::::from_transformed(&post_transformed_asset); + let saved_asset = SavedAsset::::from_transformed(&post_transformed_asset); - let output_settings = self - .saver - .save(writer, saved_asset, &settings.saver_settings) - .await - .map_err(|error| ProcessError::AssetSaveError(error.into()))?; - Ok(output_settings) - }) + let output_settings = self + .saver + .save(writer, saved_asset, &settings.saver_settings) + .await + .map_err(|error| ProcessError::AssetSaveError(error.into()))?; + Ok(output_settings) } } @@ -217,29 +216,27 @@ impl> Process type Settings = LoadAndSaveSettings; type OutputLoader = Saver::OutputLoader; - fn process<'a>( + async fn process<'a>( &'a self, - context: &'a mut ProcessContext, + context: &'a mut ProcessContext<'_>, meta: AssetMeta<(), Self>, writer: &'a mut Writer, - ) -> BoxedFuture<'a, Result<::Settings, ProcessError>> { - Box::pin(async move { - let AssetAction::Process { settings, .. } = meta.asset else { - return Err(ProcessError::WrongMetaType); - }; - let loader_meta = AssetMeta::::new(AssetAction::Load { - loader: std::any::type_name::().to_string(), - settings: settings.loader_settings, - }); - let loaded_asset = context.load_source_asset(loader_meta).await?; - let saved_asset = SavedAsset::::from_loaded(&loaded_asset).unwrap(); - let output_settings = self - .saver - .save(writer, saved_asset, &settings.saver_settings) - .await - .map_err(|error| ProcessError::AssetSaveError(error.into()))?; - Ok(output_settings) - }) + ) -> Result<::Settings, ProcessError> { + let AssetAction::Process { settings, .. } = meta.asset else { + return Err(ProcessError::WrongMetaType); + }; + let loader_meta = AssetMeta::::new(AssetAction::Load { + loader: std::any::type_name::().to_string(), + settings: settings.loader_settings, + }); + let loaded_asset = context.load_source_asset(loader_meta).await?; + let saved_asset = SavedAsset::::from_loaded(&loaded_asset).unwrap(); + let output_settings = self + .saver + .save(writer, saved_asset, &settings.saver_settings) + .await + .map_err(|error| ProcessError::AssetSaveError(error.into()))?; + Ok(output_settings) } } diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index a366338f7c..36408dd125 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -1,7 +1,7 @@ use crate::transformer::TransformedAsset; use crate::{io::Writer, meta::Settings, Asset, ErasedLoadedAsset}; use crate::{AssetLoader, Handle, LabeledAsset, UntypedHandle}; -use bevy_utils::{BoxedFuture, CowArc, HashMap}; +use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap}; use serde::{Deserialize, Serialize}; use std::{borrow::Borrow, hash::Hash, ops::Deref}; @@ -24,7 +24,9 @@ pub trait AssetSaver: Send + Sync + 'static { writer: &'a mut Writer, asset: SavedAsset<'a, Self::Asset>, settings: &'a Self::Settings, - ) -> BoxedFuture<'a, Result<::Settings, Self::Error>>; + ) -> impl ConditionalSendFuture< + Output = Result<::Settings, Self::Error>, + >; } /// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`]. diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 98cc3bce9d..05d2c4873e 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -341,21 +341,19 @@ mod tests { type Error = String; - fn load<'a>( + async fn load<'a>( &'a self, - _: &'a mut crate::io::Reader, + _: &'a mut crate::io::Reader<'_>, _: &'a Self::Settings, - _: &'a mut crate::LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { + _: &'a mut crate::LoadContext<'_>, + ) -> Result { self.sender.send(()).unwrap(); - Box::pin(async move { - Err(format!( - "Loaded {}:{}", - std::any::type_name::(), - N - )) - }) + Err(format!( + "Loaded {}:{}", + std::any::type_name::(), + N + )) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 2be2848aec..60536cc405 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -4,8 +4,8 @@ mod loaders; use crate::{ folder::LoadedFolder, io::{ - AssetReader, AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources, - MissingAssetSourceError, MissingProcessedAssetReaderError, Reader, + AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources, + ErasedAssetReader, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader, }, loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset}, meta::{ @@ -30,6 +30,10 @@ use std::path::PathBuf; use std::{any::TypeId, path::Path, sync::Arc}; use thiserror::Error; +// Needed for doc string +#[allow(unused_imports)] +use crate::io::{AssetReader, AssetWriter}; + /// Loads and tracks the state of [`Asset`] values from a configured [`AssetReader`]. This can be used to kick off new asset loads and /// retrieve their current load states. /// @@ -657,7 +661,7 @@ impl AssetServer { fn load_folder<'a>( source: AssetSourceId<'static>, path: &'a Path, - reader: &'a dyn AssetReader, + reader: &'a dyn ErasedAssetReader, server: &'a AssetServer, handles: &'a mut Vec, ) -> bevy_utils::BoxedFuture<'a, Result<(), AssetLoadError>> { diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 3b8ae58bc3..0ffddc4658 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -1,5 +1,5 @@ use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; -use bevy_utils::{BoxedFuture, CowArc, HashMap}; +use bevy_utils::{ConditionalSendFuture, CowArc, HashMap}; use serde::{Deserialize, Serialize}; use std::{ borrow::Borrow, @@ -25,7 +25,7 @@ pub trait AssetTransformer: Send + Sync + 'static { &'a self, asset: TransformedAsset, settings: &'a Self::Settings, - ) -> BoxedFuture<'a, Result, Self::Error>>; + ) -> impl ConditionalSendFuture, Self::Error>>; } /// An [`Asset`] (and any "sub assets") intended to be transformed diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 8b0c7090ea..242c6a6a7c 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -3,7 +3,6 @@ use bevy_asset::{ Asset, AssetLoader, LoadContext, }; use bevy_reflect::TypePath; -use bevy_utils::BoxedFuture; use std::{io::Cursor, sync::Arc}; /// A source of audio data @@ -43,18 +42,16 @@ impl AssetLoader for AudioLoader { type Settings = (); type Error = std::io::Error; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a Self::Settings, - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - Ok(AudioSource { - bytes: bytes.into(), - }) + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + Ok(AudioSource { + bytes: bytes.into(), }) } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 6f2f279698..1a8caa4531 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -162,17 +162,15 @@ impl AssetLoader for GltfLoader { type Asset = Gltf; type Settings = GltfLoaderSettings; type Error = GltfError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, settings: &'a GltfLoaderSettings, - load_context: &'a mut LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - load_gltf(self, &bytes, load_context, settings).await - }) + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + load_gltf(self, &bytes, load_context, settings).await } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 677378cc90..49d61533ea 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -2,7 +2,7 @@ use super::ShaderDefVal; use crate::define_atomic_id; use bevy_asset::{io::Reader, Asset, AssetLoader, AssetPath, Handle, LoadContext}; use bevy_reflect::TypePath; -use bevy_utils::{tracing::error, BoxedFuture}; +use bevy_utils::tracing::error; use futures_lite::AsyncReadExt; use std::{borrow::Cow, marker::Copy}; use thiserror::Error; @@ -259,43 +259,39 @@ impl AssetLoader for ShaderLoader { type Asset = Shader; type Settings = (); type Error = ShaderLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a Self::Settings, - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let path = load_context.asset_path().to_string(); - // On windows, the path will inconsistently use \ or /. - // TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511 - let path = path.replace(std::path::MAIN_SEPARATOR, "/"); - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let mut shader = match ext { - "spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()), - "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path), - "vert" => { - Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path) - } - "frag" => { - Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path) - } - "comp" => { - Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Compute, path) - } - _ => panic!("unhandled extension: {ext}"), - }; - - // collect and store file dependencies - for import in &shader.imports { - if let ShaderImport::AssetPath(asset_path) = import { - shader.file_dependencies.push(load_context.load(asset_path)); - } + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let ext = load_context.path().extension().unwrap().to_str().unwrap(); + let path = load_context.asset_path().to_string(); + // On windows, the path will inconsistently use \ or /. + // TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511 + let path = path.replace(std::path::MAIN_SEPARATOR, "/"); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let mut shader = match ext { + "spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()), + "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path), + "vert" => Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path), + "frag" => { + Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path) } - Ok(shader) - }) + "comp" => { + Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Compute, path) + } + _ => panic!("unhandled extension: {ext}"), + }; + + // collect and store file dependencies + for import in &shader.imports { + if let ShaderImport::AssetPath(asset_path) = import { + shader.file_dependencies.push(load_context.load(asset_path)); + } + } + Ok(shader) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/compressed_image_saver.rs b/crates/bevy_render/src/texture/compressed_image_saver.rs index dde2a900b4..0ab053df33 100644 --- a/crates/bevy_render/src/texture/compressed_image_saver.rs +++ b/crates/bevy_render/src/texture/compressed_image_saver.rs @@ -1,6 +1,6 @@ use crate::texture::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings}; use bevy_asset::saver::{AssetSaver, SavedAsset}; -use futures_lite::{AsyncWriteExt, FutureExt}; +use futures_lite::AsyncWriteExt; use thiserror::Error; pub struct CompressedImageSaver; @@ -19,46 +19,46 @@ impl AssetSaver for CompressedImageSaver { type OutputLoader = ImageLoader; type Error = CompressedImageSaverError; - fn save<'a>( + async fn save<'a>( &'a self, writer: &'a mut bevy_asset::io::Writer, image: SavedAsset<'a, Self::Asset>, _settings: &'a Self::Settings, - ) -> bevy_utils::BoxedFuture<'a, Result> { - // PERF: this should live inside the future, but CompressorParams and Compressor are not Send / can't be owned by the BoxedFuture (which _is_ Send) - let mut compressor_params = basis_universal::CompressorParams::new(); - compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4); - compressor_params.set_generate_mipmaps(true); + ) -> Result { let is_srgb = image.texture_descriptor.format.is_srgb(); - let color_space = if is_srgb { - basis_universal::ColorSpace::Srgb - } else { - basis_universal::ColorSpace::Linear + + let compressed_basis_data = { + let mut compressor_params = basis_universal::CompressorParams::new(); + compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4); + compressor_params.set_generate_mipmaps(true); + let color_space = if is_srgb { + basis_universal::ColorSpace::Srgb + } else { + basis_universal::ColorSpace::Linear + }; + compressor_params.set_color_space(color_space); + compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT); + + let mut source_image = compressor_params.source_image_mut(0); + let size = image.size(); + source_image.init(&image.data, size.x, size.y, 4); + + let mut compressor = basis_universal::Compressor::new(4); + // SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal + // library bindings note that invalid params might produce undefined behavior. + unsafe { + compressor.init(&compressor_params); + compressor.process().unwrap(); + } + compressor.basis_file().to_vec() }; - compressor_params.set_color_space(color_space); - compressor_params.set_uastc_quality_level(basis_universal::UASTC_QUALITY_DEFAULT); - let mut source_image = compressor_params.source_image_mut(0); - let size = image.size(); - source_image.init(&image.data, size.x, size.y, 4); - - let mut compressor = basis_universal::Compressor::new(4); - // SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal - // library bindings note that invalid params might produce undefined behavior. - unsafe { - compressor.init(&compressor_params); - compressor.process().unwrap(); - } - let compressed_basis_data = compressor.basis_file().to_vec(); - async move { - writer.write_all(&compressed_basis_data).await?; - Ok(ImageLoaderSettings { - format: ImageFormatSetting::Format(ImageFormat::Basis), - is_srgb, - sampler: image.sampler.clone(), - asset_usage: image.asset_usage, - }) - } - .boxed() + writer.write_all(&compressed_basis_data).await?; + Ok(ImageLoaderSettings { + format: ImageFormatSetting::Format(ImageFormat::Basis), + is_srgb, + sampler: image.sampler.clone(), + asset_usage: image.asset_usage, + }) } } diff --git a/crates/bevy_render/src/texture/exr_texture_loader.rs b/crates/bevy_render/src/texture/exr_texture_loader.rs index 9f0d670658..d5f39aa3c0 100644 --- a/crates/bevy_render/src/texture/exr_texture_loader.rs +++ b/crates/bevy_render/src/texture/exr_texture_loader.rs @@ -6,7 +6,6 @@ use bevy_asset::{ io::{AsyncReadExt, Reader}, AssetLoader, LoadContext, }; -use bevy_utils::BoxedFuture; use image::ImageDecoder; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -36,45 +35,43 @@ impl AssetLoader for ExrTextureLoader { type Settings = ExrTextureLoaderSettings; type Error = ExrTextureLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, settings: &'a Self::Settings, - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let format = TextureFormat::Rgba32Float; - debug_assert_eq!( - format.pixel_size(), - 4 * 4, - "Format should have 32bit x 4 size" - ); + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let format = TextureFormat::Rgba32Float; + debug_assert_eq!( + format.pixel_size(), + 4 * 4, + "Format should have 32bit x 4 size" + ); - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let decoder = image::codecs::openexr::OpenExrDecoder::with_alpha_preference( - std::io::Cursor::new(bytes), - Some(true), - )?; - let (width, height) = decoder.dimensions(); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let decoder = image::codecs::openexr::OpenExrDecoder::with_alpha_preference( + std::io::Cursor::new(bytes), + Some(true), + )?; + let (width, height) = decoder.dimensions(); - let total_bytes = decoder.total_bytes() as usize; + let total_bytes = decoder.total_bytes() as usize; - let mut buf = vec![0u8; total_bytes]; - decoder.read_image(buf.as_mut_slice())?; + let mut buf = vec![0u8; total_bytes]; + decoder.read_image(buf.as_mut_slice())?; - Ok(Image::new( - Extent3d { - width, - height, - depth_or_array_layers: 1, - }, - TextureDimension::D2, - buf, - format, - settings.asset_usage, - )) - }) + Ok(Image::new( + Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + buf, + format, + settings.asset_usage, + )) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index 1fd0218639..641055690f 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -29,48 +29,46 @@ impl AssetLoader for HdrTextureLoader { type Asset = Image; type Settings = HdrTextureLoaderSettings; type Error = HdrTextureLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, settings: &'a Self::Settings, - _load_context: &'a mut LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { - Box::pin(async move { - let format = TextureFormat::Rgba32Float; - debug_assert_eq!( - format.pixel_size(), - 4 * 4, - "Format should have 32bit x 4 size" - ); + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let format = TextureFormat::Rgba32Float; + debug_assert_eq!( + format.pixel_size(), + 4 * 4, + "Format should have 32bit x 4 size" + ); - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let decoder = image::codecs::hdr::HdrDecoder::new(bytes.as_slice())?; - let info = decoder.metadata(); - let rgb_data = decoder.read_image_hdr()?; - let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let decoder = image::codecs::hdr::HdrDecoder::new(bytes.as_slice())?; + let info = decoder.metadata(); + let rgb_data = decoder.read_image_hdr()?; + let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); - for rgb in rgb_data { - let alpha = 1.0f32; + for rgb in rgb_data { + let alpha = 1.0f32; - rgba_data.extend_from_slice(&rgb.0[0].to_ne_bytes()); - rgba_data.extend_from_slice(&rgb.0[1].to_ne_bytes()); - rgba_data.extend_from_slice(&rgb.0[2].to_ne_bytes()); - rgba_data.extend_from_slice(&alpha.to_ne_bytes()); - } + rgba_data.extend_from_slice(&rgb.0[0].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[1].to_ne_bytes()); + rgba_data.extend_from_slice(&rgb.0[2].to_ne_bytes()); + rgba_data.extend_from_slice(&alpha.to_ne_bytes()); + } - Ok(Image::new( - Extent3d { - width: info.width, - height: info.height, - depth_or_array_layers: 1, - }, - TextureDimension::D2, - rgba_data, - format, - settings.asset_usage, - )) - }) + Ok(Image::new( + Extent3d { + width: info.width, + height: info.height, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + rgba_data, + format, + settings.asset_usage, + )) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index 44a4fdb925..534d064409 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -85,37 +85,35 @@ impl AssetLoader for ImageLoader { type Asset = Image; type Settings = ImageLoaderSettings; type Error = ImageLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, settings: &'a ImageLoaderSettings, - load_context: &'a mut LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { - Box::pin(async move { - // use the file extension for the image type - let ext = load_context.path().extension().unwrap().to_str().unwrap(); + load_context: &'a mut LoadContext<'_>, + ) -> Result { + // use the file extension for the image type + let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let image_type = match settings.format { - ImageFormatSetting::FromExtension => ImageType::Extension(ext), - ImageFormatSetting::Format(format) => ImageType::Format(format), - }; - Ok(Image::from_buffer( - #[cfg(all(debug_assertions, feature = "dds"))] - load_context.path().display().to_string(), - &bytes, - image_type, - self.supported_compressed_formats, - settings.is_srgb, - settings.sampler.clone(), - settings.asset_usage, - ) - .map_err(|err| FileTextureError { - error: err, - path: format!("{}", load_context.path().display()), - })?) - }) + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let image_type = match settings.format { + ImageFormatSetting::FromExtension => ImageType::Extension(ext), + ImageFormatSetting::Format(format) => ImageType::Format(format), + }; + Ok(Image::from_buffer( + #[cfg(all(debug_assertions, feature = "dds"))] + load_context.path().display().to_string(), + &bytes, + image_type, + self.supported_compressed_formats, + settings.is_srgb, + settings.sampler.clone(), + settings.asset_usage, + ) + .map_err(|err| FileTextureError { + error: err, + path: format!("{}", load_context.path().display()), + })?) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index f4dce7c66a..107d014b2b 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -6,7 +6,6 @@ use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}; use bevy_ecs::reflect::AppTypeRegistry; use bevy_ecs::world::{FromWorld, World}; use bevy_reflect::TypeRegistryArc; -use bevy_utils::BoxedFuture; #[cfg(feature = "serialize")] use serde::de::DeserializeSeed; use thiserror::Error; @@ -44,23 +43,21 @@ impl AssetLoader for SceneLoader { type Settings = (); type Error = SceneLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a (), - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; - let scene_deserializer = SceneDeserializer { - type_registry: &self.type_registry.read(), - }; - Ok(scene_deserializer - .deserialize(&mut deserializer) - .map_err(|e| deserializer.span_error(e))?) - }) + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; + let scene_deserializer = SceneDeserializer { + type_registry: &self.type_registry.read(), + }; + Ok(scene_deserializer + .deserialize(&mut deserializer) + .map_err(|e| deserializer.span_error(e))?) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_text/src/font_loader.rs b/crates/bevy_text/src/font_loader.rs index a47abbd961..45f3e9701e 100644 --- a/crates/bevy_text/src/font_loader.rs +++ b/crates/bevy_text/src/font_loader.rs @@ -21,17 +21,15 @@ impl AssetLoader for FontLoader { type Asset = Font; type Settings = (); type Error = FontLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a (), - _load_context: &'a mut LoadContext, - ) -> bevy_utils::BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - Ok(Font::try_from_bytes(bytes)?) - }) + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + Ok(Font::try_from_bytes(bytes)?) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 33f5312171..d142f43fbc 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -36,21 +36,36 @@ use hashbrown::hash_map::RawEntryMut; use std::{ any::TypeId, fmt::Debug, - future::Future, hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, marker::PhantomData, mem::ManuallyDrop, ops::Deref, - pin::Pin, }; -/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection. #[cfg(not(target_arch = "wasm32"))] -pub type BoxedFuture<'a, T> = Pin + 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 ConditionalSend for T {} +} -#[allow(missing_docs)] #[cfg(target_arch = "wasm32")] -pub type BoxedFuture<'a, T> = Pin + 'a>>; +#[allow(missing_docs)] +mod conditional_send { + pub trait ConditionalSend {} + impl 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 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 + 'a>>; /// A shortcut alias for [`hashbrown::hash_map::Entry`]. pub type Entry<'a, K, V, S = BuildHasherDefault> = hashbrown::hash_map::Entry<'a, K, V, S>; diff --git a/examples/asset/asset_decompression.rs b/examples/asset/asset_decompression.rs index cd17a38a89..f003471a72 100644 --- a/examples/asset/asset_decompression.rs +++ b/examples/asset/asset_decompression.rs @@ -7,7 +7,6 @@ use bevy::{ }, prelude::*, reflect::TypePath, - utils::BoxedFuture, }; use flate2::read::GzDecoder; use std::io::prelude::*; @@ -41,44 +40,42 @@ impl AssetLoader for GzAssetLoader { type Asset = GzAsset; type Settings = (); type Error = GzAssetLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a (), - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let compressed_path = load_context.path(); - let file_name = compressed_path - .file_name() - .ok_or(GzAssetLoaderError::IndeterminateFilePath)? - .to_string_lossy(); - let uncompressed_file_name = file_name - .strip_suffix(".gz") - .ok_or(GzAssetLoaderError::IndeterminateFilePath)?; - let contained_path = compressed_path.join(uncompressed_file_name); + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let compressed_path = load_context.path(); + let file_name = compressed_path + .file_name() + .ok_or(GzAssetLoaderError::IndeterminateFilePath)? + .to_string_lossy(); + let uncompressed_file_name = file_name + .strip_suffix(".gz") + .ok_or(GzAssetLoaderError::IndeterminateFilePath)?; + let contained_path = compressed_path.join(uncompressed_file_name); - let mut bytes_compressed = Vec::new(); + let mut bytes_compressed = Vec::new(); - reader.read_to_end(&mut bytes_compressed).await?; + reader.read_to_end(&mut bytes_compressed).await?; - let mut decoder = GzDecoder::new(bytes_compressed.as_slice()); + let mut decoder = GzDecoder::new(bytes_compressed.as_slice()); - let mut bytes_uncompressed = Vec::new(); + let mut bytes_uncompressed = Vec::new(); - decoder.read_to_end(&mut bytes_uncompressed)?; + decoder.read_to_end(&mut bytes_uncompressed)?; - // Now that we have decompressed the asset, let's pass it back to the - // context to continue loading + // Now that we have decompressed the asset, let's pass it back to the + // context to continue loading - let mut reader = VecReader::new(bytes_uncompressed); + let mut reader = VecReader::new(bytes_uncompressed); - let uncompressed = load_context - .load_direct_with_reader(&mut reader, contained_path) - .await?; + let uncompressed = load_context + .load_direct_with_reader(&mut reader, contained_path) + .await?; - Ok(GzAsset { uncompressed }) - }) + Ok(GzAsset { uncompressed }) } fn extensions(&self) -> &[&str] { diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index c10e297c38..b3167bbd1d 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -4,7 +4,6 @@ use bevy::{ asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext}, prelude::*, reflect::TypePath, - utils::BoxedFuture, }; use serde::Deserialize; use thiserror::Error; @@ -34,18 +33,16 @@ impl AssetLoader for CustomAssetLoader { type Asset = CustomAsset; type Settings = (); type Error = CustomAssetLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a (), - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let custom_asset = ron::de::from_bytes::(&bytes)?; - Ok(custom_asset) - }) + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let custom_asset = ron::de::from_bytes::(&bytes)?; + Ok(custom_asset) } fn extensions(&self) -> &[&str] { @@ -75,19 +72,17 @@ impl AssetLoader for BlobAssetLoader { type Settings = (); type Error = BlobAssetLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a (), - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - info!("Loading Blob..."); - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + info!("Loading Blob..."); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; - Ok(Blob { bytes }) - }) + Ok(Blob { bytes }) } } diff --git a/examples/asset/custom_asset_reader.rs b/examples/asset/custom_asset_reader.rs index 4e4b0eede6..d302ebbf7f 100644 --- a/examples/asset/custom_asset_reader.rs +++ b/examples/asset/custom_asset_reader.rs @@ -3,42 +3,35 @@ //! It does not know anything about the asset formats, only how to talk to the underlying storage. use bevy::{ - asset::io::{AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader}, + asset::io::{ + AssetReader, AssetReaderError, AssetSource, AssetSourceId, ErasedAssetReader, PathStream, + Reader, + }, prelude::*, - utils::BoxedFuture, }; use std::path::Path; /// A custom asset reader implementation that wraps a given asset reader implementation -struct CustomAssetReader(Box); +struct CustomAssetReader(Box); impl AssetReader for CustomAssetReader { - fn read<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { + async fn read<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { info!("Reading {:?}", path); - self.0.read(path) + self.0.read(path).await } - fn read_meta<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result>, AssetReaderError>> { - self.0.read_meta(path) + async fn read_meta<'a>(&'a self, path: &'a Path) -> Result>, AssetReaderError> { + self.0.read_meta(path).await } - fn read_directory<'a>( + async fn read_directory<'a>( &'a self, path: &'a Path, - ) -> BoxedFuture<'a, Result, AssetReaderError>> { - self.0.read_directory(path) + ) -> Result, AssetReaderError> { + self.0.read_directory(path).await } - fn is_directory<'a>( - &'a self, - path: &'a Path, - ) -> BoxedFuture<'a, Result> { - self.0.is_directory(path) + async fn is_directory<'a>(&'a self, path: &'a Path) -> Result { + self.0.is_directory(path).await } } diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 3f660ef85f..f99fc4769a 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -12,7 +12,6 @@ use bevy::{ }, prelude::*, reflect::TypePath, - utils::BoxedFuture, }; use serde::{Deserialize, Serialize}; use std::convert::Infallible; @@ -83,22 +82,20 @@ impl AssetLoader for TextLoader { type Asset = Text; type Settings = TextSettings; type Error = std::io::Error; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, settings: &'a TextSettings, - _load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let value = if let Some(ref text) = settings.text_override { - text.clone() - } else { - String::from_utf8(bytes).unwrap() - }; - Ok(Text(value)) - }) + _load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let value = if let Some(ref text) = settings.text_override { + text.clone() + } else { + String::from_utf8(bytes).unwrap() + }; + Ok(Text(value)) } fn extensions(&self) -> &[&str] { @@ -138,30 +135,28 @@ impl AssetLoader for CoolTextLoader { type Settings = (); type Error = CoolTextLoaderError; - fn load<'a>( + async fn load<'a>( &'a self, - reader: &'a mut Reader, + reader: &'a mut Reader<'_>, _settings: &'a Self::Settings, - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - let mut bytes = Vec::new(); - reader.read_to_end(&mut bytes).await?; - let ron: CoolTextRon = ron::de::from_bytes(&bytes)?; - let mut base_text = ron.text; - for embedded in ron.embedded_dependencies { - let loaded = load_context.load_direct(&embedded).await?; - let text = loaded.get::().unwrap(); - base_text.push_str(&text.0); - } - Ok(CoolText { - text: base_text, - dependencies: ron - .dependencies - .iter() - .map(|p| load_context.load(p)) - .collect(), - }) + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + let ron: CoolTextRon = ron::de::from_bytes(&bytes)?; + let mut base_text = ron.text; + for embedded in ron.embedded_dependencies { + let loaded = load_context.load_direct(&embedded).await?; + let text = loaded.get::().unwrap(); + base_text.push_str(&text.0); + } + Ok(CoolText { + text: base_text, + dependencies: ron + .dependencies + .iter() + .map(|p| load_context.load(p)) + .collect(), }) } @@ -184,15 +179,13 @@ impl AssetTransformer for CoolTextTransformer { type Settings = CoolTextTransformerSettings; type Error = Infallible; - fn transform<'a>( + async fn transform<'a>( &'a self, mut asset: TransformedAsset, settings: &'a Self::Settings, - ) -> BoxedFuture<'a, Result, Self::Error>> { - Box::pin(async move { - asset.text = format!("{}{}", asset.text, settings.appended); - Ok(asset) - }) + ) -> Result, Self::Error> { + asset.text = format!("{}{}", asset.text, settings.appended); + Ok(asset) } } @@ -204,16 +197,14 @@ impl AssetSaver for CoolTextSaver { type OutputLoader = TextLoader; type Error = std::io::Error; - fn save<'a>( + async fn save<'a>( &'a self, writer: &'a mut Writer, asset: SavedAsset<'a, Self::Asset>, _settings: &'a Self::Settings, - ) -> BoxedFuture<'a, Result> { - Box::pin(async move { - writer.write_all(asset.text.as_bytes()).await?; - Ok(TextSettings::default()) - }) + ) -> Result { + writer.write_all(asset.text.as_bytes()).await?; + Ok(TextSettings::default()) } }