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

Fix embedded asset path manipulation #10383

Merged
merged 9 commits into from
Feb 2, 2024
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = true

[[example]]
name = "hot_asset_reloading"
path = "examples/asset/hot_asset_reloading.rs"
Expand Down
104 changes: 96 additions & 8 deletions crates/bevy_asset/src/io/embedded/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,48 @@ 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();
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
let after_src = file!().split($source_path).nth(1).unwrap();
let file_path = std::path::Path::new(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,
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
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();
};
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
let asset_path = after_src.parent().unwrap().join(asset_path);
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
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`].
///
Expand Down Expand Up @@ -186,7 +214,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) => {{
Expand Down Expand Up @@ -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"));
}
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added examples/asset/bevy_pixel_light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions examples/asset/embedded.rs
Original file line number Diff line number Diff line change
@@ -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<AssetServer>) {
commands.spawn(Camera2dBundle::default());

// Each example is its own crate (with name from [[example]] in Cargo.toml).
let crate_name = "embedded_asset";
bonsairobo marked this conversation as resolved.
Show resolved Hide resolved

// 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()
});
}
Loading