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.
This commit is contained in:
Arthur Brussee 2024-03-18 17:56:57 +00:00 committed by GitHub
parent ce75dec3b8
commit ac49dce4ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1090 additions and 1095 deletions

View file

@ -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,21 +335,19 @@ 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<Self::Asset, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
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)
let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
.map_err(|err| deserializer.span_error(err))?;
// Load all `AssetPath`s to convert from a
@ -370,7 +367,6 @@ impl AssetLoader for AnimationGraphAssetLoader {
),
root: serialized_animation_graph.root,
})
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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,11 +16,7 @@ use std::{ffi::CString, path::Path};
pub struct AndroidAssetReader;
impl AssetReader for AndroidAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
let asset_manager = bevy_winit::ANDROID_APP
.get()
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
@ -32,14 +27,9 @@ impl AssetReader for AndroidAssetReader {
let bytes = opened_asset.buffer()?;
let reader: Box<Reader> = Box::new(VecReader::new(bytes.to_vec()));
Ok(reader)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
let meta_path = get_meta_path(path);
let asset_manager = bevy_winit::ANDROID_APP
.get()
@ -51,23 +41,22 @@ impl AssetReader for AndroidAssetReader {
let bytes = opened_asset.buffer()?;
let reader: Box<Reader> = 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<Box<PathStream>, AssetReaderError>> {
) -> Result<Box<PathStream>, AssetReaderError> {
let stream: Box<PathStream> = 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<bool, AssetReaderError>> {
) -> std::result::Result<bool, AssetReaderError> {
error!("Reading directories is not supported with the AndroidAssetReader");
Box::pin(async move { Ok(false) })
Ok(false)
}
}

View file

@ -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,11 +10,7 @@ use std::path::Path;
use super::{FileAssetReader, FileAssetWriter};
impl AssetReader for FileAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
let full_path = self.root_path.join(path);
match File::open(&full_path).await {
Ok(file) => {
@ -30,15 +25,10 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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) => {
@ -53,14 +43,12 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn read_directory<'a>(
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
Box::pin(async move {
) -> Result<Box<PathStream>, AssetReaderError> {
let full_path = self.root_path.join(path);
match read_dir(&full_path).await {
Ok(read_dir) => {
@ -89,29 +77,19 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
Box::pin(async move {
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
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<Box<Writer>, AssetWriterError>> {
Box::pin(async move {
async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let full_path = self.root_path.join(path);
if let Some(parent) = full_path.parent() {
async_fs::create_dir_all(parent).await?;
@ -119,14 +97,9 @@ impl AssetWriter for FileAssetWriter {
let file = File::create(&full_path).await?;
let writer: Box<Writer> = Box::new(file);
Ok(writer)
})
}
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
Box::pin(async move {
async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
if let Some(parent) = full_path.parent() {
@ -135,32 +108,26 @@ impl AssetWriter for FileAssetWriter {
let file = File::create(&full_path).await?;
let writer: Box<Writer> = Box::new(file);
Ok(writer)
})
}
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(async move {
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 {
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 {
) -> 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() {
@ -168,15 +135,13 @@ impl AssetWriter for FileAssetWriter {
}
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 {
) -> 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);
@ -186,40 +151,27 @@ impl AssetWriter for FileAssetWriter {
}
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 {
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 {
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 {
) -> 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(())
})
}
}

View file

@ -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,11 +75,7 @@ impl Stream for DirReader {
}
impl AssetReader for FileAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
let full_path = self.root_path.join(path);
match File::open(&full_path) {
Ok(file) => {
@ -95,15 +90,10 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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) => {
@ -118,14 +108,12 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn read_directory<'a>(
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
Box::pin(async move {
) -> Result<Box<PathStream>, AssetReaderError> {
let full_path = self.root_path.join(path);
match read_dir(&full_path) {
Ok(read_dir) => {
@ -154,29 +142,22 @@ impl AssetReader for FileAssetReader {
}
}
}
})
}
fn is_directory<'a>(
async fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> {
Box::pin(async move {
) -> std::result::Result<bool, AssetReaderError> {
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<Box<Writer>, AssetWriterError>> {
Box::pin(async move {
async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let full_path = self.root_path.join(path);
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
@ -184,14 +165,9 @@ impl AssetWriter for FileAssetWriter {
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
})
}
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
Box::pin(async move {
async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
if let Some(parent) = full_path.parent() {
@ -200,72 +176,57 @@ impl AssetWriter for FileAssetWriter {
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
})
}
fn remove<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> {
Box::pin(async move {
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 {
) -> 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 {
) -> 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 {
) -> 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 {
) -> 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 {
) -> 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() {
@ -273,15 +234,13 @@ impl AssetWriter for FileAssetWriter {
}
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 {
) -> 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);
@ -291,6 +250,5 @@ impl AssetWriter for FileAssetWriter {
}
std::fs::rename(full_old_path, full_new_path)?;
Ok(())
})
}
}

View file

@ -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<R: AssetReader> GatedReader<R> {
}
impl<R: AssetReader> AssetReader for GatedReader<R> {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
let receiver = {
let mut gates = self.gates.write();
let gates = gates
@ -66,31 +63,23 @@ impl<R: AssetReader> AssetReader for GatedReader<R> {
.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)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
self.reader.read_meta(path)
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
self.reader.read_meta(path).await
}
fn read_directory<'a>(
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
self.reader.read_directory(path)
) -> Result<Box<PathStream>, AssetReaderError> {
self.reader.read_directory(path).await
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.reader.is_directory(path)
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.reader.is_directory(path).await
}
}

View file

@ -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,11 +237,7 @@ impl AsyncRead for DataReader {
}
impl AssetReader for MemoryAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
self.root
.get_asset(path)
.map(|data| {
@ -252,14 +248,9 @@ impl AssetReader for MemoryAssetReader {
reader
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
self.root
.get_metadata(path)
.map(|data| {
@ -270,14 +261,12 @@ impl AssetReader for MemoryAssetReader {
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<Box<PathStream>, AssetReaderError>> {
Box::pin(async move {
) -> Result<Box<PathStream>, AssetReaderError> {
self.root
.get_dir(path)
.map(|dir| {
@ -285,14 +274,10 @@ impl AssetReader for MemoryAssetReader {
stream
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
Box::pin(async move { Ok(self.root.get_dir(path).is_some()) })
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
Ok(self.root.get_dir(path).is_some())
}
}

View file

@ -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<Box<Reader<'a>>, AssetReaderError>>;
) -> impl ConditionalSendFuture<Output = Result<Box<Reader<'a>>, 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<Box<Reader<'a>>, AssetReaderError>>;
) -> impl ConditionalSendFuture<Output = Result<Box<Reader<'a>>, AssetReaderError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
/// Returns an iterator of directory entry names at the provided path.
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. 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<Vec<u8>, AssetReaderError>> {
Box::pin(async move {
) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, 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<Result<Box<Reader<'a>>, AssetReaderError>>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Reader<'a>>, AssetReaderError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`ErasedAssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Vec<u8>, AssetReaderError>>;
}
impl<T: AssetReader> ErasedAssetReader for T {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(Self::read(self, path))
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(Self::read_meta(self, path))
}
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<PathStream>, AssetReaderError>> {
Box::pin(Self::read_directory(self, path))
}
fn is_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<bool, AssetReaderError>> {
Box::pin(Self::is_directory(self, path))
}
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Vec<u8>, 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<Box<Writer>, AssetWriterError>>;
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, 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<Box<Writer>, AssetWriterError>>;
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>>;
/// 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<Output = Result<(), AssetWriterError>> {
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<Output = Result<(), AssetWriterError>> {
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<Result<Box<Writer>, 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<Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path.
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>>;
/// 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<Result<(), AssetWriterError>>;
/// Renames the asset at `old_path` to `new_path`
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// 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<Result<(), AssetWriterError>>;
/// 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<Result<(), AssetWriterError>>;
/// 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<Result<(), AssetWriterError>>;
/// 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<Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>>;
/// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>>;
}
impl<T: AssetWriter> ErasedAssetWriter for T {
fn write<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write(self, path))
}
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write_meta(self, path))
}
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove(self, path))
}
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_meta(self, path))
}
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::rename(self, old_path, new_path))
}
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::rename_meta(self, old_path, new_path))
}
fn remove_directory<'a>(&'a self, path: &'a Path) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_directory(self, path))
}
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_empty_directory(self, path))
}
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::remove_assets_in_directory(self, path))
}
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::write_bytes(self, path, bytes))
}
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<Result<(), AssetWriterError>> {
Box::pin(Self::write_meta_bytes(self, path, bytes))
}
}

View file

@ -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<dyn AssetReader>,
reader: Box<dyn ErasedAssetReader>,
source: AssetSourceId<'static>,
processor_data: Arc<AssetProcessorData>,
}
@ -23,7 +24,7 @@ impl ProcessorGatedReader {
/// Creates a new [`ProcessorGatedReader`].
pub fn new(
source: AssetSourceId<'static>,
reader: Box<dyn AssetReader>,
reader: Box<dyn ErasedAssetReader>,
processor_data: Arc<AssetProcessorData>,
) -> Self {
Self {
@ -48,11 +49,7 @@ impl ProcessorGatedReader {
}
impl AssetReader for ProcessorGatedReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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
@ -68,17 +65,11 @@ impl AssetReader for ProcessorGatedReader {
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<Reader<'a>> =
Box::new(TransactionLockedReader::new(asset_reader, lock));
let reader: Box<Reader<'a>> = Box::new(TransactionLockedReader::new(asset_reader, lock));
Ok(reader)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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
@ -96,14 +87,12 @@ impl AssetReader for ProcessorGatedReader {
let meta_reader = self.reader.read_meta(path).await?;
let reader: Box<Reader<'a>> = 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<Box<PathStream>, AssetReaderError>> {
Box::pin(async move {
) -> Result<Box<PathStream>, AssetReaderError> {
trace!(
"Waiting for processing to finish before reading directory {:?}",
path
@ -112,14 +101,9 @@ impl AssetReader for ProcessorGatedReader {
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<bool, AssetReaderError>> {
Box::pin(async move {
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
trace!(
"Waiting for processing to finish before reading directory {:?}",
path
@ -128,7 +112,6 @@ impl AssetReader for ProcessorGatedReader {
trace!("Processing finished, getting directory status {:?}", path);
let result = self.reader.is_directory(path).await?;
Ok(result)
})
}
}

View file

@ -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<dyn FnMut() -> Box<dyn AssetReader> + Send + Sync>>,
pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn AssetWriter>> + Send + Sync>>,
pub reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
pub watcher: Option<
Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
@ -119,9 +122,9 @@ pub struct AssetSourceBuilder {
+ Sync,
>,
>,
pub processed_reader: Option<Box<dyn FnMut() -> Box<dyn AssetReader> + Send + Sync>>,
pub processed_reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
pub processed_writer:
Option<Box<dyn FnMut(bool) -> Option<Box<dyn AssetWriter>> + Send + Sync>>,
Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
pub processed_watcher: Option<
Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
@ -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<dyn AssetReader> + Send + Sync + 'static,
reader: impl FnMut() -> Box<dyn ErasedAssetReader> + 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<Box<dyn AssetWriter>> + Send + Sync + 'static,
writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + 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<dyn AssetReader> + Send + Sync + 'static,
reader: impl FnMut() -> Box<dyn ErasedAssetReader> + 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<Box<dyn AssetWriter>> + Send + Sync + 'static,
writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + 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<dyn AssetReader>,
writer: Option<Box<dyn AssetWriter>>,
processed_reader: Option<Box<dyn AssetReader>>,
processed_writer: Option<Box<dyn AssetWriter>>,
reader: Box<dyn ErasedAssetReader>,
writer: Option<Box<dyn ErasedAssetWriter>>,
processed_reader: Option<Box<dyn ErasedAssetReader>>,
processed_writer: Option<Box<dyn ErasedAssetWriter>>,
watcher: Option<Box<dyn AssetWatcher>>,
processed_watcher: Option<Box<dyn AssetWatcher>>,
event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
@ -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<dyn AssetReader> + Send + Sync {
pub fn get_default_reader(
_path: String,
) -> impl FnMut() -> Box<dyn ErasedAssetReader> + 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<Box<dyn AssetWriter>> + Send + Sync {
) -> impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync {
move |_create_root: bool| {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return Some(Box::new(super::file::FileAssetWriter::new(

View file

@ -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<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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<Box<PathStream>, AssetReaderError>> {
) -> Result<Box<PathStream>, AssetReaderError> {
let stream: Box<PathStream> = 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<bool, AssetReaderError>> {
) -> std::result::Result<bool, AssetReaderError> {
error!("Reading directories is not supported with the HttpWasmAssetReader");
Box::pin(async move { Ok(false) })
Ok(false)
}
}

View file

@ -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,13 +495,12 @@ 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<Self::Asset, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
@ -531,7 +528,6 @@ mod tests {
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
.collect(),
})
})
}
fn extensions(&self) -> &[&str] {
@ -560,31 +556,25 @@ mod tests {
}
impl AssetReader for UnstableMemoryAssetReader {
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.memory_reader.is_directory(path)
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.memory_reader.is_directory(path).await
}
fn read_directory<'a>(
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<bevy_asset::io::PathStream>, AssetReaderError>> {
self.memory_reader.read_directory(path)
) -> Result<Box<bevy_asset::io::PathStream>, 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<Box<bevy_asset::io::Reader<'a>>, AssetReaderError>> {
self.memory_reader.read_meta(path)
) -> Result<Box<bevy_asset::io::Reader<'a>>, AssetReaderError> {
self.memory_reader.read_meta(path).await
}
fn read<'a>(
async fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<
'a,
Result<Box<bevy_asset::io::Reader<'a>>, bevy_asset::io::AssetReaderError>,
> {
) -> Result<Box<bevy_asset::io::Reader<'a>>, 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
}
}

View file

@ -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<Self::Asset, Self::Error>>;
) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
/// 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.

View file

@ -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<Self::Asset, Self::Error>> {
_load_context: &'a mut crate::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
unreachable!();
}

View file

@ -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<PathBuf>,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {

View file

@ -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<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>>;
) -> impl ConditionalSendFuture<
Output = Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>,
>;
}
/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms
@ -173,13 +175,12 @@ impl<
type Settings = LoadTransformAndSaveSettings<Loader::Settings, T::Settings, Saver::Settings>;
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<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> {
Box::pin(async move {
) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
let AssetAction::Process { settings, .. } = meta.asset else {
return Err(ProcessError::WrongMetaType);
};
@ -198,8 +199,7 @@ impl<
.await
.map_err(|err| ProcessError::AssetTransformError(err.into()))?;
let saved_asset =
SavedAsset::<T::AssetOutput>::from_transformed(&post_transformed_asset);
let saved_asset = SavedAsset::<T::AssetOutput>::from_transformed(&post_transformed_asset);
let output_settings = self
.saver
@ -207,7 +207,6 @@ impl<
.await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
})
}
}
@ -217,13 +216,12 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
type Settings = LoadAndSaveSettings<Loader::Settings, Saver::Settings>;
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<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> {
Box::pin(async move {
) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
let AssetAction::Process { settings, .. } = meta.asset else {
return Err(ProcessError::WrongMetaType);
};
@ -239,7 +237,6 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
.await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
})
}
}

View file

@ -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<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>>;
) -> impl ConditionalSendFuture<
Output = Result<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>,
>;
}
/// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`].

View file

@ -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<Self::Asset, Self::Error>> {
_: &'a mut crate::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
self.sender.send(()).unwrap();
Box::pin(async move {
Err(format!(
"Loaded {}:{}",
std::any::type_name::<Self::Asset>(),
N
))
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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<UntypedHandle>,
) -> bevy_utils::BoxedFuture<'a, Result<(), AssetLoadError>> {

View file

@ -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<Self::AssetInput>,
settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
) -> impl ConditionalSendFuture<Output = Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
}
/// An [`Asset`] (and any "sub assets") intended to be transformed

View file

@ -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,19 +42,17 @@ 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<AudioSource, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<AudioSource, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(AudioSource {
bytes: bytes.into(),
})
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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<Gltf, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Gltf, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
load_gltf(self, &bytes, load_context, settings).await
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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,13 +259,12 @@ 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<Shader, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Shader, Self::Error> {
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 /.
@ -276,9 +275,7 @@ impl AssetLoader for ShaderLoader {
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)
}
"vert" => Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path),
"frag" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, path)
}
@ -295,7 +292,6 @@ impl AssetLoader for ShaderLoader {
}
}
Ok(shader)
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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,17 +19,18 @@ 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<ImageLoaderSettings, Self::Error>> {
// PERF: this should live inside the future, but CompressorParams and Compressor are not Send / can't be owned by the BoxedFuture (which _is_ Send)
) -> Result<ImageLoaderSettings, Self::Error> {
let is_srgb = image.texture_descriptor.format.is_srgb();
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 is_srgb = image.texture_descriptor.format.is_srgb();
let color_space = if is_srgb {
basis_universal::ColorSpace::Srgb
} else {
@ -49,8 +50,9 @@ impl AssetSaver for CompressedImageSaver {
compressor.init(&compressor_params);
compressor.process().unwrap();
}
let compressed_basis_data = compressor.basis_file().to_vec();
async move {
compressor.basis_file().to_vec()
};
writer.write_all(&compressed_basis_data).await?;
Ok(ImageLoaderSettings {
format: ImageFormatSetting::Format(ImageFormat::Basis),
@ -59,6 +61,4 @@ impl AssetSaver for CompressedImageSaver {
asset_usage: image.asset_usage,
})
}
.boxed()
}
}

View file

@ -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,13 +35,12 @@ 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<Image, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Image, Self::Error> {
let format = TextureFormat::Rgba32Float;
debug_assert_eq!(
format.pixel_size(),
@ -74,7 +72,6 @@ impl AssetLoader for ExrTextureLoader {
format,
settings.asset_usage,
))
})
}
fn extensions(&self) -> &[&str] {

View file

@ -29,13 +29,12 @@ 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<Image, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Image, Self::Error> {
let format = TextureFormat::Rgba32Float;
debug_assert_eq!(
format.pixel_size(),
@ -70,7 +69,6 @@ impl AssetLoader for HdrTextureLoader {
format,
settings.asset_usage,
))
})
}
fn extensions(&self) -> &[&str] {

View file

@ -85,13 +85,12 @@ 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<Image, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Image, Self::Error> {
// use the file extension for the image type
let ext = load_context.path().extension().unwrap().to_str().unwrap();
@ -115,7 +114,6 @@ impl AssetLoader for ImageLoader {
error: err,
path: format!("{}", load_context.path().display()),
})?)
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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,13 +43,12 @@ 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<Self::Asset, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
@ -60,7 +58,6 @@ impl AssetLoader for SceneLoader {
Ok(scene_deserializer
.deserialize(&mut deserializer)
.map_err(|e| deserializer.span_error(e))?)
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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<Font, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Font, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(Font::try_from_bytes(bytes)?)
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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<Box<dyn Future<Output = T> + Send + 'a>>;
mod conditional_send {
/// Use [`ConditionalSend`] to mark an optional Send trait bound. Useful as on certain platforms (eg. WASM),
/// futures aren't Send.
pub trait ConditionalSend: Send {}
impl<T: Send> ConditionalSend for T {}
}
#[allow(missing_docs)]
#[cfg(target_arch = "wasm32")]
pub type BoxedFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[allow(missing_docs)]
mod conditional_send {
pub trait ConditionalSend {}
impl<T> ConditionalSend for T {}
}
pub use conditional_send::*;
/// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. WASM),
/// futures aren't Send.
pub trait ConditionalSendFuture: std::future::Future + ConditionalSend {}
impl<T: std::future::Future + ConditionalSend> ConditionalSendFuture for T {}
/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection.
pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>;
/// A shortcut alias for [`hashbrown::hash_map::Entry`].
pub type Entry<'a, K, V, S = BuildHasherDefault<AHasher>> = hashbrown::hash_map::Entry<'a, K, V, S>;

View file

@ -7,7 +7,6 @@ use bevy::{
},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
};
use flate2::read::GzDecoder;
use std::io::prelude::*;
@ -41,13 +40,12 @@ 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<Self::Asset, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let compressed_path = load_context.path();
let file_name = compressed_path
.file_name()
@ -78,7 +76,6 @@ impl AssetLoader for GzAssetLoader {
.await?;
Ok(GzAsset { uncompressed })
})
}
fn extensions(&self) -> &[&str] {

View file

@ -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<Self::Asset, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?;
Ok(custom_asset)
})
}
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<Self::Asset, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
info!("Loading Blob...");
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
Ok(Blob { bytes })
})
}
}

View file

@ -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<dyn AssetReader>);
struct CustomAssetReader(Box<dyn ErasedAssetReader>);
impl AssetReader for CustomAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, 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<Box<Reader<'a>>, AssetReaderError>> {
self.0.read_meta(path)
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
self.0.read_meta(path).await
}
fn read_directory<'a>(
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
self.0.read_directory(path)
) -> Result<Box<PathStream>, AssetReaderError> {
self.0.read_directory(path).await
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.0.is_directory(path)
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.0.is_directory(path).await
}
}

View file

@ -12,7 +12,6 @@ use bevy::{
},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
};
use serde::{Deserialize, Serialize};
use std::convert::Infallible;
@ -83,13 +82,12 @@ 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<Text, Self::Error>> {
Box::pin(async move {
_load_context: &'a mut LoadContext<'_>,
) -> Result<Text, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let value = if let Some(ref text) = settings.text_override {
@ -98,7 +96,6 @@ impl AssetLoader for TextLoader {
String::from_utf8(bytes).unwrap()
};
Ok(Text(value))
})
}
fn extensions(&self) -> &[&str] {
@ -138,13 +135,12 @@ 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<CoolText, Self::Error>> {
Box::pin(async move {
load_context: &'a mut LoadContext<'_>,
) -> Result<CoolText, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
@ -162,7 +158,6 @@ impl AssetLoader for CoolTextLoader {
.map(|p| load_context.load(p))
.collect(),
})
})
}
fn extensions(&self) -> &[&str] {
@ -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<Self::AssetInput>,
settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<TransformedAsset<Self::AssetOutput>, Self::Error>> {
Box::pin(async move {
) -> Result<TransformedAsset<Self::AssetOutput>, 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<TextSettings, Self::Error>> {
Box::pin(async move {
) -> Result<TextSettings, Self::Error> {
writer.write_all(asset.text.as_bytes()).await?;
Ok(TextSettings::default())
})
}
}