Path: blob/main/crates/bevy_asset/src/io/embedded/mod.rs
6601 views
#[cfg(feature = "embedded_watcher")]1mod embedded_watcher;23#[cfg(feature = "embedded_watcher")]4pub use embedded_watcher::*;56use crate::io::{7memory::{Dir, MemoryAssetReader, Value},8AssetSource, AssetSourceBuilders,9};10use crate::AssetServer;11use alloc::boxed::Box;12use bevy_app::App;13use bevy_ecs::{resource::Resource, world::World};14use std::path::{Path, PathBuf};1516#[cfg(feature = "embedded_watcher")]17use alloc::borrow::ToOwned;1819/// The name of the `embedded` [`AssetSource`],20/// as stored in the [`AssetSourceBuilders`] resource.21pub const EMBEDDED: &str = "embedded";2223/// A [`Resource`] that manages "rust source files" in a virtual in memory [`Dir`], which is intended24/// to be shared with a [`MemoryAssetReader`].25/// Generally this should not be interacted with directly. The [`embedded_asset`] will populate this.26///27/// [`embedded_asset`]: crate::embedded_asset28#[derive(Resource, Default)]29pub struct EmbeddedAssetRegistry {30dir: Dir,31#[cfg(feature = "embedded_watcher")]32root_paths: alloc::sync::Arc<33parking_lot::RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>,34>,35}3637impl EmbeddedAssetRegistry {38/// Inserts a new asset. `full_path` is the full path (as [`file`] would return for that file, if it was capable of39/// running in a non-rust file). `asset_path` is the path that will be used to identify the asset in the `embedded`40/// [`AssetSource`]. `value` is the bytes that will be returned for the asset. This can be _either_ a `&'static [u8]`41/// or a [`Vec<u8>`](alloc::vec::Vec).42#[cfg_attr(43not(feature = "embedded_watcher"),44expect(45unused_variables,46reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."47)48)]49pub fn insert_asset(&self, full_path: PathBuf, asset_path: &Path, value: impl Into<Value>) {50#[cfg(feature = "embedded_watcher")]51self.root_paths52.write()53.insert(full_path.into(), asset_path.to_owned());54self.dir.insert_asset(asset_path, value);55}5657/// Inserts new asset metadata. `full_path` is the full path (as [`file`] would return for that file, if it was capable of58/// running in a non-rust file). `asset_path` is the path that will be used to identify the asset in the `embedded`59/// [`AssetSource`]. `value` is the bytes that will be returned for the asset. This can be _either_ a `&'static [u8]`60/// or a [`Vec<u8>`](alloc::vec::Vec).61#[cfg_attr(62not(feature = "embedded_watcher"),63expect(64unused_variables,65reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."66)67)]68pub fn insert_meta(&self, full_path: &Path, asset_path: &Path, value: impl Into<Value>) {69#[cfg(feature = "embedded_watcher")]70self.root_paths71.write()72.insert(full_path.into(), asset_path.to_owned());73self.dir.insert_meta(asset_path, value);74}7576/// Removes an asset stored using `full_path` (the full path as [`file`] would return for that file, if it was capable of77/// running in a non-rust file). If no asset is stored with at `full_path` its a no-op.78/// It returning `Option` contains the originally stored `Data` or `None`.79pub fn remove_asset(&self, full_path: &Path) -> Option<super::memory::Data> {80self.dir.remove_asset(full_path)81}8283/// Registers the [`EMBEDDED`] [`AssetSource`] with the given [`AssetSourceBuilders`].84pub fn register_source(&self, sources: &mut AssetSourceBuilders) {85let dir = self.dir.clone();86let processed_dir = self.dir.clone();8788#[cfg_attr(89not(feature = "embedded_watcher"),90expect(91unused_mut,92reason = "Variable is only mutated when `embedded_watcher` feature is enabled."93)94)]95let mut source = AssetSource::build()96.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() }))97.with_processed_reader(move || {98Box::new(MemoryAssetReader {99root: processed_dir.clone(),100})101})102// Note that we only add a processed watch warning because we don't want to warn103// noisily about embedded watching (which is niche) when users enable file watching.104.with_processed_watch_warning(105"Consider enabling the `embedded_watcher` cargo feature.",106);107108#[cfg(feature = "embedded_watcher")]109{110let root_paths = self.root_paths.clone();111let dir = self.dir.clone();112let processed_root_paths = self.root_paths.clone();113let processed_dir = self.dir.clone();114source = source115.with_watcher(move |sender| {116Some(Box::new(EmbeddedWatcher::new(117dir.clone(),118root_paths.clone(),119sender,120core::time::Duration::from_millis(300),121)))122})123.with_processed_watcher(move |sender| {124Some(Box::new(EmbeddedWatcher::new(125processed_dir.clone(),126processed_root_paths.clone(),127sender,128core::time::Duration::from_millis(300),129)))130});131}132sources.insert(EMBEDDED, source);133}134}135136/// Trait for the [`load_embedded_asset!`] macro, to access [`AssetServer`]137/// from arbitrary things.138///139/// [`load_embedded_asset!`]: crate::load_embedded_asset140pub trait GetAssetServer {141fn get_asset_server(&self) -> &AssetServer;142}143144impl GetAssetServer for App {145fn get_asset_server(&self) -> &AssetServer {146self.world().get_asset_server()147}148}149150impl GetAssetServer for World {151fn get_asset_server(&self) -> &AssetServer {152self.resource()153}154}155156impl GetAssetServer for AssetServer {157fn get_asset_server(&self) -> &AssetServer {158self159}160}161162/// Load an [embedded asset](crate::embedded_asset).163///164/// This is useful if the embedded asset in question is not publicly exposed, but165/// you need to use it internally.166///167/// # Syntax168///169/// This macro takes two arguments and an optional third one:170/// 1. The asset source. It may be `AssetServer`, `World` or `App`.171/// 2. The path to the asset to embed, as a string literal.172/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`].173/// Consider explicitly typing the closure argument in case of type error.174///175/// # Usage176///177/// The advantage compared to using directly [`AssetServer::load`] is:178/// - This also accepts [`World`] and [`App`] arguments.179/// - This uses the exact same path as `embedded_asset!`, so you can keep it180/// consistent.181///182/// As a rule of thumb:183/// - If the asset in used in the same module as it is declared using `embedded_asset!`,184/// use this macro.185/// - Otherwise, use `AssetServer::load`.186#[macro_export]187macro_rules! load_embedded_asset {188(@get: $path: literal, $provider: expr) => {{189let path = $crate::embedded_path!($path);190let path = $crate::AssetPath::from_path_buf(path).with_source("embedded");191let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider);192(path, asset_server)193}};194($provider: expr, $path: literal, $settings: expr) => {{195let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);196asset_server.load_with_settings(path, $settings)197}};198($provider: expr, $path: literal) => {{199let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);200asset_server.load(path)201}};202}203204/// Returns the [`Path`] for a given `embedded` asset.205/// This is used internally by [`embedded_asset`] and can be used to get a [`Path`]206/// that matches the [`AssetPath`](crate::AssetPath) used by that asset.207///208/// [`embedded_asset`]: crate::embedded_asset209#[macro_export]210macro_rules! embedded_path {211($path_str: expr) => {{212$crate::embedded_path!("src", $path_str)213}};214215($source_path: expr, $path_str: expr) => {{216let crate_name = module_path!().split(':').next().unwrap();217$crate::io::embedded::_embedded_asset_path(218crate_name,219$source_path.as_ref(),220file!().as_ref(),221$path_str.as_ref(),222)223}};224}225226/// Implementation detail of `embedded_path`, do not use this!227///228/// Returns an embedded asset path, given:229/// - `crate_name`: name of the crate where the asset is embedded230/// - `src_prefix`: path prefix of the crate's source directory, relative to the workspace root231/// - `file_path`: `std::file!()` path of the source file where `embedded_path!` is called232/// - `asset_path`: path of the embedded asset relative to `file_path`233#[doc(hidden)]234pub fn _embedded_asset_path(235crate_name: &str,236src_prefix: &Path,237file_path: &Path,238asset_path: &Path,239) -> PathBuf {240let file_path = if cfg!(not(target_family = "windows")) {241// Work around bug: https://github.com/bevyengine/bevy/issues/14246242// Note, this will break any paths on Linux/Mac containing "\"243PathBuf::from(file_path.to_str().unwrap().replace("\\", "/"))244} else {245PathBuf::from(file_path)246};247let mut maybe_parent = file_path.parent();248let after_src = loop {249let Some(parent) = maybe_parent else {250panic!("Failed to find src_prefix {src_prefix:?} in {file_path:?}")251};252if parent.ends_with(src_prefix) {253break file_path.strip_prefix(parent).unwrap();254}255maybe_parent = parent.parent();256};257let asset_path = after_src.parent().unwrap().join(asset_path);258Path::new(crate_name).join(asset_path)259}260261/// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary262/// and registering those bytes with the `embedded` [`AssetSource`].263///264/// This accepts the current [`App`] as the first parameter and a path `&str` (relative to the current file) as the second.265///266/// By default this will generate an [`AssetPath`] using the following rules:267///268/// 1. Search for the first `$crate_name/src/` in the path and trim to the path past that point.269/// 2. Re-add the current `$crate_name` to the front of the path270///271/// For example, consider the following file structure in the theoretical `bevy_rock` crate, which provides a Bevy [`Plugin`](bevy_app::Plugin)272/// that renders fancy rocks for scenes.273///274/// ```text275/// bevy_rock276/// ├── src277/// │ ├── render278/// │ │ ├── rock.wgsl279/// │ │ └── mod.rs280/// │ └── lib.rs281/// └── Cargo.toml282/// ```283///284/// `rock.wgsl` is a WGSL shader asset that the `bevy_rock` plugin author wants to bundle with their crate. They invoke the following285/// in `bevy_rock/src/render/mod.rs`:286///287/// `embedded_asset!(app, "rock.wgsl")`288///289/// `rock.wgsl` can now be loaded by the [`AssetServer`] as follows:290///291/// ```no_run292/// # use bevy_asset::{Asset, AssetServer, load_embedded_asset};293/// # use bevy_reflect::TypePath;294/// # let asset_server: AssetServer = panic!();295/// # #[derive(Asset, TypePath)]296/// # struct Shader;297/// // If we are loading the shader in the same module we used `embedded_asset!`:298/// let shader = load_embedded_asset!(&asset_server, "rock.wgsl");299/// # let _: bevy_asset::Handle<Shader> = shader;300///301/// // If the goal is to expose the asset **to the end user**:302/// let shader = asset_server.load::<Shader>("embedded://bevy_rock/render/rock.wgsl");303/// ```304///305/// Some things to note in the path:306/// 1. The non-default `embedded://` [`AssetSource`]307/// 2. `src` is trimmed from the path308///309/// The default behavior also works for cargo workspaces. Pretend the `bevy_rock` crate now exists in a larger workspace in310/// `$SOME_WORKSPACE/crates/bevy_rock`. The asset path would remain the same, because [`embedded_asset`] searches for the311/// _first instance_ of `bevy_rock/src` in the path.312///313/// For most "standard crate structures" the default works just fine. But for some niche cases (such as cargo examples),314/// the `src` path will not be present. You can override this behavior by adding it as the second argument to [`embedded_asset`]:315///316/// `embedded_asset!(app, "/examples/rock_stuff/", "rock.wgsl")`317///318/// When there are three arguments, the second argument will replace the default `/src/` value. Note that these two are319/// equivalent:320///321/// `embedded_asset!(app, "rock.wgsl")`322/// `embedded_asset!(app, "/src/", "rock.wgsl")`323///324/// This macro uses the [`include_bytes`] macro internally and _will not_ reallocate the bytes.325/// Generally the [`AssetPath`] generated will be predictable, but if your asset isn't326/// available for some reason, you can use the [`embedded_path`] macro to debug.327///328/// Hot-reloading `embedded` assets is supported. Just enable the `embedded_watcher` cargo feature.329///330/// [`AssetPath`]: crate::AssetPath331/// [`embedded_asset`]: crate::embedded_asset332/// [`embedded_path`]: crate::embedded_path333#[macro_export]334macro_rules! embedded_asset {335($app: expr, $path: expr) => {{336$crate::embedded_asset!($app, "src", $path)337}};338339($app: expr, $source_path: expr, $path: expr) => {{340let mut embedded = $app341.world_mut()342.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();343let path = $crate::embedded_path!($source_path, $path);344let watched_path = $crate::io::embedded::watched_path(file!(), $path);345embedded.insert_asset(watched_path, &path, include_bytes!($path));346}};347}348349/// Returns the path used by the watcher.350#[doc(hidden)]351#[cfg(feature = "embedded_watcher")]352pub fn watched_path(source_file_path: &'static str, asset_path: &'static str) -> PathBuf {353PathBuf::from(source_file_path)354.parent()355.unwrap()356.join(asset_path)357}358359/// Returns an empty PathBuf.360#[doc(hidden)]361#[cfg(not(feature = "embedded_watcher"))]362pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str) -> PathBuf {363PathBuf::from("")364}365366/// Loads an "internal" asset by embedding the string stored in the given `path_str` and associates it with the given handle.367#[macro_export]368macro_rules! load_internal_asset {369($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{370let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();371assets.insert($handle.id(), ($loader)(372include_str!($path_str),373std::path::Path::new(file!())374.parent()375.unwrap()376.join($path_str)377.to_string_lossy()378)).unwrap();379}};380// we can't support params without variadic arguments, so internal assets with additional params can't be hot-reloaded381($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{382let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();383assets.insert($handle.id(), ($loader)(384include_str!($path_str),385std::path::Path::new(file!())386.parent()387.unwrap()388.join($path_str)389.to_string_lossy(),390$($param),+391)).unwrap();392}};393}394395/// Loads an "internal" binary asset by embedding the bytes stored in the given `path_str` and associates it with the given handle.396#[macro_export]397macro_rules! load_internal_binary_asset {398($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{399let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();400assets401.insert(402$handle.id(),403($loader)(404include_bytes!($path_str).as_ref(),405std::path::Path::new(file!())406.parent()407.unwrap()408.join($path_str)409.to_string_lossy()410.into(),411),412)413.unwrap();414}};415}416417#[cfg(test)]418mod tests {419use super::{EmbeddedAssetRegistry, _embedded_asset_path};420use std::path::Path;421422// Relative paths show up if this macro is being invoked by a local crate.423// In this case we know the relative path is a sub- path of the workspace424// root.425426#[test]427fn embedded_asset_path_from_local_crate() {428let asset_path = _embedded_asset_path(429"my_crate",430"src".as_ref(),431"src/foo/plugin.rs".as_ref(),432"the/asset.png".as_ref(),433);434assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));435}436437// A blank src_path removes the embedded's file path altogether only the438// asset path remains.439#[test]440fn embedded_asset_path_from_local_crate_blank_src_path_questionable() {441let asset_path = _embedded_asset_path(442"my_crate",443"".as_ref(),444"src/foo/some/deep/path/plugin.rs".as_ref(),445"the/asset.png".as_ref(),446);447assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));448}449450#[test]451#[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")]452fn embedded_asset_path_from_local_crate_bad_src() {453let _asset_path = _embedded_asset_path(454"my_crate",455"NOT-THERE".as_ref(),456"src/foo/plugin.rs".as_ref(),457"the/asset.png".as_ref(),458);459}460461#[test]462fn embedded_asset_path_from_local_example_crate() {463let asset_path = _embedded_asset_path(464"example_name",465"examples/foo".as_ref(),466"examples/foo/example.rs".as_ref(),467"the/asset.png".as_ref(),468);469assert_eq!(asset_path, Path::new("example_name/the/asset.png"));470}471472// Absolute paths show up if this macro is being invoked by an external473// dependency, e.g. one that's being checked out from a crates repo or git.474#[test]475fn embedded_asset_path_from_external_crate() {476let asset_path = _embedded_asset_path(477"my_crate",478"src".as_ref(),479"/path/to/crate/src/foo/plugin.rs".as_ref(),480"the/asset.png".as_ref(),481);482assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));483}484485#[test]486fn embedded_asset_path_from_external_crate_root_src_path() {487let asset_path = _embedded_asset_path(488"my_crate",489"/path/to/crate/src".as_ref(),490"/path/to/crate/src/foo/plugin.rs".as_ref(),491"the/asset.png".as_ref(),492);493assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));494}495496// Although extraneous slashes are permitted at the end, e.g., "src////",497// one or more slashes at the beginning are not.498#[test]499#[should_panic(expected = "Failed to find src_prefix \"////src\" in")]500fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {501let asset_path = _embedded_asset_path(502"my_crate",503"////src".as_ref(),504"/path/to/crate/src/foo/plugin.rs".as_ref(),505"the/asset.png".as_ref(),506);507assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));508}509510// We don't handle this edge case because it is ambiguous with the511// information currently available to the embedded_path macro.512#[test]513fn embedded_asset_path_from_external_crate_is_ambiguous() {514let asset_path = _embedded_asset_path(515"my_crate",516"src".as_ref(),517"/path/to/.cargo/registry/src/crate/src/src/plugin.rs".as_ref(),518"the/asset.png".as_ref(),519);520// Really, should be "my_crate/src/the/asset.png"521assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));522}523524#[test]525fn remove_embedded_asset() {526let reg = EmbeddedAssetRegistry::default();527let path = std::path::PathBuf::from("a/b/asset.png");528reg.insert_asset(path.clone(), &path, &[]);529assert!(reg.dir.get_asset(&path).is_some());530assert!(reg.remove_asset(&path).is_some());531assert!(reg.dir.get_asset(&path).is_none());532assert!(reg.remove_asset(&path).is_none());533}534}535536537