diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index 07f896061da450..c7eea2916c0a88 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -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) diff --git a/crates/bevy_asset/src/filesystem_watcher.rs b/crates/bevy_asset/src/filesystem_watcher.rs index ace2500cd5b8ff..e91dc651457a39 100644 --- a/crates/bevy_asset/src/filesystem_watcher.rs +++ b/crates/bevy_asset/src/filesystem_watcher.rs @@ -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>, + pub path_map: HashMap>, } 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>(&mut self, path: P) -> Result<()> { - self.watcher.watch(path.as_ref(), RecursiveMode::Recursive) + pub fn watch>(&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) } } diff --git a/crates/bevy_asset/src/io/android_asset_io.rs b/crates/bevy_asset/src/io/android_asset_io.rs index 5bd583aee20920..bcd0adada700b9 100644 --- a/crates/bevy_asset/src/io/android_asset_io.rs +++ b/crates/bevy_asset/src/io/android_asset_io.rs @@ -51,7 +51,11 @@ impl AssetIo for AndroidAssetIo { Ok(Box::new(std::iter::empty::())) } - fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> { + fn watch_path_for_changes( + &self, + _to_watch: &Path, + _to_reload: Option, + ) -> Result<(), AssetIoError> { Ok(()) } diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index e4f9a57bba0cdc..93c6878c3147df 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -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>(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, + ) -> 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) { - let mut changed = HashSet::default(); let asset_io = if let Some(asset_io) = asset_server.server.asset_io.downcast_ref::() { asset_io @@ -178,6 +182,7 @@ pub fn filesystem_watcher_system(asset_server: Res) { }; 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) { } = 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); } } } diff --git a/crates/bevy_asset/src/io/mod.rs b/crates/bevy_asset/src/io/mod.rs index 7efbd7ca8882fa..67ea2ff8e4f914 100644 --- a/crates/bevy_asset/src/io/mod.rs +++ b/crates/bevy_asset/src/io/mod.rs @@ -66,7 +66,19 @@ pub trait AssetIo: Downcast + Send + Sync + 'static { fn get_metadata(&self, path: &Path) -> Result; /// 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, + ) -> Result<(), AssetIoError>; /// Enables change tracking in this asset I/O. fn watch_for_changes(&self) -> Result<(), AssetIoError>; diff --git a/crates/bevy_asset/src/io/wasm_asset_io.rs b/crates/bevy_asset/src/io/wasm_asset_io.rs index 1cf8d3b2715ea9..ce70fe295ae2bd 100644 --- a/crates/bevy_asset/src/io/wasm_asset_io.rs +++ b/crates/bevy_asset/src/io/wasm_asset_io.rs @@ -56,7 +56,11 @@ impl AssetIo for WasmAssetIo { Ok(Box::new(std::iter::empty::())) } - fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> { + fn watch_path_for_changes( + &self, + _to_watch: &Path, + _to_reload: Option, + ) -> Result<(), AssetIoError> { Ok(()) } diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 931efacb4c42a8..2b76edadf84346 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -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>(&self, path: P) -> Result, 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 } diff --git a/examples/asset/custom_asset_io.rs b/examples/asset/custom_asset_io.rs index e8f369effa9cdf..773046507e6c11 100644 --- a/examples/asset/custom_asset_io.rs +++ b/examples/asset/custom_asset_io.rs @@ -18,7 +18,7 @@ struct CustomAssetIo(Box); impl AssetIo for CustomAssetIo { fn load_path<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result, 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>, 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, + ) -> 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 { - info!("get_metadata({:?})", path); + info!("get_metadata({path:?})"); self.0.get_metadata(path) } }