Removed anyhow (#10003)

# Objective

- Fixes #8140

## Solution

- Added Explicit Error Typing for `AssetLoader` and `AssetSaver`, which
were the last instances of `anyhow` in use across Bevy.

---

## Changelog

- Added an associated type `Error` to `AssetLoader` and `AssetSaver` for
use with the `load` and `save` methods respectively.
- Changed `ErasedAssetLoader` and `ErasedAssetSaver` `load` and `save`
methods to use `Box<dyn Error + Send + Sync + 'static>` to allow for
arbitrary `Error` types from the non-erased trait variants. Note the
strict requirements match the pre-existing requirements around
`anyhow::Error`.

## Migration Guide

- `anyhow` is no longer exported by `bevy_asset`; Add it to your own
project (if required).
- `AssetLoader` and `AssetSaver` have an associated type `Error`; Define
an appropriate error type (e.g., using `thiserror`), or use a pre-made
error type (e.g., `anyhow::Error`). Note that using `anyhow::Error` is a
drop-in replacement.
- `AssetLoaderError` has been removed; Define a new error type, or use
an alternative (e.g., `anyhow::Error`)
- All the first-party `AssetLoader`'s and `AssetSaver`'s now return
relevant (and narrow) error types instead of a single ambiguous type;
Match over the specific error type, or encapsulate (`Box<dyn>`,
`thiserror`, `anyhow`, etc.)

## Notes

A simpler PR to resolve this issue would simply define a Bevy `Error`
type defined as `Box<dyn std::error::Error + Send + Sync + 'static>`,
but I think this type of error handling should be discouraged when
possible. Since only 2 traits required the use of `anyhow`, it isn't a
substantive body of work to solidify these error types, and remove
`anyhow` entirely. End users are still encouraged to use `anyhow` if
that is their preferred error handling style. Arguably, adding the
`Error` associated type gives more freedom to end-users to decide
whether they want more or less explicit error handling (`anyhow` vs
`thiserror`).

As an aside, I didn't perform any testing on Android or WASM. CI passed
locally, but there may be mistakes for those platforms I missed.
This commit is contained in:
Zachary Harrold 2023-10-06 18:20:13 +11:00 committed by GitHub
parent 30cb95d96e
commit dd46fd3aee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 226 additions and 123 deletions

View File

@ -252,7 +252,6 @@ bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0-dev", default-featu
bevy_internal = { path = "crates/bevy_internal", version = "0.12.0-dev", default-features = false } bevy_internal = { path = "crates/bevy_internal", version = "0.12.0-dev", default-features = false }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0" rand = "0.8.0"
ron = "0.8.0" ron = "0.8.0"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@ -23,7 +23,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.12.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.12.0-dev" }
anyhow = "1.0"
async-broadcast = "0.5" async-broadcast = "0.5"
async-fs = "1.5" async-fs = "1.5"
async-lock = "2.8" async-lock = "2.8"

View File

@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream, get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream,
Reader, VecReader, Reader, VecReader,
}; };
use anyhow::Result;
use bevy_log::error; use bevy_log::error;
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;
use std::{ffi::CString, path::Path}; use std::{ffi::CString, path::Path};

View File

@ -1,5 +1,4 @@
use crate::io::{AssetSourceEvent, AssetWatcher}; use crate::io::{AssetSourceEvent, AssetWatcher};
use anyhow::Result;
use bevy_log::error; use bevy_log::error;
use bevy_utils::Duration; use bevy_utils::Duration;
use crossbeam_channel::Sender; use crossbeam_channel::Sender;

View File

@ -5,7 +5,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, AssetWriter, AssetWriterError, get_meta_path, AssetReader, AssetReaderError, AssetWatcher, AssetWriter, AssetWriterError,
PathStream, Reader, Writer, PathStream, Reader, Writer,
}; };
use anyhow::Result;
use async_fs::{read_dir, File}; use async_fs::{read_dir, File};
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;
use futures_lite::StreamExt; use futures_lite::StreamExt;

View File

@ -1,5 +1,4 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use anyhow::Result;
use bevy_utils::{BoxedFuture, HashMap}; use bevy_utils::{BoxedFuture, HashMap};
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use parking_lot::RwLock; use parking_lot::RwLock;

View File

@ -1,5 +1,4 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader}; use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use anyhow::Result;
use bevy_utils::{BoxedFuture, HashMap}; use bevy_utils::{BoxedFuture, HashMap};
use futures_io::AsyncRead; use futures_io::AsyncRead;
use futures_lite::{ready, Stream}; use futures_lite::{ready, Stream};

View File

@ -3,7 +3,6 @@ use crate::{
processor::{AssetProcessorData, ProcessStatus}, processor::{AssetProcessorData, ProcessStatus},
AssetPath, AssetPath,
}; };
use anyhow::Result;
use async_lock::RwLockReadGuardArc; use async_lock::RwLockReadGuardArc;
use bevy_log::trace; use bevy_log::trace;
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;

View File

@ -2,7 +2,6 @@ use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream, get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream,
Reader, VecReader, Reader, VecReader,
}; };
use anyhow::Result;
use bevy_log::error; use bevy_log::error;
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;
use js_sys::{Uint8Array, JSON}; use js_sys::{Uint8Array, JSON};

View File

@ -35,7 +35,6 @@ pub use path::*;
pub use reflect::*; pub use reflect::*;
pub use server::*; pub use server::*;
pub use anyhow;
pub use bevy_utils::BoxedFuture; pub use bevy_utils::BoxedFuture;
use crate::{ use crate::{
@ -428,8 +427,9 @@ mod tests {
Reader, Reader,
}, },
loader::{AssetLoader, LoadContext}, loader::{AssetLoader, LoadContext},
Asset, AssetApp, AssetEvent, AssetId, AssetPlugin, AssetProvider, AssetProviders, Asset, AssetApp, AssetEvent, AssetId, AssetPath, AssetPlugin, AssetProvider,
AssetServer, Assets, DependencyLoadState, LoadState, RecursiveDependencyLoadState, AssetProviders, AssetServer, Assets, DependencyLoadState, LoadState,
RecursiveDependencyLoadState,
}; };
use bevy_app::{App, Update}; use bevy_app::{App, Update};
use bevy_core::TaskPoolPlugin; use bevy_core::TaskPoolPlugin;
@ -444,6 +444,7 @@ mod tests {
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path; use std::path::Path;
use thiserror::Error;
#[derive(Asset, TypePath, Debug)] #[derive(Asset, TypePath, Debug)]
pub struct CoolText { pub struct CoolText {
@ -471,24 +472,40 @@ mod tests {
#[derive(Default)] #[derive(Default)]
struct CoolTextLoader; struct CoolTextLoader;
#[derive(Error, Debug)]
enum CoolTextLoaderError {
#[error("Could not load dependency: {dependency}")]
CannotLoadDependency { dependency: AssetPath<'static> },
#[error("A RON error occurred during loading")]
RonSpannedError(#[from] ron::error::SpannedError),
#[error("An IO error occurred during loading")]
Io(#[from] std::io::Error),
}
impl AssetLoader for CoolTextLoader { impl AssetLoader for CoolTextLoader {
type Asset = CoolText; type Asset = CoolText;
type Settings = (); type Settings = ();
type Error = CoolTextLoaderError;
fn load<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move { 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?; let loaded = load_context.load_direct(&dep).await.map_err(|_| {
Self::Error::CannotLoadDependency {
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);
} }

View File

@ -26,13 +26,15 @@ pub trait AssetLoader: Send + Sync + 'static {
type Asset: crate::Asset; type Asset: crate::Asset;
/// The settings type used by this [`AssetLoader`]. /// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: std::error::Error + Send + Sync + 'static;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`]. /// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load<'a>( 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, anyhow::Error>>; ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot. /// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
@ -46,7 +48,10 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
reader: &'a mut Reader, reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>, meta: Box<dyn AssetMetaDyn>,
load_context: LoadContext<'a>, load_context: LoadContext<'a>,
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, AssetLoaderError>>; ) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot. /// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
@ -64,17 +69,6 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
fn asset_type_id(&self) -> TypeId; fn asset_type_id(&self) -> TypeId;
} }
/// An error encountered during [`AssetLoader::load`].
#[derive(Error, Debug)]
pub enum AssetLoaderError {
/// Any error that occurs during load.
#[error(transparent)]
Load(#[from] anyhow::Error),
/// A failure to deserialize metadata during load.
#[error(transparent)]
DeserializeMeta(#[from] DeserializeMetaError),
}
impl<L> ErasedAssetLoader for L impl<L> ErasedAssetLoader for L
where where
L: AssetLoader + Send + Sync, L: AssetLoader + Send + Sync,
@ -85,7 +79,10 @@ where
reader: &'a mut Reader, reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>, meta: Box<dyn AssetMetaDyn>,
mut load_context: LoadContext<'a>, mut load_context: LoadContext<'a>,
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, AssetLoaderError>> { ) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn std::error::Error + Send + Sync + 'static>>,
> {
Box::pin(async move { Box::pin(async move {
let settings = meta let settings = meta
.loader_settings() .loader_settings()

View File

@ -193,12 +193,13 @@ impl VisitAssetDependencies for () {
impl AssetLoader for () { impl AssetLoader for () {
type Asset = (); type Asset = ();
type Settings = (); type Settings = ();
type Error = std::io::Error;
fn load<'a>( 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, anyhow::Error>> { ) -> bevy_utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
unreachable!(); unreachable!();
} }

View File

@ -13,8 +13,8 @@ use crate::{
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta, get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal, AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
}, },
AssetLoadError, AssetLoaderError, AssetPath, AssetServer, DeserializeMetaError, AssetLoadError, AssetPath, AssetServer, DeserializeMetaError,
LoadDirectError, MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE, MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE,
}; };
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_log::{debug, error, trace, warn}; use bevy_log::{debug, error, trace, warn};
@ -1130,20 +1130,17 @@ impl ProcessorAssetInfos {
Err(err) => { Err(err) => {
error!("Failed to process asset {:?}: {:?}", asset_path, err); error!("Failed to process asset {:?}: {:?}", asset_path, err);
// if this failed because a dependency could not be loaded, make sure it is reprocessed if that dependency is reprocessed // if this failed because a dependency could not be loaded, make sure it is reprocessed if that dependency is reprocessed
if let ProcessError::AssetLoadError(AssetLoadError::AssetLoaderError { if let ProcessError::AssetLoadError(AssetLoadError::CannotLoadDependency {
error: AssetLoaderError::Load(loader_error), path: dependency,
..
}) = err }) = err
{ {
if let Some(error) = loader_error.downcast_ref::<LoadDirectError>() { let info = self.get_mut(&asset_path).expect("info should exist");
let info = self.get_mut(&asset_path).expect("info should exist"); info.processed_info = Some(ProcessedInfo {
info.processed_info = Some(ProcessedInfo { hash: AssetHash::default(),
hash: AssetHash::default(), full_hash: AssetHash::default(),
full_hash: AssetHash::default(), process_dependencies: vec![],
process_dependencies: vec![], });
}); self.add_dependant(&dependency, asset_path.to_owned());
self.add_dependant(&error.dependency, asset_path.to_owned());
}
} }
let info = self.get_mut(&asset_path).expect("info should exist"); let info = self.get_mut(&asset_path).expect("info should exist");

View File

@ -91,7 +91,7 @@ pub enum ProcessError {
#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")] #[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
WrongMetaType, WrongMetaType,
#[error("Encountered an error while saving the asset: {0}")] #[error("Encountered an error while saving the asset: {0}")]
AssetSaveError(anyhow::Error), AssetSaveError(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Assets without extensions are not supported.")] #[error("Assets without extensions are not supported.")]
ExtensionRequired, ExtensionRequired,
} }
@ -122,7 +122,7 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
.saver .saver
.save(writer, saved_asset, &settings.saver_settings) .save(writer, saved_asset, &settings.saver_settings)
.await .await
.map_err(ProcessError::AssetSaveError)?; .map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?;
Ok(output_settings) Ok(output_settings)
}) })
} }

View File

@ -7,9 +7,14 @@ use std::ops::Deref;
/// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset /// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset
/// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read. /// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read.
pub trait AssetSaver: Send + Sync + 'static { pub trait AssetSaver: Send + Sync + 'static {
/// The top level [`Asset`] saved by this [`AssetSaver`].
type Asset: Asset; type Asset: Asset;
/// The settings type used by this [`AssetSaver`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>; type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [`AssetLoader`] used to load this [`Asset`]
type OutputLoader: AssetLoader; type OutputLoader: AssetLoader;
/// The type of [error](`std::error::Error`) which could be encountered by this saver.
type Error: std::error::Error + Send + Sync + 'static;
/// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the /// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
/// `asset` is saved. /// `asset` is saved.
@ -18,7 +23,7 @@ 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, anyhow::Error>>; ) -> BoxedFuture<'a, 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`].
@ -30,7 +35,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static {
writer: &'a mut Writer, writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset, asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings, settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), anyhow::Error>>; ) -> BoxedFuture<'a, Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>>;
/// The type name of the [`AssetSaver`]. /// The type name of the [`AssetSaver`].
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
@ -42,7 +47,7 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
writer: &'a mut Writer, writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset, asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings, settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), anyhow::Error>> { ) -> BoxedFuture<'a, Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>> {
Box::pin(async move { Box::pin(async move {
let settings = settings let settings = settings
.downcast_ref::<S::Settings>() .downcast_ref::<S::Settings>()

View File

@ -3,7 +3,7 @@ mod info;
use crate::{ use crate::{
folder::LoadedFolder, folder::LoadedFolder,
io::{AssetReader, AssetReaderError, AssetSourceEvent, AssetWatcher, Reader}, io::{AssetReader, AssetReaderError, AssetSourceEvent, AssetWatcher, Reader},
loader::{AssetLoader, AssetLoaderError, ErasedAssetLoader, LoadContext, LoadedAsset}, loader::{AssetLoader, ErasedAssetLoader, LoadContext, LoadedAsset},
meta::{ meta::{
loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal, loader_settings_meta_transform, AssetActionMinimal, AssetMetaDyn, AssetMetaMinimal,
MetaTransform, Settings, MetaTransform, Settings,
@ -666,11 +666,9 @@ impl AssetServer {
} }
}; };
let loader = self.get_asset_loader_with_type_name(&loader_name).await?; let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| { let meta = loader.deserialize_meta(&meta_bytes).map_err(|_| {
AssetLoadError::AssetLoaderError { AssetLoadError::CannotLoadDependency {
path: asset_path.clone().into_owned(), path: asset_path.clone().into_owned(),
loader: loader.type_name(),
error: AssetLoaderError::DeserializeMeta(e),
} }
})?; })?;
@ -698,13 +696,10 @@ impl AssetServer {
let asset_path = asset_path.clone().into_owned(); let asset_path = asset_path.clone().into_owned();
let load_context = let load_context =
LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes); LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
loader.load(reader, meta, load_context).await.map_err(|e| { loader
AssetLoadError::AssetLoaderError { .load(reader, meta, load_context)
loader: loader.type_name(), .await
path: asset_path, .map_err(|_| AssetLoadError::CannotLoadDependency { path: asset_path })
error: e,
}
})
} }
} }
@ -861,12 +856,8 @@ pub enum AssetLoadError {
CannotLoadProcessedAsset { path: AssetPath<'static> }, CannotLoadProcessedAsset { path: AssetPath<'static> },
#[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")] #[error("Asset '{path}' is configured to be ignored. It cannot be loaded.")]
CannotLoadIgnoredAsset { path: AssetPath<'static> }, CannotLoadIgnoredAsset { path: AssetPath<'static> },
#[error("Asset '{path}' encountered an error in {loader}: {error}")] #[error("Asset '{path}' is a dependency. It cannot be loaded directly.")]
AssetLoaderError { CannotLoadDependency { path: AssetPath<'static> },
path: AssetPath<'static>,
loader: &'static str,
error: AssetLoaderError,
},
} }
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension. /// An error that occurs when an [`AssetLoader`] is not registered for a given extension.

View File

@ -1,5 +1,4 @@
use bevy_asset::{ use bevy_asset::{
anyhow::Error,
io::{AsyncReadExt, Reader}, io::{AsyncReadExt, Reader},
Asset, AssetLoader, LoadContext, Asset, AssetLoader, LoadContext,
}; };
@ -42,13 +41,14 @@ pub struct AudioLoader;
impl AssetLoader for AudioLoader { impl AssetLoader for AudioLoader {
type Asset = AudioSource; type Asset = AudioSource;
type Settings = (); type Settings = ();
type Error = std::io::Error;
fn load<'a>( 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, Error>> { ) -> BoxedFuture<'a, Result<AudioSource, Self::Error>> {
Box::pin(async move { 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?;

View File

@ -1,7 +1,6 @@
use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode}; use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode};
use bevy_asset::{ use bevy_asset::{
anyhow, io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext, io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext, ReadAssetBytesError,
ReadAssetBytesError,
}; };
use bevy_core::Name; use bevy_core::Name;
use bevy_core_pipeline::prelude::Camera3dBundle; use bevy_core_pipeline::prelude::Camera3dBundle;
@ -88,6 +87,9 @@ pub enum GltfError {
/// Failed to generate morph targets. /// Failed to generate morph targets.
#[error("failed to generate morph targets: {0}")] #[error("failed to generate morph targets: {0}")]
MorphTarget(#[from] bevy_render::mesh::morph::MorphBuildError), MorphTarget(#[from] bevy_render::mesh::morph::MorphBuildError),
/// Failed to load a file.
#[error("failed to load file: {0}")]
Io(#[from] std::io::Error),
} }
/// Loads glTF files with all of their data as their corresponding bevy representations. /// Loads glTF files with all of their data as their corresponding bevy representations.
@ -105,16 +107,17 @@ pub struct GltfLoader {
impl AssetLoader for GltfLoader { impl AssetLoader for GltfLoader {
type Asset = Gltf; type Asset = Gltf;
type Settings = (); type Settings = ();
type Error = GltfError;
fn load<'a>( 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<Gltf, anyhow::Error>> { ) -> bevy_utils::BoxedFuture<'a, Result<Gltf, Self::Error>> {
Box::pin(async move { 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(load_gltf(self, &bytes, load_context).await?) load_gltf(self, &bytes, load_context).await
}) })
} }

View File

@ -1,6 +1,6 @@
use super::ShaderDefVal; use super::ShaderDefVal;
use crate::define_atomic_id; use crate::define_atomic_id;
use bevy_asset::{anyhow, 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, BoxedFuture};
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
@ -238,15 +238,25 @@ impl From<&Source> for naga_oil::compose::ShaderType {
#[derive(Default)] #[derive(Default)]
pub struct ShaderLoader; pub struct ShaderLoader;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ShaderLoaderError {
#[error("Could not load shader: {0}")]
Io(#[from] std::io::Error),
#[error("Could not parse shader: {0}")]
Parse(#[from] std::string::FromUtf8Error),
}
impl AssetLoader for ShaderLoader { impl AssetLoader for ShaderLoader {
type Asset = Shader; type Asset = Shader;
type Settings = (); type Settings = ();
type Error = ShaderLoaderError;
fn load<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<Shader, Self::Error>> {
Box::pin(async move { Box::pin(async move {
let ext = load_context.path().extension().unwrap().to_str().unwrap(); let ext = load_context.path().extension().unwrap().to_str().unwrap();

View File

@ -1,24 +1,30 @@
use crate::texture::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings}; use crate::texture::{Image, ImageFormat, ImageFormatSetting, ImageLoader, ImageLoaderSettings};
use bevy_asset::{ use bevy_asset::saver::{AssetSaver, SavedAsset};
anyhow::Error,
saver::{AssetSaver, SavedAsset},
};
use futures_lite::{AsyncWriteExt, FutureExt}; use futures_lite::{AsyncWriteExt, FutureExt};
use thiserror::Error;
pub struct CompressedImageSaver; pub struct CompressedImageSaver;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CompressedImageSaverError {
#[error(transparent)]
Io(#[from] std::io::Error),
}
impl AssetSaver for CompressedImageSaver { impl AssetSaver for CompressedImageSaver {
type Asset = Image; type Asset = Image;
type Settings = (); type Settings = ();
type OutputLoader = ImageLoader; type OutputLoader = ImageLoader;
type Error = CompressedImageSaverError;
fn save<'a>( 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, std::result::Result<ImageLoaderSettings, Error>> { ) -> bevy_utils::BoxedFuture<'a, std::result::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) // 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(); let mut compressor_params = basis_universal::CompressorParams::new();
compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4); compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4);

View File

@ -1,27 +1,38 @@
use crate::texture::{Image, TextureFormatPixelInfo}; use crate::texture::{Image, TextureFormatPixelInfo};
use bevy_asset::{ use bevy_asset::{
anyhow::Error,
io::{AsyncReadExt, Reader}, io::{AsyncReadExt, Reader},
AssetLoader, LoadContext, AssetLoader, LoadContext,
}; };
use bevy_utils::BoxedFuture; use bevy_utils::BoxedFuture;
use image::ImageDecoder; use image::ImageDecoder;
use thiserror::Error;
use wgpu::{Extent3d, TextureDimension, TextureFormat}; use wgpu::{Extent3d, TextureDimension, TextureFormat};
/// Loads EXR textures as Texture assets /// Loads EXR textures as Texture assets
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct ExrTextureLoader; pub struct ExrTextureLoader;
/// Possible errors that can be produced by [`ExrTextureLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ExrTextureLoaderError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
ImageError(#[from] image::ImageError),
}
impl AssetLoader for ExrTextureLoader { impl AssetLoader for ExrTextureLoader {
type Asset = Image; type Asset = Image;
type Settings = (); type Settings = ();
type Error = ExrTextureLoaderError;
fn load<'a>( 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, Error>> { ) -> BoxedFuture<'a, Result<Image, Self::Error>> {
Box::pin(async move { Box::pin(async move {
let format = TextureFormat::Rgba32Float; let format = TextureFormat::Rgba32Float;
debug_assert_eq!( debug_assert_eq!(

View File

@ -1,20 +1,31 @@
use crate::texture::{Image, TextureFormatPixelInfo}; use crate::texture::{Image, TextureFormatPixelInfo};
use bevy_asset::{anyhow::Error, io::Reader, AssetLoader, AsyncReadExt, LoadContext}; use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use thiserror::Error;
use wgpu::{Extent3d, TextureDimension, TextureFormat}; use wgpu::{Extent3d, TextureDimension, TextureFormat};
/// Loads HDR textures as Texture assets /// Loads HDR textures as Texture assets
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct HdrTextureLoader; pub struct HdrTextureLoader;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum HdrTextureLoaderError {
#[error("Could load texture: {0}")]
Io(#[from] std::io::Error),
#[error("Could not extract image: {0}")]
Image(#[from] image::ImageError),
}
impl AssetLoader for HdrTextureLoader { impl AssetLoader for HdrTextureLoader {
type Asset = Image; type Asset = Image;
type Settings = (); type Settings = ();
type Error = HdrTextureLoaderError;
fn load<'a>( 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<Image, Error>> { ) -> bevy_utils::BoxedFuture<'a, Result<Image, Self::Error>> {
Box::pin(async move { Box::pin(async move {
let format = TextureFormat::Rgba32Float; let format = TextureFormat::Rgba32Float;
debug_assert_eq!( debug_assert_eq!(

View File

@ -1,5 +1,4 @@
use anyhow::Result; use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use bevy_asset::{anyhow, io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use bevy_ecs::prelude::{FromWorld, World}; use bevy_ecs::prelude::{FromWorld, World};
use thiserror::Error; use thiserror::Error;
@ -68,15 +67,25 @@ impl Default for ImageLoaderSettings {
} }
} }
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ImageLoaderError {
#[error("Could load shader: {0}")]
Io(#[from] std::io::Error),
#[error("Could not load texture file: {0}")]
FileTexture(#[from] FileTextureError),
}
impl AssetLoader for ImageLoader { impl AssetLoader for ImageLoader {
type Asset = Image; type Asset = Image;
type Settings = ImageLoaderSettings; type Settings = ImageLoaderSettings;
type Error = ImageLoaderError;
fn load<'a>( 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, anyhow::Error>> { ) -> bevy_utils::BoxedFuture<'a, Result<Image, Self::Error>> {
Box::pin(async move { 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();

View File

@ -1,6 +1,6 @@
use crate::texture::{Image, TextureFormatPixelInfo}; use crate::texture::{Image, TextureFormatPixelInfo};
use bevy_asset::anyhow;
use image::{DynamicImage, ImageBuffer}; use image::{DynamicImage, ImageBuffer};
use thiserror::Error;
use wgpu::{Extent3d, TextureDimension, TextureFormat}; use wgpu::{Extent3d, TextureDimension, TextureFormat};
impl Image { impl Image {
@ -163,7 +163,7 @@ impl Image {
/// - `TextureFormat::Bgra8UnormSrgb` /// - `TextureFormat::Bgra8UnormSrgb`
/// ///
/// To convert [`Image`] to a different format see: [`Image::convert`]. /// To convert [`Image`] to a different format see: [`Image::convert`].
pub fn try_into_dynamic(self) -> Result<DynamicImage, anyhow::Error> { pub fn try_into_dynamic(self) -> Result<DynamicImage, IntoDynamicImageError> {
match self.texture_descriptor.format { match self.texture_descriptor.format {
TextureFormat::R8Unorm => ImageBuffer::from_raw( TextureFormat::R8Unorm => ImageBuffer::from_raw(
self.texture_descriptor.size.width, self.texture_descriptor.size.width,
@ -198,22 +198,27 @@ impl Image {
) )
.map(DynamicImage::ImageRgba8), .map(DynamicImage::ImageRgba8),
// Throw and error if conversion isn't supported // Throw and error if conversion isn't supported
texture_format => { texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
return Err(anyhow::anyhow!(
"Conversion into dynamic image not supported for {:?}.",
texture_format
))
}
} }
.ok_or_else(|| { .ok_or(IntoDynamicImageError::UnknownConversionError(
anyhow::anyhow!( self.texture_descriptor.format,
"Failed to convert into {:?}.", ))
self.texture_descriptor.format
)
})
} }
} }
/// Errors that occur while converting an [`Image`] into a [`DynamicImage`]
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum IntoDynamicImageError {
/// Conversion into dynamic image not supported for source format.
#[error("Conversion into dynamic image not supported for {0:?}.")]
UnsupportedFormat(TextureFormat),
/// Encountered an unknown error during conversion.
#[error("Failed to convert into {0:?}.")]
UnknownConversionError(TextureFormat),
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use image::{GenericImage, Rgba}; use image::{GenericImage, Rgba};

View File

@ -1,13 +1,14 @@
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use crate::serde::SceneDeserializer; use crate::serde::SceneDeserializer;
use crate::DynamicScene; use crate::DynamicScene;
use bevy_asset::{anyhow, io::Reader, AssetLoader, AsyncReadExt, LoadContext}; 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; use bevy_utils::BoxedFuture;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use serde::de::DeserializeSeed; use serde::de::DeserializeSeed;
use thiserror::Error;
/// [`AssetLoader`] for loading serialized Bevy scene files as [`DynamicScene`]. /// [`AssetLoader`] for loading serialized Bevy scene files as [`DynamicScene`].
#[derive(Debug)] #[derive(Debug)]
@ -24,17 +25,30 @@ impl FromWorld for SceneLoader {
} }
} }
/// Possible errors that can be produced by [`SceneLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SceneLoaderError {
/// An [IO](std::io) Error
#[error("Could load shader: {0}")]
Io(#[from] std::io::Error),
/// A [RON](ron) Error
#[error("Could not parse RON: {0}")]
RonSpannedError(#[from] ron::error::SpannedError),
}
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
impl AssetLoader for SceneLoader { impl AssetLoader for SceneLoader {
type Asset = DynamicScene; type Asset = DynamicScene;
type Settings = (); type Settings = ();
type Error = SceneLoaderError;
fn load<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move { 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?;
@ -42,17 +56,9 @@ impl AssetLoader for SceneLoader {
let scene_deserializer = SceneDeserializer { let scene_deserializer = SceneDeserializer {
type_registry: &self.type_registry.read(), type_registry: &self.type_registry.read(),
}; };
scene_deserializer Ok(scene_deserializer
.deserialize(&mut deserializer) .deserialize(&mut deserializer)
.map_err(|e| { .map_err(|e| deserializer.span_error(e))?)
let span_error = deserializer.span_error(e);
anyhow::anyhow!(
"{} at {}:{}",
span_error.code,
load_context.path().to_string_lossy(),
span_error.position,
)
})
}) })
} }

View File

@ -1,23 +1,36 @@
use crate::Font; use crate::Font;
use bevy_asset::{anyhow::Error, io::Reader, AssetLoader, AsyncReadExt, LoadContext}; use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use thiserror::Error;
#[derive(Default)] #[derive(Default)]
pub struct FontLoader; pub struct FontLoader;
/// Possible errors that can be produced by [`FontLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum FontLoaderError {
/// An [IO](std::io) Error
#[error(transparent)]
Io(#[from] std::io::Error),
/// An [InvalidFont](ab_glyph::InvalidFont) Error
#[error(transparent)]
FontInvalid(#[from] ab_glyph::InvalidFont),
}
impl AssetLoader for FontLoader { impl AssetLoader for FontLoader {
type Asset = Font; type Asset = Font;
type Settings = (); type Settings = ();
type Error = FontLoaderError;
fn load<'a>( 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, Error>> { ) -> bevy_utils::BoxedFuture<'a, Result<Font, Self::Error>> {
Box::pin(async move { 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 font = Font::try_from_bytes(bytes)?; Ok(Font::try_from_bytes(bytes)?)
Ok(font)
}) })
} }

View File

@ -1,13 +1,15 @@
//! Implements loader for a custom asset type. //! Implements loader for a custom asset type.
use bevy::utils::thiserror;
use bevy::{ use bevy::{
asset::{anyhow::Error, io::Reader, AssetLoader, LoadContext}, asset::{io::Reader, AssetLoader, LoadContext},
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
utils::BoxedFuture, utils::BoxedFuture,
}; };
use futures_lite::AsyncReadExt; use futures_lite::AsyncReadExt;
use serde::Deserialize; use serde::Deserialize;
use thiserror::Error;
#[derive(Asset, TypePath, Debug, Deserialize)] #[derive(Asset, TypePath, Debug, Deserialize)]
pub struct CustomAsset { pub struct CustomAsset {
@ -17,15 +19,28 @@ pub struct CustomAsset {
#[derive(Default)] #[derive(Default)]
pub struct CustomAssetLoader; pub struct CustomAssetLoader;
/// Possible errors that can be produced by [`CustomAssetLoader`]
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CustomAssetLoaderError {
/// An [IO](std::io) Error
#[error("Could load shader: {0}")]
Io(#[from] std::io::Error),
/// A [RON](ron) Error
#[error("Could not parse RON: {0}")]
RonSpannedError(#[from] ron::error::SpannedError),
}
impl AssetLoader for CustomAssetLoader { impl AssetLoader for CustomAssetLoader {
type Asset = CustomAsset; type Asset = CustomAsset;
type Settings = (); type Settings = ();
type Error = CustomAssetLoaderError;
fn load<'a>( 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, Error>> { ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move { 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?;

View File

@ -9,9 +9,10 @@ use bevy::{
}, },
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
utils::BoxedFuture, utils::{thiserror, BoxedFuture},
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error;
fn main() { fn main() {
App::new() App::new()
@ -75,12 +76,13 @@ struct TextSettings {
impl AssetLoader for TextLoader { impl AssetLoader for TextLoader {
type Asset = Text; type Asset = Text;
type Settings = TextSettings; type Settings = TextSettings;
type Error = std::io::Error;
fn load<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<Text, Self::Error>> {
Box::pin(async move { 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?;
@ -115,17 +117,29 @@ pub struct CoolText {
#[derive(Default)] #[derive(Default)]
struct CoolTextLoader; struct CoolTextLoader;
#[derive(Debug, Error)]
enum CoolTextLoaderError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
RonSpannedError(#[from] ron::error::SpannedError),
#[error(transparent)]
LoadDirectError(#[from] bevy::asset::LoadDirectError),
}
impl AssetLoader for CoolTextLoader { impl AssetLoader for CoolTextLoader {
type Asset = CoolText; type Asset = CoolText;
type Settings = (); type Settings = ();
type Error = CoolTextLoaderError;
fn load<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<CoolText, Self::Error>> {
Box::pin(async move { 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?;
@ -163,13 +177,14 @@ impl AssetSaver for CoolTextSaver {
type Asset = CoolText; type Asset = CoolText;
type Settings = CoolTextSaverSettings; type Settings = CoolTextSaverSettings;
type OutputLoader = TextLoader; type OutputLoader = TextLoader;
type Error = std::io::Error;
fn save<'a>( 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, anyhow::Error>> { ) -> BoxedFuture<'a, Result<TextSettings, Self::Error>> {
Box::pin(async move { Box::pin(async move {
let text = format!("{}{}", asset.text.clone(), settings.appended); let text = format!("{}{}", asset.text.clone(), settings.appended);
writer.write_all(text.as_bytes()).await?; writer.write_all(text.as_bytes()).await?;