Skip to content

Commit 49d5c6b

Browse files
authored
Hot reload labeled assets whose source asset is not loaded (#9736)
# Objective As called out in #9714, Bevy Asset V2 fails to hot-reload labeled assets whose source asset has changed (in cases where the root asset is not alive). ## Solution Track alive labeled assets for a given source asset and allow hot reloads in cases where a labeled asset is still alive.
1 parent 5781806 commit 49d5c6b

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

crates/bevy_asset/src/server/info.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ pub(crate) struct AssetInfos {
6969
/// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
7070
/// This should only be set when watching for changes to avoid unnecessary work.
7171
pub(crate) loader_dependants: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
72+
/// Tracks living labeled assets for a given source asset.
73+
/// This should only be set when watching for changes to avoid unnecessary work.
74+
pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<String>>,
7275
pub(crate) handle_providers: HashMap<TypeId, AssetHandleProvider>,
7376
pub(crate) dependency_loaded_event_sender: HashMap<TypeId, fn(&mut World, UntypedAssetId)>,
7477
}
@@ -88,6 +91,8 @@ impl AssetInfos {
8891
Self::create_handle_internal(
8992
&mut self.infos,
9093
&self.handle_providers,
94+
&mut self.living_labeled_assets,
95+
self.watching_for_changes,
9196
TypeId::of::<A>(),
9297
None,
9398
None,
@@ -107,6 +112,8 @@ impl AssetInfos {
107112
Self::create_handle_internal(
108113
&mut self.infos,
109114
&self.handle_providers,
115+
&mut self.living_labeled_assets,
116+
self.watching_for_changes,
110117
type_id,
111118
None,
112119
None,
@@ -116,9 +123,12 @@ impl AssetInfos {
116123
)
117124
}
118125

126+
#[allow(clippy::too_many_arguments)]
119127
fn create_handle_internal(
120128
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
121129
handle_providers: &HashMap<TypeId, AssetHandleProvider>,
130+
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
131+
watching_for_changes: bool,
122132
type_id: TypeId,
123133
path: Option<AssetPath<'static>>,
124134
meta_transform: Option<MetaTransform>,
@@ -128,6 +138,16 @@ impl AssetInfos {
128138
.get(&type_id)
129139
.ok_or(MissingHandleProviderError(type_id))?;
130140

141+
if watching_for_changes {
142+
if let Some(path) = &path {
143+
let mut without_label = path.to_owned();
144+
if let Some(label) = without_label.take_label() {
145+
let labels = living_labeled_assets.entry(without_label).or_default();
146+
labels.insert(label.to_string());
147+
}
148+
}
149+
}
150+
131151
let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
132152
let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
133153
if loading {
@@ -136,6 +156,7 @@ impl AssetInfos {
136156
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
137157
}
138158
infos.insert(handle.id, info);
159+
139160
Ok(UntypedHandle::Strong(handle))
140161
}
141162

@@ -226,6 +247,8 @@ impl AssetInfos {
226247
let handle = Self::create_handle_internal(
227248
&mut self.infos,
228249
&self.handle_providers,
250+
&mut self.living_labeled_assets,
251+
self.watching_for_changes,
229252
type_id,
230253
Some(path),
231254
meta_transform,
@@ -256,7 +279,7 @@ impl AssetInfos {
256279
Some(UntypedHandle::Strong(strong_handle))
257280
}
258281

259-
/// Returns `true` if this path has
282+
/// Returns `true` if the asset this path points to is still alive
260283
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
261284
let path = path.into();
262285
if let Some(id) = self.path_to_id.get(&path) {
@@ -267,12 +290,26 @@ impl AssetInfos {
267290
false
268291
}
269292

293+
/// Returns `true` if the asset at this path should be reloaded
294+
pub(crate) fn should_reload(&self, path: &AssetPath) -> bool {
295+
if self.is_path_alive(path) {
296+
return true;
297+
}
298+
299+
if let Some(living) = self.living_labeled_assets.get(path) {
300+
!living.is_empty()
301+
} else {
302+
false
303+
}
304+
}
305+
270306
// Returns `true` if the asset should be removed from the collection
271307
pub(crate) fn process_handle_drop(&mut self, id: UntypedAssetId) -> bool {
272308
Self::process_handle_drop_internal(
273309
&mut self.infos,
274310
&mut self.path_to_id,
275311
&mut self.loader_dependants,
312+
&mut self.living_labeled_assets,
276313
self.watching_for_changes,
277314
id,
278315
)
@@ -521,6 +558,7 @@ impl AssetInfos {
521558
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
522559
path_to_id: &mut HashMap<AssetPath<'static>, UntypedAssetId>,
523560
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
561+
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
524562
watching_for_changes: bool,
525563
id: UntypedAssetId,
526564
) -> bool {
@@ -540,6 +578,18 @@ impl AssetInfos {
540578
dependants.remove(&path);
541579
}
542580
}
581+
if let Some(label) = path.label() {
582+
let mut without_label = path.to_owned();
583+
without_label.remove_label();
584+
if let Entry::Occupied(mut entry) =
585+
living_labeled_assets.entry(without_label)
586+
{
587+
entry.get_mut().remove(label);
588+
if entry.get().is_empty() {
589+
entry.remove();
590+
}
591+
};
592+
}
543593
}
544594
path_to_id.remove(&path);
545595
}
@@ -566,6 +616,7 @@ impl AssetInfos {
566616
&mut self.infos,
567617
&mut self.path_to_id,
568618
&mut self.loader_dependants,
619+
&mut self.living_labeled_assets,
569620
self.watching_for_changes,
570621
id.untyped(provider.type_id),
571622
);

crates/bevy_asset/src/server/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ impl AssetServer {
395395
let path = path.into().into_owned();
396396
IoTaskPool::get()
397397
.spawn(async move {
398-
if server.data.infos.read().is_path_alive(&path) {
398+
if server.data.infos.read().should_reload(&path) {
399399
info!("Reloading {path} because it has changed");
400400
if let Err(err) = server.load_internal(None, path, true, None).await {
401401
error!("{}", err);

0 commit comments

Comments
 (0)