From 73ca256d677bb4a51c0a93ee8cc2715cde5e960f Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 5 Nov 2023 01:04:38 -0700 Subject: [PATCH 1/8] add example of embedded assets --- Cargo.toml | 11 ++++++ examples/asset/bevy_pixel_light.png | Bin 0 -> 182 bytes examples/asset/embedded.rs | 53 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 examples/asset/bevy_pixel_light.png create mode 100644 examples/asset/embedded.rs diff --git a/Cargo.toml b/Cargo.toml index 70e9a4ca7cb15..a0fdc09e81829 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1099,6 +1099,17 @@ description = "Implements a custom AssetReader" category = "Assets" wasm = true +[[example]] +name = "embedded_asset" +path = "examples/asset/embedded.rs" +doc-scrape-examples = true + +[package.metadata.example.embedded_asset] +name = "Embedded Asset" +description = "Embed an asset in the application binary and load it" +category = "Assets" +wasm = false + [[example]] name = "hot_asset_reloading" path = "examples/asset/hot_asset_reloading.rs" diff --git a/examples/asset/bevy_pixel_light.png b/examples/asset/bevy_pixel_light.png new file mode 100644 index 0000000000000000000000000000000000000000..f6225fe25eabb74acbc9256f2a4fec01eb28ba21 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^4M3d0#0(@?h)lW#q<8{+LR^8ghK9!B!-q>tOPA}i zlmq#UB|(0{3=Yq3qyae*o-U3d9>;$*z3Hx^S(u z={dg%H{z8g?*FLHGW)<{9 literal 0 HcmV?d00001 diff --git a/examples/asset/embedded.rs b/examples/asset/embedded.rs new file mode 100644 index 0000000000000..af865b6007f5e --- /dev/null +++ b/examples/asset/embedded.rs @@ -0,0 +1,53 @@ +//! Example of loading an embedded asset. + +use bevy::asset::{embedded_asset, io::AssetSourceId, AssetPath}; +use bevy::prelude::*; +use std::path::Path; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, EmbeddedAssetPlugin)) + .add_systems(Startup, setup) + .run(); +} + +struct EmbeddedAssetPlugin; + +impl Plugin for EmbeddedAssetPlugin { + fn build(&self, app: &mut App) { + // We get to choose some prefix relative to the workspace root which + // will be ignored in "embedded://" asset paths. + let omit_prefix = "examples/asset"; + // Path to asset must be relative to this file, because that's how + // include_bytes! works. + embedded_asset!(app, omit_prefix, "bevy_pixel_light.png"); + } +} + +fn setup(mut commands: Commands, asset_server: Res) { + commands.spawn(Camera2dBundle::default()); + + // Each example is its own crate (with name from [[example]] in Cargo.toml). + let crate_name = "embedded_asset"; + + // The actual file path relative to workspace root is + // "examples/asset/bevy_pixel_light.png". + // + // We omit the "examples/asset" from the embedded_asset! call and replace it + // with the crate name. + let path = Path::new(crate_name).join("bevy_pixel_light.png"); + let source = AssetSourceId::from("embedded"); + let asset_path = AssetPath::from_path(&path).with_source(source); + + // You could also parse this URL-like string representation for the asset + // path. + assert_eq!( + asset_path, + "embedded://embedded_asset/bevy_pixel_light.png".into() + ); + + commands.spawn(SpriteBundle { + texture: asset_server.load(asset_path), + ..default() + }); +} From cc90faf2adef605a2adabdce2428d2bb66239214 Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sat, 4 Nov 2023 19:40:42 -0700 Subject: [PATCH 2/8] fix: use Path API for AssetPath manipulation in embedded_path macro Path manipulation is far less error prone when using the std::path::Path API. This lets us remove all instances of "/" which is not a portable path separator. --- crates/bevy_asset/src/io/embedded/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index e5470cd3d5c3f..6370f09348970 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -104,16 +104,16 @@ impl EmbeddedAssetRegistry { #[macro_export] macro_rules! embedded_path { ($path_str: expr) => {{ - embedded_path!("/src/", $path_str) + embedded_path!("src", $path_str) }}; ($source_path: expr, $path_str: expr) => {{ let crate_name = module_path!().split(':').next().unwrap(); - let after_src = file!().split($source_path).nth(1).unwrap(); - let file_path = std::path::Path::new(after_src) - .parent() - .unwrap() - .join($path_str); + let file_path = std::path::Path::new(file!()); + let after_src = file_path + .strip_prefix($source_path) + .unwrap_or_else(|_| panic!("{file_path:?} does not have prefix {}", $source_path)); + let file_path = after_src.parent().unwrap().join($path_str); std::path::Path::new(crate_name).join(file_path) }}; } @@ -186,7 +186,7 @@ macro_rules! embedded_path { #[macro_export] macro_rules! embedded_asset { ($app: ident, $path: expr) => {{ - embedded_asset!($app, "/src/", $path) + embedded_asset!($app, "src", $path) }}; ($app: ident, $source_path: expr, $path: expr) => {{ From 7973c9fcff3db94a404140b59ab7a6d23c5189e8 Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 5 Nov 2023 01:54:09 -0800 Subject: [PATCH 3/8] allow "wasm = true" in embedded_asset example I tested it with wasm-server-runner, and it should work. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a0fdc09e81829..ca4d064f4903e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1108,7 +1108,7 @@ doc-scrape-examples = true name = "Embedded Asset" description = "Embed an asset in the application binary and load it" category = "Assets" -wasm = false +wasm = true [[example]] name = "hot_asset_reloading" From 21eb8077e3c09d87e5463b5c9f9e29dcf8ebe576 Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 5 Nov 2023 01:56:05 -0800 Subject: [PATCH 4/8] add example to examples/README.md --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index db73c982f060c..4770f7c7daa8b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -185,6 +185,7 @@ Example | Description [Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets [Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader [Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader +[Embedded Asset](../examples/asset/embedded.rs) | Embed an asset in the application binary and load it [Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk ## Async Tasks From f8e8e0d56934d7e5c4f8d4bc996901a72b1130f5 Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 5 Nov 2023 16:37:22 -0800 Subject: [PATCH 5/8] fix: support running embedded_path! from a "remote" crate This includes if cargo is building a crate that depends on some crates.io or git repo crate that contains embedded assets. --- crates/bevy_asset/src/io/embedded/mod.rs | 100 +++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index 6370f09348970..951569412126c 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -109,15 +109,43 @@ macro_rules! embedded_path { ($source_path: expr, $path_str: expr) => {{ let crate_name = module_path!().split(':').next().unwrap(); - let file_path = std::path::Path::new(file!()); - let after_src = file_path - .strip_prefix($source_path) - .unwrap_or_else(|_| panic!("{file_path:?} does not have prefix {}", $source_path)); - let file_path = after_src.parent().unwrap().join($path_str); - std::path::Path::new(crate_name).join(file_path) + $crate::io::embedded::_embedded_asset_path( + crate_name, + $source_path.as_ref(), + file!().as_ref(), + $path_str.as_ref(), + ) }}; } +/// Implementation detail of `embedded_path`, do not use this! +/// +/// Returns an embedded asset path, given: +/// - `crate_name`: name of the crate where the asset is embedded +/// - `src_prefix`: path prefix of the crate's source directory, relative to the workspace root +/// - `file_path`: `std::file!()` path of the source file where `embedded_path!` is called +/// - `asset_path`: path of the embedded asset relative to `file_path` +#[doc(hidden)] +pub fn _embedded_asset_path( + crate_name: &str, + src_prefix: &Path, + file_path: &Path, + asset_path: &Path, +) -> PathBuf { + 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) +} + /// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary /// and registering those bytes with the `embedded` [`AssetSource`]. /// @@ -250,3 +278,63 @@ macro_rules! load_internal_binary_asset { ); }}; } + +#[cfg(test)] +mod tests { + use super::_embedded_asset_path; + use std::path::Path; + + // Relative paths show up if this macro is being invoked by a local crate. + // In this case we know the relative path is a sub- path of the workspace + // root. + + #[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_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")); + } + + // Absolute paths show up if this macro is being invoked by an external + // dependency, e.g. one that's being checked out from a crates repo or git. + + #[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")); + } + + // We don't handle this edge case because it is ambiguous with the + // information currently available to the embedded_path macro. + #[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(), + ); + // Really, should be "my_crate/src/the/asset.png" + assert_eq!(asset_path, Path::new("my_crate/the/asset.png")); + } +} From 56664c9209547b58a16cecca17d3a080809e4b9f Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Mon, 13 Nov 2023 01:17:15 -0800 Subject: [PATCH 6/8] rename embedded.rs to embedded_asset.rs to match example name --- Cargo.toml | 2 +- examples/README.md | 2 +- examples/asset/{embedded.rs => embedded_asset.rs} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename examples/asset/{embedded.rs => embedded_asset.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index ca4d064f4903e..fb9e44f8b093f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1101,7 +1101,7 @@ wasm = true [[example]] name = "embedded_asset" -path = "examples/asset/embedded.rs" +path = "examples/asset/embedded_asset.rs" doc-scrape-examples = true [package.metadata.example.embedded_asset] diff --git a/examples/README.md b/examples/README.md index 4770f7c7daa8b..39e13a8647fa4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -185,7 +185,7 @@ Example | Description [Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets [Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader [Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader -[Embedded Asset](../examples/asset/embedded.rs) | Embed an asset in the application binary and load it +[Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it [Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk ## Async Tasks diff --git a/examples/asset/embedded.rs b/examples/asset/embedded_asset.rs similarity index 100% rename from examples/asset/embedded.rs rename to examples/asset/embedded_asset.rs From 04b1e9874bf443fc6b706bc582497a6d924374b8 Mon Sep 17 00:00:00 2001 From: Shane Celis Date: Mon, 15 Jan 2024 02:27:54 -0500 Subject: [PATCH 7/8] test: Add tests, include failure conditions. --- crates/bevy_asset/src/io/embedded/mod.rs | 107 ++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index 951569412126c..67e4b182309a6 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -299,6 +299,73 @@ mod tests { assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png")); } + // A blank src_path removes the embedded's file path altogether. + #[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")); + } + + #[should_panic(expected = "Failed to find src_prefix \"src\" in \"\"")] + #[test] + fn embedded_asset_path_from_local_crate_blank_file_path() { + let asset_path = _embedded_asset_path( + "my_crate", + "src".as_ref(), + "".as_ref(), + "the/asset.png".as_ref(), + ); + assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png")); + } + + // Can have extraneous slashses at the end of source_path. + #[test] + fn embedded_asset_path_from_local_crate_extraneous_slalsh() { + 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")); + + 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] + #[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(), + ); + } + + // Trying to provoke a panic from `parent().unwrap()`, but it's solid. + #[test] + #[should_panic(expected = "Failed to find src_prefix \"mod.rs\" in \"src")] + fn embedded_asset_path_bad_src_path() { + let _asset_path = _embedded_asset_path( + "my_crate", + "mod.rs".as_ref(), + "src/mod.rs".as_ref(), + "the/asset.png".as_ref(), + ); + } + #[test] fn embedded_asset_path_from_local_example_crate() { let asset_path = _embedded_asset_path( @@ -312,7 +379,6 @@ mod tests { // Absolute paths show up if this macro is being invoked by an external // dependency, e.g. one that's being checked out from a crates repo or git. - #[test] fn embedded_asset_path_from_external_crate() { let asset_path = _embedded_asset_path( @@ -324,6 +390,45 @@ mod tests { 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")); + } + + // Although extraneous slashes are permitted at the end, e.g., "src////", a + // slash at the beginning is not. + #[test] + #[should_panic(expected = "Failed to find src_prefix \"/src\" in")] + fn embedded_asset_path_from_external_crate_bad_root_src_path() { + 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")); + } + + // Although extraneous slashes are permitted at the end, e.g., "src////", a + // slash at the beginning is not. + #[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")); + } + // We don't handle this edge case because it is ambiguous with the // information currently available to the embedded_path macro. #[test] From fb28f6925ed6c889fdfb30c690700adc89bc0aea Mon Sep 17 00:00:00 2001 From: Shane Celis Date: Mon, 15 Jan 2024 06:18:48 -0500 Subject: [PATCH 8/8] excise: Remove some tests and take feedback from bonsairobo. --- crates/bevy_asset/src/io/embedded/mod.rs | 65 ++---------------------- 1 file changed, 4 insertions(+), 61 deletions(-) diff --git a/crates/bevy_asset/src/io/embedded/mod.rs b/crates/bevy_asset/src/io/embedded/mod.rs index 67e4b182309a6..13b0ec423b9c8 100644 --- a/crates/bevy_asset/src/io/embedded/mod.rs +++ b/crates/bevy_asset/src/io/embedded/mod.rs @@ -299,7 +299,8 @@ mod tests { assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png")); } - // A blank src_path removes the embedded's file path altogether. + // A blank src_path removes the embedded's file path altogether only the + // asset path remains. #[test] fn embedded_asset_path_from_local_crate_blank_src_path_questionable() { let asset_path = _embedded_asset_path( @@ -311,38 +312,6 @@ mod tests { assert_eq!(asset_path, Path::new("my_crate/the/asset.png")); } - #[should_panic(expected = "Failed to find src_prefix \"src\" in \"\"")] - #[test] - fn embedded_asset_path_from_local_crate_blank_file_path() { - let asset_path = _embedded_asset_path( - "my_crate", - "src".as_ref(), - "".as_ref(), - "the/asset.png".as_ref(), - ); - assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png")); - } - - // Can have extraneous slashses at the end of source_path. - #[test] - fn embedded_asset_path_from_local_crate_extraneous_slalsh() { - 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")); - - 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] #[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")] fn embedded_asset_path_from_local_crate_bad_src() { @@ -354,18 +323,6 @@ mod tests { ); } - // Trying to provoke a panic from `parent().unwrap()`, but it's solid. - #[test] - #[should_panic(expected = "Failed to find src_prefix \"mod.rs\" in \"src")] - fn embedded_asset_path_bad_src_path() { - let _asset_path = _embedded_asset_path( - "my_crate", - "mod.rs".as_ref(), - "src/mod.rs".as_ref(), - "the/asset.png".as_ref(), - ); - } - #[test] fn embedded_asset_path_from_local_example_crate() { let asset_path = _embedded_asset_path( @@ -401,22 +358,8 @@ mod tests { assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png")); } - // Although extraneous slashes are permitted at the end, e.g., "src////", a - // slash at the beginning is not. - #[test] - #[should_panic(expected = "Failed to find src_prefix \"/src\" in")] - fn embedded_asset_path_from_external_crate_bad_root_src_path() { - 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")); - } - - // Although extraneous slashes are permitted at the end, e.g., "src////", a - // slash at the beginning is not. + // Although extraneous slashes are permitted at the end, e.g., "src////", + // one or more slashes at the beginning are not. #[test] #[should_panic(expected = "Failed to find src_prefix \"////src\" in")] fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {