Skip to content

Commit ed1143b

Browse files
authored
Make adding a subasset label return a result for if there is a duplicate label. (#18013)
# Objective - Makes #18010 more easily debuggable. This doesn't solve that issue, but protects us from it in the future. ## Solution - Make `LoadContext::add_labeled_asset` and friends return an error if it finds a duplicate asset. ## Testing - Added a test - it fails before the fix. --- ## Migration Guide - `AssetLoader`s must now handle the case of a duplicate subasset label when using `LoadContext::add_labeled_asset` and its variants. If you know your subasset labels are unique by construction (e.g., they include an index number), you can simply unwrap this result.
1 parent 6bae04a commit ed1143b

File tree

3 files changed

+316
-234
lines changed

3 files changed

+316
-234
lines changed

crates/bevy_asset/src/lib.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ mod tests {
639639
},
640640
loader::{AssetLoader, LoadContext},
641641
Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
642-
AssetPlugin, AssetServer, Assets,
642+
AssetPlugin, AssetServer, Assets, DuplicateLabelAssetError, LoadState,
643643
};
644644
use alloc::{
645645
boxed::Box,
@@ -695,6 +695,8 @@ mod tests {
695695
CannotLoadDependency { dependency: AssetPath<'static> },
696696
#[error("A RON error occurred during loading")]
697697
RonSpannedError(#[from] ron::error::SpannedError),
698+
#[error(transparent)]
699+
DuplicateLabelAssetError(#[from] DuplicateLabelAssetError),
698700
#[error("An IO error occurred during loading")]
699701
Io(#[from] std::io::Error),
700702
}
@@ -740,7 +742,7 @@ mod tests {
740742
.sub_texts
741743
.drain(..)
742744
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
743-
.collect(),
745+
.collect::<Result<Vec<_>, _>>()?,
744746
})
745747
}
746748

@@ -1778,6 +1780,49 @@ mod tests {
17781780
app.world_mut().run_schedule(Update);
17791781
}
17801782

1783+
#[test]
1784+
fn fails_to_load_for_duplicate_subasset_labels() {
1785+
let mut app = App::new();
1786+
1787+
let dir = Dir::default();
1788+
dir.insert_asset_text(
1789+
Path::new("a.ron"),
1790+
r#"(
1791+
text: "b",
1792+
dependencies: [],
1793+
embedded_dependencies: [],
1794+
sub_texts: ["A", "A"],
1795+
)"#,
1796+
);
1797+
1798+
app.register_asset_source(
1799+
AssetSourceId::Default,
1800+
AssetSource::build()
1801+
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
1802+
)
1803+
.add_plugins((
1804+
TaskPoolPlugin::default(),
1805+
LogPlugin::default(),
1806+
AssetPlugin::default(),
1807+
));
1808+
1809+
app.init_asset::<CoolText>()
1810+
.init_asset::<SubText>()
1811+
.register_asset_loader(CoolTextLoader);
1812+
1813+
let asset_server = app.world().resource::<AssetServer>().clone();
1814+
let handle = asset_server.load::<CoolText>("a.ron");
1815+
1816+
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
1817+
LoadState::Loading => None,
1818+
LoadState::Failed(err) => {
1819+
assert!(matches!(*err, AssetLoadError::AssetLoaderError(_)));
1820+
Some(())
1821+
}
1822+
state => panic!("Unexpected asset state: {state:?}"),
1823+
});
1824+
}
1825+
17811826
// validate the Asset derive macro for various asset types
17821827
#[derive(Asset, TypePath)]
17831828
pub struct TestAsset;

crates/bevy_asset/src/loader.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use alloc::{
1313
};
1414
use atomicow::CowArc;
1515
use bevy_ecs::world::World;
16-
use bevy_log::warn;
1716
use bevy_platform_support::collections::{HashMap, HashSet};
1817
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
1918
use core::any::{Any, TypeId};
@@ -458,7 +457,7 @@ impl<'a> LoadContext<'a> {
458457
&mut self,
459458
label: String,
460459
load: impl FnOnce(&mut LoadContext) -> A,
461-
) -> Handle<A> {
460+
) -> Result<Handle<A>, DuplicateLabelAssetError> {
462461
let mut context = self.begin_labeled_asset();
463462
let asset = load(&mut context);
464463
let complete_asset = context.finish(asset);
@@ -475,7 +474,11 @@ impl<'a> LoadContext<'a> {
475474
/// new [`LoadContext`] to track the dependencies for the labeled asset.
476475
///
477476
/// See [`AssetPath`] for more on labeled assets.
478-
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
477+
pub fn add_labeled_asset<A: Asset>(
478+
&mut self,
479+
label: String,
480+
asset: A,
481+
) -> Result<Handle<A>, DuplicateLabelAssetError> {
479482
self.labeled_asset_scope(label, |_| asset)
480483
}
481484

@@ -488,7 +491,7 @@ impl<'a> LoadContext<'a> {
488491
&mut self,
489492
label: impl Into<CowArc<'static, str>>,
490493
loaded_asset: CompleteLoadedAsset<A>,
491-
) -> Handle<A> {
494+
) -> Result<Handle<A>, DuplicateLabelAssetError> {
492495
let label = label.into();
493496
let CompleteLoadedAsset {
494497
asset,
@@ -499,19 +502,25 @@ impl<'a> LoadContext<'a> {
499502
let handle = self
500503
.asset_server
501504
.get_or_create_path_handle(labeled_path, None);
502-
self.labeled_assets.insert(
503-
label,
504-
LabeledAsset {
505-
asset: loaded_asset,
506-
handle: handle.clone().untyped(),
507-
},
508-
);
505+
let has_duplicate = self
506+
.labeled_assets
507+
.insert(
508+
label.clone(),
509+
LabeledAsset {
510+
asset: loaded_asset,
511+
handle: handle.clone().untyped(),
512+
},
513+
)
514+
.is_some();
515+
if has_duplicate {
516+
return Err(DuplicateLabelAssetError(label.to_string()));
517+
}
509518
for (label, asset) in labeled_assets {
510519
if self.labeled_assets.insert(label.clone(), asset).is_some() {
511-
warn!("A labeled asset with the label \"{label}\" already exists. Replacing with the new asset.");
520+
return Err(DuplicateLabelAssetError(label.to_string()));
512521
}
513522
}
514-
handle
523+
Ok(handle)
515524
}
516525

517526
/// Returns `true` if an asset with the label `label` exists in this context.
@@ -661,3 +670,8 @@ pub enum ReadAssetBytesError {
661670
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
662671
MissingAssetHash,
663672
}
673+
674+
/// An error when labeled assets have the same label, containing the duplicate label.
675+
#[derive(Error, Debug)]
676+
#[error("Encountered a duplicate label while loading an asset: \"{0}\"")]
677+
pub struct DuplicateLabelAssetError(pub String);

0 commit comments

Comments
 (0)