mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
# Objective - Make the problem with #21269 more concrete. ## Solution - Add a test to show that loaders during processing can read processed assets. - Add an ignored test to show that loaders during processing cannot currently read source assets. - This also tests that nested asset loads during processing actually works. ## Testing - Oops! All tests!
This commit is contained in:
@@ -725,6 +725,8 @@ mod tests {
|
||||
AssetPath, AssetPlugin, AssetServer, Assets, InvalidGenerationError, LoadState,
|
||||
UnapprovedPathMode, UntypedHandle,
|
||||
};
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
format,
|
||||
@@ -2602,4 +2604,440 @@ mod tests {
|
||||
r#"(text:"abc_def",dependencies:[],embedded_dependencies:[],sub_texts:[])"#
|
||||
);
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(Asset, TypePath, Serialize, Deserialize)]
|
||||
struct FakeGltf {
|
||||
gltf_nodes: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
struct FakeGltfLoader;
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl AssetLoader for FakeGltfLoader {
|
||||
type Asset = FakeGltf;
|
||||
type Settings = ();
|
||||
type Error = std::io::Error;
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
reader: &mut dyn Reader,
|
||||
_settings: &Self::Settings,
|
||||
_load_context: &mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
let mut bytes = vec![];
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
ron::de::from_bytes(&bytes)
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["gltf"]
|
||||
}
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(Asset, TypePath, Serialize, Deserialize)]
|
||||
struct FakeBsn {
|
||||
parent_bsn: Option<String>,
|
||||
nodes: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
// This loader loads the BSN but as an "inlined" scene. We read the original BSN and create a
|
||||
// scene that holds all the data including parents.
|
||||
// TODO: It would be nice if the inlining was actually done as an `AssetTransformer`, but
|
||||
// `Process` currently has no way to load nested assets.
|
||||
struct FakeBsnLoader;
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl AssetLoader for FakeBsnLoader {
|
||||
type Asset = FakeBsn;
|
||||
type Settings = ();
|
||||
type Error = std::io::Error;
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
reader: &mut dyn Reader,
|
||||
_settings: &Self::Settings,
|
||||
load_context: &mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
let mut bytes = vec![];
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
let bsn: FakeBsn = ron::de::from_bytes(&bytes)
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))?;
|
||||
|
||||
if bsn.parent_bsn.is_none() {
|
||||
return Ok(bsn);
|
||||
}
|
||||
|
||||
let parent_bsn = bsn.parent_bsn.unwrap();
|
||||
let parent_bsn = load_context
|
||||
.loader()
|
||||
.immediate()
|
||||
.load(parent_bsn)
|
||||
.await
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
|
||||
let mut new_bsn: FakeBsn = parent_bsn.take();
|
||||
for (name, node) in bsn.nodes {
|
||||
new_bsn.nodes.insert(name, node);
|
||||
}
|
||||
Ok(new_bsn)
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["bsn"]
|
||||
}
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(TypePath)]
|
||||
struct GltfToBsn;
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl AssetTransformer for GltfToBsn {
|
||||
type AssetInput = FakeGltf;
|
||||
type AssetOutput = FakeBsn;
|
||||
type Settings = ();
|
||||
type Error = std::io::Error;
|
||||
|
||||
async fn transform<'a>(
|
||||
&'a self,
|
||||
mut asset: TransformedAsset<Self::AssetInput>,
|
||||
_settings: &'a Self::Settings,
|
||||
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
||||
let bsn = FakeBsn {
|
||||
parent_bsn: None,
|
||||
// Pretend we converted all the glTF nodes into BSN's format.
|
||||
nodes: core::mem::take(&mut asset.get_mut().gltf_nodes),
|
||||
};
|
||||
Ok(asset.replace_asset(bsn))
|
||||
}
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(TypePath)]
|
||||
struct FakeBsnSaver;
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl AssetSaver for FakeBsnSaver {
|
||||
type Asset = FakeBsn;
|
||||
type Error = std::io::Error;
|
||||
type OutputLoader = FakeBsnLoader;
|
||||
type Settings = ();
|
||||
|
||||
async fn save(
|
||||
&self,
|
||||
writer: &mut crate::io::Writer,
|
||||
asset: crate::saver::SavedAsset<'_, Self::Asset>,
|
||||
_settings: &Self::Settings,
|
||||
) -> Result<(), Self::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
use ron::ser::PrettyConfig;
|
||||
|
||||
let ron_string =
|
||||
ron::ser::to_string_pretty(asset.get(), PrettyConfig::new().new_line("\n"))
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
|
||||
|
||||
writer.write_all(ron_string.as_bytes()).await
|
||||
}
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[test]
|
||||
fn asset_processor_loading_can_read_processed_assets() {
|
||||
use crate::transformer::IdentityAssetTransformer;
|
||||
|
||||
let AppWithProcessor {
|
||||
mut app,
|
||||
source_dir,
|
||||
processed_dir,
|
||||
} = create_app_with_asset_processor();
|
||||
|
||||
// This processor loads a gltf file, converts it to BSN and then saves out the BSN.
|
||||
type GltfProcessor = LoadTransformAndSave<FakeGltfLoader, GltfToBsn, FakeBsnSaver>;
|
||||
// This processor loads a BSN file (which "inlines" parent BSNs at load), and then saves the
|
||||
// inlined BSN.
|
||||
type BsnProcessor =
|
||||
LoadTransformAndSave<FakeBsnLoader, IdentityAssetTransformer<FakeBsn>, FakeBsnSaver>;
|
||||
app.register_asset_loader(FakeBsnLoader)
|
||||
.register_asset_loader(FakeGltfLoader)
|
||||
.register_asset_processor(GltfProcessor::new(GltfToBsn, FakeBsnSaver))
|
||||
.register_asset_processor(BsnProcessor::new(
|
||||
IdentityAssetTransformer::new(),
|
||||
FakeBsnSaver,
|
||||
))
|
||||
.set_default_asset_processor::<GltfProcessor>("gltf")
|
||||
.set_default_asset_processor::<BsnProcessor>("bsn");
|
||||
|
||||
let gltf_path = Path::new("abc.gltf");
|
||||
source_dir.insert_asset_text(
|
||||
gltf_path,
|
||||
r#"(
|
||||
gltf_nodes: {
|
||||
"name": "thing",
|
||||
"position": "123",
|
||||
}
|
||||
)"#,
|
||||
);
|
||||
let bsn_path = Path::new("def.bsn");
|
||||
// The bsn tries to load the gltf as a bsn. This only works if the bsn can read processed
|
||||
// assets.
|
||||
source_dir.insert_asset_text(
|
||||
bsn_path,
|
||||
r#"(
|
||||
parent_bsn: Some("abc.gltf"),
|
||||
nodes: {
|
||||
"position": "456",
|
||||
"color": "red",
|
||||
},
|
||||
)"#,
|
||||
);
|
||||
|
||||
// Start the app, which also starts the asset processor.
|
||||
app.update();
|
||||
|
||||
// Wait for all processing to finish.
|
||||
bevy_tasks::block_on(
|
||||
app.world()
|
||||
.resource::<AssetProcessor>()
|
||||
.data()
|
||||
.wait_until_finished(),
|
||||
);
|
||||
|
||||
let processed_bsn = processed_dir.get_asset(bsn_path).unwrap();
|
||||
let processed_bsn = str::from_utf8(processed_bsn.value()).unwrap();
|
||||
// The processed bsn should have been "inlined", so no parent and "overlaid" nodes.
|
||||
assert_eq!(
|
||||
processed_bsn,
|
||||
r#"(
|
||||
parent_bsn: None,
|
||||
nodes: {
|
||||
"color": "red",
|
||||
"name": "thing",
|
||||
"position": "456",
|
||||
},
|
||||
)"#
|
||||
);
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[test]
|
||||
fn asset_processor_loading_can_read_source_assets() {
|
||||
let AppWithProcessor {
|
||||
mut app,
|
||||
source_dir,
|
||||
processed_dir,
|
||||
} = create_app_with_asset_processor();
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct FakeGltfxData {
|
||||
// These are the file paths to the gltfs.
|
||||
gltfs: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Asset, TypePath)]
|
||||
struct FakeGltfx {
|
||||
gltfs: Vec<FakeGltf>,
|
||||
}
|
||||
|
||||
#[derive(TypePath)]
|
||||
struct FakeGltfxLoader;
|
||||
|
||||
impl AssetLoader for FakeGltfxLoader {
|
||||
type Asset = FakeGltfx;
|
||||
type Error = std::io::Error;
|
||||
type Settings = ();
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
reader: &mut dyn Reader,
|
||||
_settings: &Self::Settings,
|
||||
load_context: &mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
let mut buf = vec![];
|
||||
reader.read_to_end(&mut buf).await?;
|
||||
|
||||
let gltfx_data: FakeGltfxData = ron::de::from_bytes(&buf)
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
|
||||
|
||||
let mut gltfs = vec![];
|
||||
for gltf in gltfx_data.gltfs.into_iter() {
|
||||
// gltfx files come from "generic" software that doesn't know anything about
|
||||
// Bevy, so it needs to load the source assets to make sense.
|
||||
let gltf = load_context
|
||||
.loader()
|
||||
.immediate()
|
||||
.load(gltf)
|
||||
.await
|
||||
.map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
|
||||
gltfs.push(gltf.take());
|
||||
}
|
||||
|
||||
Ok(FakeGltfx { gltfs })
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["gltfx"]
|
||||
}
|
||||
}
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[derive(TypePath)]
|
||||
struct GltfxToBsn;
|
||||
|
||||
// The asset processor currently requires multi_threaded.
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
impl AssetTransformer for GltfxToBsn {
|
||||
type AssetInput = FakeGltfx;
|
||||
type AssetOutput = FakeBsn;
|
||||
type Settings = ();
|
||||
type Error = std::io::Error;
|
||||
|
||||
async fn transform<'a>(
|
||||
&'a self,
|
||||
mut asset: TransformedAsset<Self::AssetInput>,
|
||||
_settings: &'a Self::Settings,
|
||||
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
||||
let gltfx = asset.get_mut();
|
||||
|
||||
// Merge together all the gltfs from the gltfx into one big bsn.
|
||||
let bsn = gltfx.gltfs.drain(..).fold(
|
||||
FakeBsn {
|
||||
parent_bsn: None,
|
||||
nodes: Default::default(),
|
||||
},
|
||||
|mut bsn, gltf| {
|
||||
for (key, value) in gltf.gltf_nodes {
|
||||
bsn.nodes.insert(key, value);
|
||||
}
|
||||
bsn
|
||||
},
|
||||
);
|
||||
|
||||
Ok(asset.replace_asset(bsn))
|
||||
}
|
||||
}
|
||||
|
||||
// This processor loads a gltf file, converts it to BSN and then saves out the BSN.
|
||||
type GltfProcessor = LoadTransformAndSave<FakeGltfLoader, GltfToBsn, FakeBsnSaver>;
|
||||
// This processor loads a gltfx file (including its gltf files) and converts it to BSN.
|
||||
type GltfxProcessor = LoadTransformAndSave<FakeGltfxLoader, GltfxToBsn, FakeBsnSaver>;
|
||||
app.register_asset_loader(FakeGltfLoader)
|
||||
.register_asset_loader(FakeGltfxLoader)
|
||||
.register_asset_loader(FakeBsnLoader)
|
||||
.register_asset_processor(GltfProcessor::new(GltfToBsn, FakeBsnSaver))
|
||||
.register_asset_processor(GltfxProcessor::new(GltfxToBsn, FakeBsnSaver))
|
||||
.set_default_asset_processor::<GltfProcessor>("gltf")
|
||||
.set_default_asset_processor::<GltfxProcessor>("gltfx");
|
||||
|
||||
let gltf_path_1 = Path::new("abc.gltf");
|
||||
source_dir.insert_asset_text(
|
||||
gltf_path_1,
|
||||
r#"(
|
||||
gltf_nodes: {
|
||||
"name": "thing",
|
||||
"position": "123",
|
||||
}
|
||||
)"#,
|
||||
);
|
||||
let gltf_path_2 = Path::new("def.gltf");
|
||||
source_dir.insert_asset_text(
|
||||
gltf_path_2,
|
||||
r#"(
|
||||
gltf_nodes: {
|
||||
"velocity": "456",
|
||||
"color": "red",
|
||||
}
|
||||
)"#,
|
||||
);
|
||||
|
||||
let gltfx_path = Path::new("xyz.gltfx");
|
||||
source_dir.insert_asset_text(
|
||||
gltfx_path,
|
||||
r#"(
|
||||
gltfs: ["abc.gltf", "def.gltf"],
|
||||
)"#,
|
||||
);
|
||||
|
||||
// Start the app, which also starts the asset processor.
|
||||
app.update();
|
||||
|
||||
// Wait for all processing to finish.
|
||||
bevy_tasks::block_on(
|
||||
app.world()
|
||||
.resource::<AssetProcessor>()
|
||||
.data()
|
||||
.wait_until_finished(),
|
||||
);
|
||||
|
||||
// Sanity check that the two gltf files were actually processed.
|
||||
let processed_gltf_1 = processed_dir.get_asset(gltf_path_1).unwrap();
|
||||
let processed_gltf_1 = str::from_utf8(processed_gltf_1.value()).unwrap();
|
||||
assert_eq!(
|
||||
processed_gltf_1,
|
||||
r#"(
|
||||
parent_bsn: None,
|
||||
nodes: {
|
||||
"name": "thing",
|
||||
"position": "123",
|
||||
},
|
||||
)"#
|
||||
);
|
||||
let processed_gltf_2 = processed_dir.get_asset(gltf_path_2).unwrap();
|
||||
let processed_gltf_2 = str::from_utf8(processed_gltf_2.value()).unwrap();
|
||||
assert_eq!(
|
||||
processed_gltf_2,
|
||||
r#"(
|
||||
parent_bsn: None,
|
||||
nodes: {
|
||||
"color": "red",
|
||||
"velocity": "456",
|
||||
},
|
||||
)"#
|
||||
);
|
||||
|
||||
// The processed gltfx should have been able to load and merge the gltfs despite them having
|
||||
// been processed into bsn.
|
||||
|
||||
// Blocked on https://github.com/bevyengine/bevy/issues/21269. This is the actual assertion.
|
||||
// let processed_gltfx = processed_dir.get_asset(gltfx_path).unwrap();
|
||||
// let processed_gltfx = str::from_utf8(processed_gltfx.value()).unwrap();
|
||||
// assert_eq!(
|
||||
// processed_gltfx,
|
||||
// r#"(
|
||||
// parent_bsn: None,
|
||||
// nodes: {
|
||||
// "color": "red",
|
||||
// "name": "thing",
|
||||
// "position": "123",
|
||||
// "velocity": "456",
|
||||
// },
|
||||
// )"#
|
||||
// );
|
||||
|
||||
// This assertion exists to "prove" that this problem exists.
|
||||
assert!(processed_dir.get_asset(gltfx_path).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user