mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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:
parent
30cb95d96e
commit
dd46fd3aee
28 changed files with 226 additions and 123 deletions
|
@ -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"] }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::io::{AssetSourceEvent, AssetWatcher};
|
||||
use anyhow::Result;
|
||||
use bevy_log::error;
|
||||
use bevy_utils::Duration;
|
||||
use crossbeam_channel::Sender;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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!();
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
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");
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,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};
|
||||
|
|
|
@ -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))?)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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)?)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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?;
|
||||
|
|
Loading…
Reference in a new issue