use std::{marker::PhantomData, time::Duration};
use bevy::{
ecs::{
change_detection::DetectChanges as _,
component::{Component, Mutable},
entity::Entity,
message::{Message, Messages},
system::{IntoSystem, System},
world::{Mut, World},
},
math::{Quat, Vec3, Vec4},
time::Time,
transform::components::Transform,
};
pub(crate) trait AbsDiffEq: std::fmt::Debug {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool;
fn delta(a: Self, b: Self) -> Self;
}
impl AbsDiffEq for f32 {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool {
(a - b).abs() < tol
}
fn delta(a: Self, b: Self) -> Self {
(a - b).abs()
}
}
impl AbsDiffEq for f64 {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool {
(a - b).abs() < tol as f64
}
fn delta(a: Self, b: Self) -> Self {
(a - b).abs()
}
}
impl AbsDiffEq for Vec3 {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool {
Vec3::abs_diff_eq(a, b, tol)
}
fn delta(a: Self, b: Self) -> Self {
(a - b).abs()
}
}
impl AbsDiffEq for Quat {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool {
Quat::abs_diff_eq(a, b, tol)
}
fn delta(a: Self, b: Self) -> Self {
Quat::from_vec4((Vec4::from(a) - Vec4::from(b)).abs())
}
}
impl AbsDiffEq for Transform {
fn abs_diff_eq(a: Self, b: Self, tol: f32) -> bool {
a.translation.abs_diff_eq(b.translation, tol)
&& a.rotation.abs_diff_eq(b.rotation, tol)
&& a.scale.abs_diff_eq(b.scale, tol)
}
fn delta(a: Self, b: Self) -> Self {
Transform {
translation: AbsDiffEq::delta(a.translation, b.translation),
rotation: AbsDiffEq::delta(a.rotation, b.rotation),
scale: AbsDiffEq::delta(a.scale, b.scale),
}
}
}
macro_rules! assert_approx_eq {
($left:expr, $right:expr $(,)?) => {
match (&$left, &$right) {
(left_val, right_val) => {
assert!(
crate::test_utils::AbsDiffEq::abs_diff_eq(*left_val, *right_val, 1e-5),
"assertion failed: expected={:?} actual={:?} delta={:?} tol=1e-5(default)",
left_val,
right_val,
crate::test_utils::AbsDiffEq::delta(*left_val, *right_val),
);
}
}
};
($left:expr, $right:expr, $tol:expr $(,)?) => {
match (&$left, &$right, &$tol) {
(left_val, right_val, tol_val) => {
assert!(
crate::test_utils::AbsDiffEq::abs_diff_eq(*left_val, *right_val, *tol_val),
"assertion failed: expected={:?} actual={:?} delta={:?} tol={}",
left_val,
right_val,
crate::test_utils::AbsDiffEq::delta(*left_val, *right_val),
tol_val
);
}
}
};
}
pub(crate) use assert_approx_eq;
use crate::{AnimCompletedEvent, CycleCompletedEvent, TweenAnim, TweenResolver, Tweenable};
pub(crate) struct TestEnv<T: Component> {
pub world: World,
pub entity: Entity,
system: Box<dyn System<In = (), Out = ()>>,
_phantom: PhantomData<T>,
}
impl<T: Component<Mutability = Mutable> + Default> TestEnv<T> {
pub fn new(tweenable: impl Tweenable + 'static) -> Self {
let mut world = World::new();
world.init_resource::<Time>();
world.init_resource::<Messages<CycleCompletedEvent>>();
world.init_resource::<Messages<AnimCompletedEvent>>();
world.init_resource::<TweenResolver>();
let mut system = IntoSystem::into_system(crate::plugin::animator_system);
system.initialize(&mut world);
let entity = world.spawn((T::default(), TweenAnim::new(tweenable))).id();
Self {
world,
entity,
system: Box::new(system),
_phantom: PhantomData,
}
}
pub fn empty() -> Self {
let mut world = World::new();
world.init_resource::<Time>();
world.init_resource::<Messages<CycleCompletedEvent>>();
world.init_resource::<Messages<AnimCompletedEvent>>();
world.init_resource::<TweenResolver>();
let mut system = IntoSystem::into_system(crate::plugin::animator_system);
system.initialize(&mut world);
Self {
world,
entity: Entity::PLACEHOLDER,
system: Box::new(system),
_phantom: PhantomData,
}
}
}
impl<T: Component<Mutability = Mutable>> TestEnv<T> {
pub fn step_all(&mut self, duration: Duration) {
{
let mut time = self.world.resource_mut::<Time>();
time.advance_by(duration);
}
self.world.clear_trackers();
if self.entity != Entity::PLACEHOLDER {
assert!(!self.component_mut().is_changed());
}
let _ = self.system.run((), &mut self.world);
let mut events = self.world.resource_mut::<Messages<CycleCompletedEvent>>();
events.update();
let mut events = self.world.resource_mut::<Messages<AnimCompletedEvent>>();
events.update();
}
pub fn anim(&self) -> Option<&TweenAnim> {
self.world.get::<TweenAnim>(self.entity)
}
pub fn anim_mut(&mut self) -> Option<Mut<'_, TweenAnim>> {
self.world.get_mut::<TweenAnim>(self.entity)
}
pub fn component(&self) -> &T {
self.world.get::<T>(self.entity).unwrap()
}
pub fn component_mut(&mut self) -> Mut<'_, T> {
self.world.get_mut::<T>(self.entity).unwrap()
}
pub fn event_count<E: Message>(&self) -> usize {
let events = self.world.resource::<Messages<E>>();
events.get_cursor().len(events)
}
}