#[cfg(feature = "embedded_watcher")]
mod embedded_watcher;
#[cfg(feature = "embedded_watcher")]
pub use embedded_watcher::*;
use crate::io::{
memory::{Dir, MemoryAssetReader, Value},
AssetSourceBuilder, AssetSourceBuilders,
};
use crate::AssetServer;
use alloc::boxed::Box;
use bevy_app::App;
use bevy_ecs::{resource::Resource, world::World};
#[cfg(feature = "embedded_watcher")]
use bevy_platform::sync::{Arc, PoisonError, RwLock};
use std::path::{Path, PathBuf};
#[cfg(feature = "embedded_watcher")]
use alloc::borrow::ToOwned;
pub const EMBEDDED: &str = "embedded";
#[derive(Resource, Default)]
pub struct EmbeddedAssetRegistry {
dir: Dir,
#[cfg(feature = "embedded_watcher")]
root_paths: Arc<RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>>,
}
impl EmbeddedAssetRegistry {
pub fn insert_asset(&self, full_path: PathBuf, asset_path: &Path, value: impl Into<Value>) {
self.insert_asset_internal(full_path, asset_path, value.into());
}
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_variables,
reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
)
)]
fn insert_asset_internal(&self, full_path: PathBuf, asset_path: &Path, value: Value) {
#[cfg(feature = "embedded_watcher")]
self.root_paths
.write()
.unwrap_or_else(PoisonError::into_inner)
.insert(full_path.into(), asset_path.to_owned());
self.dir.insert_asset(asset_path, value);
}
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_variables,
reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
)
)]
pub fn insert_meta(&self, full_path: &Path, asset_path: &Path, value: impl Into<Value>) {
#[cfg(feature = "embedded_watcher")]
self.root_paths
.write()
.unwrap_or_else(PoisonError::into_inner)
.insert(full_path.into(), asset_path.to_owned());
self.dir.insert_meta(asset_path, value);
}
pub fn remove_asset(&self, full_path: &Path) -> Option<super::memory::Data> {
self.dir.remove_asset(full_path)
}
pub fn register_source(&self, sources: &mut AssetSourceBuilders) {
let dir = self.dir.clone();
let processed_dir = self.dir.clone();
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_mut,
reason = "Variable is only mutated when `embedded_watcher` feature is enabled."
)
)]
let mut source =
AssetSourceBuilder::new(move || Box::new(MemoryAssetReader { root: dir.clone() }))
.with_processed_reader(move || {
Box::new(MemoryAssetReader {
root: processed_dir.clone(),
})
})
.with_processed_watch_warning(
"Consider enabling the `embedded_watcher` cargo feature.",
);
#[cfg(feature = "embedded_watcher")]
{
let root_paths = self.root_paths.clone();
let dir = self.dir.clone();
let processed_root_paths = self.root_paths.clone();
let processed_dir = self.dir.clone();
source = source
.with_watcher(move |sender| {
Some(Box::new(EmbeddedWatcher::new(
dir.clone(),
root_paths.clone(),
sender,
core::time::Duration::from_millis(300),
)))
})
.with_processed_watcher(move |sender| {
Some(Box::new(EmbeddedWatcher::new(
processed_dir.clone(),
processed_root_paths.clone(),
sender,
core::time::Duration::from_millis(300),
)))
});
}
sources.insert(EMBEDDED, source);
}
}
pub trait GetAssetServer {
fn get_asset_server(&self) -> &AssetServer;
}
impl GetAssetServer for App {
fn get_asset_server(&self) -> &AssetServer {
self.world().get_asset_server()
}
}
impl GetAssetServer for World {
fn get_asset_server(&self) -> &AssetServer {
self.resource()
}
}
impl GetAssetServer for AssetServer {
fn get_asset_server(&self) -> &AssetServer {
self
}
}
#[macro_export]
macro_rules! load_embedded_asset {
(@get: $path: literal, $provider: expr) => {{
let path = $crate::embedded_path!($path);
let path = $crate::AssetPath::from_path_buf(path).with_source("embedded");
let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider);
(path, asset_server)
}};
($provider: expr, $path: literal, $settings: expr) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load_builder().with_settings($settings).load(path)
}};
($provider: expr, $path: literal) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load(path)
}};
}
#[macro_export]
macro_rules! embedded_path {
($path_str: expr) => {{
$crate::embedded_path!("src", $path_str)
}};
($source_path: expr, $path_str: expr) => {{
let crate_name = module_path!().split(':').next().unwrap();
$crate::io::embedded::_embedded_asset_path(
crate_name,
$source_path.as_ref(),
file!().as_ref(),
$path_str.as_ref(),
)
}};
}
#[doc(hidden)]
pub fn _embedded_asset_path(
crate_name: &str,
src_prefix: &Path,
file_path: &Path,
asset_path: &Path,
) -> PathBuf {
let file_path = if cfg!(not(target_family = "windows")) {
PathBuf::from(file_path.to_str().unwrap().replace("\\", "/"))
} else {
PathBuf::from(file_path)
};
let mut maybe_parent = file_path.parent();
let after_src = loop {
let Some(parent) = maybe_parent else {
panic!("Failed to find src_prefix {src_prefix:?} in {file_path:?}")
};
if parent.ends_with(src_prefix) {
break file_path.strip_prefix(parent).unwrap();
}
maybe_parent = parent.parent();
};
let asset_path = after_src.parent().unwrap().join(asset_path);
Path::new(crate_name).join(asset_path)
}
#[macro_export]
macro_rules! embedded_asset {
($app: expr, $path: expr) => {{
$crate::embedded_asset!($app, "src", $path)
}};
($app: expr, $source_path: expr, $path: expr) => {{
let mut embedded = $app
.world_mut()
.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();
let path = $crate::embedded_path!($source_path, $path);
let watched_path = $crate::io::embedded::watched_path(file!(), $path);
embedded.insert_asset(watched_path, &path, include_bytes!($path));
}};
}
#[doc(hidden)]
#[cfg(feature = "embedded_watcher")]
pub fn watched_path(source_file_path: &'static str, asset_path: &'static str) -> PathBuf {
PathBuf::from(source_file_path)
.parent()
.unwrap()
.join(asset_path)
}
#[doc(hidden)]
#[cfg(not(feature = "embedded_watcher"))]
pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str) -> PathBuf {
PathBuf::from("")
}
#[macro_export]
macro_rules! load_internal_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy()
)).unwrap();
}};
($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy(),
$($param),+
)).unwrap();
}};
}
#[macro_export]
macro_rules! load_internal_binary_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets
.insert(
$handle.id(),
($loader)(
include_bytes!($path_str).as_ref(),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy()
.into(),
),
)
.unwrap();
}};
}
#[cfg(test)]
mod tests {
use super::{_embedded_asset_path, EmbeddedAssetRegistry};
use std::path::Path;
#[test]
fn embedded_asset_path_from_local_crate() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
#[test]
fn embedded_asset_path_from_local_crate_blank_src_path_questionable() {
let asset_path = _embedded_asset_path(
"my_crate",
"".as_ref(),
"src/foo/some/deep/path/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
}
#[test]
#[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")]
fn embedded_asset_path_from_local_crate_bad_src() {
let _asset_path = _embedded_asset_path(
"my_crate",
"NOT-THERE".as_ref(),
"src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
}
#[test]
fn embedded_asset_path_from_local_example_crate() {
let asset_path = _embedded_asset_path(
"example_name",
"examples/foo".as_ref(),
"examples/foo/example.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("example_name/the/asset.png"));
}
#[test]
fn embedded_asset_path_from_external_crate() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
#[test]
fn embedded_asset_path_from_external_crate_root_src_path() {
let asset_path = _embedded_asset_path(
"my_crate",
"/path/to/crate/src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
#[test]
#[should_panic(expected = "Failed to find src_prefix \"////src\" in")]
fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {
let asset_path = _embedded_asset_path(
"my_crate",
"////src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
#[test]
fn embedded_asset_path_from_external_crate_is_ambiguous() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"/path/to/.cargo/registry/src/crate/src/src/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
}
#[test]
fn remove_embedded_asset() {
let reg = EmbeddedAssetRegistry::default();
let path = std::path::PathBuf::from("a/b/asset.png");
reg.insert_asset(path.clone(), &path, &[]);
assert!(reg.dir.get_asset(&path).is_some());
assert!(reg.remove_asset(&path).is_some());
assert!(reg.dir.get_asset(&path).is_none());
assert!(reg.remove_asset(&path).is_none());
}
}