From 9cb30cef38fd62d2b81c0da3544c2c77d5ebbd9d Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Fri, 6 Oct 2023 18:20:13 +1100 Subject: [PATCH] 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` 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`, `thiserror`, `anyhow`, etc.) ## Notes A simpler PR to resolve this issue would simply define a Bevy `Error` type defined as `Box`, 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. --- Cargo.toml | 1 - crates/bevy_asset/Cargo.toml | 1 - crates/bevy_asset/src/io/android.rs | 1 - crates/bevy_asset/src/io/file/file_watcher.rs | 1 - crates/bevy_asset/src/io/file/mod.rs | 1 - crates/bevy_asset/src/io/gated.rs | 1 - crates/bevy_asset/src/io/memory.rs | 1 - crates/bevy_asset/src/io/processor_gated.rs | 1 - crates/bevy_asset/src/io/wasm.rs | 1 - crates/bevy_asset/src/lib.rs | 27 ++++++++++++--- crates/bevy_asset/src/loader.rs | 25 +++++++------- crates/bevy_asset/src/meta.rs | 3 +- crates/bevy_asset/src/processor/mod.rs | 25 +++++++------- crates/bevy_asset/src/processor/process.rs | 4 +-- crates/bevy_asset/src/saver.rs | 11 +++++-- crates/bevy_asset/src/server/mod.rs | 27 +++++---------- crates/bevy_audio/src/audio_source.rs | 4 +-- crates/bevy_gltf/src/loader.rs | 11 ++++--- .../bevy_render/src/render_resource/shader.rs | 14 ++++++-- .../src/texture/compressed_image_saver.rs | 16 ++++++--- .../src/texture/exr_texture_loader.rs | 15 +++++++-- .../src/texture/hdr_texture_loader.rs | 15 +++++++-- .../bevy_render/src/texture/image_loader.rs | 15 +++++++-- .../src/texture/image_texture_conversion.rs | 33 +++++++++++-------- crates/bevy_scene/src/scene_loader.rs | 32 ++++++++++-------- crates/bevy_text/src/font_loader.rs | 21 +++++++++--- examples/asset/custom_asset.rs | 19 +++++++++-- examples/asset/processing/processing.rs | 23 ++++++++++--- 28 files changed, 226 insertions(+), 123 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4c417d9eb70af..a56514cc4b0e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 5f032abaed50c..c2877844f94fa 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -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" diff --git a/crates/bevy_asset/src/io/android.rs b/crates/bevy_asset/src/io/android.rs index 7ee1f5a1c927a..a07043c4dd4bc 100644 --- a/crates/bevy_asset/src/io/android.rs +++ b/crates/bevy_asset/src/io/android.rs @@ -2,7 +2,6 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream, Reader, VecReader, }; -use anyhow::Result; use bevy_log::error; use bevy_utils::BoxedFuture; use std::{ffi::CString, path::Path}; diff --git a/crates/bevy_asset/src/io/file/file_watcher.rs b/crates/bevy_asset/src/io/file/file_watcher.rs index 03351278ea6fe..7f2d622135932 100644 --- a/crates/bevy_asset/src/io/file/file_watcher.rs +++ b/crates/bevy_asset/src/io/file/file_watcher.rs @@ -1,5 +1,4 @@ use crate::io::{AssetSourceEvent, AssetWatcher}; -use anyhow::Result; use bevy_log::error; use bevy_utils::Duration; use crossbeam_channel::Sender; diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index 3e53981a643d2..859db14eda710 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -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; diff --git a/crates/bevy_asset/src/io/gated.rs b/crates/bevy_asset/src/io/gated.rs index cc8bd9ab117ff..f200483759d7c 100644 --- a/crates/bevy_asset/src/io/gated.rs +++ b/crates/bevy_asset/src/io/gated.rs @@ -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; diff --git a/crates/bevy_asset/src/io/memory.rs b/crates/bevy_asset/src/io/memory.rs index 9ca193e08d6ee..043592435192c 100644 --- a/crates/bevy_asset/src/io/memory.rs +++ b/crates/bevy_asset/src/io/memory.rs @@ -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}; diff --git a/crates/bevy_asset/src/io/processor_gated.rs b/crates/bevy_asset/src/io/processor_gated.rs index ca37b47ea8029..1e578771f723e 100644 --- a/crates/bevy_asset/src/io/processor_gated.rs +++ b/crates/bevy_asset/src/io/processor_gated.rs @@ -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; diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index a90a5a0569379..f0acbae067d6c 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -2,7 +2,6 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, AssetWatcher, EmptyPathStream, PathStream, Reader, VecReader, }; -use anyhow::Result; use bevy_log::error; use bevy_utils::BoxedFuture; use js_sys::{Uint8Array, JSON}; diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 952cf60f68b14..31bdc3a2e62ca 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let mut ron: CoolTextRon = ron::de::from_bytes(&bytes)?; let mut embedded = String::new(); for dep in ron.embedded_dependencies { - let loaded = load_context.load_direct(&dep).await?; + let loaded = load_context.load_direct(&dep).await.map_err(|_| { + Self::Error::CannotLoadDependency { + dependency: dep.into(), + } + })?; let cool = loaded.get::().unwrap(); embedded.push_str(&cool.text); } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 6652f7243bd46..04be5ce834508 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -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>; + ) -> BoxedFuture<'a, Result>; /// 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, load_context: LoadContext<'a>, - ) -> BoxedFuture<'a, Result>; + ) -> BoxedFuture< + 'a, + Result>, + >; /// 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 ErasedAssetLoader for L where L: AssetLoader + Send + Sync, @@ -85,7 +79,10 @@ where reader: &'a mut Reader, meta: Box, mut load_context: LoadContext<'a>, - ) -> BoxedFuture<'a, Result> { + ) -> BoxedFuture< + 'a, + Result>, + > { Box::pin(async move { let settings = meta .loader_settings() diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs index c2945e5887e99..e6d65b8ecd535 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, Result> { unreachable!(); } diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 4019f4ae04dd7..07803ee6ba932 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -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::() { - 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()); - } + 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(&dependency, asset_path.to_owned()); } let info = self.get_mut(&asset_path).expect("info should exist"); diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index e63dae81ff8e4..0d0d3f468e2fb 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -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), #[error("Assets without extensions are not supported.")] ExtensionRequired, } @@ -122,7 +122,7 @@ impl> 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) }) } diff --git a/crates/bevy_asset/src/saver.rs b/crates/bevy_asset/src/saver.rs index 16d9007419c28..0b01b7d91e550 100644 --- a/crates/bevy_asset/src/saver.rs +++ b/crates/bevy_asset/src/saver.rs @@ -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<::Settings, anyhow::Error>>; + ) -> BoxedFuture<'a, Result<::Settings, Self::Error>>; } /// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`]. @@ -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>>; /// The type name of the [`AssetSaver`]. fn type_name(&self) -> &'static str; @@ -42,7 +47,7 @@ impl ErasedAssetSaver for S { writer: &'a mut Writer, asset: &'a ErasedLoadedAsset, settings: &'a dyn Settings, - ) -> BoxedFuture<'a, Result<(), anyhow::Error>> { + ) -> BoxedFuture<'a, Result<(), Box>> { Box::pin(async move { let settings = settings .downcast_ref::() diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index b4e8470b13c2d..121633cfade16 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -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. diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 93c4093f462ad..30f9c8cf8da9e 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 322bee5b7254c..842290e2d4fcd 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, Result> { 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 }) } diff --git a/crates/bevy_render/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs index 782a206ac8f67..465fbb193d35a 100644 --- a/crates/bevy_render/src/render_resource/shader.rs +++ b/crates/bevy_render/src/render_resource/shader.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let ext = load_context.path().extension().unwrap().to_str().unwrap(); diff --git a/crates/bevy_render/src/texture/compressed_image_saver.rs b/crates/bevy_render/src/texture/compressed_image_saver.rs index d3e6f88f00721..26ab7d22785ce 100644 --- a/crates/bevy_render/src/texture/compressed_image_saver.rs +++ b/crates/bevy_render/src/texture/compressed_image_saver.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, std::result::Result> { // PERF: this should live inside the future, but CompressorParams and Compressor are not Send / can't be owned by the BoxedFuture (which _is_ Send) let mut compressor_params = basis_universal::CompressorParams::new(); compressor_params.set_basis_format(basis_universal::BasisTextureFormat::UASTC4x4); diff --git a/crates/bevy_render/src/texture/exr_texture_loader.rs b/crates/bevy_render/src/texture/exr_texture_loader.rs index 6f548c49d88e8..e5494e9935d52 100644 --- a/crates/bevy_render/src/texture/exr_texture_loader.rs +++ b/crates/bevy_render/src/texture/exr_texture_loader.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let format = TextureFormat::Rgba32Float; debug_assert_eq!( diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index 339429b319400..e54b38b806606 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, Result> { Box::pin(async move { let format = TextureFormat::Rgba32Float; debug_assert_eq!( diff --git a/crates/bevy_render/src/texture/image_loader.rs b/crates/bevy_render/src/texture/image_loader.rs index d21847ec05082..8f8a283289481 100644 --- a/crates/bevy_render/src/texture/image_loader.rs +++ b/crates/bevy_render/src/texture/image_loader.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, Result> { Box::pin(async move { // use the file extension for the image type let ext = load_context.path().extension().unwrap().to_str().unwrap(); diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index 82a931f6ca308..6fce5e5ecf1a4 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -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 { + pub fn try_into_dynamic(self) -> Result { match self.texture_descriptor.format { TextureFormat::R8Unorm => ImageBuffer::from_raw( self.texture_descriptor.size.width, @@ -198,22 +198,27 @@ 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_else(|| { - anyhow::anyhow!( - "Failed to convert into {:?}.", - self.texture_descriptor.format - ) - }) + .ok_or(IntoDynamicImageError::UnknownConversionError( + 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)] mod test { use image::{GenericImage, Rgba}; diff --git a/crates/bevy_scene/src/scene_loader.rs b/crates/bevy_scene/src/scene_loader.rs index e86c358432a90..88d6ca8027fb1 100644 --- a/crates/bevy_scene/src/scene_loader.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -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> { + _load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result> { 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))?) }) } diff --git a/crates/bevy_text/src/font_loader.rs b/crates/bevy_text/src/font_loader.rs index ec784fb84e40a..a47abbd9619a0 100644 --- a/crates/bevy_text/src/font_loader.rs +++ b/crates/bevy_text/src/font_loader.rs @@ -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> { + ) -> bevy_utils::BoxedFuture<'a, Result> { 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)?) }) } diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index 600f4087f56ae..c0aa0ad9b83b9 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; diff --git a/examples/asset/processing/processing.rs b/examples/asset/processing/processing.rs index 400508c02cfca..b4deedd59036e 100644 --- a/examples/asset/processing/processing.rs +++ b/examples/asset/processing/processing.rs @@ -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> { + ) -> BoxedFuture<'a, Result> { 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> { + ) -> BoxedFuture<'a, Result> { 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> { + ) -> BoxedFuture<'a, Result> { Box::pin(async move { let text = format!("{}{}", asset.text.clone(), settings.appended); writer.write_all(text.as_bytes()).await?;