mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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
5 changed files with 209 additions and 8 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -1193,6 +1193,17 @@ description = "Implements a custom AssetReader"
|
|||
category = "Assets"
|
||||
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]]
|
||||
name = "hot_asset_reloading"
|
||||
path = "examples/asset/hot_asset_reloading.rs"
|
||||
|
|
|
@ -107,20 +107,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();
|
||||
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,
|
||||
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`].
|
||||
///
|
||||
|
@ -191,7 +219,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) => {{
|
||||
|
@ -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
|
||||
[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_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
|
||||
|
|
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 a new issue