Reuse and hot reload folder handles (#10210)

# Objective

- Folder handles are not shared. Loading the same folder multiple times
will result in different handles.
- Once folder handles are shared, they can no longer be manually
reloaded, so we should add support for hot-reloading them


## Solution

- Reuse folder handles based on their path
- Trigger a reload of a folder if a file contained in it (or a sub
folder) is added or removed
- This also covers adding/removing/moving sub folders containing files

---------

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Niklas Eicker 2023-10-27 00:19:57 +02:00 committed by GitHub
parent 77309ba5d8
commit bfca4384cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 24 deletions

View file

@ -86,23 +86,6 @@ impl std::fmt::Debug for AssetInfos {
}
impl AssetInfos {
pub(crate) fn create_loading_handle<A: Asset>(&mut self) -> Handle<A> {
unwrap_with_context(
Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
&mut self.living_labeled_assets,
self.watching_for_changes,
TypeId::of::<A>(),
None,
None,
true,
),
std::any::type_name::<A>(),
)
.typed_debug_checked()
}
pub(crate) fn create_loading_handle_untyped(
&mut self,
type_id: TypeId,

View file

@ -23,6 +23,7 @@ use crossbeam_channel::{Receiver, Sender};
use futures_lite::StreamExt;
use info::*;
use parking_lot::RwLock;
use std::path::PathBuf;
use std::{any::TypeId, path::Path, sync::Arc};
use thiserror::Error;
@ -525,15 +526,33 @@ impl AssetServer {
/// Loads all assets from the specified folder recursively. The [`LoadedFolder`] asset (when it loads) will
/// contain handles to all assets in the folder. You can wait for all assets to load by checking the [`LoadedFolder`]'s
/// [`RecursiveDependencyLoadState`].
///
/// Loading the same folder multiple times will return the same handle. If the `file_watcher`
/// feature is enabled, [`LoadedFolder`] handles will reload when a file in the folder is
/// removed, added or moved. This includes files in subdirectories and moving, adding,
/// or removing complete subdirectories.
#[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
let handle = {
let mut infos = self.data.infos.write();
infos.create_loading_handle::<LoadedFolder>()
};
let id = handle.id().untyped();
let path = path.into().into_owned();
let (handle, should_load) = self
.data
.infos
.write()
.get_or_create_path_handle::<LoadedFolder>(
path.clone(),
HandleLoadingMode::Request,
None,
);
if !should_load {
return handle;
}
let id = handle.id().untyped();
self.load_folder_internal(id, path);
handle
}
pub(crate) fn load_folder_internal(&self, id: UntypedAssetId, path: AssetPath) {
fn load_folder<'a>(
source: AssetSourceId<'static>,
path: &'a Path,
@ -568,6 +587,7 @@ impl AssetServer {
})
}
let path = path.into_owned();
let server = self.clone();
IoTaskPool::get()
.spawn(async move {
@ -607,8 +627,6 @@ impl AssetServer {
}
})
.detach();
handle
}
fn send_asset_event(&self, event: InternalAssetEvent) {
@ -860,6 +878,19 @@ pub fn handle_internal_asset_events(world: &mut World) {
}
}
let reload_parent_folders = |path: PathBuf, source: &AssetSourceId<'static>| {
let mut current_folder = path;
while let Some(parent) = current_folder.parent() {
current_folder = parent.to_path_buf();
let parent_asset_path =
AssetPath::from(current_folder.clone()).with_source(source.clone());
if let Some(folder_handle) = infos.get_path_handle(parent_asset_path.clone()) {
info!("Reloading folder {parent_asset_path} because the content has changed");
server.load_folder_internal(folder_handle.id(), parent_asset_path);
}
}
};
let mut paths_to_reload = HashSet::new();
let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| {
match event {
@ -870,6 +901,16 @@ pub fn handle_internal_asset_events(world: &mut World) {
queue_ancestors(&path, &infos, &mut paths_to_reload);
paths_to_reload.insert(path);
}
AssetSourceEvent::RenamedFolder { old, new } => {
reload_parent_folders(old, &source);
reload_parent_folders(new, &source);
}
AssetSourceEvent::AddedAsset(path)
| AssetSourceEvent::RemovedAsset(path)
| AssetSourceEvent::RemovedFolder(path)
| AssetSourceEvent::AddedFolder(path) => {
reload_parent_folders(path, &source);
}
_ => {}
}
};