Skip to content

Commit 0e71446

Browse files
committed
test: Add self path load tests.
Test for deferred, immediate, and unknown type.
1 parent c392d28 commit 0e71446

File tree

2 files changed

+303
-0
lines changed

2 files changed

+303
-0
lines changed

crates/bevy_asset/src/lib.rs

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1987,6 +1987,301 @@ mod tests {
19871987
});
19881988
}
19891989

1990+
#[test]
1991+
fn error_on_immediate_load_of_self_dependency() {
1992+
let (mut app, dir, _source_events) = create_app_with_source_event_sender();
1993+
let asset_server = app.world().resource::<AssetServer>().clone();
1994+
1995+
dir.insert_asset_text(Path::new("abc.cool.ron"), "");
1996+
1997+
struct ImmediateSelfLoader;
1998+
1999+
impl AssetLoader for ImmediateSelfLoader {
2000+
type Asset = TestAsset;
2001+
type Error = crate::loader::LoadDirectError;
2002+
type Settings = ();
2003+
2004+
async fn load(
2005+
&self,
2006+
_: &mut dyn Reader,
2007+
_: &Self::Settings,
2008+
load_context: &mut LoadContext<'_>,
2009+
) -> Result<Self::Asset, Self::Error> {
2010+
let asset_path = load_context.asset_path().clone();
2011+
let loaded_asset = load_context.loader()
2012+
.immediate()
2013+
.load::<TestAsset>(asset_path)
2014+
.await?;
2015+
load_context.add_loaded_labeled_asset("myself".to_string(), loaded_asset);
2016+
Ok(TestAsset)
2017+
}
2018+
2019+
fn extensions(&self) -> &[&str] {
2020+
&["ron"]
2021+
}
2022+
}
2023+
2024+
app.init_asset::<TestAsset>()
2025+
.register_asset_loader(ImmediateSelfLoader);
2026+
2027+
let handle: Handle<TestAsset> = asset_server.load("abc.cool.ron");
2028+
2029+
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
2030+
LoadState::Loading => None,
2031+
LoadState::Failed(err) => {
2032+
assert!(format!("{:?}", &err).contains("AssetDependentOnSelf"), "Error did not contain AssetDependentOnSelf: {:?}", &err);
2033+
Some(())
2034+
}
2035+
state => panic!("Unexpected asset state: {state:?}"),
2036+
});
2037+
}
2038+
2039+
#[test]
2040+
fn error_on_unknown_type_immediate_load_of_self_dependency() {
2041+
let (mut app, dir, _source_events) = create_app_with_source_event_sender();
2042+
let asset_server = app.world().resource::<AssetServer>().clone();
2043+
2044+
dir.insert_asset_text(Path::new("abc.cool.ron"), "");
2045+
2046+
struct ImmediateSelfLoader;
2047+
2048+
impl AssetLoader for ImmediateSelfLoader {
2049+
type Asset = TestAsset;
2050+
type Error = crate::loader::LoadDirectError;
2051+
type Settings = ();
2052+
2053+
async fn load(
2054+
&self,
2055+
_: &mut dyn Reader,
2056+
_: &Self::Settings,
2057+
load_context: &mut LoadContext<'_>,
2058+
) -> Result<Self::Asset, Self::Error> {
2059+
let asset_path = load_context.asset_path().clone();
2060+
let loaded_asset = load_context.loader()
2061+
.immediate()
2062+
.with_unknown_type()
2063+
.load(asset_path)
2064+
.await?;
2065+
let loaded_asset = match loaded_asset.downcast::<TestAsset>() {
2066+
Ok(a) => a,
2067+
Err(_) => panic!("Could not downcast to `TestAsset`"),
2068+
};
2069+
load_context.add_loaded_labeled_asset("myself".to_string(), loaded_asset);
2070+
Ok(TestAsset)
2071+
}
2072+
2073+
fn extensions(&self) -> &[&str] {
2074+
&["ron"]
2075+
}
2076+
}
2077+
2078+
app.init_asset::<TestAsset>()
2079+
.register_asset_loader(ImmediateSelfLoader);
2080+
2081+
let handle: Handle<TestAsset> = asset_server.load("abc.cool.ron");
2082+
2083+
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
2084+
LoadState::Loading => None,
2085+
LoadState::Failed(err) => {
2086+
assert!(format!("{:?}", &err).contains("AssetDependentOnSelf"), "Error did not contain AssetDependentOnSelf: {:?}", &err);
2087+
Some(())
2088+
}
2089+
state => panic!("Unexpected asset state: {state:?}"),
2090+
});
2091+
}
2092+
2093+
/// This is not a statement of intent but one of behavior: One may load
2094+
/// their own path deferred without error. It has the correct handle to
2095+
/// itself. And it can reload.
2096+
///
2097+
/// I would have wanted this to produce an error. Instead it produces a
2098+
/// warning.
2099+
#[test]
2100+
fn no_error_on_deferred_load_of_self_dependency() {
2101+
let (mut app, dir, source_events) = create_app_with_source_event_sender();
2102+
let asset_server = app.world().resource::<AssetServer>().clone();
2103+
2104+
dir.insert_asset_text(Path::new("abc.cool.ron"), "");
2105+
2106+
#[derive(Asset, TypePath)]
2107+
pub struct TestAsset(Handle<TestAsset>);
2108+
struct DeferredSelfLoader;
2109+
2110+
impl AssetLoader for DeferredSelfLoader {
2111+
type Asset = TestAsset;
2112+
type Error = crate::loader::LoadDirectError;
2113+
type Settings = ();
2114+
2115+
async fn load(
2116+
&self,
2117+
_: &mut dyn Reader,
2118+
_: &Self::Settings,
2119+
load_context: &mut LoadContext<'_>,
2120+
) -> Result<Self::Asset, Self::Error> {
2121+
let asset_path = load_context.asset_path().clone();
2122+
let loaded_asset = load_context.load::<TestAsset>(asset_path);
2123+
Ok(TestAsset(loaded_asset))
2124+
}
2125+
2126+
fn extensions(&self) -> &[&str] {
2127+
&["ron"]
2128+
}
2129+
}
2130+
2131+
app.init_asset::<TestAsset>()
2132+
.register_asset_loader(DeferredSelfLoader);
2133+
2134+
let handle: Handle<TestAsset> = asset_server.load("abc.cool.ron");
2135+
2136+
run_app_until(&mut app, |world| match asset_server.load_state(&handle) {
2137+
LoadState::Loading => None,
2138+
LoadState::Loaded => {
2139+
let test_assets = world.resource::<Assets<TestAsset>>();
2140+
let asset = test_assets.get(&handle).unwrap();
2141+
assert_eq!(handle, asset.0);
2142+
Some(())
2143+
}
2144+
state => panic!("Unexpected asset state: {state:?}"),
2145+
});
2146+
2147+
run_app_until(&mut app, |world| {
2148+
let messages = collect_asset_events(world);
2149+
if messages.is_empty() {
2150+
return None;
2151+
}
2152+
assert_eq!(
2153+
messages,
2154+
[
2155+
AssetEvent::LoadedWithDependencies { id: handle.id() },
2156+
AssetEvent::Added { id: handle.id() }
2157+
]
2158+
);
2159+
Some(())
2160+
});
2161+
2162+
// Sending an asset event should result in the asset being reloaded - resulting in a
2163+
// "Modified" message.
2164+
source_events
2165+
.send(AssetSourceEvent::ModifiedAsset(PathBuf::from(
2166+
"abc.cool.ron",
2167+
)))
2168+
.unwrap();
2169+
2170+
run_app_until(&mut app, |world| {
2171+
let messages = collect_asset_events(world);
2172+
if messages.is_empty() {
2173+
return None;
2174+
}
2175+
assert_eq!(
2176+
messages,
2177+
[
2178+
AssetEvent::LoadedWithDependencies { id: handle.id() },
2179+
AssetEvent::Modified { id: handle.id() }
2180+
]
2181+
);
2182+
Some(())
2183+
});
2184+
}
2185+
2186+
/// This is not a statement of intent but one of behavior: One may load
2187+
/// their own path deferred of unknown type without error. It has the
2188+
/// correct handle to itself. And it can reload.
2189+
///
2190+
/// I would have wanted this to produce an error. Instead it produces a
2191+
/// warning.
2192+
#[test]
2193+
fn no_error_on_unknown_type_deferred_load_of_self_dependency() {
2194+
let (mut app, dir, source_events) = create_app_with_source_event_sender();
2195+
let asset_server = app.world().resource::<AssetServer>().clone();
2196+
2197+
dir.insert_asset_text(Path::new("abc.cool.ron"), "");
2198+
2199+
#[derive(Asset, TypePath)]
2200+
pub struct TestAssetUD(UntypedHandle);
2201+
struct ImmediateSelfLoader;
2202+
2203+
impl AssetLoader for ImmediateSelfLoader {
2204+
type Asset = TestAssetUD;
2205+
type Error = crate::loader::LoadDirectError;
2206+
type Settings = ();
2207+
2208+
async fn load(
2209+
&self,
2210+
_: &mut dyn Reader,
2211+
_: &Self::Settings,
2212+
load_context: &mut LoadContext<'_>,
2213+
) -> Result<Self::Asset, Self::Error> {
2214+
let asset_path = load_context.asset_path().clone();
2215+
let untyped_handle: UntypedHandle = load_context
2216+
.loader()
2217+
.with_unknown_type()
2218+
.load(asset_path).into();
2219+
2220+
Ok(TestAssetUD(untyped_handle))
2221+
}
2222+
2223+
fn extensions(&self) -> &[&str] {
2224+
&["ron"]
2225+
}
2226+
}
2227+
2228+
app.init_asset::<TestAssetUD>()
2229+
.register_asset_loader(ImmediateSelfLoader);
2230+
2231+
let handle: Handle<TestAssetUD> = asset_server.load("abc.cool.ron");
2232+
2233+
run_app_until(&mut app, |world| match asset_server.load_state(&handle) {
2234+
LoadState::Loading => None,
2235+
LoadState::Loaded => {
2236+
let test_assets = world.resource::<Assets<TestAssetUD>>();
2237+
let asset = test_assets.get(&handle).unwrap();
2238+
assert_eq!(handle.id(), asset.0.id().typed_unchecked::<TestAssetUD>());
2239+
// This one fails.
2240+
// assert_eq!(handle.id(), asset.0.id().typed::<TestAssetUD>());
2241+
Some(())
2242+
}
2243+
state => panic!("Unexpected asset state: {state:?}"),
2244+
});
2245+
2246+
run_app_until(&mut app, |world| {
2247+
let messages = collect_asset_events(world);
2248+
if messages.is_empty() {
2249+
return None;
2250+
}
2251+
assert_eq!(
2252+
messages,
2253+
[
2254+
AssetEvent::LoadedWithDependencies { id: handle.id() },
2255+
AssetEvent::Added { id: handle.id() }
2256+
]
2257+
);
2258+
Some(())
2259+
});
2260+
2261+
// Sending an asset event should result in the asset being reloaded - resulting in a
2262+
// "Modified" message.
2263+
source_events
2264+
.send(AssetSourceEvent::ModifiedAsset(PathBuf::from(
2265+
"abc.cool.ron",
2266+
)))
2267+
.unwrap();
2268+
2269+
run_app_until(&mut app, |world| {
2270+
let messages = collect_asset_events(world);
2271+
if messages.is_empty() {
2272+
return None;
2273+
}
2274+
assert_eq!(
2275+
messages,
2276+
[
2277+
AssetEvent::LoadedWithDependencies { id: handle.id() },
2278+
AssetEvent::Modified { id: handle.id() }
2279+
]
2280+
);
2281+
Some(())
2282+
});
2283+
}
2284+
19902285
// validate the Asset derive macro for various asset types
19912286
#[derive(Asset, TypePath)]
19922287
pub struct TestAsset;

crates/bevy_asset/src/loader.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,8 +305,14 @@ pub enum LoadDirectError {
305305
dependency: AssetPath<'static>,
306306
error: AssetLoadError,
307307
},
308+
#[error(transparent)]
309+
AssetDependentOnSelf(#[from] AssetDependentOnSelf)
308310
}
309311

312+
#[derive(Error, Debug, Clone)]
313+
#[error("The asset at path `{}` loads itself as a dependent.", asset_path)]
314+
pub struct AssetDependentOnSelf { pub asset_path: AssetPath<'static> }
315+
310316
/// An error that occurs while deserializing [`AssetMeta`].
311317
#[derive(Error, Debug, Clone, PartialEq, Eq)]
312318
pub enum DeserializeMetaError {
@@ -593,4 +599,6 @@ pub enum ReadAssetBytesError {
593599
},
594600
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
595601
MissingAssetHash,
602+
#[error(transparent)]
603+
AssetDependentOnSelf(#[from] AssetDependentOnSelf)
596604
}

0 commit comments

Comments
 (0)