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.
This commit is contained in:
parent
5781806e72
commit
49d5c6b8a3
@ -69,6 +69,9 @@ pub(crate) struct AssetInfos {
|
|||||||
/// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
|
/// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
|
||||||
/// This should only be set when watching for changes to avoid unnecessary work.
|
/// This should only be set when watching for changes to avoid unnecessary work.
|
||||||
pub(crate) loader_dependants: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
|
pub(crate) loader_dependants: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
|
||||||
|
/// Tracks living labeled assets for a given source asset.
|
||||||
|
/// This should only be set when watching for changes to avoid unnecessary work.
|
||||||
|
pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<String>>,
|
||||||
pub(crate) handle_providers: HashMap<TypeId, AssetHandleProvider>,
|
pub(crate) handle_providers: HashMap<TypeId, AssetHandleProvider>,
|
||||||
pub(crate) dependency_loaded_event_sender: HashMap<TypeId, fn(&mut World, UntypedAssetId)>,
|
pub(crate) dependency_loaded_event_sender: HashMap<TypeId, fn(&mut World, UntypedAssetId)>,
|
||||||
}
|
}
|
||||||
@ -88,6 +91,8 @@ impl AssetInfos {
|
|||||||
Self::create_handle_internal(
|
Self::create_handle_internal(
|
||||||
&mut self.infos,
|
&mut self.infos,
|
||||||
&self.handle_providers,
|
&self.handle_providers,
|
||||||
|
&mut self.living_labeled_assets,
|
||||||
|
self.watching_for_changes,
|
||||||
TypeId::of::<A>(),
|
TypeId::of::<A>(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -107,6 +112,8 @@ impl AssetInfos {
|
|||||||
Self::create_handle_internal(
|
Self::create_handle_internal(
|
||||||
&mut self.infos,
|
&mut self.infos,
|
||||||
&self.handle_providers,
|
&self.handle_providers,
|
||||||
|
&mut self.living_labeled_assets,
|
||||||
|
self.watching_for_changes,
|
||||||
type_id,
|
type_id,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -116,9 +123,12 @@ impl AssetInfos {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn create_handle_internal(
|
fn create_handle_internal(
|
||||||
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
|
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
|
||||||
handle_providers: &HashMap<TypeId, AssetHandleProvider>,
|
handle_providers: &HashMap<TypeId, AssetHandleProvider>,
|
||||||
|
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
|
||||||
|
watching_for_changes: bool,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
path: Option<AssetPath<'static>>,
|
path: Option<AssetPath<'static>>,
|
||||||
meta_transform: Option<MetaTransform>,
|
meta_transform: Option<MetaTransform>,
|
||||||
@ -128,6 +138,16 @@ impl AssetInfos {
|
|||||||
.get(&type_id)
|
.get(&type_id)
|
||||||
.ok_or(MissingHandleProviderError(type_id))?;
|
.ok_or(MissingHandleProviderError(type_id))?;
|
||||||
|
|
||||||
|
if watching_for_changes {
|
||||||
|
if let Some(path) = &path {
|
||||||
|
let mut without_label = path.to_owned();
|
||||||
|
if let Some(label) = without_label.take_label() {
|
||||||
|
let labels = living_labeled_assets.entry(without_label).or_default();
|
||||||
|
labels.insert(label.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
|
let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
|
||||||
let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
|
let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
|
||||||
if loading {
|
if loading {
|
||||||
@ -136,6 +156,7 @@ impl AssetInfos {
|
|||||||
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
|
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
|
||||||
}
|
}
|
||||||
infos.insert(handle.id, info);
|
infos.insert(handle.id, info);
|
||||||
|
|
||||||
Ok(UntypedHandle::Strong(handle))
|
Ok(UntypedHandle::Strong(handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +247,8 @@ impl AssetInfos {
|
|||||||
let handle = Self::create_handle_internal(
|
let handle = Self::create_handle_internal(
|
||||||
&mut self.infos,
|
&mut self.infos,
|
||||||
&self.handle_providers,
|
&self.handle_providers,
|
||||||
|
&mut self.living_labeled_assets,
|
||||||
|
self.watching_for_changes,
|
||||||
type_id,
|
type_id,
|
||||||
Some(path),
|
Some(path),
|
||||||
meta_transform,
|
meta_transform,
|
||||||
@ -256,7 +279,7 @@ impl AssetInfos {
|
|||||||
Some(UntypedHandle::Strong(strong_handle))
|
Some(UntypedHandle::Strong(strong_handle))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this path has
|
/// Returns `true` if the asset this path points to is still alive
|
||||||
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
|
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
|
||||||
let path = path.into();
|
let path = path.into();
|
||||||
if let Some(id) = self.path_to_id.get(&path) {
|
if let Some(id) = self.path_to_id.get(&path) {
|
||||||
@ -267,12 +290,26 @@ impl AssetInfos {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the asset at this path should be reloaded
|
||||||
|
pub(crate) fn should_reload(&self, path: &AssetPath) -> bool {
|
||||||
|
if self.is_path_alive(path) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(living) = self.living_labeled_assets.get(path) {
|
||||||
|
!living.is_empty()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns `true` if the asset should be removed from the collection
|
// Returns `true` if the asset should be removed from the collection
|
||||||
pub(crate) fn process_handle_drop(&mut self, id: UntypedAssetId) -> bool {
|
pub(crate) fn process_handle_drop(&mut self, id: UntypedAssetId) -> bool {
|
||||||
Self::process_handle_drop_internal(
|
Self::process_handle_drop_internal(
|
||||||
&mut self.infos,
|
&mut self.infos,
|
||||||
&mut self.path_to_id,
|
&mut self.path_to_id,
|
||||||
&mut self.loader_dependants,
|
&mut self.loader_dependants,
|
||||||
|
&mut self.living_labeled_assets,
|
||||||
self.watching_for_changes,
|
self.watching_for_changes,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
@ -521,6 +558,7 @@ impl AssetInfos {
|
|||||||
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
|
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
|
||||||
path_to_id: &mut HashMap<AssetPath<'static>, UntypedAssetId>,
|
path_to_id: &mut HashMap<AssetPath<'static>, UntypedAssetId>,
|
||||||
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
|
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
|
||||||
|
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
|
||||||
watching_for_changes: bool,
|
watching_for_changes: bool,
|
||||||
id: UntypedAssetId,
|
id: UntypedAssetId,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@ -540,6 +578,18 @@ impl AssetInfos {
|
|||||||
dependants.remove(&path);
|
dependants.remove(&path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(label) = path.label() {
|
||||||
|
let mut without_label = path.to_owned();
|
||||||
|
without_label.remove_label();
|
||||||
|
if let Entry::Occupied(mut entry) =
|
||||||
|
living_labeled_assets.entry(without_label)
|
||||||
|
{
|
||||||
|
entry.get_mut().remove(label);
|
||||||
|
if entry.get().is_empty() {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
path_to_id.remove(&path);
|
path_to_id.remove(&path);
|
||||||
}
|
}
|
||||||
@ -566,6 +616,7 @@ impl AssetInfos {
|
|||||||
&mut self.infos,
|
&mut self.infos,
|
||||||
&mut self.path_to_id,
|
&mut self.path_to_id,
|
||||||
&mut self.loader_dependants,
|
&mut self.loader_dependants,
|
||||||
|
&mut self.living_labeled_assets,
|
||||||
self.watching_for_changes,
|
self.watching_for_changes,
|
||||||
id.untyped(provider.type_id),
|
id.untyped(provider.type_id),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -395,7 +395,7 @@ impl AssetServer {
|
|||||||
let path = path.into().into_owned();
|
let path = path.into().into_owned();
|
||||||
IoTaskPool::get()
|
IoTaskPool::get()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
if server.data.infos.read().is_path_alive(&path) {
|
if server.data.infos.read().should_reload(&path) {
|
||||||
info!("Reloading {path} because it has changed");
|
info!("Reloading {path} because it has changed");
|
||||||
if let Err(err) = server.load_internal(None, path, true, None).await {
|
if let Err(err) = server.load_internal(None, path, true, None).await {
|
||||||
error!("{}", err);
|
error!("{}", err);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user