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

Multiple Asset Sources #9885

Merged
merged 12 commits into from
Oct 13, 2023
Merged
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,10 @@ shader_format_spirv = ["bevy_internal/shader_format_spirv"]
webgl2 = ["bevy_internal/webgl"]

# Enables watching the filesystem for Bevy Asset hot-reloading
filesystem_watcher = ["bevy_internal/filesystem_watcher"]
file_watcher = ["bevy_internal/file_watcher"]

# Enables watching in memory asset providers for Bevy Asset hot-reloading
rust_src_watcher = ["bevy_internal/rust_src_watcher"]

[dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.12.0-dev", default-features = false, optional = true }
Expand Down Expand Up @@ -1053,6 +1056,7 @@ wasm = true
name = "hot_asset_reloading"
path = "examples/asset/hot_asset_reloading.rs"
doc-scrape-examples = true
required-features = ["file_watcher"]

[package.metadata.example.hot_asset_reloading]
name = "Hot Reloading of Assets"
Expand All @@ -1064,7 +1068,7 @@ wasm = true
name = "asset_processing"
path = "examples/asset/processing/processing.rs"
doc-scrape-examples = true
required-features = ["filesystem_watcher"]
required-features = ["file_watcher"]

[package.metadata.example.asset_processing]
name = "Asset Processing"
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ keywords = ["bevy"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
filesystem_watcher = ["notify-debouncer-full"]
file_watcher = ["notify-debouncer-full", "watch"]
rust_src_watcher = ["file_watcher"]
multi-threaded = ["bevy_tasks/multi-threaded"]
watch = []

[dependencies]
bevy_app = { path = "../bevy_app", version = "0.12.0-dev" }
Expand Down
7 changes: 0 additions & 7 deletions crates/bevy_asset/src/io/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,4 @@ impl AssetReader for AndroidAssetReader {
error!("Reading directories is not supported with the AndroidAssetReader");
Box::pin(async move { Ok(false) })
}

fn watch_for_changes(
&self,
_event_sender: crossbeam_channel::Sender<super::AssetSourceEvent>,
) -> Option<Box<dyn AssetWatcher>> {
None
}
}
337 changes: 213 additions & 124 deletions crates/bevy_asset/src/io/file/file_watcher.rs

Large diffs are not rendered by default.

25 changes: 5 additions & 20 deletions crates/bevy_asset/src/io/file/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#[cfg(feature = "filesystem_watcher")]
#[cfg(feature = "file_watcher")]
mod file_watcher;
#[cfg(feature = "file_watcher")]
pub use file_watcher::*;

use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWatcher, AssetWriter, AssetWriterError,
PathStream, Reader, Writer,
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, PathStream,
Reader, Writer,
};
use anyhow::Result;
use async_fs::{read_dir, File};
Expand Down Expand Up @@ -165,23 +167,6 @@ impl AssetReader for FileAssetReader {
Ok(metadata.file_type().is_dir())
})
}

fn watch_for_changes(
&self,
_event_sender: crossbeam_channel::Sender<super::AssetSourceEvent>,
) -> Option<Box<dyn AssetWatcher>> {
#[cfg(feature = "filesystem_watcher")]
return Some(Box::new(
file_watcher::FileWatcher::new(
self.root_path.clone(),
_event_sender,
std::time::Duration::from_millis(300),
)
.unwrap(),
));
#[cfg(not(feature = "filesystem_watcher"))]
return None;
}
}

pub struct FileAssetWriter {
Expand Down
7 changes: 0 additions & 7 deletions crates/bevy_asset/src/io/gated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,4 @@ impl<R: AssetReader> AssetReader for GatedReader<R> {
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> {
self.reader.is_directory(path)
}

fn watch_for_changes(
&self,
event_sender: Sender<super::AssetSourceEvent>,
) -> Option<Box<dyn super::AssetWatcher>> {
self.reader.watch_for_changes(event_sender)
}
}
99 changes: 71 additions & 28 deletions crates/bevy_asset/src/io/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,31 @@ impl Dir {
self.insert_meta(path, asset.as_bytes().to_vec());
}

pub fn insert_asset(&self, path: &Path, asset: Vec<u8>) {
pub fn insert_asset(&self, path: &Path, value: impl Into<Value>) {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = self.get_or_insert_dir(parent);
}
dir.0.write().assets.insert(
path.file_name().unwrap().to_string_lossy().to_string(),
Data(Arc::new((asset, path.to_owned()))),
Data {
value: value.into(),
path: path.to_owned(),
},
);
}

pub fn insert_meta(&self, path: &Path, asset: Vec<u8>) {
pub fn insert_meta(&self, path: &Path, value: impl Into<Value>) {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = self.get_or_insert_dir(parent);
}
dir.0.write().metadata.insert(
path.file_name().unwrap().to_string_lossy().to_string(),
Data(Arc::new((asset, path.to_owned()))),
Data {
value: value.into(),
path: path.to_owned(),
},
);
}

Expand Down Expand Up @@ -118,11 +124,16 @@ impl Dir {
pub struct DirStream {
dir: Dir,
index: usize,
dir_index: usize,
}

impl DirStream {
fn new(dir: Dir) -> Self {
Self { dir, index: 0 }
Self {
dir,
index: 0,
dir_index: 0,
}
}
}

Expand All @@ -134,10 +145,17 @@ impl Stream for DirStream {
_cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
let index = this.index;
this.index += 1;
let dir = this.dir.0.read();
Poll::Ready(dir.assets.values().nth(index).map(|d| d.path().to_owned()))

let dir_index = this.dir_index;
if let Some(dir_path) = dir.dirs.keys().nth(dir_index).map(|d| dir.path.join(d)) {
this.dir_index += 1;
Poll::Ready(Some(dir_path))
} else {
let index = this.index;
this.index += 1;
Poll::Ready(dir.assets.values().nth(index).map(|d| d.path().to_owned()))
}
}
}

Expand All @@ -150,14 +168,45 @@ pub struct MemoryAssetReader {

/// Asset data stored in a [`Dir`].
#[derive(Clone, Debug)]
pub struct Data(Arc<(Vec<u8>, PathBuf)>);
pub struct Data {
path: PathBuf,
value: Value,
}

/// Stores either an allocated vec of bytes or a static array of bytes.
#[derive(Clone, Debug)]
pub enum Value {
Vec(Arc<Vec<u8>>),
Static(&'static [u8]),
}

impl Data {
fn path(&self) -> &Path {
&self.0 .1
&self.path
}
fn data(&self) -> &[u8] {
&self.0 .0
fn value(&self) -> &[u8] {
match &self.value {
Value::Vec(vec) => vec,
Value::Static(value) => value,
}
}
}

impl From<Vec<u8>> for Value {
fn from(value: Vec<u8>) -> Self {
Self::Vec(Arc::new(value))
}
}

impl From<&'static [u8]> for Value {
fn from(value: &'static [u8]) -> Self {
Self::Static(value)
}
}

impl<const N: usize> From<&'static [u8; N]> for Value {
fn from(value: &'static [u8; N]) -> Self {
Self::Static(value)
}
}

Expand All @@ -172,10 +221,11 @@ impl AsyncRead for DataReader {
cx: &mut std::task::Context<'_>,
buf: &mut [u8],
) -> std::task::Poll<futures_io::Result<usize>> {
if self.bytes_read >= self.data.data().len() {
if self.bytes_read >= self.data.value().len() {
Poll::Ready(Ok(0))
} else {
let n = ready!(Pin::new(&mut &self.data.data()[self.bytes_read..]).poll_read(cx, buf))?;
let n =
ready!(Pin::new(&mut &self.data.value()[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
Expand All @@ -197,7 +247,7 @@ impl AssetReader for MemoryAssetReader {
});
reader
})
.ok_or(AssetReaderError::NotFound(PathBuf::new()))
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
}

Expand All @@ -215,7 +265,7 @@ impl AssetReader for MemoryAssetReader {
});
reader
})
.ok_or(AssetReaderError::NotFound(PathBuf::new()))
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
}

Expand All @@ -230,7 +280,7 @@ impl AssetReader for MemoryAssetReader {
let stream: Box<PathStream> = Box::new(DirStream::new(dir));
stream
})
.ok_or(AssetReaderError::NotFound(PathBuf::new()))
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
})
}

Expand All @@ -240,13 +290,6 @@ impl AssetReader for MemoryAssetReader {
) -> BoxedFuture<'a, std::result::Result<bool, AssetReaderError>> {
Box::pin(async move { Ok(self.root.get_dir(path).is_some()) })
}

fn watch_for_changes(
&self,
_event_sender: crossbeam_channel::Sender<super::AssetSourceEvent>,
) -> Option<Box<dyn super::AssetWatcher>> {
None
}
}

#[cfg(test)]
Expand All @@ -264,12 +307,12 @@ pub mod test {
dir.insert_asset(a_path, a_data.clone());
let asset = dir.get_asset(a_path).unwrap();
assert_eq!(asset.path(), a_path);
assert_eq!(asset.data(), a_data);
assert_eq!(asset.value(), a_data);

dir.insert_meta(a_path, a_meta.clone());
let meta = dir.get_metadata(a_path).unwrap();
assert_eq!(meta.path(), a_path);
assert_eq!(meta.data(), a_meta);
assert_eq!(meta.value(), a_meta);

let b_path = Path::new("x/y/b.txt");
let b_data = "b".as_bytes().to_vec();
Expand All @@ -279,10 +322,10 @@ pub mod test {

let asset = dir.get_asset(b_path).unwrap();
assert_eq!(asset.path(), b_path);
assert_eq!(asset.data(), b_data);
assert_eq!(asset.value(), b_data);

let meta = dir.get_metadata(b_path).unwrap();
assert_eq!(meta.path(), b_path);
assert_eq!(meta.data(), b_meta);
assert_eq!(meta.value(), b_meta);
}
}
15 changes: 4 additions & 11 deletions crates/bevy_asset/src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ pub mod file;
pub mod gated;
pub mod memory;
pub mod processor_gated;
pub mod rust_src;
#[cfg(target_arch = "wasm32")]
pub mod wasm;

mod provider;
mod source;

pub use futures_lite::{AsyncReadExt, AsyncWriteExt};
pub use provider::*;
pub use source::*;

use bevy_utils::BoxedFuture;
use crossbeam_channel::Sender;
use futures_io::{AsyncRead, AsyncWrite};
use futures_lite::{ready, Stream};
use std::{
Expand Down Expand Up @@ -65,13 +65,6 @@ pub trait AssetReader: Send + Sync + 'static {
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;

/// Returns an Asset watcher that will send events on the given channel.
/// If this reader does not support watching for changes, this will return [`None`].
fn watch_for_changes(
&self,
event_sender: Sender<AssetSourceEvent>,
) -> Option<Box<dyn AssetWatcher>>;

/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`AssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
Expand Down Expand Up @@ -179,7 +172,7 @@ pub trait AssetWriter: Send + Sync + 'static {
}

/// An "asset source change event" that occurs whenever asset (or asset metadata) is created/added/removed
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssetSourceEvent {
/// An asset at this path was added.
AddedAsset(PathBuf),
Expand Down
Loading