Fix hot reloading for read_asset_bytes (#6797)
# Objective Fixes #6780 ## Solution - record the asset path of each watched file - call `AssetIo::watch_for_changes` in `LoadContext::read_asset_bytes` --- ## Changelog ### Fixed - fixed hot reloading for `LoadContext::read_asset_bytes` ### Changed - `AssetIo::watch_path_for_changes` allows watched path and path to reload to differ ## Migration Guide - for custom `AssetIo`s, differentiate paths to watch and asset paths to reload as a consequence Co-authored-by: Vincent Junge <specificprotagonist@posteo.org>
This commit is contained in:
parent
71cf35ce42
commit
91ff782439
@ -422,7 +422,7 @@ impl AssetServer {
|
||||
}
|
||||
|
||||
self.asset_io()
|
||||
.watch_path_for_changes(asset_path.path())
|
||||
.watch_path_for_changes(asset_path.path(), None)
|
||||
.unwrap();
|
||||
self.create_assets_in_load_context(&mut load_context);
|
||||
Ok(asset_path_id)
|
||||
|
@ -1,6 +1,7 @@
|
||||
use bevy_utils::{default, HashMap, HashSet};
|
||||
use crossbeam_channel::Receiver;
|
||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||
use std::path::Path;
|
||||
use notify::{Event, RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Watches for changes to files on the local filesystem.
|
||||
///
|
||||
@ -9,6 +10,7 @@ use std::path::Path;
|
||||
pub struct FilesystemWatcher {
|
||||
pub watcher: RecommendedWatcher,
|
||||
pub receiver: Receiver<Result<Event>>,
|
||||
pub path_map: HashMap<PathBuf, HashSet<PathBuf>>,
|
||||
}
|
||||
|
||||
impl Default for FilesystemWatcher {
|
||||
@ -18,16 +20,25 @@ impl Default for FilesystemWatcher {
|
||||
move |res| {
|
||||
sender.send(res).expect("Watch event send failure.");
|
||||
},
|
||||
Config::default(),
|
||||
default(),
|
||||
)
|
||||
.expect("Failed to create filesystem watcher.");
|
||||
FilesystemWatcher { watcher, receiver }
|
||||
FilesystemWatcher {
|
||||
watcher,
|
||||
receiver,
|
||||
path_map: default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilesystemWatcher {
|
||||
/// Watch for changes recursively at the provided path.
|
||||
pub fn watch<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||
self.watcher.watch(path.as_ref(), RecursiveMode::Recursive)
|
||||
pub fn watch<P: AsRef<Path>>(&mut self, to_watch: P, to_reload: PathBuf) -> Result<()> {
|
||||
self.path_map
|
||||
.entry(to_watch.as_ref().to_owned())
|
||||
.or_default()
|
||||
.insert(to_reload);
|
||||
self.watcher
|
||||
.watch(to_watch.as_ref(), RecursiveMode::Recursive)
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,11 @@ impl AssetIo for AndroidAssetIo {
|
||||
Ok(Box::new(std::iter::empty::<PathBuf>()))
|
||||
}
|
||||
|
||||
fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> {
|
||||
fn watch_path_for_changes(
|
||||
&self,
|
||||
_to_watch: &Path,
|
||||
_to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ use anyhow::Result;
|
||||
use bevy_ecs::system::Res;
|
||||
use bevy_utils::BoxedFuture;
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
use bevy_utils::HashSet;
|
||||
use bevy_utils::{default, HashSet};
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
use crossbeam_channel::TryRecvError;
|
||||
use fs::File;
|
||||
@ -38,7 +38,7 @@ impl FileAssetIo {
|
||||
pub fn new<P: AsRef<Path>>(path: P, watch_for_changes: bool) -> Self {
|
||||
let file_asset_io = FileAssetIo {
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
filesystem_watcher: Default::default(),
|
||||
filesystem_watcher: default(),
|
||||
root_path: Self::get_base_path().join(path.as_ref()),
|
||||
};
|
||||
if watch_for_changes {
|
||||
@ -122,15 +122,20 @@ impl AssetIo for FileAssetIo {
|
||||
)))
|
||||
}
|
||||
|
||||
fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> {
|
||||
fn watch_path_for_changes(
|
||||
&self,
|
||||
to_watch: &Path,
|
||||
to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError> {
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
{
|
||||
let path = self.root_path.join(_path);
|
||||
let to_reload = to_reload.unwrap_or_else(|| to_watch.to_owned());
|
||||
let to_watch = self.root_path.join(to_watch);
|
||||
let mut watcher = self.filesystem_watcher.write();
|
||||
if let Some(ref mut watcher) = *watcher {
|
||||
watcher
|
||||
.watch(&path)
|
||||
.map_err(|_error| AssetIoError::PathWatchError(path))?;
|
||||
.watch(&to_watch, to_reload)
|
||||
.map_err(|_error| AssetIoError::PathWatchError(to_watch))?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +145,7 @@ impl AssetIo for FileAssetIo {
|
||||
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
{
|
||||
*self.filesystem_watcher.write() = Some(FilesystemWatcher::default());
|
||||
*self.filesystem_watcher.write() = Some(default());
|
||||
}
|
||||
#[cfg(not(feature = "filesystem_watcher"))]
|
||||
bevy_log::warn!("Watching for changes is not supported when the `filesystem_watcher` feature is disabled");
|
||||
@ -169,7 +174,6 @@ impl AssetIo for FileAssetIo {
|
||||
all(not(target_arch = "wasm32"), not(target_os = "android"))
|
||||
))]
|
||||
pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
|
||||
let mut changed = HashSet::default();
|
||||
let asset_io =
|
||||
if let Some(asset_io) = asset_server.server.asset_io.downcast_ref::<FileAssetIo>() {
|
||||
asset_io
|
||||
@ -178,6 +182,7 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
|
||||
};
|
||||
let watcher = asset_io.filesystem_watcher.read();
|
||||
if let Some(ref watcher) = *watcher {
|
||||
let mut changed = HashSet::<&PathBuf>::default();
|
||||
loop {
|
||||
let event = match watcher.receiver.try_recv() {
|
||||
Ok(result) => result.unwrap(),
|
||||
@ -191,12 +196,14 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
|
||||
} = event
|
||||
{
|
||||
for path in &paths {
|
||||
if !changed.contains(path) {
|
||||
let relative_path = path.strip_prefix(&asset_io.root_path).unwrap();
|
||||
let _ = asset_server.load_untracked(relative_path.into(), true);
|
||||
let Some(set) = watcher.path_map.get(path) else {continue};
|
||||
for to_reload in set {
|
||||
if !changed.contains(to_reload) {
|
||||
changed.insert(to_reload);
|
||||
let _ = asset_server.load_untracked(to_reload.as_path().into(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
changed.extend(paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,19 @@ pub trait AssetIo: Downcast + Send + Sync + 'static {
|
||||
fn get_metadata(&self, path: &Path) -> Result<Metadata, AssetIoError>;
|
||||
|
||||
/// Tells the asset I/O to watch for changes recursively at the provided path.
|
||||
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>;
|
||||
///
|
||||
/// No-op if `watch_for_changes` hasn't been called yet.
|
||||
/// Otherwise triggers a reload each time `to_watch` changes.
|
||||
/// In most cases the asset found at the watched path should be changed,
|
||||
/// but when an asset depends on data at another path, the asset's path
|
||||
/// is provided in `to_reload`.
|
||||
/// Note that there may be a many-to-many correspondence between
|
||||
/// `to_watch` and `to_reload` paths.
|
||||
fn watch_path_for_changes(
|
||||
&self,
|
||||
to_watch: &Path,
|
||||
to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError>;
|
||||
|
||||
/// Enables change tracking in this asset I/O.
|
||||
fn watch_for_changes(&self) -> Result<(), AssetIoError>;
|
||||
|
@ -56,7 +56,11 @@ impl AssetIo for WasmAssetIo {
|
||||
Ok(Box::new(std::iter::empty::<PathBuf>()))
|
||||
}
|
||||
|
||||
fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> {
|
||||
fn watch_path_for_changes(
|
||||
&self,
|
||||
_to_watch: &Path,
|
||||
_to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -174,6 +174,8 @@ impl<'a> LoadContext<'a> {
|
||||
/// Reads the contents of the file at the specified path through the [`AssetIo`] associated
|
||||
/// with this context.
|
||||
pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
|
||||
self.asset_io
|
||||
.watch_path_for_changes(path.as_ref(), Some(self.path.to_owned()))?;
|
||||
self.asset_io.load_path(path.as_ref()).await
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ struct CustomAssetIo(Box<dyn AssetIo>);
|
||||
|
||||
impl AssetIo for CustomAssetIo {
|
||||
fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<Vec<u8>, AssetIoError>> {
|
||||
info!("load_path({:?})", path);
|
||||
info!("load_path({path:?})");
|
||||
self.0.load_path(path)
|
||||
}
|
||||
|
||||
@ -26,13 +26,17 @@ impl AssetIo for CustomAssetIo {
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Box<dyn Iterator<Item = PathBuf>>, AssetIoError> {
|
||||
info!("read_directory({:?})", path);
|
||||
info!("read_directory({path:?})");
|
||||
self.0.read_directory(path)
|
||||
}
|
||||
|
||||
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> {
|
||||
info!("watch_path_for_changes({:?})", path);
|
||||
self.0.watch_path_for_changes(path)
|
||||
fn watch_path_for_changes(
|
||||
&self,
|
||||
to_watch: &Path,
|
||||
to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError> {
|
||||
info!("watch_path_for_changes({to_watch:?}, {to_reload:?})");
|
||||
self.0.watch_path_for_changes(to_watch, to_reload)
|
||||
}
|
||||
|
||||
fn watch_for_changes(&self) -> Result<(), AssetIoError> {
|
||||
@ -41,7 +45,7 @@ impl AssetIo for CustomAssetIo {
|
||||
}
|
||||
|
||||
fn get_metadata(&self, path: &Path) -> Result<Metadata, AssetIoError> {
|
||||
info!("get_metadata({:?})", path);
|
||||
info!("get_metadata({path:?})");
|
||||
self.0.get_metadata(path)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user