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 }
[dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0"
ron = "0.8.0"
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_utils = { path = "../bevy_utils", version = "0.12.0-dev" }
anyhow = "1.0"
async-broadcast = "0.5"
async-fs = "1.5"
async-lock = "2.8"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -35,7 +35,6 @@ pub use path::*;
pub use reflect::*;
pub use server::*;
pub use anyhow;
pub use bevy_utils::BoxedFuture;
use crate::{
@ -428,8 +427,9 @@ mod tests {
Reader,
},
loader::{AssetLoader, LoadContext},
Asset, AssetApp, AssetEvent, AssetId, AssetPlugin, AssetProvider, AssetProviders,
AssetServer, Assets, DependencyLoadState, LoadState, RecursiveDependencyLoadState,
Asset, AssetApp, AssetEvent, AssetId, AssetPath, AssetPlugin, AssetProvider,
AssetProviders, AssetServer, Assets, DependencyLoadState, LoadState,
RecursiveDependencyLoadState,
};
use bevy_app::{App, Update};
use bevy_core::TaskPoolPlugin;
@ -444,6 +444,7 @@ mod tests {
use futures_lite::AsyncReadExt;
use serde::{Deserialize, Serialize};
use std::path::Path;
use thiserror::Error;
#[derive(Asset, TypePath, Debug)]
pub struct CoolText {
@ -471,24 +472,40 @@ mod tests {
#[derive(Default)]
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 {
type Asset = CoolText;
type Settings = ();
type Error = CoolTextLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?;
let mut embedded = String::new();
for dep in ron.embedded_dependencies {
let loaded = load_context.load_direct(&dep).await?;
let loaded = load_context.load_direct(&dep).await.map_err(|_| {
Self::Error::CannotLoadDependency {
dependency: dep.into(),
}
})?;
let cool = loaded.get::<CoolText>().unwrap();
embedded.push_str(&cool.text);
}

View file

@ -26,13 +26,15 @@ pub trait AssetLoader: Send + Sync + 'static {
type Asset: crate::Asset;
/// The settings type used by this [`AssetLoader`].
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`].
fn load<'a>(
&'a self,
reader: &'a mut Reader,
settings: &'a Self::Settings,
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.
fn extensions(&self) -> &[&str];
@ -46,7 +48,10 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>,
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.
fn extensions(&self) -> &[&str];
@ -64,17 +69,6 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
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
where
L: AssetLoader + Send + Sync,
@ -85,7 +79,10 @@ where
reader: &'a mut Reader,
meta: Box<dyn AssetMetaDyn>,
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 {
let settings = meta
.loader_settings()

View file

@ -193,12 +193,13 @@ impl VisitAssetDependencies for () {
impl AssetLoader for () {
type Asset = ();
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
_reader: &'a mut crate::io::Reader,
_settings: &'a Self::Settings,
_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!();
}

View file

@ -13,8 +13,8 @@ use crate::{
get_asset_hash, get_full_asset_hash, AssetAction, AssetActionMinimal, AssetHash, AssetMeta,
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
},
AssetLoadError, AssetLoaderError, AssetPath, AssetServer, DeserializeMetaError,
LoadDirectError, MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE,
AssetLoadError, AssetPath, AssetServer, DeserializeMetaError,
MissingAssetLoaderForExtensionError, CANNOT_WATCH_ERROR_MESSAGE,
};
use bevy_ecs::prelude::*;
use bevy_log::{debug, error, trace, warn};
@ -1130,20 +1130,17 @@ impl ProcessorAssetInfos {
Err(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 let ProcessError::AssetLoadError(AssetLoadError::AssetLoaderError {
error: AssetLoaderError::Load(loader_error),
..
if let ProcessError::AssetLoadError(AssetLoadError::CannotLoadDependency {
path: dependency,
}) = err
{
if let Some(error) = loader_error.downcast_ref::<LoadDirectError>() {
let info = self.get_mut(&asset_path).expect("info should exist");
info.processed_info = Some(ProcessedInfo {
hash: AssetHash::default(),
full_hash: AssetHash::default(),
process_dependencies: vec![],
});
self.add_dependant(&error.dependency, asset_path.to_owned());
}
self.add_dependant(&dependency, asset_path.to_owned());
}
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.")]
WrongMetaType,
#[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.")]
ExtensionRequired,
}
@ -122,7 +122,7 @@ impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(ProcessError::AssetSaveError)?;
.map_err(|error| ProcessError::AssetSaveError(Box::new(error)))?;
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
/// 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 {
/// The top level [`Asset`] saved by this [`AssetSaver`].
type Asset: Asset;
/// The settings type used by this [`AssetSaver`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [`AssetLoader`] used to load this [`Asset`]
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
/// `asset` is saved.
@ -18,7 +23,7 @@ pub trait AssetSaver: Send + Sync + 'static {
writer: &'a mut Writer,
asset: SavedAsset<'a, Self::Asset>,
settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<<Self::OutputLoader as AssetLoader>::Settings, 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`].
@ -30,7 +35,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static {
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
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`].
fn type_name(&self) -> &'static str;
@ -42,7 +47,7 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), anyhow::Error>> {
) -> BoxedFuture<'a, Result<(), Box<dyn std::error::Error + Send + Sync + 'static>>> {
Box::pin(async move {
let settings = settings
.downcast_ref::<S::Settings>()

View file

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

View file

@ -1,5 +1,4 @@
use bevy_asset::{
anyhow::Error,
io::{AsyncReadExt, Reader},
Asset, AssetLoader, LoadContext,
};
@ -42,13 +41,14 @@ pub struct AudioLoader;
impl AssetLoader for AudioLoader {
type Asset = AudioSource;
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<AudioSource, Error>> {
) -> BoxedFuture<'a, Result<AudioSource, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;

View file

@ -1,7 +1,6 @@
use crate::{vertex_attributes::convert_attribute, Gltf, GltfExtras, GltfNode};
use bevy_asset::{
anyhow, io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext,
ReadAssetBytesError,
io::Reader, AssetLoadError, AssetLoader, AsyncReadExt, Handle, LoadContext, ReadAssetBytesError,
};
use bevy_core::Name;
use bevy_core_pipeline::prelude::Camera3dBundle;
@ -88,6 +87,9 @@ pub enum GltfError {
/// Failed to generate morph targets.
#[error("failed to generate morph targets: {0}")]
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.
@ -105,16 +107,17 @@ pub struct GltfLoader {
impl AssetLoader for GltfLoader {
type Asset = Gltf;
type Settings = ();
type Error = GltfError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
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 {
let mut bytes = Vec::new();
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 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_utils::{tracing::error, BoxedFuture};
use futures_lite::AsyncReadExt;
@ -238,15 +238,25 @@ impl From<&Source> for naga_oil::compose::ShaderType {
#[derive(Default)]
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 {
type Asset = Shader;
type Settings = ();
type Error = ShaderLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Shader, anyhow::Error>> {
) -> BoxedFuture<'a, Result<Shader, Self::Error>> {
Box::pin(async move {
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 bevy_asset::{
anyhow::Error,
saver::{AssetSaver, SavedAsset},
};
use bevy_asset::saver::{AssetSaver, SavedAsset};
use futures_lite::{AsyncWriteExt, FutureExt};
use thiserror::Error;
pub struct CompressedImageSaver;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum CompressedImageSaverError {
#[error(transparent)]
Io(#[from] std::io::Error),
}
impl AssetSaver for CompressedImageSaver {
type Asset = Image;
type Settings = ();
type OutputLoader = ImageLoader;
type Error = CompressedImageSaverError;
fn save<'a>(
&'a self,
writer: &'a mut bevy_asset::io::Writer,
image: SavedAsset<'a, Self::Asset>,
_settings: &'a Self::Settings,
) -> bevy_utils::BoxedFuture<'a, 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)
let mut compressor_params = basis_universal::CompressorParams::new();
compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4);

View file

@ -1,27 +1,38 @@
use crate::texture::{Image, TextureFormatPixelInfo};
use bevy_asset::{
anyhow::Error,
io::{AsyncReadExt, Reader},
AssetLoader, LoadContext,
};
use bevy_utils::BoxedFuture;
use image::ImageDecoder;
use thiserror::Error;
use wgpu::{Extent3d, TextureDimension, TextureFormat};
/// Loads EXR textures as Texture assets
#[derive(Clone, Default)]
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 {
type Asset = Image;
type Settings = ();
type Error = ExrTextureLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Image, Error>> {
) -> BoxedFuture<'a, Result<Image, Self::Error>> {
Box::pin(async move {
let format = TextureFormat::Rgba32Float;
debug_assert_eq!(

View file

@ -1,20 +1,31 @@
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};
/// Loads HDR textures as Texture assets
#[derive(Clone, Default)]
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 {
type Asset = Image;
type Settings = ();
type Error = HdrTextureLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
_load_context: &'a mut LoadContext,
) -> bevy_utils::BoxedFuture<'a, Result<Image, Error>> {
) -> bevy_utils::BoxedFuture<'a, Result<Image, Self::Error>> {
Box::pin(async move {
let format = TextureFormat::Rgba32Float;
debug_assert_eq!(

View file

@ -1,5 +1,4 @@
use anyhow::Result;
use bevy_asset::{anyhow, io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use bevy_asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext};
use bevy_ecs::prelude::{FromWorld, World};
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 {
type Asset = Image;
type Settings = ImageLoaderSettings;
type Error = ImageLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
settings: &'a ImageLoaderSettings,
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 {
// use the file extension for the image type
let ext = load_context.path().extension().unwrap().to_str().unwrap();

View file

@ -1,6 +1,6 @@
use crate::texture::{Image, TextureFormatPixelInfo};
use bevy_asset::anyhow;
use image::{DynamicImage, ImageBuffer};
use thiserror::Error;
use wgpu::{Extent3d, TextureDimension, TextureFormat};
impl Image {
@ -163,7 +163,7 @@ impl Image {
/// - `TextureFormat::Bgra8UnormSrgb`
///
/// 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 {
TextureFormat::R8Unorm => ImageBuffer::from_raw(
self.texture_descriptor.size.width,
@ -198,20 +198,25 @@ impl Image {
)
.map(DynamicImage::ImageRgba8),
// Throw and error if conversion isn't supported
texture_format => {
return Err(anyhow::anyhow!(
"Conversion into dynamic image not supported for {:?}.",
texture_format
texture_format => return Err(IntoDynamicImageError::UnsupportedFormat(texture_format)),
}
.ok_or(IntoDynamicImageError::UnknownConversionError(
self.texture_descriptor.format,
))
}
}
.ok_or_else(|| {
anyhow::anyhow!(
"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)]

View file

@ -1,13 +1,14 @@
#[cfg(feature = "serialize")]
use crate::serde::SceneDeserializer;
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::world::{FromWorld, World};
use bevy_reflect::TypeRegistryArc;
use bevy_utils::BoxedFuture;
#[cfg(feature = "serialize")]
use serde::de::DeserializeSeed;
use thiserror::Error;
/// [`AssetLoader`] for loading serialized Bevy scene files as [`DynamicScene`].
#[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")]
impl AssetLoader for SceneLoader {
type Asset = DynamicScene;
type Settings = ();
type Error = SceneLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, anyhow::Error>> {
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
@ -42,17 +56,9 @@ impl AssetLoader for SceneLoader {
let scene_deserializer = SceneDeserializer {
type_registry: &self.type_registry.read(),
};
scene_deserializer
Ok(scene_deserializer
.deserialize(&mut deserializer)
.map_err(|e| {
let span_error = deserializer.span_error(e);
anyhow::anyhow!(
"{} at {}:{}",
span_error.code,
load_context.path().to_string_lossy(),
span_error.position,
)
})
.map_err(|e| deserializer.span_error(e))?)
})
}

View file

@ -1,23 +1,36 @@
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)]
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 {
type Asset = Font;
type Settings = ();
type Error = FontLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
_load_context: &'a mut LoadContext,
) -> bevy_utils::BoxedFuture<'a, Result<Font, Error>> {
) -> bevy_utils::BoxedFuture<'a, Result<Font, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let font = Font::try_from_bytes(bytes)?;
Ok(font)
Ok(Font::try_from_bytes(bytes)?)
})
}

View file

@ -1,13 +1,15 @@
//! Implements loader for a custom asset type.
use bevy::utils::thiserror;
use bevy::{
asset::{anyhow::Error, io::Reader, AssetLoader, LoadContext},
asset::{io::Reader, AssetLoader, LoadContext},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
};
use futures_lite::AsyncReadExt;
use serde::Deserialize;
use thiserror::Error;
#[derive(Asset, TypePath, Debug, Deserialize)]
pub struct CustomAsset {
@ -17,15 +19,28 @@ pub struct CustomAsset {
#[derive(Default)]
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 {
type Asset = CustomAsset;
type Settings = ();
type Error = CustomAssetLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a (),
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Self::Asset, Error>> {
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;

View file

@ -9,9 +9,10 @@ use bevy::{
},
prelude::*,
reflect::TypePath,
utils::BoxedFuture,
utils::{thiserror, BoxedFuture},
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
fn main() {
App::new()
@ -75,12 +76,13 @@ struct TextSettings {
impl AssetLoader for TextLoader {
type Asset = Text;
type Settings = TextSettings;
type Error = std::io::Error;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
settings: &'a TextSettings,
_load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<Text, anyhow::Error>> {
) -> BoxedFuture<'a, Result<Text, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
@ -115,17 +117,29 @@ pub struct CoolText {
#[derive(Default)]
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 {
type Asset = CoolText;
type Settings = ();
type Error = CoolTextLoaderError;
fn load<'a>(
&'a self,
reader: &'a mut Reader,
_settings: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<CoolText, anyhow::Error>> {
) -> BoxedFuture<'a, Result<CoolText, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
@ -163,13 +177,14 @@ impl AssetSaver for CoolTextSaver {
type Asset = CoolText;
type Settings = CoolTextSaverSettings;
type OutputLoader = TextLoader;
type Error = std::io::Error;
fn save<'a>(
&'a self,
writer: &'a mut Writer,
asset: SavedAsset<'a, Self::Asset>,
settings: &'a Self::Settings,
) -> BoxedFuture<'a, Result<TextSettings, anyhow::Error>> {
) -> BoxedFuture<'a, Result<TextSettings, Self::Error>> {
Box::pin(async move {
let text = format!("{}{}", asset.text.clone(), settings.appended);
writer.write_all(text.as_bytes()).await?;