Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Fix hot reloading for read_asset_bytes #6797

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bevy_asset/src/asset_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 17 additions & 6 deletions crates/bevy_asset/src/filesystem_watcher.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand All @@ -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 {
Expand All @@ -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)
}
}
6 changes: 5 additions & 1 deletion crates/bevy_asset/src/io/android_asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
31 changes: 19 additions & 12 deletions crates/bevy_asset/src/io/file_asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))?;
}
}

Expand All @@ -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");
Expand Down Expand Up @@ -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
Expand All @@ -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(),
Expand All @@ -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);
}
}
}
Expand Down
14 changes: 13 additions & 1 deletion crates/bevy_asset/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
SpecificProtagonist marked this conversation as resolved.
Show resolved Hide resolved
/// 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>;
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_asset/src/io/wasm_asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_asset/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
16 changes: 10 additions & 6 deletions examples/asset/custom_asset_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,25 @@ 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)
}

fn read_directory(
&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> {
Expand All @@ -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)
}
}
Expand Down