Fix embedded asset path manipulation (#10383)
# Objective Fixes #10377 ## Solution Use `Path::strip_prefix` instead of `str::split`. Avoid any explicit "/" characters in path manipulation. --- ## Changelog - Added: example of embedded asset loading - Added: support embedded assets in external crates - Fixed: resolution of embedded assets - Fixed: unexpected runtime panic during asset path resolution ## Migration Guide No API changes. --------- Co-authored-by: Shane Celis <shane.celis@gmail.com>
This commit is contained in:
parent
6f2eec8f78
commit
176223b406
11
Cargo.toml
11
Cargo.toml
@ -1193,6 +1193,17 @@ description = "Implements a custom AssetReader"
|
|||||||
category = "Assets"
|
category = "Assets"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "embedded_asset"
|
||||||
|
path = "examples/asset/embedded_asset.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]]
|
[[example]]
|
||||||
name = "hot_asset_reloading"
|
name = "hot_asset_reloading"
|
||||||
path = "examples/asset/hot_asset_reloading.rs"
|
path = "examples/asset/hot_asset_reloading.rs"
|
||||||
|
@ -107,20 +107,48 @@ impl EmbeddedAssetRegistry {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! embedded_path {
|
macro_rules! embedded_path {
|
||||||
($path_str: expr) => {{
|
($path_str: expr) => {{
|
||||||
embedded_path!("/src/", $path_str)
|
embedded_path!("src", $path_str)
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($source_path: expr, $path_str: expr) => {{
|
($source_path: expr, $path_str: expr) => {{
|
||||||
let crate_name = module_path!().split(':').next().unwrap();
|
let crate_name = module_path!().split(':').next().unwrap();
|
||||||
let after_src = file!().split($source_path).nth(1).unwrap();
|
$crate::io::embedded::_embedded_asset_path(
|
||||||
let file_path = std::path::Path::new(after_src)
|
crate_name,
|
||||||
.parent()
|
$source_path.as_ref(),
|
||||||
.unwrap()
|
file!().as_ref(),
|
||||||
.join($path_str);
|
$path_str.as_ref(),
|
||||||
std::path::Path::new(crate_name).join(file_path)
|
)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// 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`].
|
/// and registering those bytes with the `embedded` [`AssetSource`].
|
||||||
///
|
///
|
||||||
@ -191,7 +219,7 @@ macro_rules! embedded_path {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! embedded_asset {
|
macro_rules! embedded_asset {
|
||||||
($app: ident, $path: expr) => {{
|
($app: ident, $path: expr) => {{
|
||||||
embedded_asset!($app, "/src/", $path)
|
embedded_asset!($app, "src", $path)
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($app: ident, $source_path: expr, $path: expr) => {{
|
($app: ident, $source_path: expr, $path: expr) => {{
|
||||||
@ -269,3 +297,111 @@ 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
"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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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////",
|
||||||
|
// 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() {
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -193,6 +193,7 @@ Example | Description
|
|||||||
[Asset Processing](../examples/asset/processing/asset_processing.rs) | Demonstrates how to process and load custom assets
|
[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](../examples/asset/custom_asset.rs) | Implements a custom asset loader
|
||||||
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
|
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
|
||||||
|
[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
|
[Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk
|
||||||
|
|
||||||
## Async Tasks
|
## Async Tasks
|
||||||
|
BIN
examples/asset/bevy_pixel_light.png
Normal file
BIN
examples/asset/bevy_pixel_light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 B |
53
examples/asset/embedded_asset.rs
Normal file
53
examples/asset/embedded_asset.rs
Normal file
@ -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";
|
||||||
|
|
||||||
|
// 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…
Reference in New Issue
Block a user