diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 711b7a327e..d008c48b03 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -1823,6 +1823,83 @@ mod tests { }); } + // This test is not checking a requirement, but documenting a current limitation. We simply are + // not capable of loading subassets when doing nested immediate loads. + #[test] + fn error_on_nested_immediate_load_of_subasset() { + let mut app = App::new(); + + let dir = Dir::default(); + dir.insert_asset_text( + Path::new("a.cool.ron"), + r#"( + text: "b", + dependencies: [], + embedded_dependencies: [], + sub_texts: ["A"], +)"#, + ); + dir.insert_asset_text(Path::new("empty.txt"), ""); + + app.register_asset_source( + AssetSourceId::Default, + AssetSource::build() + .with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })), + ) + .add_plugins(( + TaskPoolPlugin::default(), + LogPlugin::default(), + AssetPlugin::default(), + )); + + app.init_asset::() + .init_asset::() + .register_asset_loader(CoolTextLoader); + + struct NestedLoadOfSubassetLoader; + + impl AssetLoader for NestedLoadOfSubassetLoader { + type Asset = TestAsset; + type Error = crate::loader::LoadDirectError; + type Settings = (); + + async fn load( + &self, + _: &mut dyn Reader, + _: &Self::Settings, + load_context: &mut LoadContext<'_>, + ) -> Result { + // We expect this load to fail. + load_context + .loader() + .immediate() + .load::("a.cool.ron#A") + .await?; + Ok(TestAsset) + } + + fn extensions(&self) -> &[&str] { + &["txt"] + } + } + + app.init_asset::() + .register_asset_loader(NestedLoadOfSubassetLoader); + + let asset_server = app.world().resource::().clone(); + let handle = asset_server.load::("empty.txt"); + + run_app_until(&mut app, |_world| match asset_server.load_state(&handle) { + LoadState::Loading => None, + LoadState::Failed(err) => { + let error_message = format!("{err}"); + assert!(error_message.contains("Requested to load an asset path (a.cool.ron#A) with a subasset, but this is unsupported"), "what? \"{error_message}\""); + Some(()) + } + state => panic!("Unexpected asset state: {state:?}"), + }); + } + // validate the Asset derive macro for various asset types #[derive(Asset, TypePath)] pub struct TestAsset; diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 7a8d6ca310..8b02e5b3db 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -359,10 +359,14 @@ impl From> for CompleteErasedLoadedAsset { /// [`NestedLoader::load`]: crate::NestedLoader::load /// [immediately]: crate::Immediate #[derive(Error, Debug)] -#[error("Failed to load dependency {dependency:?} {error}")] -pub struct LoadDirectError { - pub dependency: AssetPath<'static>, - pub error: AssetLoadError, +pub enum LoadDirectError { + #[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")] + RequestedSubasset(AssetPath<'static>), + #[error("Failed to load dependency {dependency:?} {error}")] + LoadError { + dependency: AssetPath<'static>, + error: AssetLoadError, + }, } /// An error that occurs while deserializing [`AssetMeta`]. @@ -621,7 +625,7 @@ impl<'a> LoadContext<'a> { self.populate_hashes, ) .await - .map_err(|error| LoadDirectError { + .map_err(|error| LoadDirectError::LoadError { dependency: path.clone(), error, })?; diff --git a/crates/bevy_asset/src/loader_builders.rs b/crates/bevy_asset/src/loader_builders.rs index 630ab1f92c..1867e5b03c 100644 --- a/crates/bevy_asset/src/loader_builders.rs +++ b/crates/bevy_asset/src/loader_builders.rs @@ -387,13 +387,16 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> path: &AssetPath<'static>, asset_type_id: Option, ) -> Result<(Arc, CompleteErasedLoadedAsset), LoadDirectError> { + if path.label().is_some() { + return Err(LoadDirectError::RequestedSubasset(path.clone())); + } let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader { let loader = if let Some(asset_type_id) = asset_type_id { self.load_context .asset_server .get_asset_loader_with_asset_type_id(asset_type_id) .await - .map_err(|error| LoadDirectError { + .map_err(|error| LoadDirectError::LoadError { dependency: path.clone(), error: error.into(), })? @@ -402,7 +405,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> .asset_server .get_path_asset_loader(path) .await - .map_err(|error| LoadDirectError { + .map_err(|error| LoadDirectError::LoadError { dependency: path.clone(), error: error.into(), })? @@ -415,7 +418,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> .asset_server .get_meta_loader_and_reader(path, asset_type_id) .await - .map_err(|error| LoadDirectError { + .map_err(|error| LoadDirectError::LoadError { dependency: path.clone(), error, })?; @@ -453,15 +456,17 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> { self.load_internal(&path, Some(TypeId::of::())) .await .and_then(move |(loader, untyped_asset)| { - untyped_asset.downcast::().map_err(|_| LoadDirectError { - dependency: path.clone(), - error: AssetLoadError::RequestedHandleTypeMismatch { - path, - requested: TypeId::of::(), - actual_asset_name: loader.asset_type_name(), - loader_name: loader.type_name(), - }, - }) + untyped_asset + .downcast::() + .map_err(|_| LoadDirectError::LoadError { + dependency: path.clone(), + error: AssetLoadError::RequestedHandleTypeMismatch { + path, + requested: TypeId::of::(), + actual_asset_name: loader.asset_type_name(), + loader_name: loader.type_name(), + }, + }) }) } }