use bevy::asset::AssetPath;
use bevy::asset::{io::Reader, AssetLoader, LoadContext};
use bevy::reflect::TypeRegistry;
use bevy::reflect::{TypePath, TypeRegistryArc};
use bevy::{
asset::{Asset, Assets, Handle},
log::trace,
math::{Vec2, Vec3},
platform::collections::HashSet,
prelude::{Component, Entity, FromWorld, Mesh, Plane3d, Resource, World},
reflect::Reflect,
utils::default,
};
use bevy::{ecs::reflect::AppTypeRegistry, reflect::serde::TypedReflectSerializer};
use serde::de::DeserializeSeed as _;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState};
use crate::Modifiers;
use crate::{
modifier::{Modifier, RenderModifier},
ExprHandle, ModifierContext, Module, ParticleLayout, Property, PropertyLayout, SimulationSpace,
SpawnerSettings, TextureLayout,
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum MotionIntegration {
None,
PreUpdate,
#[default]
PostUpdate,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum SimulationCondition {
#[default]
WhenVisible,
Always,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
#[non_exhaustive]
pub enum AlphaMode {
#[default]
Blend,
Premultiply,
Add,
Multiply,
Mask(ExprHandle),
Opaque,
}
impl From<AlphaMode> for BlendState {
fn from(value: AlphaMode) -> Self {
match value {
AlphaMode::Blend => BlendState::ALPHA_BLENDING,
AlphaMode::Premultiply => BlendState::PREMULTIPLIED_ALPHA_BLENDING,
AlphaMode::Add => BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
},
AlphaMode::Multiply => BlendState {
color: BlendComponent {
src_factor: BlendFactor::Dst,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent::OVER,
},
_ => BlendState::ALPHA_BLENDING,
}
}
}
#[derive(Debug, Clone, Resource)]
pub struct DefaultMesh(pub Handle<Mesh>);
impl FromWorld for DefaultMesh {
fn from_world(world: &mut World) -> Self {
let mut meshes = world.resource_mut::<Assets<Mesh>>();
let handle = meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(0.5)));
trace!("Created DefaultMesh(Plane3d/Z): handle={handle:?}");
Self(handle)
}
}
#[derive(Asset, Default, Clone, Reflect)]
#[reflect(from_reflect = false)]
pub struct EffectAsset {
pub name: String,
capacity: u32,
pub spawner: SpawnerSettings,
pub z_layer_2d: f32,
pub simulation_space: SimulationSpace,
pub simulation_condition: SimulationCondition,
pub prng_seed: u32,
init_modifiers: Modifiers,
update_modifiers: Modifiers,
render_modifiers: Modifiers,
pub motion_integration: MotionIntegration,
module: Module,
pub alpha_mode: AlphaMode,
pub mesh: Option<AssetPath<'static>>,
}
impl EffectAsset {
pub fn new(capacity: u32, spawner: SpawnerSettings, module: Module) -> Self {
Self {
capacity,
spawner,
module,
..default()
}
}
pub fn capacity(&self) -> u32 {
self.capacity
}
pub fn module(&self) -> &Module {
&self.module
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn with_simulation_condition(mut self, simulation_condition: SimulationCondition) -> Self {
self.simulation_condition = simulation_condition;
self
}
pub fn with_simulation_space(mut self, simulation_space: SimulationSpace) -> Self {
self.simulation_space = simulation_space;
self
}
pub fn with_alpha_mode(mut self, alpha_mode: AlphaMode) -> Self {
self.alpha_mode = alpha_mode;
self
}
pub fn with_motion_integration(mut self, motion_integration: MotionIntegration) -> Self {
self.motion_integration = motion_integration;
self
}
pub fn properties(&self) -> &[Property] {
self.module.properties()
}
#[inline]
pub fn init<M>(mut self, modifier: M) -> Self
where
M: Modifier + Send + Sync,
{
assert!(modifier.context().contains(ModifierContext::Init));
self.init_modifiers.push(Box::new(modifier));
self
}
#[inline]
pub fn update<M>(mut self, modifier: M) -> Self
where
M: Modifier + Send + Sync,
{
assert!(modifier.context().contains(ModifierContext::Update));
self.update_modifiers.push(Box::new(modifier));
self
}
pub fn add_modifier(mut self, context: ModifierContext, modifier: Box<dyn Modifier>) -> Self {
assert!(context == ModifierContext::Init || context == ModifierContext::Update);
assert!(modifier.context().contains(context));
if context == ModifierContext::Init {
self.init_modifiers.push(modifier);
} else {
self.update_modifiers.push(modifier);
}
self
}
#[inline]
pub fn render<M>(mut self, modifier: M) -> Self
where
M: RenderModifier + Send + Sync,
{
assert!(modifier.context().contains(ModifierContext::Render));
self.render_modifiers.push(Box::new(modifier));
self
}
pub fn add_render_modifier(mut self, modifier: Box<dyn RenderModifier>) -> Self {
assert!(modifier.context().contains(ModifierContext::Render));
self.render_modifiers.push(modifier.boxed_clone());
self
}
pub fn modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
self.init_modifiers
.iter()
.map(|bm| &**bm)
.chain(self.update_modifiers.iter().map(|bm| &**bm))
.chain(self.render_modifiers.iter().map(|bm| &**bm))
}
pub fn init_modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
self.init_modifiers.iter().map(|bm| &**bm)
}
pub fn update_modifiers(&self) -> impl Iterator<Item = &dyn Modifier> {
self.update_modifiers.iter().map(|bm| &**bm)
}
pub fn render_modifiers(&self) -> impl Iterator<Item = &dyn RenderModifier> {
self.render_modifiers.iter().filter_map(|m| m.as_render())
}
pub fn particle_layout(&self) -> ParticleLayout {
let mut set = HashSet::new();
for modifier in self.modifiers() {
for &attr in modifier.attributes() {
set.insert(attr);
}
}
self.module.gather_attributes(&mut set);
let mut layout = ParticleLayout::new();
for attr in set {
layout = layout.append(attr);
}
layout.build()
}
pub fn property_layout(&self) -> PropertyLayout {
PropertyLayout::new(self.properties().iter())
}
pub fn texture_layout(&self) -> TextureLayout {
self.module.texture_layout()
}
pub fn mesh(mut self, mesh: AssetPath<'static>) -> Self {
self.mesh = Some(mesh);
self
}
pub fn serialize(&self, type_registry: &TypeRegistry) -> Result<String, ron::Error> {
let serializer = EffectAssetSerializer::new(self, type_registry);
let pretty_config = ron::ser::PrettyConfig::default()
.indentor(" ".to_string())
.new_line("\n".to_string());
ron::ser::to_string_pretty(&serializer, pretty_config)
}
pub fn deserialize(s: &str, type_registry: &TypeRegistry) -> Result<Self, ron::Error> {
let mut deserializer = ron::de::Deserializer::from_str(s)?;
let deserialize = EffectAssetDeserializer::new(type_registry);
let asset = deserialize.deserialize(&mut deserializer)?;
Ok(asset)
}
}
impl bevy::reflect::serde::SerializeWithRegistry for EffectAsset {
fn serialize<S>(&self, serializer: S, registry: &TypeRegistry) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct as _;
let mut s = serializer.serialize_struct("EffectAsset", 14)?;
s.serialize_field("name", &self.name)?;
s.serialize_field("capacity", &self.capacity)?;
s.serialize_field("spawner", &self.spawner)?;
s.serialize_field("z_layer_2d", &self.z_layer_2d)?;
s.serialize_field("simulation_space", &self.simulation_space)?;
s.serialize_field("simulation_condition", &self.simulation_condition)?;
s.serialize_field("prng_seed", &self.prng_seed)?;
s.serialize_field(
"init_modifiers",
&TypedReflectSerializer::new(&self.init_modifiers, registry),
)?;
s.serialize_field(
"update_modifiers",
&TypedReflectSerializer::new(&self.update_modifiers, registry),
)?;
s.serialize_field(
"render_modifiers",
&TypedReflectSerializer::new(&self.render_modifiers, registry),
)?;
s.serialize_field("motion_integration", &self.motion_integration)?;
s.serialize_field("module", &self.module)?;
s.serialize_field("alpha_mode", &self.alpha_mode)?;
s.serialize_field("mesh", &self.mesh)?;
s.end()
}
}
impl<'de> bevy::reflect::serde::DeserializeWithRegistry<'de> for EffectAsset {
fn deserialize<D>(deserializer: D, registry: &TypeRegistry) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "snake_case")]
enum Field {
Name,
Capacity,
Spawner,
#[serde(rename = "z_layer_2d")]
ZLayer2d,
SimulationSpace,
SimulationCondition,
PrngSeed,
InitModifiers,
UpdateModifiers,
RenderModifiers,
MotionIntegration,
Module,
AlphaMode,
Mesh,
}
struct SerializedEffectAssetVisitor<'a> {
pub registry: &'a TypeRegistry,
}
impl<'a, 'de> serde::de::Visitor<'de> for SerializedEffectAssetVisitor<'a> {
type Value = EffectAsset;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("struct EffectAsset")
}
fn visit_map<V>(self, mut map: V) -> Result<EffectAsset, V::Error>
where
V: serde::de::MapAccess<'de>,
{
let modifiers = self
.registry
.get(std::any::TypeId::of::<Modifiers>())
.ok_or_else(|| {
serde::de::Error::custom("Failed to find type registration for Modifiers.")
})?;
let mut name = None;
let mut capacity = None;
let mut spawner = None;
let mut z_layer_2d = None;
let mut simulation_space = None;
let mut simulation_condition = None;
let mut prng_seed = None;
let mut init_modifiers = None;
let mut update_modifiers = None;
let mut render_modifiers = None;
let mut motion_integration = None;
let mut module = None;
let mut alpha_mode = None;
let mut mesh = None;
while let Some(key) = map.next_key()? {
match key {
Field::Name => {
if name.is_some() {
return Err(serde::de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
}
Field::Capacity => {
if capacity.is_some() {
return Err(serde::de::Error::duplicate_field("capacity"));
}
capacity = Some(map.next_value()?);
}
Field::Spawner => {
if spawner.is_some() {
return Err(serde::de::Error::duplicate_field("spawner"));
}
spawner = Some(map.next_value()?);
}
Field::ZLayer2d => {
if z_layer_2d.is_some() {
return Err(serde::de::Error::duplicate_field("z_layer_2d"));
}
z_layer_2d = Some(map.next_value()?);
}
Field::SimulationSpace => {
if simulation_space.is_some() {
return Err(serde::de::Error::duplicate_field("simulation_space"));
}
simulation_space = Some(map.next_value()?);
}
Field::SimulationCondition => {
if simulation_condition.is_some() {
return Err(serde::de::Error::duplicate_field(
"simulation_condition",
));
}
simulation_condition = Some(map.next_value()?);
}
Field::PrngSeed => {
if prng_seed.is_some() {
return Err(serde::de::Error::duplicate_field("prng_seed"));
}
prng_seed = Some(map.next_value()?);
}
Field::InitModifiers => {
if init_modifiers.is_some() {
return Err(serde::de::Error::duplicate_field("init_modifiers"));
}
init_modifiers = Some(map.next_value_seed(
bevy::reflect::serde::TypedReflectDeserializer::new(
modifiers,
self.registry,
),
)?);
}
Field::UpdateModifiers => {
if update_modifiers.is_some() {
return Err(serde::de::Error::duplicate_field("update_modifiers"));
}
update_modifiers = Some(map.next_value_seed(
bevy::reflect::serde::TypedReflectDeserializer::new(
modifiers,
self.registry,
),
)?);
}
Field::RenderModifiers => {
if render_modifiers.is_some() {
return Err(serde::de::Error::duplicate_field("render_modifiers"));
}
render_modifiers = Some(map.next_value_seed(
bevy::reflect::serde::TypedReflectDeserializer::new(
modifiers,
self.registry,
),
)?);
}
Field::MotionIntegration => {
if motion_integration.is_some() {
return Err(serde::de::Error::duplicate_field(
"motion_integration",
));
}
motion_integration = Some(map.next_value()?);
}
Field::Module => {
if module.is_some() {
return Err(serde::de::Error::duplicate_field("module"));
}
module = Some(map.next_value()?);
}
Field::AlphaMode => {
if alpha_mode.is_some() {
return Err(serde::de::Error::duplicate_field("alpha_mode"));
}
alpha_mode = Some(map.next_value()?);
}
Field::Mesh => {
if mesh.is_some() {
return Err(serde::de::Error::duplicate_field("mesh"));
}
mesh = Some(map.next_value()?);
}
}
}
let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?;
let capacity =
capacity.ok_or_else(|| serde::de::Error::missing_field("capacity"))?;
let spawner = spawner.ok_or_else(|| serde::de::Error::missing_field("spawner"))?;
let z_layer_2d =
z_layer_2d.ok_or_else(|| serde::de::Error::missing_field("z_layer_2d"))?;
let simulation_space = simulation_space
.ok_or_else(|| serde::de::Error::missing_field("simulation_space"))?;
let simulation_condition = simulation_condition
.ok_or_else(|| serde::de::Error::missing_field("simulation_condition"))?;
let prng_seed =
prng_seed.ok_or_else(|| serde::de::Error::missing_field("prng_seed"))?;
let motion_integration = motion_integration
.ok_or_else(|| serde::de::Error::missing_field("motion_integration"))?;
let module = module.ok_or_else(|| serde::de::Error::missing_field("module"))?;
let alpha_mode =
alpha_mode.ok_or_else(|| serde::de::Error::missing_field("alpha_mode"))?;
let mesh = mesh.ok_or_else(|| serde::de::Error::missing_field("mesh"))?;
let init_modifiers = init_modifiers
.map(|m| m.try_take::<Modifiers>())
.transpose()
.map_err(|_| serde::de::Error::custom("Failed to get Modifiers"))?
.unwrap_or_default();
let update_modifiers = update_modifiers
.map(|m| m.try_take::<Modifiers>())
.transpose()
.map_err(|_| serde::de::Error::custom("Failed to get Modifiers"))?
.unwrap_or_default();
let render_modifiers = render_modifiers
.map(|m| m.try_take::<Modifiers>())
.transpose()
.map_err(|_| serde::de::Error::custom("Failed to get Modifiers"))?
.unwrap_or_default();
Ok(EffectAsset {
name,
capacity,
spawner,
z_layer_2d,
simulation_space,
simulation_condition,
prng_seed,
init_modifiers,
update_modifiers,
render_modifiers,
motion_integration,
module,
alpha_mode,
mesh,
})
}
}
const FIELDS: &[&str] = &[
"name",
"capacity",
"spawner",
"z_layer_2d",
"simulation_space",
"simulation_condition",
"prng_seed",
"init_modifiers",
"update_modifiers",
"render_modifiers",
"motion_integration",
"module",
"alpha_mode",
"mesh",
];
deserializer.deserialize_struct(
"EffectAsset",
FIELDS,
SerializedEffectAssetVisitor { registry },
)
}
}
pub struct EffectAssetSerializer<'a> {
asset: &'a EffectAsset,
type_registry: &'a TypeRegistry,
}
impl<'a> EffectAssetSerializer<'a> {
pub fn new(asset: &'a EffectAsset, type_registry: &'a TypeRegistry) -> Self {
Self {
asset,
type_registry,
}
}
}
impl<'a> serde::Serialize for EffectAssetSerializer<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use bevy::reflect::serde::SerializeWithRegistry;
<EffectAsset as SerializeWithRegistry>::serialize(
self.asset,
serializer,
self.type_registry,
)
}
}
pub struct EffectAssetDeserializer<'a> {
type_registry: &'a TypeRegistry,
}
impl<'a> EffectAssetDeserializer<'a> {
pub fn new(type_registry: &'a TypeRegistry) -> Self {
Self { type_registry }
}
}
impl<'a, 'de> serde::de::DeserializeSeed<'de> for EffectAssetDeserializer<'a> {
type Value = EffectAsset;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
<EffectAsset as bevy::reflect::serde::DeserializeWithRegistry>::deserialize(
deserializer,
self.type_registry,
)
}
}
#[derive(Debug, TypePath)]
pub struct EffectAssetLoader {
pub type_registry: TypeRegistryArc,
}
impl FromWorld for EffectAssetLoader {
fn from_world(world: &mut World) -> Self {
let type_registry = world.resource::<AppTypeRegistry>();
EffectAssetLoader {
type_registry: type_registry.0.clone(),
}
}
}
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum EffectAssetLoaderError {
#[error("An IO error occurred during loading of a particle effect")]
Io(#[from] std::io::Error),
#[error("An encoding error occurred during loading of a particle effect")]
Encoding(#[from] std::string::FromUtf8Error),
#[error("A RON format error occurred during loading of a particle effect")]
RonSpan(#[from] ron::error::SpannedError),
#[error("A RON error occurred during loading of a particle effect")]
Ron(#[from] ron::error::Error),
}
impl AssetLoader for EffectAssetLoader {
type Asset = EffectAsset;
type Settings = ();
type Error = EffectAssetLoaderError;
async fn load(
&self,
reader: &mut dyn Reader,
_settings: &Self::Settings,
_load_context: &mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let s = String::from_utf8(bytes)?;
let type_registry = self.type_registry.read();
let asset = EffectAsset::deserialize(&s, &type_registry)?;
Ok(asset)
}
fn extensions(&self) -> &[&str] {
&["effect"]
}
}
#[derive(Debug, Clone, Copy, Component, Reflect)]
pub struct EffectParent {
pub entity: Entity,
}
impl EffectParent {
pub fn new(parent: Entity) -> Self {
Self { entity: parent }
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
pub struct ParticleTrails {
pub spawn_period: f32,
}
#[cfg(test)]
mod tests {
use std::path::Path;
use bevy::{
asset::{
io::{
memory::{Dir, MemoryAssetReader, MemoryAssetWriter},
AssetSourceBuilder, AssetSourceId,
},
LoadState,
},
diagnostic::DiagnosticsPlugin,
};
use super::*;
use crate::*;
#[test]
fn add_modifiers() {
let mut m = Module::default();
let expr = m.lit(3.);
for modifier_context in [ModifierContext::Init, ModifierContext::Update] {
let effect = EffectAsset::default().add_modifier(
modifier_context,
Box::new(SetAttributeModifier::new(Attribute::POSITION, expr)),
);
assert_eq!(effect.modifiers().count(), 1);
let m = effect.modifiers().next().unwrap();
assert!(m.context().contains(modifier_context));
}
{
let effect = EffectAsset::default().add_render_modifier(Box::new(SetColorModifier {
color: CpuValue::Single(Vec4::ONE),
blend: ColorBlendMode::Overwrite,
mask: ColorBlendMask::RGBA,
}));
assert_eq!(effect.modifiers().count(), 1);
let m = effect.modifiers().next().unwrap();
assert!(m.context().contains(ModifierContext::Render));
}
}
#[test]
fn test_apply_modifiers() {
let mut module = Module::default();
let origin = module.lit(Vec3::ZERO);
let one = module.lit(1.);
let slot_zero = module.lit(0u32);
let init_age = SetAttributeModifier::new(Attribute::AGE, one);
let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, one);
let init_pos_sphere = SetPositionSphereModifier {
center: module.lit(Vec3::ZERO),
radius: module.lit(1.),
dimension: ShapeDimension::Volume,
};
let init_vel_sphere = SetVelocitySphereModifier {
center: module.lit(Vec3::ZERO),
speed: module.lit(1.),
};
let mut effect = EffectAsset::new(4096, SpawnerSettings::rate(30.0.into()), module)
.init(init_pos_sphere)
.init(init_vel_sphere)
.update(LinearDragModifier::new(one))
.update(ConformToSphereModifier::new(origin, one, one, one, one))
.render(ParticleTextureModifier::new(slot_zero))
.render(ColorOverLifetimeModifier::default())
.render(SizeOverLifetimeModifier::default())
.render(OrientModifier::new(OrientMode::ParallelCameraDepthPlane))
.render(OrientModifier::new(OrientMode::FaceCameraPosition))
.render(OrientModifier::new(OrientMode::AlongVelocity));
assert_eq!(effect.capacity, 4096);
let module = &mut effect.module;
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut init_context =
ShaderWriter::new(ModifierContext::Init, &property_layout, &particle_layout);
assert!(init_pos_sphere.apply(module, &mut init_context).is_ok());
assert!(init_vel_sphere.apply(module, &mut init_context).is_ok());
assert!(init_age.apply(module, &mut init_context).is_ok());
assert!(init_lifetime.apply(module, &mut init_context).is_ok());
let accel_mod = AccelModifier::constant(module, Vec3::ONE);
let drag_mod = LinearDragModifier::constant(module, 3.5);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut update_context =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
assert!(accel_mod.apply(module, &mut update_context).is_ok());
assert!(drag_mod.apply(module, &mut update_context).is_ok());
assert!(ConformToSphereModifier::new(origin, one, one, one, one)
.apply(module, &mut update_context)
.is_ok());
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = TextureLayout::default();
let mut render_context =
RenderContext::new(&property_layout, &particle_layout, &texture_layout);
ParticleTextureModifier::new(slot_zero)
.apply_render(module, &mut render_context)
.unwrap();
ColorOverLifetimeModifier::default()
.apply_render(module, &mut render_context)
.unwrap();
SizeOverLifetimeModifier::default()
.apply_render(module, &mut render_context)
.unwrap();
OrientModifier::new(OrientMode::ParallelCameraDepthPlane)
.apply_render(module, &mut render_context)
.unwrap();
OrientModifier::new(OrientMode::FaceCameraPosition)
.apply_render(module, &mut render_context)
.unwrap();
OrientModifier::new(OrientMode::AlongVelocity)
.apply_render(module, &mut render_context)
.unwrap();
}
#[test]
fn serde_asset() {
let w = ExprWriter::new();
let pos = w.lit(Vec3::new(1.2, -3.45, 87.54485));
let x = w.lit(BVec2::new(false, true));
let _ = x + pos.clone();
let mod_pos = SetAttributeModifier::new(Attribute::POSITION, pos.expr());
let mut module = w.finish();
let prop = module.add_property("my_prop", Vec3::new(1.2, -2.3, 55.32).into());
let prop = module.prop(prop);
let _ = module.abs(prop);
let effect = EffectAsset {
name: "Effect".into(),
capacity: 4096,
spawner: SpawnerSettings::rate(30.0.into()),
module,
z_layer_2d: 1.5,
simulation_space: SimulationSpace::Local,
simulation_condition: SimulationCondition::Always,
prng_seed: 4284,
motion_integration: MotionIntegration::PreUpdate,
alpha_mode: AlphaMode::Multiply,
..Default::default()
}
.init(mod_pos);
let type_registry = AppTypeRegistry::new_with_derived_types();
register_modifiers(&type_registry);
let registry = type_registry.read();
let s = effect.serialize(®istry).unwrap();
eprintln!("{}", s);
let effect_serde = EffectAsset::deserialize(&s, ®istry).unwrap();
assert_eq!(effect.name, effect_serde.name);
assert_eq!(effect.capacity, effect_serde.capacity);
assert_eq!(effect.spawner, effect_serde.spawner);
assert_eq!(effect.z_layer_2d, effect_serde.z_layer_2d);
assert_eq!(effect.simulation_space, effect_serde.simulation_space);
assert_eq!(
effect.simulation_condition,
effect_serde.simulation_condition
);
assert_eq!(effect.motion_integration, effect_serde.motion_integration);
assert_eq!(effect.module, effect_serde.module);
assert_eq!(effect.alpha_mode, effect_serde.alpha_mode);
assert_eq!(
effect.init_modifiers().count(),
effect_serde.init_modifiers().count()
);
assert_eq!(
effect.update_modifiers().count(),
effect_serde.update_modifiers().count()
);
assert_eq!(
effect.render_modifiers().count(),
effect_serde.render_modifiers().count()
);
}
#[test]
fn alpha_mode_blend_state() {
assert_eq!(BlendState::ALPHA_BLENDING, AlphaMode::Blend.into());
assert_eq!(
BlendState::PREMULTIPLIED_ALPHA_BLENDING,
AlphaMode::Premultiply.into()
);
let blend_state = BlendState {
color: BlendComponent {
src_factor: BlendFactor::SrcAlpha,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
alpha: BlendComponent {
src_factor: BlendFactor::Zero,
dst_factor: BlendFactor::One,
operation: BlendOperation::Add,
},
};
assert_eq!(blend_state, AlphaMode::Add.into());
let blend_state = BlendState {
color: BlendComponent {
src_factor: BlendFactor::Dst,
dst_factor: BlendFactor::OneMinusSrcAlpha,
operation: BlendOperation::Add,
},
alpha: BlendComponent::OVER,
};
assert_eq!(blend_state, AlphaMode::Multiply.into());
let expr = Module::default().lit(0.5);
assert_eq!(BlendState::ALPHA_BLENDING, AlphaMode::Mask(expr).into());
}
#[test]
fn transitive_attr() {
let mut m = Module::default();
let age = m.attr(Attribute::F32_0);
let modifier = SetAttributeModifier::new(Attribute::AGE, age);
let asset = EffectAsset::new(32, SpawnerSettings::once(3.0.into()), m).init(modifier);
let particle_layout = asset.particle_layout();
assert!(particle_layout.contains(Attribute::AGE)); assert!(particle_layout.contains(Attribute::F32_0)); }
fn create_app() -> (App, Dir) {
let mut app = App::new();
let dir = Dir::default();
let dir1 = dir.clone();
let dir2 = dir.clone();
app.register_asset_source(
AssetSourceId::Default,
AssetSourceBuilder::new(move || Box::new(MemoryAssetReader { root: dir1.clone() }))
.with_writer(move |_| Some(Box::new(MemoryAssetWriter { root: dir2.clone() }))),
)
.add_plugins((
TaskPoolPlugin::default(),
AssetPlugin {
watch_for_changes_override: Some(false),
use_asset_processor_override: Some(false),
..Default::default()
},
DiagnosticsPlugin,
));
(app, dir)
}
fn run_app_until(app: &mut App, mut predicate: impl FnMut(&mut World) -> Option<()>) {
for _ in 0..10_000 {
app.update();
if predicate(app.world_mut()).is_some() {
return;
}
}
panic!("Ran out of loops to return `Some` from `predicate`");
}
#[test]
fn loader() {
let (mut app, dir) = create_app();
app.init_asset::<EffectAsset>()
.init_asset_loader::<EffectAssetLoader>();
let asset_ref = {
let type_registry = app.world().resource::<AppTypeRegistry>().clone();
let type_registry = type_registry.read();
let spawner = SpawnerSettings::rate(3.0.into());
let module = Module::default();
let effect = EffectAsset::new(256, spawner, module);
let s = effect.serialize(&type_registry).unwrap();
dir.insert_asset_text(Path::new("test.effect"), &s[..]);
effect
};
let asset_server = app.world().resource::<AssetServer>().clone();
let handle = asset_server.load::<EffectAsset>("test.effect");
run_app_until(&mut app, |world| {
let asset_server = world.resource::<AssetServer>();
match asset_server.get_load_state(&handle).unwrap() {
LoadState::NotLoaded | LoadState::Loading => None,
LoadState::Loaded => Some(()),
LoadState::Failed(err) => panic!("Failed to load asset: {err:?}"),
}
});
let asset = app
.world()
.resource::<Assets<EffectAsset>>()
.get(&handle)
.unwrap();
assert_eq!(asset.capacity(), asset_ref.capacity());
assert_eq!(asset.spawner, asset_ref.spawner);
}
}