Error info has been added to LoadState::Failed (#12709)

# Objective

Fixes #12667.

## Solution

- Stored
[AssetLoadError](https://docs.rs/bevy/latest/bevy/asset/enum.AssetLoadError.html)
inside of
[LoadState::Failed](https://docs.rs/bevy/latest/bevy/asset/enum.LoadState.html)
as a Box<AssetLoadError> to avoid bloating the size of all variants of
LoadState.
- Fixed dependent code

## Migration guide

Added
[AssetLoadError](https://docs.rs/bevy/latest/bevy/asset/enum.AssetLoadError.html)
to
[LoadState::Failed](https://docs.rs/bevy/latest/bevy/asset/enum.LoadState.html)
option
Removed `Copy`, `Ord` and `PartialOrd` implementations for
[LoadState](https://docs.rs/bevy/latest/bevy/asset/enum.LoadState.html)
enum
Added `Eq` and `PartialEq` implementations for
[MissingAssetSourceError](https://docs.rs/bevy/latest/bevy/asset/io/struct.MissingAssetSourceError.html),
[MissingProcessedAssetReaderError](https://docs.rs/bevy/latest/bevy/asset/io/struct.MissingProcessedAssetReaderError.html),
[DeserializeMetaError](https://docs.rs/bevy/latest/bevy/asset/enum.DeserializeMetaError.html),
[LoadState](https://docs.rs/bevy/latest/bevy/asset/enum.LoadState.html),
[AssetLoadError](https://docs.rs/bevy/latest/bevy/asset/enum.AssetLoadError.html),
[MissingAssetLoaderForTypeNameError](https://docs.rs/bevy/latest/bevy/asset/struct.MissingAssetLoaderForTypeNameError.html)
and
[MissingAssetLoaderForTypeIdError](https://docs.rs/bevy/latest/bevy/asset/struct.MissingAssetLoaderForTypeIdError.html)
This commit is contained in:
Vitaliy Sapronenko 2024-04-04 17:04:27 +03:00 committed by GitHub
parent 4ca8cf5d66
commit 4da4493449
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 72 additions and 34 deletions

View file

@ -51,6 +51,21 @@ pub enum AssetReaderError {
HttpError(u16),
}
impl PartialEq for AssetReaderError {
/// Equality comparison for `AssetReaderError::Io` is not full (only through `ErrorKind` of inner error)
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
(Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
(Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
_ => false,
}
}
}
impl Eq for AssetReaderError {}
impl From<std::io::Error> for AssetReaderError {
fn from(value: std::io::Error) -> Self {
Self::Io(Arc::new(value))

View file

@ -584,7 +584,7 @@ impl AssetSources {
}
/// An error returned when an [`AssetSource`] does not exist for a given id.
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("Asset Source '{0}' does not exist")]
pub struct MissingAssetSourceError(AssetSourceId<'static>);
@ -594,7 +594,7 @@ pub struct MissingAssetSourceError(AssetSourceId<'static>);
pub struct MissingAssetWriterError(AssetSourceId<'static>);
/// An error returned when a processed [`AssetReader`] does not exist for a given id.
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("Asset Source '{0}' does not have a processed AssetReader.")]
pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);

View file

@ -1071,13 +1071,13 @@ mod tests {
let d_id = c_text.dependencies[0].id();
let d_text = get::<CoolText>(world, d_id);
let (d_load, d_deps, d_rec_deps) = asset_server.get_load_states(d_id).unwrap();
if d_load != LoadState::Failed {
if !matches!(d_load, LoadState::Failed(_)) {
// wait until d has exited the loading state
return None;
}
assert!(d_text.is_none());
assert_eq!(d_load, LoadState::Failed);
assert!(matches!(d_load, LoadState::Failed(_)));
assert_eq!(d_deps, DependencyLoadState::Failed);
assert_eq!(d_rec_deps, RecursiveDependencyLoadState::Failed);
@ -1384,7 +1384,7 @@ mod tests {
// Check what just failed
for error in errors.read() {
let (load_state, _, _) = server.get_load_states(error.id).unwrap();
assert_eq!(load_state, LoadState::Failed);
assert!(matches!(load_state, LoadState::Failed(_)));
assert_eq!(*error.path.source(), AssetSourceId::Name("unstable".into()));
match &error.error {
AssetLoadError::AssetReaderError(read_error) => match read_error {

View file

@ -257,7 +257,7 @@ pub struct LoadDirectError {
}
/// An error that occurs while deserializing [`AssetMeta`].
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum DeserializeMetaError {
#[error("Failed to deserialize asset meta: {0:?}")]
DeserializeSettings(#[from] SpannedError),

View file

@ -1203,10 +1203,8 @@ 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 {
path: dependency,
..
}) = err
if let ProcessError::AssetLoadError(AssetLoadError::AssetLoaderError(dependency)) =
err
{
let info = self.get_mut(&asset_path).expect("info should exist");
info.processed_info = Some(ProcessedInfo {
@ -1214,7 +1212,7 @@ impl ProcessorAssetInfos {
full_hash: AssetHash::default(),
process_dependencies: vec![],
});
self.add_dependant(&dependency, asset_path.to_owned());
self.add_dependant(dependency.path(), asset_path.to_owned());
}
let info = self.get_mut(&asset_path).expect("info should exist");

View file

@ -212,8 +212,7 @@ impl AssetInfos {
let mut should_load = false;
if loading_mode == HandleLoadingMode::Force
|| (loading_mode == HandleLoadingMode::Request
&& (info.load_state == LoadState::NotLoaded
|| info.load_state == LoadState::Failed))
&& matches!(info.load_state, LoadState::NotLoaded | LoadState::Failed(_)))
{
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
@ -412,7 +411,7 @@ impl AssetInfos {
// If dependency is loaded, reduce our count by one
false
}
LoadState::Failed => {
LoadState::Failed(_) => {
failed_deps.insert(*dep_id);
false
}
@ -582,12 +581,12 @@ impl AssetInfos {
}
}
pub(crate) fn process_asset_fail(&mut self, failed_id: UntypedAssetId) {
pub(crate) fn process_asset_fail(&mut self, failed_id: UntypedAssetId, error: AssetLoadError) {
let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = {
let info = self
.get_mut(failed_id)
.expect("Asset info should always exist at this point");
info.load_state = LoadState::Failed;
info.load_state = LoadState::Failed(Box::new(error));
info.dep_load_state = DependencyLoadState::Failed;
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed;
(

View file

@ -26,7 +26,7 @@ use futures_lite::StreamExt;
use info::*;
use loaders::*;
use parking_lot::RwLock;
use std::path::PathBuf;
use std::{any::Any, path::PathBuf};
use std::{any::TypeId, path::Path, sync::Arc};
use thiserror::Error;
@ -754,7 +754,7 @@ impl AssetServer {
.infos
.read()
.get(id.into())
.map(|i| (i.load_state, i.dep_load_state, i.rec_dep_load_state))
.map(|i| (i.load_state.clone(), i.dep_load_state, i.rec_dep_load_state))
}
/// Retrieves the main [`LoadState`] of a given asset `id`.
@ -762,7 +762,11 @@ impl AssetServer {
/// Note that this is "just" the root asset load state. To check if an asset _and_ its recursive
/// dependencies have loaded, see [`AssetServer::is_loaded_with_dependencies`].
pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
self.data.infos.read().get(id.into()).map(|i| i.load_state)
self.data
.infos
.read()
.get(id.into())
.map(|i| i.load_state.clone())
}
/// Retrieves the [`RecursiveDependencyLoadState`] of a given asset `id`.
@ -1041,11 +1045,11 @@ impl AssetServer {
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 {
AssetLoadError::AssetLoaderError(AssetLoaderError {
path: asset_path.clone_owned(),
loader_name: loader.type_name(),
error: e.into(),
}
})
})
}
}
@ -1073,7 +1077,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
sender(world, id);
}
InternalAssetEvent::Failed { id, path, error } => {
infos.process_asset_fail(id);
infos.process_asset_fail(id, error.clone());
// Send untyped failure event
untyped_failures.push(UntypedAssetLoadFailedEvent {
@ -1190,7 +1194,7 @@ pub(crate) enum InternalAssetEvent {
}
/// The load state of an asset.
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[derive(Component, Clone, Debug, PartialEq, Eq)]
pub enum LoadState {
/// The asset has not started loading yet
NotLoaded,
@ -1199,7 +1203,7 @@ pub enum LoadState {
/// The asset has been loaded and has been added to the [`World`]
Loaded,
/// The asset failed to load.
Failed,
Failed(Box<AssetLoadError>),
}
/// The load state of an asset's dependencies.
@ -1229,7 +1233,7 @@ pub enum RecursiveDependencyLoadState {
}
/// An error that occurs during an [`Asset`] load.
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum AssetLoadError {
#[error("Requested handle of type {requested:?} for asset '{path}' does not match actual asset type '{actual_asset_name}', which used loader '{loader_name}'")]
RequestedHandleTypeMismatch {
@ -1268,12 +1272,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("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
AssetLoaderError {
path: AssetPath<'static>,
loader_name: &'static str,
error: Arc<dyn std::error::Error + Send + Sync + 'static>,
},
#[error(transparent)]
AssetLoaderError(#[from] AssetLoaderError),
#[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
base_path,
label,
@ -1286,22 +1286,48 @@ pub enum AssetLoadError {
},
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
#[derive(Error, Debug, Clone)]
#[error("Failed to load asset '{path}' with asset loader '{loader_name}': {error}")]
pub struct AssetLoaderError {
path: AssetPath<'static>,
loader_name: &'static str,
error: Arc<dyn std::error::Error + Send + Sync + 'static>,
}
impl PartialEq for AssetLoaderError {
/// Equality comparison for `AssetLoaderError::error` is not full (only through `TypeId`)
#[inline]
fn eq(&self, other: &Self) -> bool {
self.path == other.path
&& self.loader_name == other.loader_name
&& self.error.type_id() == other.error.type_id()
}
}
impl Eq for AssetLoaderError {}
impl AssetLoaderError {
pub fn path(&self) -> &AssetPath<'static> {
&self.path
}
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]
pub struct MissingAssetLoaderForExtensionError {
extensions: Vec<String>,
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given [`std::any::type_name`].
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("no `AssetLoader` found with the name '{type_name}'")]
pub struct MissingAssetLoaderForTypeNameError {
type_name: String,
}
/// An error that occurs when an [`AssetLoader`] is not registered for a given [`Asset`] [`TypeId`].
#[derive(Error, Debug, Clone)]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("no `AssetLoader` found with the ID '{type_id:?}'")]
pub struct MissingAssetLoaderForTypeIdError {
pub type_id: TypeId,