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::io::Reader;
use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, AsyncReadExt as _, Handle, LoadContext}; use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, AsyncReadExt as _, Handle, LoadContext};
use bevy_reflect::{Reflect, ReflectSerialize}; use bevy_reflect::{Reflect, ReflectSerialize};
use bevy_utils::BoxedFuture;
use petgraph::graph::{DiGraph, NodeIndex}; use petgraph::graph::{DiGraph, NodeIndex};
use ron::de::SpannedError; use ron::de::SpannedError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -336,40 +335,37 @@ impl AssetLoader for AnimationGraphAssetLoader {
type Error = AnimationGraphLoadError; type Error = AnimationGraphLoadError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_: &'a Self::Settings, _: &'a Self::Settings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?;
// Deserialize a `SerializedAnimationGraph` directly, so that we can // Deserialize a `SerializedAnimationGraph` directly, so that we can
// get the list of the animation clips it refers to and load them. // get the list of the animation clips it refers to and load them.
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let serialized_animation_graph = let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
SerializedAnimationGraph::deserialize(&mut deserializer) .map_err(|err| deserializer.span_error(err))?;
.map_err(|err| deserializer.span_error(err))?;
// Load all `AssetPath`s to convert from a // Load all `AssetPath`s to convert from a
// `SerializedAnimationGraph` to a real `AnimationGraph`. // `SerializedAnimationGraph` to a real `AnimationGraph`.
Ok(AnimationGraph { Ok(AnimationGraph {
graph: serialized_animation_graph.graph.map( graph: serialized_animation_graph.graph.map(
|_, serialized_node| AnimationGraphNode { |_, serialized_node| AnimationGraphNode {
clip: serialized_node.clip.as_ref().map(|clip| match clip { clip: serialized_node.clip.as_ref().map(|clip| match clip {
SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id), SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id),
SerializedAnimationClip::AssetPath(asset_path) => { SerializedAnimationClip::AssetPath(asset_path) => {
load_context.load(asset_path) load_context.load(asset_path)
} }
}), }),
weight: serialized_node.weight, weight: serialized_node.weight,
}, },
|_, _| (), |_, _| (),
), ),
root: serialized_animation_graph.root, root: serialized_animation_graph.root,
})
}) })
} }

View file

@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader, get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
}; };
use bevy_utils::tracing::error; use bevy_utils::tracing::error;
use bevy_utils::BoxedFuture;
use std::{ffi::CString, path::Path}; use std::{ffi::CString, path::Path};
/// [`AssetReader`] implementation for Android devices, built on top of Android's [`AssetManager`]. /// [`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; pub struct AndroidAssetReader;
impl AssetReader for AndroidAssetReader { impl AssetReader for AndroidAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let asset_manager = bevy_winit::ANDROID_APP
path: &'a Path, .get()
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { .expect("Bevy must be setup with the #[bevy_main] macro on Android")
Box::pin(async move { .asset_manager();
let asset_manager = bevy_winit::ANDROID_APP let mut opened_asset = asset_manager
.get() .open(&CString::new(path.to_str().unwrap()).unwrap())
.expect("Bevy must be setup with the #[bevy_main] macro on Android") .ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
.asset_manager(); let bytes = opened_asset.buffer()?;
let mut opened_asset = asset_manager let reader: Box<Reader> = Box::new(VecReader::new(bytes.to_vec()));
.open(&CString::new(path.to_str().unwrap()).unwrap()) Ok(reader)
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
let bytes = opened_asset.buffer()?;
let reader: Box<Reader> = Box::new(VecReader::new(bytes.to_vec()));
Ok(reader)
})
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let meta_path = get_meta_path(path);
path: &'a Path, let asset_manager = bevy_winit::ANDROID_APP
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { .get()
Box::pin(async move { .expect("Bevy must be setup with the #[bevy_main] macro on Android")
let meta_path = get_meta_path(path); .asset_manager();
let asset_manager = bevy_winit::ANDROID_APP let mut opened_asset = asset_manager
.get() .open(&CString::new(meta_path.to_str().unwrap()).unwrap())
.expect("Bevy must be setup with the #[bevy_main] macro on Android") .ok_or(AssetReaderError::NotFound(meta_path))?;
.asset_manager(); let bytes = opened_asset.buffer()?;
let mut opened_asset = asset_manager let reader: Box<Reader> = Box::new(VecReader::new(bytes.to_vec()));
.open(&CString::new(meta_path.to_str().unwrap()).unwrap()) Ok(reader)
.ok_or(AssetReaderError::NotFound(meta_path))?;
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, &'a self,
_path: &'a Path, _path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
let stream: Box<PathStream> = Box::new(EmptyPathStream); let stream: Box<PathStream> = Box::new(EmptyPathStream);
error!("Reading directories is not supported with the AndroidAssetReader"); 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, &'a self,
_path: &'a Path, _path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> { ) -> std::result::Result<bool, AssetReaderError> {
error!("Reading directories is not supported with the AndroidAssetReader"); 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, Reader, Writer,
}; };
use async_fs::{read_dir, File}; use async_fs::{read_dir, File};
use bevy_utils::BoxedFuture;
use futures_lite::StreamExt; use futures_lite::StreamExt;
use std::path::Path; use std::path::Path;
@ -11,215 +10,168 @@ use std::path::Path;
use super::{FileAssetReader, FileAssetWriter}; use super::{FileAssetReader, FileAssetWriter};
impl AssetReader for FileAssetReader { impl AssetReader for FileAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, match File::open(&full_path).await {
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { Ok(file) => {
Box::pin(async move { let reader: Box<Reader> = Box::new(file);
let full_path = self.root_path.join(path); Ok(reader)
match File::open(&full_path).await { }
Ok(file) => { Err(e) => {
let reader: Box<Reader> = Box::new(file); if e.kind() == std::io::ErrorKind::NotFound {
Ok(reader) Err(AssetReaderError::NotFound(full_path))
} } else {
Err(e) => { Err(e.into())
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
} }
} }
}) }
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
let meta_path = get_meta_path(path); let meta_path = get_meta_path(path);
Box::pin(async move { let full_path = self.root_path.join(meta_path);
let full_path = self.root_path.join(meta_path); match File::open(&full_path).await {
match File::open(&full_path).await { Ok(file) => {
Ok(file) => { let reader: Box<Reader> = Box::new(file);
let reader: Box<Reader> = Box::new(file); Ok(reader)
Ok(reader) }
} Err(e) => {
Err(e) => { if e.kind() == std::io::ErrorKind::NotFound {
if e.kind() == std::io::ErrorKind::NotFound { Err(AssetReaderError::NotFound(full_path))
Err(AssetReaderError::NotFound(full_path)) } else {
} else { Err(e.into())
Err(e.into())
}
} }
} }
}) }
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); match read_dir(&full_path).await {
match read_dir(&full_path).await { Ok(read_dir) => {
Ok(read_dir) => { let root_path = self.root_path.clone();
let root_path = self.root_path.clone(); let mapped_stream = read_dir.filter_map(move |f| {
let mapped_stream = read_dir.filter_map(move |f| { f.ok().and_then(|dir_entry| {
f.ok().and_then(|dir_entry| { let path = dir_entry.path();
let path = dir_entry.path(); // filter out meta files as they are not considered assets
// filter out meta files as they are not considered assets if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) { if ext.eq_ignore_ascii_case("meta") {
if ext.eq_ignore_ascii_case("meta") { return None;
return None;
}
} }
let relative_path = path.strip_prefix(&root_path).unwrap(); }
Some(relative_path.to_owned()) let relative_path = path.strip_prefix(&root_path).unwrap();
}) Some(relative_path.to_owned())
}); })
let read_dir: Box<PathStream> = Box::new(mapped_stream); });
Ok(read_dir) let read_dir: Box<PathStream> = Box::new(mapped_stream);
} Ok(read_dir)
Err(e) => { }
if e.kind() == std::io::ErrorKind::NotFound { Err(e) => {
Err(AssetReaderError::NotFound(full_path)) if e.kind() == std::io::ErrorKind::NotFound {
} else { Err(AssetReaderError::NotFound(full_path))
Err(e.into()) } else {
} Err(e.into())
} }
} }
}) }
} }
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, let metadata = full_path
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> { .metadata()
Box::pin(async move { .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
let full_path = self.root_path.join(path); Ok(metadata.file_type().is_dir())
let metadata = full_path
.metadata()
.map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
Ok(metadata.file_type().is_dir())
})
} }
} }
impl AssetWriter for FileAssetWriter { impl AssetWriter for FileAssetWriter {
fn write<'a>( async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, if let Some(parent) = full_path.parent() {
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> { async_fs::create_dir_all(parent).await?;
Box::pin(async move { }
let full_path = self.root_path.join(path); let file = File::create(&full_path).await?;
if let Some(parent) = full_path.parent() { let writer: Box<Writer> = Box::new(file);
async_fs::create_dir_all(parent).await?; Ok(writer)
}
let file = File::create(&full_path).await?;
let writer: Box<Writer> = Box::new(file);
Ok(writer)
})
} }
fn write_meta<'a>( async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
&'a self, let meta_path = get_meta_path(path);
path: &'a Path, let full_path = self.root_path.join(meta_path);
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> { if let Some(parent) = full_path.parent() {
Box::pin(async move { async_fs::create_dir_all(parent).await?;
let meta_path = get_meta_path(path); }
let full_path = self.root_path.join(meta_path); let file = File::create(&full_path).await?;
if let Some(parent) = full_path.parent() { let writer: Box<Writer> = Box::new(file);
async_fs::create_dir_all(parent).await?; Ok(writer)
}
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>> { async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); async_fs::remove_file(full_path).await?;
async_fs::remove_file(full_path).await?; Ok(())
Ok(())
})
} }
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> { async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
Box::pin(async move { let meta_path = get_meta_path(path);
let meta_path = get_meta_path(path); let full_path = self.root_path.join(meta_path);
let full_path = self.root_path.join(meta_path); async_fs::remove_file(full_path).await?;
async_fs::remove_file(full_path).await?; Ok(())
Ok(())
})
} }
fn rename<'a>( async fn rename<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_path: &'a Path, new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { ) -> Result<(), AssetWriterError> {
Box::pin(async move { let full_old_path = self.root_path.join(old_path);
let full_old_path = self.root_path.join(old_path); let full_new_path = self.root_path.join(new_path);
let full_new_path = self.root_path.join(new_path); if let Some(parent) = full_new_path.parent() {
if let Some(parent) = full_new_path.parent() { async_fs::create_dir_all(parent).await?;
async_fs::create_dir_all(parent).await?; }
} async_fs::rename(full_old_path, full_new_path).await?;
async_fs::rename(full_old_path, full_new_path).await?; Ok(())
Ok(())
})
} }
fn rename_meta<'a>( async fn rename_meta<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_path: &'a Path, new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { ) -> Result<(), AssetWriterError> {
Box::pin(async move { let old_meta_path = get_meta_path(old_path);
let old_meta_path = get_meta_path(old_path); let new_meta_path = get_meta_path(new_path);
let new_meta_path = get_meta_path(new_path); let full_old_path = self.root_path.join(old_meta_path);
let full_old_path = self.root_path.join(old_meta_path); let full_new_path = self.root_path.join(new_meta_path);
let full_new_path = self.root_path.join(new_meta_path); if let Some(parent) = full_new_path.parent() {
if let Some(parent) = full_new_path.parent() { async_fs::create_dir_all(parent).await?;
async_fs::create_dir_all(parent).await?; }
} async_fs::rename(full_old_path, full_new_path).await?;
async_fs::rename(full_old_path, full_new_path).await?; Ok(())
Ok(())
})
} }
fn remove_directory<'a>( async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, async_fs::remove_dir_all(full_path).await?;
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { Ok(())
Box::pin(async move {
let full_path = self.root_path.join(path);
async_fs::remove_dir_all(full_path).await?;
Ok(())
})
} }
fn remove_empty_directory<'a>( async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, async_fs::remove_dir(full_path).await?;
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { Ok(())
Box::pin(async move {
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, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { ) -> Result<(), AssetWriterError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); async_fs::remove_dir_all(&full_path).await?;
async_fs::remove_dir_all(&full_path).await?; async_fs::create_dir_all(&full_path).await?;
async_fs::create_dir_all(&full_path).await?; Ok(())
Ok(())
})
} }
} }

View file

@ -5,7 +5,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream, get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
Reader, Writer, Reader, Writer,
}; };
use bevy_utils::BoxedFuture;
use std::{ use std::{
fs::{read_dir, File}, fs::{read_dir, File},
@ -76,221 +75,180 @@ impl Stream for DirReader {
} }
impl AssetReader for FileAssetReader { impl AssetReader for FileAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, match File::open(&full_path) {
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { Ok(file) => {
Box::pin(async move { let reader: Box<Reader> = Box::new(FileReader(file));
let full_path = self.root_path.join(path); Ok(reader)
match File::open(&full_path) { }
Ok(file) => { Err(e) => {
let reader: Box<Reader> = Box::new(FileReader(file)); if e.kind() == std::io::ErrorKind::NotFound {
Ok(reader) Err(AssetReaderError::NotFound(full_path))
} } else {
Err(e) => { Err(e.into())
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
} }
} }
}) }
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
let meta_path = get_meta_path(path); let meta_path = get_meta_path(path);
Box::pin(async move { let full_path = self.root_path.join(meta_path);
let full_path = self.root_path.join(meta_path); match File::open(&full_path) {
match File::open(&full_path) { Ok(file) => {
Ok(file) => { let reader: Box<Reader> = Box::new(FileReader(file));
let reader: Box<Reader> = Box::new(FileReader(file)); Ok(reader)
Ok(reader) }
} Err(e) => {
Err(e) => { if e.kind() == std::io::ErrorKind::NotFound {
if e.kind() == std::io::ErrorKind::NotFound { Err(AssetReaderError::NotFound(full_path))
Err(AssetReaderError::NotFound(full_path)) } else {
} else { Err(e.into())
Err(e.into())
}
} }
} }
}) }
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); match read_dir(&full_path) {
match read_dir(&full_path) { Ok(read_dir) => {
Ok(read_dir) => { let root_path = self.root_path.clone();
let root_path = self.root_path.clone(); let mapped_stream = read_dir.filter_map(move |f| {
let mapped_stream = read_dir.filter_map(move |f| { f.ok().and_then(|dir_entry| {
f.ok().and_then(|dir_entry| { let path = dir_entry.path();
let path = dir_entry.path(); // filter out meta files as they are not considered assets
// filter out meta files as they are not considered assets if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) { if ext.eq_ignore_ascii_case("meta") {
if ext.eq_ignore_ascii_case("meta") { return None;
return None;
}
} }
let relative_path = path.strip_prefix(&root_path).unwrap(); }
Some(relative_path.to_owned()) let relative_path = path.strip_prefix(&root_path).unwrap();
}) Some(relative_path.to_owned())
}); })
let read_dir: Box<PathStream> = Box::new(DirReader(mapped_stream.collect())); });
Ok(read_dir) let read_dir: Box<PathStream> = Box::new(DirReader(mapped_stream.collect()));
} Ok(read_dir)
Err(e) => { }
if e.kind() == std::io::ErrorKind::NotFound { Err(e) => {
Err(AssetReaderError::NotFound(full_path)) if e.kind() == std::io::ErrorKind::NotFound {
} else { Err(AssetReaderError::NotFound(full_path))
Err(e.into()) } else {
} Err(e.into())
} }
} }
}) }
} }
fn is_directory<'a>( async fn is_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> { ) -> std::result::Result<bool, AssetReaderError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); let metadata = full_path
let metadata = full_path .metadata()
.metadata() .map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
.map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?; Ok(metadata.file_type().is_dir())
Ok(metadata.file_type().is_dir())
})
} }
} }
impl AssetWriter for FileAssetWriter { impl AssetWriter for FileAssetWriter {
fn write<'a>( async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, if let Some(parent) = full_path.parent() {
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> { std::fs::create_dir_all(parent)?;
Box::pin(async move { }
let full_path = self.root_path.join(path); let file = File::create(&full_path)?;
if let Some(parent) = full_path.parent() { let writer: Box<Writer> = Box::new(FileWriter(file));
std::fs::create_dir_all(parent)?; Ok(writer)
}
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
})
} }
fn write_meta<'a>( async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
&'a self, let meta_path = get_meta_path(path);
path: &'a Path, let full_path = self.root_path.join(meta_path);
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> { if let Some(parent) = full_path.parent() {
Box::pin(async move { std::fs::create_dir_all(parent)?;
let meta_path = get_meta_path(path); }
let full_path = self.root_path.join(meta_path); let file = File::create(&full_path)?;
if let Some(parent) = full_path.parent() { let writer: Box<Writer> = Box::new(FileWriter(file));
std::fs::create_dir_all(parent)?; Ok(writer)
}
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
})
} }
fn remove<'a>( async fn remove<'a>(&'a self, path: &'a Path) -> std::result::Result<(), AssetWriterError> {
&'a self, let full_path = self.root_path.join(path);
path: &'a Path, std::fs::remove_file(full_path)?;
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { Ok(())
Box::pin(async move {
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, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let meta_path = get_meta_path(path);
let meta_path = get_meta_path(path); let full_path = self.root_path.join(meta_path);
let full_path = self.root_path.join(meta_path); std::fs::remove_file(full_path)?;
std::fs::remove_file(full_path)?; Ok(())
Ok(())
})
} }
fn remove_directory<'a>( async fn remove_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); std::fs::remove_dir_all(full_path)?;
std::fs::remove_dir_all(full_path)?; Ok(())
Ok(())
})
} }
fn remove_empty_directory<'a>( async fn remove_empty_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); std::fs::remove_dir(full_path)?;
std::fs::remove_dir(full_path)?; Ok(())
Ok(())
})
} }
fn remove_assets_in_directory<'a>( async fn remove_assets_in_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let full_path = self.root_path.join(path);
let full_path = self.root_path.join(path); std::fs::remove_dir_all(&full_path)?;
std::fs::remove_dir_all(&full_path)?; std::fs::create_dir_all(&full_path)?;
std::fs::create_dir_all(&full_path)?; Ok(())
Ok(())
})
} }
fn rename<'a>( async fn rename<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_path: &'a Path, new_path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let full_old_path = self.root_path.join(old_path);
let full_old_path = self.root_path.join(old_path); let full_new_path = self.root_path.join(new_path);
let full_new_path = self.root_path.join(new_path); if let Some(parent) = full_new_path.parent() {
if let Some(parent) = full_new_path.parent() { std::fs::create_dir_all(parent)?;
std::fs::create_dir_all(parent)?; }
} std::fs::rename(full_old_path, full_new_path)?;
std::fs::rename(full_old_path, full_new_path)?; Ok(())
Ok(())
})
} }
fn rename_meta<'a>( async fn rename_meta<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_path: &'a Path, new_path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<(), AssetWriterError>> { ) -> std::result::Result<(), AssetWriterError> {
Box::pin(async move { let old_meta_path = get_meta_path(old_path);
let old_meta_path = get_meta_path(old_path); let new_meta_path = get_meta_path(new_path);
let new_meta_path = get_meta_path(new_path); let full_old_path = self.root_path.join(old_meta_path);
let full_old_path = self.root_path.join(old_meta_path); let full_new_path = self.root_path.join(new_meta_path);
let full_new_path = self.root_path.join(new_meta_path); if let Some(parent) = full_new_path.parent() {
if let Some(parent) = full_new_path.parent() { std::fs::create_dir_all(parent)?;
std::fs::create_dir_all(parent)?; }
} std::fs::rename(full_old_path, full_new_path)?;
std::fs::rename(full_old_path, full_new_path)?; Ok(())
Ok(())
})
} }
} }

View file

@ -1,5 +1,5 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use bevy_utils::{BoxedFuture, HashMap}; use bevy_utils::HashMap;
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
@ -55,10 +55,7 @@ impl<R: AssetReader> GatedReader<R> {
} }
impl<R: AssetReader> AssetReader for GatedReader<R> { impl<R: AssetReader> AssetReader for GatedReader<R> {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
let receiver = { let receiver = {
let mut gates = self.gates.write(); let mut gates = self.gates.write();
let gates = gates let gates = gates
@ -66,31 +63,23 @@ impl<R: AssetReader> AssetReader for GatedReader<R> {
.or_insert_with(crossbeam_channel::unbounded); .or_insert_with(crossbeam_channel::unbounded);
gates.1.clone() gates.1.clone()
}; };
Box::pin(async move { receiver.recv().unwrap();
receiver.recv().unwrap(); let result = self.reader.read(path).await?;
let result = self.reader.read(path).await?; Ok(result)
Ok(result)
})
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, self.reader.read_meta(path).await
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
self.reader.read_meta(path)
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
self.reader.read_directory(path) self.reader.read_directory(path).await
} }
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, self.reader.is_directory(path).await
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.reader.is_directory(path)
} }
} }

View file

@ -1,5 +1,5 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use bevy_utils::{BoxedFuture, HashMap}; use bevy_utils::HashMap;
use futures_io::AsyncRead; use futures_io::AsyncRead;
use futures_lite::{ready, Stream}; use futures_lite::{ready, Stream};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -237,62 +237,47 @@ impl AsyncRead for DataReader {
} }
impl AssetReader for MemoryAssetReader { impl AssetReader for MemoryAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, self.root
path: &'a Path, .get_asset(path)
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { .map(|data| {
Box::pin(async move { let reader: Box<Reader> = Box::new(DataReader {
self.root data,
.get_asset(path) bytes_read: 0,
.map(|data| { });
let reader: Box<Reader> = Box::new(DataReader { reader
data, })
bytes_read: 0, .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
});
reader
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, self.root
path: &'a Path, .get_metadata(path)
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { .map(|data| {
Box::pin(async move { let reader: Box<Reader> = Box::new(DataReader {
self.root data,
.get_metadata(path) bytes_read: 0,
.map(|data| { });
let reader: Box<Reader> = Box::new(DataReader { reader
data, })
bytes_read: 0, .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
});
reader
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
Box::pin(async move { self.root
self.root .get_dir(path)
.get_dir(path) .map(|dir| {
.map(|dir| { let stream: Box<PathStream> = Box::new(DirStream::new(dir));
let stream: Box<PathStream> = Box::new(DirStream::new(dir)); stream
stream })
}) .ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
} }
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, Ok(self.root.get_dir(path).is_some())
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
Box::pin(async move { Ok(self.root.get_dir(path).is_some()) })
} }
} }

View file

@ -21,7 +21,7 @@ mod source;
pub use futures_lite::{AsyncReadExt, AsyncWriteExt}; pub use futures_lite::{AsyncReadExt, AsyncWriteExt};
pub use source::*; pub use source::*;
use bevy_utils::BoxedFuture; use bevy_utils::{BoxedFuture, ConditionalSendFuture};
use futures_io::{AsyncRead, AsyncWrite}; use futures_io::{AsyncRead, AsyncWrite};
use futures_lite::{ready, Stream}; use futures_lite::{ready, Stream};
use std::{ 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" /// 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 /// 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`]. /// Also see [`AssetWriter`].
pub trait AssetReader: Send + Sync + 'static { pub trait AssetReader: Send + Sync + 'static {
@ -67,35 +67,90 @@ pub trait AssetReader: Send + Sync + 'static {
fn read<'a>( fn read<'a>(
&'a self, &'a self,
path: &'a Path, 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. /// Returns a future to load the full file data at the provided path.
fn read_meta<'a>( fn read_meta<'a>(
&'a self, &'a self,
path: &'a Path, 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. /// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>( fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>; ) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory. /// Returns an iterator of directory entry names at the provided path.
fn is_directory<'a>( fn is_directory<'a>(
&'a self, &'a self,
path: &'a Path, 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 /// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`AssetReader::read_meta`] by default. /// function that wraps [`AssetReader::read_meta`] by default.
fn read_meta_bytes<'a>( fn read_meta_bytes<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> { ) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
Box::pin(async move { async {
let mut meta_reader = self.read_meta(path).await?; let mut meta_reader = self.read_meta(path).await?;
let mut meta_bytes = Vec::new(); let mut meta_bytes = Vec::new();
meta_reader.read_to_end(&mut meta_bytes).await?; meta_reader.read_to_end(&mut meta_bytes).await?;
Ok(meta_bytes) 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" /// 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 /// 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`]. /// Also see [`AssetReader`].
pub trait AssetWriter: Send + Sync + 'static { pub trait AssetWriter: Send + Sync + 'static {
@ -121,72 +176,195 @@ pub trait AssetWriter: Send + Sync + 'static {
fn write<'a>( fn write<'a>(
&'a self, &'a self,
path: &'a Path, 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. /// Writes the full asset meta bytes at the provided path.
/// This _should not_ include storage specific extensions like `.meta`. /// This _should not_ include storage specific extensions like `.meta`.
fn write_meta<'a>( fn write_meta<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>; ) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path. /// 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. /// Removes the asset meta stored at the given path.
/// This _should not_ include storage specific extensions like `.meta`. /// 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` /// Renames the asset at `old_path` to `new_path`
fn rename<'a>( fn rename<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_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`. /// Renames the asset meta for the asset at `old_path` to `new_path`.
/// This _should not_ include storage specific extensions like `.meta`. /// This _should not_ include storage specific extensions like `.meta`.
fn rename_meta<'a>( fn rename_meta<'a>(
&'a self, &'a self,
old_path: &'a Path, old_path: &'a Path,
new_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. /// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>( fn remove_directory<'a>(
&'a self, &'a self,
path: &'a Path, 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 /// 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. /// directory is not empty.
fn remove_empty_directory<'a>( fn remove_empty_directory<'a>(
&'a self, &'a self,
path: &'a Path, 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. /// Removes all assets (and directories) in this directory, resulting in an empty directory.
fn remove_assets_in_directory<'a>( fn remove_assets_in_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>; ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`. /// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>( fn write_bytes<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
bytes: &'a [u8], bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
Box::pin(async move { async {
let mut writer = self.write(path).await?; let mut writer = self.write(path).await?;
writer.write_all(bytes).await?; writer.write_all(bytes).await?;
writer.flush().await?; writer.flush().await?;
Ok(()) Ok(())
}) }
} }
/// Writes the asset meta `bytes` to the given `path`. /// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>( fn write_meta_bytes<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
bytes: &'a [u8], bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>> { ) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
Box::pin(async move { async {
let mut meta_writer = self.write_meta(path).await?; let mut meta_writer = self.write_meta(path).await?;
meta_writer.write_all(bytes).await?; meta_writer.write_all(bytes).await?;
meta_writer.flush().await?; meta_writer.flush().await?;
Ok(()) 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 async_lock::RwLockReadGuardArc;
use bevy_utils::tracing::trace; use bevy_utils::tracing::trace;
use bevy_utils::BoxedFuture;
use futures_io::AsyncRead; use futures_io::AsyncRead;
use std::{path::Path, pin::Pin, sync::Arc}; 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 /// 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`]. /// given path until that path has been processed by [`AssetProcessor`].
/// ///
/// [`AssetProcessor`]: crate::processor::AssetProcessor /// [`AssetProcessor`]: crate::processor::AssetProcessor
pub struct ProcessorGatedReader { pub struct ProcessorGatedReader {
reader: Box<dyn AssetReader>, reader: Box<dyn ErasedAssetReader>,
source: AssetSourceId<'static>, source: AssetSourceId<'static>,
processor_data: Arc<AssetProcessorData>, processor_data: Arc<AssetProcessorData>,
} }
@ -23,7 +24,7 @@ impl ProcessorGatedReader {
/// Creates a new [`ProcessorGatedReader`]. /// Creates a new [`ProcessorGatedReader`].
pub fn new( pub fn new(
source: AssetSourceId<'static>, source: AssetSourceId<'static>,
reader: Box<dyn AssetReader>, reader: Box<dyn ErasedAssetReader>,
processor_data: Arc<AssetProcessorData>, processor_data: Arc<AssetProcessorData>,
) -> Self { ) -> Self {
Self { Self {
@ -48,87 +49,69 @@ impl ProcessorGatedReader {
} }
impl AssetReader for ProcessorGatedReader { impl AssetReader for ProcessorGatedReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone());
path: &'a Path, trace!("Waiting for processing to finish before reading {asset_path}");
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { let process_result = self
Box::pin(async move { .processor_data
let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); .wait_until_processed(asset_path.clone())
trace!("Waiting for processing to finish before reading {asset_path}"); .await;
let process_result = self match process_result {
.processor_data ProcessStatus::Processed => {}
.wait_until_processed(asset_path.clone()) ProcessStatus::Failed | ProcessStatus::NonExistent => {
.await; return Err(AssetReaderError::NotFound(path.to_owned()));
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?; trace!("Processing finished with {asset_path}, reading {process_result:?}",);
let asset_reader = self.reader.read(path).await?; let lock = self.get_transaction_lock(&asset_path).await?;
let reader: Box<Reader<'a>> = let asset_reader = self.reader.read(path).await?;
Box::new(TransactionLockedReader::new(asset_reader, lock)); let reader: Box<Reader<'a>> = Box::new(TransactionLockedReader::new(asset_reader, lock));
Ok(reader) Ok(reader)
})
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone());
path: &'a Path, trace!("Waiting for processing to finish before reading meta for {asset_path}",);
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> { let process_result = self
Box::pin(async move { .processor_data
let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone()); .wait_until_processed(asset_path.clone())
trace!("Waiting for processing to finish before reading meta for {asset_path}",); .await;
let process_result = self match process_result {
.processor_data ProcessStatus::Processed => {}
.wait_until_processed(asset_path.clone()) ProcessStatus::Failed | ProcessStatus::NonExistent => {
.await; return Err(AssetReaderError::NotFound(path.to_owned()));
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?; trace!("Processing finished with {process_result:?}, reading meta for {asset_path}",);
let meta_reader = self.reader.read_meta(path).await?; let lock = self.get_transaction_lock(&asset_path).await?;
let reader: Box<Reader<'a>> = Box::new(TransactionLockedReader::new(meta_reader, lock)); let meta_reader = self.reader.read_meta(path).await?;
Ok(reader) 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, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
Box::pin(async move { trace!(
trace!( "Waiting for processing to finish before reading directory {:?}",
"Waiting for processing to finish before reading directory {:?}", path
path );
); self.processor_data.wait_until_finished().await;
self.processor_data.wait_until_finished().await; trace!("Processing finished, reading directory {:?}", path);
trace!("Processing finished, reading directory {:?}", path); let result = self.reader.read_directory(path).await?;
let result = self.reader.read_directory(path).await?; Ok(result)
Ok(result)
})
} }
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, trace!(
path: &'a Path, "Waiting for processing to finish before reading directory {:?}",
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> { path
Box::pin(async move { );
trace!( self.processor_data.wait_until_finished().await;
"Waiting for processing to finish before reading directory {:?}", trace!("Processing finished, getting directory status {:?}", path);
path let result = self.reader.is_directory(path).await?;
); Ok(result)
self.processor_data.wait_until_finished().await;
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::{ use crate::{
io::{ io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher},
processor_gated::ProcessorGatedReader, AssetReader, AssetSourceEvent, AssetWatcher,
AssetWriter,
},
processor::AssetProcessorData, processor::AssetProcessorData,
}; };
use bevy_ecs::system::Resource; use bevy_ecs::system::Resource;
@ -11,6 +8,12 @@ use bevy_utils::{CowArc, Duration, HashMap};
use std::{fmt::Display, hash::Hash, sync::Arc}; use std::{fmt::Display, hash::Hash, sync::Arc};
use thiserror::Error; 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`]. /// 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` /// * [`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. /// and whether or not the source is processed.
#[derive(Default)] #[derive(Default)]
pub struct AssetSourceBuilder { pub struct AssetSourceBuilder {
pub reader: Option<Box<dyn FnMut() -> Box<dyn AssetReader> + Send + Sync>>, pub reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn AssetWriter>> + Send + Sync>>, pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
pub watcher: Option< pub watcher: Option<
Box< Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>> dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
@ -119,9 +122,9 @@ pub struct AssetSourceBuilder {
+ Sync, + 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: 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< pub processed_watcher: Option<
Box< Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>> 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. /// Will use the given `reader` function to construct unprocessed [`AssetReader`] instances.
pub fn with_reader( pub fn with_reader(
mut self, mut self,
reader: impl FnMut() -> Box<dyn AssetReader> + Send + Sync + 'static, reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
) -> Self { ) -> Self {
self.reader = Some(Box::new(reader)); self.reader = Some(Box::new(reader));
self self
@ -201,7 +204,7 @@ impl AssetSourceBuilder {
/// Will use the given `writer` function to construct unprocessed [`AssetWriter`] instances. /// Will use the given `writer` function to construct unprocessed [`AssetWriter`] instances.
pub fn with_writer( pub fn with_writer(
mut self, 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 {
self.writer = Some(Box::new(writer)); self.writer = Some(Box::new(writer));
self self
@ -222,7 +225,7 @@ impl AssetSourceBuilder {
/// Will use the given `reader` function to construct processed [`AssetReader`] instances. /// Will use the given `reader` function to construct processed [`AssetReader`] instances.
pub fn with_processed_reader( pub fn with_processed_reader(
mut self, mut self,
reader: impl FnMut() -> Box<dyn AssetReader> + Send + Sync + 'static, reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
) -> Self { ) -> Self {
self.processed_reader = Some(Box::new(reader)); self.processed_reader = Some(Box::new(reader));
self self
@ -231,7 +234,7 @@ impl AssetSourceBuilder {
/// Will use the given `writer` function to construct processed [`AssetWriter`] instances. /// Will use the given `writer` function to construct processed [`AssetWriter`] instances.
pub fn with_processed_writer( pub fn with_processed_writer(
mut self, 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 {
self.processed_writer = Some(Box::new(writer)); self.processed_writer = Some(Box::new(writer));
self self
@ -355,10 +358,10 @@ impl AssetSourceBuilders {
/// for a specific asset source, identified by an [`AssetSourceId`]. /// for a specific asset source, identified by an [`AssetSourceId`].
pub struct AssetSource { pub struct AssetSource {
id: AssetSourceId<'static>, id: AssetSourceId<'static>,
reader: Box<dyn AssetReader>, reader: Box<dyn ErasedAssetReader>,
writer: Option<Box<dyn AssetWriter>>, writer: Option<Box<dyn ErasedAssetWriter>>,
processed_reader: Option<Box<dyn AssetReader>>, processed_reader: Option<Box<dyn ErasedAssetReader>>,
processed_writer: Option<Box<dyn AssetWriter>>, processed_writer: Option<Box<dyn ErasedAssetWriter>>,
watcher: Option<Box<dyn AssetWatcher>>, watcher: Option<Box<dyn AssetWatcher>>,
processed_watcher: Option<Box<dyn AssetWatcher>>, processed_watcher: Option<Box<dyn AssetWatcher>>,
event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>, event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
@ -379,13 +382,13 @@ impl AssetSource {
/// Return's this source's unprocessed [`AssetReader`]. /// Return's this source's unprocessed [`AssetReader`].
#[inline] #[inline]
pub fn reader(&self) -> &dyn AssetReader { pub fn reader(&self) -> &dyn ErasedAssetReader {
&*self.reader &*self.reader
} }
/// Return's this source's unprocessed [`AssetWriter`], if it exists. /// Return's this source's unprocessed [`AssetWriter`], if it exists.
#[inline] #[inline]
pub fn writer(&self) -> Result<&dyn AssetWriter, MissingAssetWriterError> { pub fn writer(&self) -> Result<&dyn ErasedAssetWriter, MissingAssetWriterError> {
self.writer self.writer
.as_deref() .as_deref()
.ok_or_else(|| MissingAssetWriterError(self.id.clone_owned())) .ok_or_else(|| MissingAssetWriterError(self.id.clone_owned()))
@ -393,7 +396,9 @@ impl AssetSource {
/// Return's this source's processed [`AssetReader`], if it exists. /// Return's this source's processed [`AssetReader`], if it exists.
#[inline] #[inline]
pub fn processed_reader(&self) -> Result<&dyn AssetReader, MissingProcessedAssetReaderError> { pub fn processed_reader(
&self,
) -> Result<&dyn ErasedAssetReader, MissingProcessedAssetReaderError> {
self.processed_reader self.processed_reader
.as_deref() .as_deref()
.ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned())) .ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned()))
@ -401,7 +406,9 @@ impl AssetSource {
/// Return's this source's processed [`AssetWriter`], if it exists. /// Return's this source's processed [`AssetWriter`], if it exists.
#[inline] #[inline]
pub fn processed_writer(&self) -> Result<&dyn AssetWriter, MissingProcessedAssetWriterError> { pub fn processed_writer(
&self,
) -> Result<&dyn ErasedAssetWriter, MissingProcessedAssetWriterError> {
self.processed_writer self.processed_writer
.as_deref() .as_deref()
.ok_or_else(|| MissingProcessedAssetWriterError(self.id.clone_owned())) .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 /// Returns a builder function for this platform's default [`AssetReader`]. `path` is the relative path to
/// the asset root. /// 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 || { move || {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return Box::new(super::file::FileAssetReader::new(&_path)); 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. /// the asset root. This will return [`None`] if this platform does not support writing assets by default.
pub fn get_default_writer( pub fn get_default_writer(
_path: String, _path: String,
) -> impl FnMut(bool) -> Option<Box<dyn AssetWriter>> + Send + Sync { ) -> impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync {
move |_create_root: bool| { move |_create_root: bool| {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))] #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return Some(Box::new(super::file::FileAssetWriter::new( 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, get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
}; };
use bevy_utils::tracing::error; use bevy_utils::tracing::error;
use bevy_utils::BoxedFuture;
use js_sys::{Uint8Array, JSON}; use js_sys::{Uint8Array, JSON};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
@ -59,40 +58,30 @@ impl HttpWasmAssetReader {
} }
impl AssetReader for HttpWasmAssetReader { impl AssetReader for HttpWasmAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let path = self.root_path.join(path);
path: &'a Path, self.fetch_bytes(path).await
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
let path = self.root_path.join(path);
self.fetch_bytes(path).await
})
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, let meta_path = get_meta_path(&self.root_path.join(path));
path: &'a Path, Ok(self.fetch_bytes(meta_path).await?)
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
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, &'a self,
_path: &'a Path, _path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
let stream: Box<PathStream> = Box::new(EmptyPathStream); let stream: Box<PathStream> = Box::new(EmptyPathStream);
error!("Reading directories is not supported with the HttpWasmAssetReader"); 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, &'a self,
_path: &'a Path, _path: &'a Path,
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> { ) -> std::result::Result<bool, AssetReaderError> {
error!("Reading directories is not supported with the HttpWasmAssetReader"); 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 reflect::*;
pub use server::*; pub use server::*;
pub use bevy_utils::BoxedFuture;
/// Rusty Object Notation, a crate used to serialize and deserialize bevy assets. /// Rusty Object Notation, a crate used to serialize and deserialize bevy assets.
pub use ron; pub use ron;
@ -448,7 +446,7 @@ mod tests {
}; };
use bevy_log::LogPlugin; use bevy_log::LogPlugin;
use bevy_reflect::TypePath; use bevy_reflect::TypePath;
use bevy_utils::{BoxedFuture, Duration, HashMap}; use bevy_utils::{Duration, HashMap};
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{path::Path, sync::Arc}; use std::{path::Path, sync::Arc};
@ -497,40 +495,38 @@ mod tests {
type Error = CoolTextLoaderError; type Error = CoolTextLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?; let mut embedded = String::new();
let mut embedded = String::new(); for dep in ron.embedded_dependencies {
for dep in ron.embedded_dependencies { let loaded = load_context.load_direct(&dep).await.map_err(|_| {
let loaded = load_context.load_direct(&dep).await.map_err(|_| { Self::Error::CannotLoadDependency {
Self::Error::CannotLoadDependency { dependency: dep.into(),
dependency: dep.into(), }
} })?;
})?; let cool = loaded.get::<CoolText>().unwrap();
let cool = loaded.get::<CoolText>().unwrap(); embedded.push_str(&cool.text);
embedded.push_str(&cool.text); }
} Ok(CoolText {
Ok(CoolText { text: ron.text,
text: ron.text, embedded,
embedded, dependencies: ron
dependencies: ron .dependencies
.dependencies .iter()
.iter() .map(|p| load_context.load(p))
.map(|p| load_context.load(p)) .collect(),
.collect(), sub_texts: ron
sub_texts: ron .sub_texts
.sub_texts .drain(..)
.drain(..) .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text })) .collect(),
.collect(),
})
}) })
} }
@ -560,31 +556,25 @@ mod tests {
} }
impl AssetReader for UnstableMemoryAssetReader { impl AssetReader for UnstableMemoryAssetReader {
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, self.memory_reader.is_directory(path).await
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.memory_reader.is_directory(path)
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<bevy_asset::io::PathStream>, AssetReaderError>> { ) -> Result<Box<bevy_asset::io::PathStream>, AssetReaderError> {
self.memory_reader.read_directory(path) self.memory_reader.read_directory(path).await
} }
fn read_meta<'a>( async fn read_meta<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<bevy_asset::io::Reader<'a>>, AssetReaderError>> { ) -> Result<Box<bevy_asset::io::Reader<'a>>, AssetReaderError> {
self.memory_reader.read_meta(path) self.memory_reader.read_meta(path).await
} }
fn read<'a>( async fn read<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture< ) -> Result<Box<bevy_asset::io::Reader<'a>>, bevy_asset::io::AssetReaderError> {
'a,
Result<Box<bevy_asset::io::Reader<'a>>, bevy_asset::io::AssetReaderError>,
> {
let attempt_number = { let attempt_number = {
let mut attempt_counters = self.attempt_counters.lock().unwrap(); let mut attempt_counters = self.attempt_counters.lock().unwrap();
if let Some(existing) = attempt_counters.get_mut(path) { if let Some(existing) = attempt_counters.get_mut(path) {
@ -605,13 +595,14 @@ mod tests {
), ),
); );
let wait = self.load_delay; let wait = self.load_delay;
return Box::pin(async move { return async move {
std::thread::sleep(wait); std::thread::sleep(wait);
Err(AssetReaderError::Io(io_error.into())) 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, UntypedAssetId, UntypedHandle,
}; };
use bevy_ecs::world::World; 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 downcast_rs::{impl_downcast, Downcast};
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
use ron::error::SpannedError; use ron::error::SpannedError;
@ -35,7 +35,7 @@ pub trait AssetLoader: Send + Sync + 'static {
reader: &'a mut Reader, reader: &'a mut Reader,
settings: &'a Self::Settings, settings: &'a Self::Settings,
load_context: &'a mut LoadContext, 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. /// 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. /// 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 Settings = ();
type OutputLoader = (); type OutputLoader = ();
fn process<'a>( async fn process<'a>(
&'a self, &'a self,
_context: &'a mut bevy_asset::processor::ProcessContext, _context: &'a mut bevy_asset::processor::ProcessContext<'_>,
_meta: AssetMeta<(), Self>, _meta: AssetMeta<(), Self>,
_writer: &'a mut bevy_asset::io::Writer, _writer: &'a mut bevy_asset::io::Writer,
) -> bevy_utils::BoxedFuture<'a, Result<(), bevy_asset::processor::ProcessError>> { ) -> Result<(), bevy_asset::processor::ProcessError> {
unreachable!() unreachable!()
} }
} }
@ -194,12 +194,12 @@ impl AssetLoader for () {
type Asset = (); type Asset = ();
type Settings = (); type Settings = ();
type Error = std::io::Error; type Error = std::io::Error;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
_reader: &'a mut crate::io::Reader, _reader: &'a mut crate::io::Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
_load_context: &'a mut crate::LoadContext, _load_context: &'a mut crate::LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
unreachable!(); unreachable!();
} }

View file

@ -6,8 +6,9 @@ pub use process::*;
use crate::{ use crate::{
io::{ io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceBuilders, AssetSourceEvent, AssetReaderError, AssetSource, AssetSourceBuilders, AssetSourceEvent, AssetSourceId,
AssetSourceId, AssetSources, AssetWriter, AssetWriterError, MissingAssetSourceError, AssetSources, AssetWriterError, ErasedAssetReader, ErasedAssetWriter,
MissingAssetSourceError,
}, },
meta::{ meta::{
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta, get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
@ -30,6 +31,10 @@ use std::{
}; };
use thiserror::Error; 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), /// 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`]. /// 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 /// Retrieves asset paths recursively. If `clean_empty_folders_writer` is Some, it will be used to clean up empty
/// folders when they are discovered. /// folders when they are discovered.
fn get_asset_paths<'a>( fn get_asset_paths<'a>(
reader: &'a dyn AssetReader, reader: &'a dyn ErasedAssetReader,
clean_empty_folders_writer: Option<&'a dyn AssetWriter>, clean_empty_folders_writer: Option<&'a dyn ErasedAssetWriter>,
path: PathBuf, path: PathBuf,
paths: &'a mut Vec<PathBuf>, paths: &'a mut Vec<PathBuf>,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> { ) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {

View file

@ -10,7 +10,7 @@ use crate::{
AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset, AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,
MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError, MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
}; };
use bevy_utils::BoxedFuture; use bevy_utils::{BoxedFuture, ConditionalSendFuture};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
use thiserror::Error; use thiserror::Error;
@ -32,7 +32,9 @@ pub trait Process: Send + Sync + Sized + 'static {
context: &'a mut ProcessContext, context: &'a mut ProcessContext,
meta: AssetMeta<(), Self>, meta: AssetMeta<(), Self>,
writer: &'a mut Writer, 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 /// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms
@ -173,41 +175,38 @@ impl<
type Settings = LoadTransformAndSaveSettings<Loader::Settings, T::Settings, Saver::Settings>; type Settings = LoadTransformAndSaveSettings<Loader::Settings, T::Settings, Saver::Settings>;
type OutputLoader = Saver::OutputLoader; type OutputLoader = Saver::OutputLoader;
fn process<'a>( async fn process<'a>(
&'a self, &'a self,
context: &'a mut ProcessContext, context: &'a mut ProcessContext<'_>,
meta: AssetMeta<(), Self>, meta: AssetMeta<(), Self>,
writer: &'a mut Writer, writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> { ) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
Box::pin(async move { let AssetAction::Process { settings, .. } = meta.asset else {
let AssetAction::Process { settings, .. } = meta.asset else { return Err(ProcessError::WrongMetaType);
return Err(ProcessError::WrongMetaType); };
}; let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load { loader: std::any::type_name::<Loader>().to_string(),
loader: std::any::type_name::<Loader>().to_string(), settings: settings.loader_settings,
settings: settings.loader_settings, });
}); let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded( context.load_source_asset(loader_meta).await?,
context.load_source_asset(loader_meta).await?, )
) .unwrap();
.unwrap();
let post_transformed_asset = self let post_transformed_asset = self
.transformer .transformer
.transform(pre_transformed_asset, &settings.transformer_settings) .transform(pre_transformed_asset, &settings.transformer_settings)
.await .await
.map_err(|err| ProcessError::AssetTransformError(err.into()))?; .map_err(|err| ProcessError::AssetTransformError(err.into()))?;
let saved_asset = let saved_asset = SavedAsset::<T::AssetOutput>::from_transformed(&post_transformed_asset);
SavedAsset::<T::AssetOutput>::from_transformed(&post_transformed_asset);
let output_settings = self let output_settings = self
.saver .saver
.save(writer, saved_asset, &settings.saver_settings) .save(writer, saved_asset, &settings.saver_settings)
.await .await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?; .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings) Ok(output_settings)
})
} }
} }
@ -217,29 +216,27 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
type Settings = LoadAndSaveSettings<Loader::Settings, Saver::Settings>; type Settings = LoadAndSaveSettings<Loader::Settings, Saver::Settings>;
type OutputLoader = Saver::OutputLoader; type OutputLoader = Saver::OutputLoader;
fn process<'a>( async fn process<'a>(
&'a self, &'a self,
context: &'a mut ProcessContext, context: &'a mut ProcessContext<'_>,
meta: AssetMeta<(), Self>, meta: AssetMeta<(), Self>,
writer: &'a mut Writer, writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>> { ) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
Box::pin(async move { let AssetAction::Process { settings, .. } = meta.asset else {
let AssetAction::Process { settings, .. } = meta.asset else { return Err(ProcessError::WrongMetaType);
return Err(ProcessError::WrongMetaType); };
}; let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load { loader: std::any::type_name::<Loader>().to_string(),
loader: std::any::type_name::<Loader>().to_string(), settings: settings.loader_settings,
settings: settings.loader_settings, });
}); let loaded_asset = context.load_source_asset(loader_meta).await?;
let loaded_asset = context.load_source_asset(loader_meta).await?; let saved_asset = SavedAsset::<Loader::Asset>::from_loaded(&loaded_asset).unwrap();
let saved_asset = SavedAsset::<Loader::Asset>::from_loaded(&loaded_asset).unwrap(); let output_settings = self
let output_settings = self .saver
.saver .save(writer, saved_asset, &settings.saver_settings)
.save(writer, saved_asset, &settings.saver_settings) .await
.await .map_err(|error| ProcessError::AssetSaveError(error.into()))?;
.map_err(|error| ProcessError::AssetSaveError(error.into()))?; Ok(output_settings)
Ok(output_settings)
})
} }
} }

View file

@ -1,7 +1,7 @@
use crate::transformer::TransformedAsset; use crate::transformer::TransformedAsset;
use crate::{io::Writer, meta::Settings, Asset, ErasedLoadedAsset}; use crate::{io::Writer, meta::Settings, Asset, ErasedLoadedAsset};
use crate::{AssetLoader, Handle, LabeledAsset, UntypedHandle}; use crate::{AssetLoader, Handle, LabeledAsset, UntypedHandle};
use bevy_utils::{BoxedFuture, CowArc, HashMap}; use bevy_utils::{BoxedFuture, ConditionalSendFuture, CowArc, HashMap};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{borrow::Borrow, hash::Hash, ops::Deref}; use std::{borrow::Borrow, hash::Hash, ops::Deref};
@ -24,7 +24,9 @@ pub trait AssetSaver: Send + Sync + 'static {
writer: &'a mut Writer, writer: &'a mut Writer,
asset: SavedAsset<'a, Self::Asset>, asset: SavedAsset<'a, Self::Asset>,
settings: &'a Self::Settings, 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`]. /// 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; type Error = String;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
_: &'a mut crate::io::Reader, _: &'a mut crate::io::Reader<'_>,
_: &'a Self::Settings, _: &'a Self::Settings,
_: &'a mut crate::LoadContext, _: &'a mut crate::LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
self.sender.send(()).unwrap(); self.sender.send(()).unwrap();
Box::pin(async move { Err(format!(
Err(format!( "Loaded {}:{}",
"Loaded {}:{}", std::any::type_name::<Self::Asset>(),
std::any::type_name::<Self::Asset>(), N
N ))
))
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -4,8 +4,8 @@ mod loaders;
use crate::{ use crate::{
folder::LoadedFolder, folder::LoadedFolder,
io::{ io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources, AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources,
MissingAssetSourceError, MissingProcessedAssetReaderError, Reader, ErasedAssetReader, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader,
}, },
loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset}, loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
meta::{ meta::{
@ -30,6 +30,10 @@ use std::path::PathBuf;
use std::{any::TypeId, path::Path, sync::Arc}; use std::{any::TypeId, path::Path, sync::Arc};
use thiserror::Error; 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 /// 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. /// retrieve their current load states.
/// ///
@ -657,7 +661,7 @@ impl AssetServer {
fn load_folder<'a>( fn load_folder<'a>(
source: AssetSourceId<'static>, source: AssetSourceId<'static>,
path: &'a Path, path: &'a Path,
reader: &'a dyn AssetReader, reader: &'a dyn ErasedAssetReader,
server: &'a AssetServer, server: &'a AssetServer,
handles: &'a mut Vec<UntypedHandle>, handles: &'a mut Vec<UntypedHandle>,
) -> bevy_utils::BoxedFuture<'a, Result<(), AssetLoadError>> { ) -> bevy_utils::BoxedFuture<'a, Result<(), AssetLoadError>> {

View file

@ -1,5 +1,5 @@
use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle}; 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 serde::{Deserialize, Serialize};
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
@ -25,7 +25,7 @@ pub trait AssetTransformer: Send + Sync + 'static {
&'a self, &'a self,
asset: TransformedAsset<Self::AssetInput>, asset: TransformedAsset<Self::AssetInput>,
settings: &'a Self::Settings, 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 /// An [`Asset`] (and any "sub assets") intended to be transformed

View file

@ -3,7 +3,6 @@ use bevy_asset::{
Asset, AssetLoader, LoadContext, Asset, AssetLoader, LoadContext,
}; };
use bevy_reflect::TypePath; use bevy_reflect::TypePath;
use bevy_utils::BoxedFuture;
use std::{io::Cursor, sync::Arc}; use std::{io::Cursor, sync::Arc};
/// A source of audio data /// A source of audio data
@ -43,18 +42,16 @@ impl AssetLoader for AudioLoader {
type Settings = (); type Settings = ();
type Error = std::io::Error; type Error = std::io::Error;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<AudioSource, Self::Error>> { ) -> Result<AudioSource, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; Ok(AudioSource {
Ok(AudioSource { bytes: bytes.into(),
bytes: bytes.into(),
})
}) })
} }

View file

@ -162,17 +162,15 @@ impl AssetLoader for GltfLoader {
type Asset = Gltf; type Asset = Gltf;
type Settings = GltfLoaderSettings; type Settings = GltfLoaderSettings;
type Error = GltfError; type Error = GltfError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
settings: &'a GltfLoaderSettings, settings: &'a GltfLoaderSettings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Gltf, Self::Error>> { ) -> Result<Gltf, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; load_gltf(self, &bytes, load_context, settings).await
load_gltf(self, &bytes, load_context, settings).await
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -2,7 +2,7 @@ use super::ShaderDefVal;
use crate::define_atomic_id; use crate::define_atomic_id;
use bevy_asset::{io::Reader, Asset, AssetLoader, AssetPath, Handle, LoadContext}; use bevy_asset::{io::Reader, Asset, AssetLoader, AssetPath, Handle, LoadContext};
use bevy_reflect::TypePath; use bevy_reflect::TypePath;
use bevy_utils::{tracing::error, BoxedFuture}; use bevy_utils::tracing::error;
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
use std::{borrow::Cow, marker::Copy}; use std::{borrow::Cow, marker::Copy};
use thiserror::Error; use thiserror::Error;
@ -259,43 +259,39 @@ impl AssetLoader for ShaderLoader {
type Asset = Shader; type Asset = Shader;
type Settings = (); type Settings = ();
type Error = ShaderLoaderError; type Error = ShaderLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Shader, Self::Error>> { ) -> Result<Shader, Self::Error> {
Box::pin(async move { let ext = load_context.path().extension().unwrap().to_str().unwrap();
let ext = load_context.path().extension().unwrap().to_str().unwrap(); let path = load_context.asset_path().to_string();
let path = load_context.asset_path().to_string(); // On windows, the path will inconsistently use \ or /.
// On windows, the path will inconsistently use \ or /. // TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511
// TODO: remove this once AssetPath forces cross-platform "slash" consistency. See #10511 let path = path.replace(std::path::MAIN_SEPARATOR, "/");
let path = path.replace(std::path::MAIN_SEPARATOR, "/"); let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let mut shader = match ext {
let mut shader = match ext { "spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()),
"spv" => Shader::from_spirv(bytes, load_context.path().to_string_lossy()), "wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path),
"wgsl" => Shader::from_wgsl(String::from_utf8(bytes)?, path), "vert" => Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path),
"vert" => { "frag" => {
Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Vertex, path) Shader::from_glsl(String::from_utf8(bytes)?, naga::ShaderStage::Fragment, 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));
}
} }
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] { fn extensions(&self) -> &[&str] {

View file

@ -1,6 +1,6 @@
use crate::texture::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings}; use crate::texture::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings};
use bevy_asset::saver::{AssetSaver, SavedAsset}; use bevy_asset::saver::{AssetSaver, SavedAsset};
use futures_lite::{AsyncWriteExt, FutureExt}; use futures_lite::AsyncWriteExt;
use thiserror::Error; use thiserror::Error;
pub struct CompressedImageSaver; pub struct CompressedImageSaver;
@ -19,46 +19,46 @@ impl AssetSaver for CompressedImageSaver {
type OutputLoader = ImageLoader; type OutputLoader = ImageLoader;
type Error = CompressedImageSaverError; type Error = CompressedImageSaverError;
fn save<'a>( async fn save<'a>(
&'a self, &'a self,
writer: &'a mut bevy_asset::io::Writer, writer: &'a mut bevy_asset::io::Writer,
image: SavedAsset<'a, Self::Asset>, image: SavedAsset<'a, Self::Asset>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
) -> bevy_utils::BoxedFuture<'a, Result<ImageLoaderSettings, Self::Error>> { ) -> 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)
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 is_srgb = image.texture_descriptor.format.is_srgb();
let color_space = if is_srgb {
basis_universal::ColorSpace::Srgb let compressed_basis_data = {
} else { let mut compressor_params = basis_universal::CompressorParams::new();
basis_universal::ColorSpace::Linear 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); writer.write_all(&compressed_basis_data).await?;
let size = image.size(); Ok(ImageLoaderSettings {
source_image.init(&image.data, size.x, size.y, 4); format: ImageFormatSetting::Format(ImageFormat::Basis),
is_srgb,
let mut compressor = basis_universal::Compressor::new(4); sampler: image.sampler.clone(),
// SAFETY: the CompressorParams are "valid" to the best of our knowledge. The basis-universal asset_usage: image.asset_usage,
// 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()
} }
} }

View file

@ -6,7 +6,6 @@ use bevy_asset::{
io::{AsyncReadExt, Reader}, io::{AsyncReadExt, Reader},
AssetLoader, LoadContext, AssetLoader, LoadContext,
}; };
use bevy_utils::BoxedFuture;
use image::ImageDecoder; use image::ImageDecoder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
@ -36,45 +35,43 @@ impl AssetLoader for ExrTextureLoader {
type Settings = ExrTextureLoaderSettings; type Settings = ExrTextureLoaderSettings;
type Error = ExrTextureLoaderError; type Error = ExrTextureLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
settings: &'a Self::Settings, settings: &'a Self::Settings,
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Image, Self::Error>> { ) -> Result<Image, Self::Error> {
Box::pin(async move { let format = TextureFormat::Rgba32Float;
let format = TextureFormat::Rgba32Float; debug_assert_eq!(
debug_assert_eq!( format.pixel_size(),
format.pixel_size(), 4 * 4,
4 * 4, "Format should have 32bit x 4 size"
"Format should have 32bit x 4 size" );
);
let mut bytes = Vec::new(); let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?; reader.read_to_end(&mut bytes).await?;
let decoder = image::codecs::openexr::OpenExrDecoder::with_alpha_preference( let decoder = image::codecs::openexr::OpenExrDecoder::with_alpha_preference(
std::io::Cursor::new(bytes), std::io::Cursor::new(bytes),
Some(true), Some(true),
)?; )?;
let (width, height) = decoder.dimensions(); 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]; let mut buf = vec![0u8; total_bytes];
decoder.read_image(buf.as_mut_slice())?; decoder.read_image(buf.as_mut_slice())?;
Ok(Image::new( Ok(Image::new(
Extent3d { Extent3d {
width, width,
height, height,
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
TextureDimension::D2, TextureDimension::D2,
buf, buf,
format, format,
settings.asset_usage, settings.asset_usage,
)) ))
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -29,48 +29,46 @@ impl AssetLoader for HdrTextureLoader {
type Asset = Image; type Asset = Image;
type Settings = HdrTextureLoaderSettings; type Settings = HdrTextureLoaderSettings;
type Error = HdrTextureLoaderError; type Error = HdrTextureLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
settings: &'a Self::Settings, settings: &'a Self::Settings,
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Image, Self::Error>> { ) -> Result<Image, Self::Error> {
Box::pin(async move { let format = TextureFormat::Rgba32Float;
let format = TextureFormat::Rgba32Float; debug_assert_eq!(
debug_assert_eq!( format.pixel_size(),
format.pixel_size(), 4 * 4,
4 * 4, "Format should have 32bit x 4 size"
"Format should have 32bit x 4 size" );
);
let mut bytes = Vec::new(); let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?; reader.read_to_end(&mut bytes).await?;
let decoder = image::codecs::hdr::HdrDecoder::new(bytes.as_slice())?; let decoder = image::codecs::hdr::HdrDecoder::new(bytes.as_slice())?;
let info = decoder.metadata(); let info = decoder.metadata();
let rgb_data = decoder.read_image_hdr()?; let rgb_data = decoder.read_image_hdr()?;
let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size());
for rgb in rgb_data { for rgb in rgb_data {
let alpha = 1.0f32; let alpha = 1.0f32;
rgba_data.extend_from_slice(&rgb.0[0].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[1].to_ne_bytes());
rgba_data.extend_from_slice(&rgb.0[2].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(&alpha.to_ne_bytes());
} }
Ok(Image::new( Ok(Image::new(
Extent3d { Extent3d {
width: info.width, width: info.width,
height: info.height, height: info.height,
depth_or_array_layers: 1, depth_or_array_layers: 1,
}, },
TextureDimension::D2, TextureDimension::D2,
rgba_data, rgba_data,
format, format,
settings.asset_usage, settings.asset_usage,
)) ))
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -85,37 +85,35 @@ impl AssetLoader for ImageLoader {
type Asset = Image; type Asset = Image;
type Settings = ImageLoaderSettings; type Settings = ImageLoaderSettings;
type Error = ImageLoaderError; type Error = ImageLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
settings: &'a ImageLoaderSettings, settings: &'a ImageLoaderSettings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Image, Self::Error>> { ) -> Result<Image, Self::Error> {
Box::pin(async move { // use the file extension for the image type
// use the file extension for the image type let ext = load_context.path().extension().unwrap().to_str().unwrap();
let ext = load_context.path().extension().unwrap().to_str().unwrap();
let mut bytes = Vec::new(); let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?; reader.read_to_end(&mut bytes).await?;
let image_type = match settings.format { let image_type = match settings.format {
ImageFormatSetting::FromExtension => ImageType::Extension(ext), ImageFormatSetting::FromExtension => ImageType::Extension(ext),
ImageFormatSetting::Format(format) => ImageType::Format(format), ImageFormatSetting::Format(format) => ImageType::Format(format),
}; };
Ok(Image::from_buffer( Ok(Image::from_buffer(
#[cfg(all(debug_assertions, feature = "dds"))] #[cfg(all(debug_assertions, feature = "dds"))]
load_context.path().display().to_string(), load_context.path().display().to_string(),
&bytes, &bytes,
image_type, image_type,
self.supported_compressed_formats, self.supported_compressed_formats,
settings.is_srgb, settings.is_srgb,
settings.sampler.clone(), settings.sampler.clone(),
settings.asset_usage, settings.asset_usage,
) )
.map_err(|err| FileTextureError { .map_err(|err| FileTextureError {
error: err, error: err,
path: format!("{}", load_context.path().display()), path: format!("{}", load_context.path().display()),
})?) })?)
})
} }
fn extensions(&self) -> &[&str] { 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::reflect::AppTypeRegistry;
use bevy_ecs::world::{FromWorld, World}; use bevy_ecs::world::{FromWorld, World};
use bevy_reflect::TypeRegistryArc; use bevy_reflect::TypeRegistryArc;
use bevy_utils::BoxedFuture;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
use thiserror::Error; use thiserror::Error;
@ -44,23 +43,21 @@ impl AssetLoader for SceneLoader {
type Settings = (); type Settings = ();
type Error = SceneLoaderError; type Error = SceneLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a (), _settings: &'a (),
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?;
let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let scene_deserializer = SceneDeserializer {
let scene_deserializer = SceneDeserializer { type_registry: &self.type_registry.read(),
type_registry: &self.type_registry.read(), };
}; Ok(scene_deserializer
Ok(scene_deserializer .deserialize(&mut deserializer)
.deserialize(&mut deserializer) .map_err(|e| deserializer.span_error(e))?)
.map_err(|e| deserializer.span_error(e))?)
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -21,17 +21,15 @@ impl AssetLoader for FontLoader {
type Asset = Font; type Asset = Font;
type Settings = (); type Settings = ();
type Error = FontLoaderError; type Error = FontLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a (), _settings: &'a (),
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> bevy_utils::BoxedFuture<'a, Result<Font, Self::Error>> { ) -> Result<Font, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; Ok(Font::try_from_bytes(bytes)?)
Ok(Font::try_from_bytes(bytes)?)
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -36,21 +36,36 @@ use hashbrown::hash_map::RawEntryMut;
use std::{ use std::{
any::TypeId, any::TypeId,
fmt::Debug, fmt::Debug,
future::Future,
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
marker::PhantomData, marker::PhantomData,
mem::ManuallyDrop, mem::ManuallyDrop,
ops::Deref, 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"))] #[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")] #[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`]. /// 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>; 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::*, prelude::*,
reflect::TypePath, reflect::TypePath,
utils::BoxedFuture,
}; };
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use std::io::prelude::*; use std::io::prelude::*;
@ -41,44 +40,42 @@ impl AssetLoader for GzAssetLoader {
type Asset = GzAsset; type Asset = GzAsset;
type Settings = (); type Settings = ();
type Error = GzAssetLoaderError; type Error = GzAssetLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a (), _settings: &'a (),
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let compressed_path = load_context.path();
let compressed_path = load_context.path(); let file_name = compressed_path
let file_name = compressed_path .file_name()
.file_name() .ok_or(GzAssetLoaderError::IndeterminateFilePath)?
.ok_or(GzAssetLoaderError::IndeterminateFilePath)? .to_string_lossy();
.to_string_lossy(); let uncompressed_file_name = file_name
let uncompressed_file_name = file_name .strip_suffix(".gz")
.strip_suffix(".gz") .ok_or(GzAssetLoaderError::IndeterminateFilePath)?;
.ok_or(GzAssetLoaderError::IndeterminateFilePath)?; let contained_path = compressed_path.join(uncompressed_file_name);
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 // Now that we have decompressed the asset, let's pass it back to the
// context to continue loading // context to continue loading
let mut reader = VecReader::new(bytes_uncompressed); let mut reader = VecReader::new(bytes_uncompressed);
let uncompressed = load_context let uncompressed = load_context
.load_direct_with_reader(&mut reader, contained_path) .load_direct_with_reader(&mut reader, contained_path)
.await?; .await?;
Ok(GzAsset { uncompressed }) Ok(GzAsset { uncompressed })
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -4,7 +4,6 @@ use bevy::{
asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext}, asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext},
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
utils::BoxedFuture,
}; };
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
@ -34,18 +33,16 @@ impl AssetLoader for CustomAssetLoader {
type Asset = CustomAsset; type Asset = CustomAsset;
type Settings = (); type Settings = ();
type Error = CustomAssetLoaderError; type Error = CustomAssetLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a (), _settings: &'a (),
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?;
let custom_asset = ron::de::from_bytes::<CustomAsset>(&bytes)?; Ok(custom_asset)
Ok(custom_asset)
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
@ -75,19 +72,17 @@ impl AssetLoader for BlobAssetLoader {
type Settings = (); type Settings = ();
type Error = BlobAssetLoaderError; type Error = BlobAssetLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a (), _settings: &'a (),
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> { ) -> Result<Self::Asset, Self::Error> {
Box::pin(async move { info!("Loading Blob...");
info!("Loading Blob..."); let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?;
Ok(Blob { bytes }) 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. //! It does not know anything about the asset formats, only how to talk to the underlying storage.
use bevy::{ use bevy::{
asset::io::{AssetReader, AssetReaderError, AssetSource, AssetSourceId, PathStream, Reader}, asset::io::{
AssetReader, AssetReaderError, AssetSource, AssetSourceId, ErasedAssetReader, PathStream,
Reader,
},
prelude::*, prelude::*,
utils::BoxedFuture,
}; };
use std::path::Path; use std::path::Path;
/// A custom asset reader implementation that wraps a given asset reader implementation /// 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 { impl AssetReader for CustomAssetReader {
fn read<'a>( async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
info!("Reading {:?}", path); info!("Reading {:?}", path);
self.0.read(path) self.0.read(path).await
} }
fn read_meta<'a>( async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
&'a self, self.0.read_meta(path).await
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
self.0.read_meta(path)
} }
fn read_directory<'a>( async fn read_directory<'a>(
&'a self, &'a self,
path: &'a Path, path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> { ) -> Result<Box<PathStream>, AssetReaderError> {
self.0.read_directory(path) self.0.read_directory(path).await
} }
fn is_directory<'a>( async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
&'a self, self.0.is_directory(path).await
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
self.0.is_directory(path)
} }
} }

View file

@ -12,7 +12,6 @@ use bevy::{
}, },
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
utils::BoxedFuture,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::convert::Infallible; use std::convert::Infallible;
@ -83,22 +82,20 @@ impl AssetLoader for TextLoader {
type Asset = Text; type Asset = Text;
type Settings = TextSettings; type Settings = TextSettings;
type Error = std::io::Error; type Error = std::io::Error;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
settings: &'a TextSettings, settings: &'a TextSettings,
_load_context: &'a mut LoadContext, _load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<Text, Self::Error>> { ) -> Result<Text, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let value = if let Some(ref text) = settings.text_override {
let value = if let Some(ref text) = settings.text_override { text.clone()
text.clone() } else {
} else { String::from_utf8(bytes).unwrap()
String::from_utf8(bytes).unwrap() };
}; Ok(Text(value))
Ok(Text(value))
})
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
@ -138,30 +135,28 @@ impl AssetLoader for CoolTextLoader {
type Settings = (); type Settings = ();
type Error = CoolTextLoaderError; type Error = CoolTextLoaderError;
fn load<'a>( async fn load<'a>(
&'a self, &'a self,
reader: &'a mut Reader, reader: &'a mut Reader<'_>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext<'_>,
) -> BoxedFuture<'a, Result<CoolText, Self::Error>> { ) -> Result<CoolText, Self::Error> {
Box::pin(async move { let mut bytes = Vec::new();
let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?;
reader.read_to_end(&mut bytes).await?; let ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let ron: CoolTextRon = ron::de::from_bytes(&bytes)?; let mut base_text = ron.text;
let mut base_text = ron.text; for embedded in ron.embedded_dependencies {
for embedded in ron.embedded_dependencies { let loaded = load_context.load_direct(&embedded).await?;
let loaded = load_context.load_direct(&embedded).await?; let text = loaded.get::<Text>().unwrap();
let text = loaded.get::<Text>().unwrap(); base_text.push_str(&text.0);
base_text.push_str(&text.0); }
} Ok(CoolText {
Ok(CoolText { text: base_text,
text: base_text, dependencies: ron
dependencies: ron .dependencies
.dependencies .iter()
.iter() .map(|p| load_context.load(p))
.map(|p| load_context.load(p)) .collect(),
.collect(),
})
}) })
} }
@ -184,15 +179,13 @@ impl AssetTransformer for CoolTextTransformer {
type Settings = CoolTextTransformerSettings; type Settings = CoolTextTransformerSettings;
type Error = Infallible; type Error = Infallible;
fn transform<'a>( async fn transform<'a>(
&'a self, &'a self,
mut asset: TransformedAsset<Self::AssetInput>, mut asset: TransformedAsset<Self::AssetInput>,
settings: &'a Self::Settings, settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<TransformedAsset<Self::AssetOutput>, Self::Error>> { ) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
Box::pin(async move { asset.text = format!("{}{}", asset.text, settings.appended);
asset.text = format!("{}{}", asset.text, settings.appended); Ok(asset)
Ok(asset)
})
} }
} }
@ -204,16 +197,14 @@ impl AssetSaver for CoolTextSaver {
type OutputLoader = TextLoader; type OutputLoader = TextLoader;
type Error = std::io::Error; type Error = std::io::Error;
fn save<'a>( async fn save<'a>(
&'a self, &'a self,
writer: &'a mut Writer, writer: &'a mut Writer,
asset: SavedAsset<'a, Self::Asset>, asset: SavedAsset<'a, Self::Asset>,
_settings: &'a Self::Settings, _settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<TextSettings, Self::Error>> { ) -> Result<TextSettings, Self::Error> {
Box::pin(async move { writer.write_all(asset.text.as_bytes()).await?;
writer.write_all(asset.text.as_bytes()).await?; Ok(TextSettings::default())
Ok(TextSettings::default())
})
} }
} }