Skip to content

Commit eba36f4

Browse files
committed
Asset events & asset load retry plugin
1 parent 8a523de commit eba36f4

File tree

12 files changed

+1302
-57
lines changed

12 files changed

+1302
-57
lines changed

crates/bevy_asset/src/event.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,49 @@
1-
use crate::{Asset, AssetId};
1+
use crate::{Asset, AssetId, AssetLoadError, AssetPath, Handle, UntypedAssetId, UntypedHandle};
22
use bevy_ecs::event::Event;
33
use std::fmt::Debug;
44

5-
/// Events that occur for a specific [`Asset`], such as "value changed" events and "dependency" events.
5+
/// An event emitted when a specific [`Asset`] fails to load.
6+
#[derive(Event, Clone, Debug)]
7+
pub struct AssetLoadFailedEvent<A: Asset> {
8+
pub id: AssetId<A>,
9+
/// The original handle returned when the asset load was requested.
10+
pub handle: Option<Handle<A>>,
11+
/// The asset path that was attempted.
12+
pub path: AssetPath<'static>,
13+
/// Why the asset failed to load.
14+
pub error: AssetLoadError,
15+
}
16+
17+
impl<A: Asset> AssetLoadFailedEvent<A> {
18+
/// Converts this to an "untyped" / "generic-less" asset error event that stores the type information.
19+
pub fn untyped(&self) -> UntypedAssetLoadFailedEvent {
20+
self.into()
21+
}
22+
}
23+
24+
/// An untyped version of [`AssetLoadFailedEvent`].
25+
#[derive(Event, Clone, Debug)]
26+
pub struct UntypedAssetLoadFailedEvent {
27+
pub id: UntypedAssetId,
28+
pub handle: Option<UntypedHandle>,
29+
/// The asset path that was attempted.
30+
pub path: AssetPath<'static>,
31+
/// Why the asset failed to load.
32+
pub error: AssetLoadError,
33+
}
34+
35+
impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
36+
fn from(value: &AssetLoadFailedEvent<A>) -> Self {
37+
UntypedAssetLoadFailedEvent {
38+
id: value.id.untyped(),
39+
handle: value.handle.clone().map(|h| h.untyped()),
40+
path: value.path.clone(),
41+
error: value.error.clone(),
42+
}
43+
}
44+
}
45+
46+
/// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
647
#[derive(Event)]
748
pub enum AssetEvent<A: Asset> {
849
/// Emitted whenever an [`Asset`] is added.

crates/bevy_asset/src/io/mod.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,34 @@ use futures_lite::{ready, Stream};
2727
use std::{
2828
path::{Path, PathBuf},
2929
pin::Pin,
30+
sync::Arc,
3031
task::Poll,
3132
};
3233
use thiserror::Error;
3334

35+
use crate::{retry::AssetLoadRetrySettings, UntypedAssetLoadFailedEvent};
36+
3437
/// Errors that occur while loading assets.
35-
#[derive(Error, Debug)]
38+
#[derive(Error, Debug, Clone)]
3639
pub enum AssetReaderError {
3740
/// Path not found.
38-
#[error("path not found: {0}")]
41+
#[error("Path not found: {0}")]
3942
NotFound(PathBuf),
4043

4144
/// Encountered an I/O error while loading an asset.
42-
#[error("encountered an io error while loading asset: {0}")]
43-
Io(#[from] std::io::Error),
45+
#[error("Encountered an I/O error while loading asset: {0}")]
46+
Io(Arc<std::io::Error>),
47+
48+
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
49+
/// If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
50+
#[error("Encountered HTTP status {0:?} when loading asset")]
51+
HttpError(u16),
52+
}
53+
54+
impl From<std::io::Error> for AssetReaderError {
55+
fn from(value: std::io::Error) -> Self {
56+
Self::Io(Arc::new(value))
57+
}
4458
}
4559

4660
pub type Reader<'a> = dyn AsyncRead + Unpin + Send + Sync + 'a;
@@ -85,6 +99,14 @@ pub trait AssetReader: Send + Sync + 'static {
8599
Ok(meta_bytes)
86100
})
87101
}
102+
103+
/// Returns default retry settings to use for a particular failed asset load attempt using this reader.
104+
fn get_default_retry_settings(
105+
&self,
106+
_load_error: &UntypedAssetLoadFailedEvent,
107+
) -> AssetLoadRetrySettings {
108+
AssetLoadRetrySettings::no_retries()
109+
}
88110
}
89111

90112
pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;

crates/bevy_asset/src/io/source.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -569,22 +569,22 @@ impl AssetSources {
569569
}
570570

571571
/// An error returned when an [`AssetSource`] does not exist for a given id.
572-
#[derive(Error, Debug)]
572+
#[derive(Error, Debug, Clone)]
573573
#[error("Asset Source '{0}' does not exist")]
574574
pub struct MissingAssetSourceError(AssetSourceId<'static>);
575575

576576
/// An error returned when an [`AssetWriter`] does not exist for a given id.
577-
#[derive(Error, Debug)]
577+
#[derive(Error, Debug, Clone)]
578578
#[error("Asset Source '{0}' does not have an AssetWriter.")]
579579
pub struct MissingAssetWriterError(AssetSourceId<'static>);
580580

581581
/// An error returned when a processed [`AssetReader`] does not exist for a given id.
582-
#[derive(Error, Debug)]
582+
#[derive(Error, Debug, Clone)]
583583
#[error("Asset Source '{0}' does not have a processed AssetReader.")]
584584
pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);
585585

586586
/// An error returned when a processed [`AssetWriter`] does not exist for a given id.
587-
#[derive(Error, Debug)]
587+
#[derive(Error, Debug, Clone)]
588588
#[error("Asset Source '{0}' does not have a processed AssetWriter.")]
589589
pub struct MissingProcessedAssetWriterError(AssetSourceId<'static>);
590590

crates/bevy_asset/src/io/wasm.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
use crate::io::{
2-
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
1+
use crate::{
2+
io::{
3+
get_meta_path,
4+
retry::{IoErrorRetrySettingsProvider, ProvideAssetLoadRetrySettings},
5+
AssetLoadRetrySettings, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader,
6+
VecReader,
7+
},
8+
UntypedAssetLoadFailedEvent,
39
};
410
use bevy_log::error;
511
use bevy_utils::BoxedFuture;
@@ -12,15 +18,24 @@ use web_sys::Response;
1218
/// Reader implementation for loading assets via HTTP in WASM.
1319
pub struct HttpWasmAssetReader {
1420
root_path: PathBuf,
21+
retry_settings_provider: Box<dyn ProvideAssetLoadRetrySettings>,
1522
}
1623

1724
impl HttpWasmAssetReader {
1825
/// Creates a new `WasmAssetReader`. The path provided will be used to build URLs to query for assets.
1926
pub fn new<P: AsRef<Path>>(path: P) -> Self {
2027
Self {
2128
root_path: path.as_ref().to_owned(),
29+
retry_settings_provider: Box::new(IoErrorRetrySettingsProvider::from(
30+
AssetLoadRetrySettings::network_default(),
31+
)),
2232
}
2333
}
34+
/// Overrides the default retry settings.
35+
pub fn with_retry_defaults(mut self, provider: Box<dyn ProvideAssetLoadRetrySettings>) -> Self {
36+
self.retry_settings_provider = provider;
37+
self
38+
}
2439
}
2540

2641
fn js_value_to_err<'a>(context: &'a str) -> impl FnOnce(JsValue) -> std::io::Error + 'a {
@@ -53,10 +68,7 @@ impl HttpWasmAssetReader {
5368
Ok(reader)
5469
}
5570
404 => Err(AssetReaderError::NotFound(path)),
56-
status => Err(AssetReaderError::Io(std::io::Error::new(
57-
std::io::ErrorKind::Other,
58-
format!("Encountered unexpected HTTP status {status}"),
59-
))),
71+
status => Err(AssetReaderError::HttpError(status as u16)),
6072
}
6173
}
6274
}
@@ -98,4 +110,12 @@ impl AssetReader for HttpWasmAssetReader {
98110
error!("Reading directories is not supported with the HttpWasmAssetReader");
99111
Box::pin(async move { Ok(false) })
100112
}
113+
114+
fn get_default_retry_settings(
115+
&self,
116+
load_error: &UntypedAssetLoadFailedEvent,
117+
) -> AssetLoadRetrySettings {
118+
self.retry_settings_provider
119+
.get_settings(AssetLoadRetrySettings::no_retries(), load_error)
120+
}
101121
}

0 commit comments

Comments
 (0)