Normalise root path in file_watcher (#12102)

# Objective

- I hit an issue using the `file_watcher` feature to hot reload assets
for my game. The change in this PR allows me to now hot reload assets.
- The issue stemmed from my project being a multi crate workspace
project structured like so:
```
└── my_game
    ├── my_game_core
    │   ├── src
    │   └── assets
    ├── my_game_editor
    │   └── src/main.rs
    └── my_game
        └── src/main.rs
```

 - `my_game_core` is a crate that holds all my game logic and assets
- `my_game` is the crate that creates the binary for my game (depends on
the game logic and assets in `my_game_core`)
- `my_game_editor` is an editor tool for my game (it also depends on the
game logic and assets in `my_game_core`)

Whilst running `my_game` and `my_game_editor` from cargo during
development I would use `AssetPlugin` like so:

```rust
default_plugins.set(AssetPlugin {
  watch_for_changes_override: Some(true),
  file_path: "../my_game_core/assets".to_string(),
  ..Default::default()
})
```

This works fine; bevy picks up the assets. However on saving an asset I
would get the following panic from `file_watcher`. It wouldn't kill the
app, but I wouldn't see the asset hot reload:

```
thread 'notify-rs debouncer loop' panicked at /Users/ian/.cargo/registry/src/index.crates.io-6f17d22bba15001f/bevy_asset-0.12.1/src/io/file/file_watcher.rs:48:58:
called `Result::unwrap()` on an `Err` value: StripPrefixError(())
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

## Solution

- The solution is to collapse dot segments in the root asset path
`FileWatcher` is using
- There was already bevy code to do this in `AssetPath`, so I extracted
that code so it could be reused in `FileWatcher`
This commit is contained in:
Ian Forsey 2024-02-25 15:21:06 +00:00 committed by GitHub
parent bc2ddce432
commit 14042b1e34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 29 deletions

View file

@ -1,4 +1,5 @@
use crate::io::{AssetSourceEvent, AssetWatcher};
use crate::path::normalize_path;
use bevy_log::error;
use bevy_utils::Duration;
use crossbeam_channel::Sender;
@ -28,7 +29,7 @@ impl FileWatcher {
sender: Sender<AssetSourceEvent>,
debounce_wait_time: Duration,
) -> Result<Self, notify::Error> {
let root = super::get_base_path().join(root);
let root = normalize_path(super::get_base_path().join(root).as_path());
let watcher = new_asset_event_debouncer(
root.clone(),
debounce_wait_time,

View file

@ -431,34 +431,13 @@ impl<'a> AssetPath<'a> {
_ => rpath,
};
let mut result_path = PathBuf::new();
if !is_absolute && source.is_none() {
for elt in base_path.iter() {
if elt == "." {
// Skip
} else if elt == ".." {
if !result_path.pop() {
// Preserve ".." if insufficient matches (per RFC 1808).
result_path.push(elt);
}
} else {
result_path.push(elt);
}
}
}
for elt in rpath.iter() {
if elt == "." {
// Skip
} else if elt == ".." {
if !result_path.pop() {
// Preserve ".." if insufficient matches (per RFC 1808).
result_path.push(elt);
}
} else {
result_path.push(elt);
}
}
let mut result_path = if !is_absolute && source.is_none() {
base_path
} else {
PathBuf::new()
};
result_path.push(rpath);
result_path = normalize_path(result_path.as_path());
Ok(AssetPath {
source: match source {
@ -723,6 +702,25 @@ impl FromReflect for AssetPath<'static> {
}
}
/// Normalizes the path by collapsing all occurrences of '.' and '..' dot-segments where possible
/// as per [RFC 1808](https://datatracker.ietf.org/doc/html/rfc1808)
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
let mut result_path = PathBuf::new();
for elt in path.iter() {
if elt == "." {
// Skip
} else if elt == ".." {
if !result_path.pop() {
// Preserve ".." if insufficient matches (per RFC 1808).
result_path.push(elt);
}
} else {
result_path.push(elt);
}
}
result_path
}
#[cfg(test)]
mod tests {
use crate::AssetPath;