Description
The problem
Calling LoadContext::add_labeled_asset
multiple times with the same label is not considered an error. This case is kinda trivial, but the more tricky case is when using LoadContext::add_loaded_labeled_asset
. Calling this multiple times can have subassets of those subassets conflict.
Prior attempts
In #15481, I flattened the subasset hierarchy, and in #18013, I took advantage of this to return an error on duplicate labels. However these were reverted in #18567 due to #18010. The TL;DR is that multiple nested asset loads with subassets can have overlapping subasset names. For example, if you have an asset loader that does two nested asset loads for GLTF files, both are likely to contain "Mesh0/Primitive0" subassets. Since nested asset loads currently are loaded as LoadedAsset
, that means that their subassets were getting flattened, resulting in them being detected as duplicates.
Basic Example
The following test fails since no error is returned by the loader. One of the subassets just silently gets dropped (which is bad since they contain different data).
#[test]
fn error_on_duplicate_subasset_names() {
let mut app = App::new();
let dir = Dir::default();
dir.insert_asset_text(Path::new("a.txt"), "");
app.register_asset_source(
AssetSourceId::Default,
AssetSource::build()
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
)
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
struct DuplicateSubassetLoader;
impl AssetLoader for DuplicateSubassetLoader {
type Asset = TestAsset;
type Error = std::io::Error;
type Settings = ();
async fn load(
&self,
_: &mut dyn Reader,
_: &Self::Settings,
load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
load_context.add_labeled_asset("A".into(), SubText { text: "one".into() });
load_context.add_labeled_asset("A".into(), SubText { text: "two".into() });
Ok(TestAsset)
}
}
app.init_asset::<TestAsset>()
.init_asset::<SubText>()
.register_asset_loader(DuplicateSubassetLoader);
let asset_server = app.world().resource::<AssetServer>().clone();
let handle = asset_server.load::<TestAsset>("a.txt");
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Failed(err) => {
// TODO: Check the error message matches here.
Some(())
}
state => panic!("Unexpected asset state: {state:?}"),
});
}