use bevy::{
app::{App, Plugin},
asset::{
AssetApp,
io::{AsyncWriteExt, Writer},
processor::LoadTransformAndSave,
saver::{AssetSaver, SavedAsset},
transformer::{AssetTransformer, TransformedAsset},
},
ecs::reflect::AppTypeRegistry,
reflect::{TypePath, TypeRegistryArc},
};
use bevy_hanabi::{EffectAsset, EffectAssetLoader};
use thiserror::Error;
use crate::{
bake::{self, BakeError},
loader::EffectGraphLoader,
model::EffectGraphAsset,
modifier_registry::ModifierRegistryPlugin,
};
pub type EffectGraphProcessor =
LoadTransformAndSave<EffectGraphLoader, EffectGraphBaker, EffectAssetSaver>;
#[derive(TypePath)]
pub struct EffectGraphBaker {
type_registry: TypeRegistryArc,
}
#[derive(Debug, Error)]
#[error("effect graph failed to bake: {0:?}")]
pub struct BakeTransformError(pub Vec<BakeError>);
impl AssetTransformer for EffectGraphBaker {
type AssetInput = EffectGraphAsset;
type AssetOutput = EffectAsset;
type Settings = ();
type Error = BakeTransformError;
async fn transform<'a>(
&'a self,
asset: TransformedAsset<Self::AssetInput>,
_settings: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
let registry = self.type_registry.read();
let effect = bake::bake(&asset.get().graph, ®istry).map_err(BakeTransformError)?;
Ok(asset.replace_asset(effect))
}
}
#[derive(TypePath)]
pub struct EffectAssetSaver {
type_registry: TypeRegistryArc,
}
#[derive(Debug, Error)]
pub enum EffectAssetSaveError {
#[error("failed to serialize EffectAsset to RON: {0}")]
Serialize(#[from] ron::Error),
#[error("failed to write EffectAsset bytes: {0}")]
Io(#[from] std::io::Error),
}
impl AssetSaver for EffectAssetSaver {
type Asset = EffectAsset;
type Settings = ();
type OutputLoader = EffectAssetLoader;
type Error = EffectAssetSaveError;
async fn save(
&self,
writer: &mut Writer,
asset: SavedAsset<'_, '_, Self::Asset>,
_settings: &Self::Settings,
_path: bevy::asset::AssetPath<'_>,
) -> Result<(), Self::Error> {
let ron = {
let registry = self.type_registry.read();
asset.get().serialize(®istry)?
};
writer.write_all(ron.as_bytes()).await?;
Ok(())
}
}
pub struct EffectGraphProcessorPlugin;
impl Plugin for EffectGraphProcessorPlugin {
fn build(&self, app: &mut App) {
if !app.is_plugin_added::<ModifierRegistryPlugin>() {
app.add_plugins(ModifierRegistryPlugin);
}
app.init_asset::<EffectGraphAsset>()
.init_asset_loader::<EffectGraphLoader>();
let type_registry = app.world().resource::<AppTypeRegistry>().0.clone();
let processor = EffectGraphProcessor::new(
EffectGraphBaker {
type_registry: type_registry.clone(),
},
EffectAssetSaver { type_registry },
);
app.register_asset_processor::<EffectGraphProcessor>(processor)
.set_default_asset_processor::<EffectGraphProcessor>("hnb");
}
}
#[cfg(test)]
mod tests {
use bevy::{
asset::{ErasedLoadedAsset, LoadedAsset, saver::SavedAsset, transformer::TransformedAsset},
ecs::reflect::AppTypeRegistry,
tasks::block_on,
};
use bevy_hanabi::EffectAsset;
use super::*;
use crate::{
bake, demo,
model::{EffectGraphAsset, FORMAT_VERSION},
};
fn test_registry() -> AppTypeRegistry {
let registry = AppTypeRegistry::default();
bevy_hanabi::register_modifiers(®istry);
registry
}
fn demo_graph_asset() -> EffectGraphAsset {
EffectGraphAsset {
version: FORMAT_VERSION,
graph: demo::demo_graph(),
layout: None,
}
}
#[test]
fn processor_bakes_and_saves_loadable_effect_asset() {
let app_registry = test_registry();
let registry_arc = app_registry.0.clone();
let transformer = EffectGraphBaker {
type_registry: registry_arc.clone(),
};
let saver = EffectAssetSaver {
type_registry: registry_arc,
};
let erased: ErasedLoadedAsset = LoadedAsset::from(demo_graph_asset()).into();
let input = TransformedAsset::<EffectGraphAsset>::from_loaded(erased).unwrap();
let output = block_on(transformer.transform(input, &())).expect("transform");
let mut bytes: Vec<u8> = Vec::new();
let path = bevy::asset::AssetPath::from("test.effect.ron");
block_on(saver.save(&mut bytes, SavedAsset::from_transformed(&output), &(), path))
.expect("save");
let ron = String::from_utf8(bytes).expect("utf8");
let registry = app_registry.read();
let loaded = EffectAsset::deserialize(&ron, ®istry).expect("loader deserialize");
let expected = bake::bake(&demo::demo_graph(), ®istry).expect("direct bake");
assert_eq!(loaded.name, expected.name);
assert_eq!(loaded.capacity(), expected.capacity());
assert_eq!(
loaded.init_modifiers().count(),
expected.init_modifiers().count()
);
assert_eq!(
loaded.update_modifiers().count(),
expected.update_modifiers().count()
);
assert_eq!(
loaded.render_modifiers().count(),
expected.render_modifiers().count()
);
}
}