From a3fecb10ef63d0e10fd8c8a4c22bbbdd171be264 Mon Sep 17 00:00:00 2001 From: Jerome Humbert <djeedai@gmail.com> Date: Wed, 9 Nov 2022 08:48:42 +0000 Subject: [PATCH] Fix incorrect change detection triggering (#71) Ensure change detection on components and assets is only triggered when an animator effectively modifies said component or asset, and not invariably just by the simple fact of the animator ticking each frame. This change modifies the signature of the `component_animator_system()` and `asset_animator_system()` public functions to directly consume a `ResMut<Events<TweenCompleted>>` instead of an `EventWriter<TweenCompleted>`, to work around some internal limitations. It also publicly exposes a new `Targetable` trait used to work around the impossibility to retrieve a `Mut<T>` from a `Mut<Assets<T>>`. Instead, the trait provides an "target dereferencing" method `target_mut()` which dereferences the target component or asset, and triggers its change detection. The trait is implemented for all components via the `ComponentTarget` object, and for all assets via the `AssetTarget` object. The 3 types described here are publicly exposed to workaround some Bevy limitations, and because the trait appears in the public `Tweenable<T>` API. However users are discouraged from taking strong dependencies on those, as they will be removed once Bevy provides a way to achieve this conditionaly dereferencing without this workaround. Fixes #33 --- CHANGELOG.md | 3 + src/lib.rs | 3 +- src/plugin.rs | 182 ++++++++++++++++++++++++--- src/tweenable.rs | 318 +++++++++++++++++++++++------------------------ 4 files changed, 330 insertions(+), 176 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a96d9..141a3fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a `speed()` getter on `Animator<T>` and `AssetAnimator<T>`. - Added `set_elapsed(Duration)` and `elapsed() -> Duration` to the `Tweenable<T>` trait. Those methods are preferable over `set_progress()` and `progress()` as they avoid the conversion to floating-point values and any rounding errors. - Added a new `bevy_text` feature for `Text`-related built-in lenses. +- Added `Targetable`, `ComponentTarget`, and `AssetTarget`, which should be considered private even though they appear in the public API. They are a workaround for Bevy 0.8 and will likely be removed in the future once the related Bevy limitation is lifted. ### Changed @@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Delay::new()` now panics if the `duration` is zero. This prevents creating no-op `Delay` objects, and avoids an internal edge case producing wrong results. - Tweens moving to `TweenState::Completed` are now guaranteed to freeze their state. In particular, this means that their direction will not flip at the end of the last loop if their repeat strategy is `RepeatStrategy::MirroredRepeat`. - Moved the `TextColorLens` lens from the `bevy_ui` feature to the new `bevy_text` one, to allow using it without the Bevy UI crate. +- Changed the signature of the `component_animator_system()` and `asset_animator_system()` public functions to directly consume a `ResMut<Events<TweenCompleted>>` instead of an `EventWriter<TweenCompleted>`, to work around some internal limitations. ### Removed @@ -29,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the animator speed feature, which got broken in #44. +- Fixed change detection triggering unconditionally when `component_animator_system()` or `asset_animator_system()` are ticked, even when the animator did not mutate its target component or asset. (#33) ## [0.5.0] - 2022-08-04 diff --git a/src/lib.rs b/src/lib.rs index f5cb2f1..6e83361 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,7 +164,8 @@ pub use lens::Lens; pub use plugin::asset_animator_system; pub use plugin::{component_animator_system, AnimationSystem, TweeningPlugin}; pub use tweenable::{ - BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable, + BoxedTweenable, Delay, Sequence, Targetable, Tracks, Tween, TweenCompleted, TweenState, + Tweenable, }; pub mod lens; diff --git a/src/plugin.rs b/src/plugin.rs index bdbf3dc..0061b4d 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -3,8 +3,8 @@ use bevy::asset::Asset; use bevy::{ecs::component::Component, prelude::*}; #[cfg(feature = "bevy_asset")] -use crate::AssetAnimator; -use crate::{Animator, AnimatorState, TweenCompleted}; +use crate::{tweenable::AssetTarget, AssetAnimator}; +use crate::{tweenable::ComponentTarget, Animator, AnimatorState, TweenCompleted}; /// Plugin to add systems related to tweening of common components and assets. /// @@ -71,16 +71,18 @@ pub enum AnimationSystem { pub fn component_animator_system<T: Component>( time: Res<Time>, mut query: Query<(Entity, &mut T, &mut Animator<T>)>, - mut event_writer: EventWriter<TweenCompleted>, + events: ResMut<Events<TweenCompleted>>, ) { - for (entity, ref mut target, ref mut animator) in query.iter_mut() { + let mut events: Mut<Events<TweenCompleted>> = events.into(); + for (entity, target, mut animator) in query.iter_mut() { if animator.state != AnimatorState::Paused { let speed = animator.speed(); + let mut target = ComponentTarget::new(target); animator.tweenable_mut().tick( time.delta().mul_f32(speed), - target, + &mut target, entity, - &mut event_writer, + &mut events, ); } } @@ -95,21 +97,169 @@ pub fn component_animator_system<T: Component>( #[cfg(feature = "bevy_asset")] pub fn asset_animator_system<T: Asset>( time: Res<Time>, - mut assets: ResMut<Assets<T>>, + assets: ResMut<Assets<T>>, mut query: Query<(Entity, &mut AssetAnimator<T>)>, - mut event_writer: EventWriter<TweenCompleted>, + events: ResMut<Events<TweenCompleted>>, ) { - for (entity, ref mut animator) in query.iter_mut() { + let mut events: Mut<Events<TweenCompleted>> = events.into(); + let mut target = AssetTarget::new(assets); + for (entity, mut animator) in query.iter_mut() { if animator.state != AnimatorState::Paused { + target.handle = animator.handle().clone(); + if !target.is_valid() { + continue; + } let speed = animator.speed(); - if let Some(target) = assets.get_mut(&animator.handle()) { - animator.tweenable_mut().tick( - time.delta().mul_f32(speed), - target, - entity, - &mut event_writer, - ); + animator.tweenable_mut().tick( + time.delta().mul_f32(speed), + &mut target, + entity, + &mut events, + ); + } + } +} + +#[cfg(test)] +mod tests { + use bevy::prelude::{Events, IntoSystem, System, Transform, World}; + + use crate::{lens::TransformPositionLens, *}; + + /// A simple isolated test environment with a [`World`] and a single + /// [`Entity`] in it. + struct TestEnv { + world: World, + entity: Entity, + } + + impl TestEnv { + /// Create a new test environment containing a single entity with a + /// [`Transform`], and add the given animator on that same entity. + pub fn new<T: Component>(animator: T) -> Self { + let mut world = World::new(); + world.init_resource::<Events<TweenCompleted>>(); + + let mut time = Time::default(); + time.update(); + world.insert_resource(time); + + let entity = world + .spawn() + .insert(Transform::default()) + .insert(animator) + .id(); + + Self { world, entity } + } + + /// Get the test world. + pub fn world_mut(&mut self) -> &mut World { + &mut self.world + } + + /// Tick the test environment, updating the simulation time and ticking + /// the given system. + pub fn tick(&mut self, duration: Duration, system: &mut dyn System<In = (), Out = ()>) { + // Simulate time passing by updating the simulation time resource + { + let mut time = self.world.resource_mut::<Time>(); + let last_update = time.last_update().unwrap(); + time.update_with_instant(last_update + duration); } + + // Reset world-related change detection + self.world.clear_trackers(); + assert!(!self.transform().is_changed()); + + // Tick system + system.run((), &mut self.world); + + // Update events after system ticked, in case system emitted some events + let mut events = self.world.resource_mut::<Events<TweenCompleted>>(); + events.update(); + } + + /// Get the animator for the transform. + pub fn animator(&self) -> &Animator<Transform> { + self.world + .entity(self.entity) + .get::<Animator<Transform>>() + .unwrap() + } + + /// Get the transform component. + pub fn transform(&mut self) -> Mut<Transform> { + self.world.get_mut::<Transform>(self.entity).unwrap() } + + /// Get the emitted event count since last tick. + pub fn event_count(&self) -> usize { + let events = self.world.resource::<Events<TweenCompleted>>(); + events.get_reader().len(&events) + } + } + + #[test] + fn change_detect_component() { + let tween = Tween::new( + EaseMethod::Linear, + Duration::from_secs(1), + TransformPositionLens { + start: Vec3::ZERO, + end: Vec3::ONE, + }, + ) + .with_completed_event(0); + + let mut env = TestEnv::new(Animator::new(tween)); + + // After being inserted, components are always considered changed + let transform = env.transform(); + assert!(transform.is_changed()); + + //fn nit() {} + //let mut system = IntoSystem::into_system(nit); + let mut system = IntoSystem::into_system(component_animator_system::<Transform>); + system.initialize(env.world_mut()); + + env.tick(Duration::ZERO, &mut system); + + let animator = env.animator(); + assert_eq!(animator.state, AnimatorState::Playing); + assert_eq!(animator.tweenable().times_completed(), 0); + let transform = env.transform(); + assert!(transform.is_changed()); + assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); + + env.tick(Duration::from_millis(500), &mut system); + + assert_eq!(env.event_count(), 0); + let animator = env.animator(); + assert_eq!(animator.state, AnimatorState::Playing); + assert_eq!(animator.tweenable().times_completed(), 0); + let transform = env.transform(); + assert!(transform.is_changed()); + assert!(transform.translation.abs_diff_eq(Vec3::splat(0.5), 1e-5)); + + env.tick(Duration::from_millis(500), &mut system); + + assert_eq!(env.event_count(), 1); + let animator = env.animator(); + assert_eq!(animator.state, AnimatorState::Playing); + assert_eq!(animator.tweenable().times_completed(), 1); + let transform = env.transform(); + assert!(transform.is_changed()); + assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); + + env.tick(Duration::from_millis(100), &mut system); + + assert_eq!(env.event_count(), 0); + let animator = env.animator(); + assert_eq!(animator.state, AnimatorState::Playing); + assert_eq!(animator.tweenable().times_completed(), 1); + let transform = env.transform(); + assert!(!transform.is_changed()); + assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); } } diff --git a/src/tweenable.rs b/src/tweenable.rs index ad1e02d..c067aae 100644 --- a/src/tweenable.rs +++ b/src/tweenable.rs @@ -1,7 +1,10 @@ -use std::time::Duration; +use std::{ops::DerefMut, time::Duration}; use bevy::prelude::*; +#[cfg(feature = "bevy_asset")] +use bevy::asset::{Asset, HandleId}; + use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// The dynamic tweenable type. @@ -22,8 +25,8 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// implement [`From`]: /// ```no_run /// # use std::time::Duration; -/// # use bevy::prelude::{Entity, EventWriter, Transform}; -/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState}; +/// # use bevy::prelude::{Entity, Events, Mut, Transform}; +/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState, Targetable}; /// # /// # struct MyTweenable; /// # impl Tweenable<Transform> for MyTweenable { @@ -32,7 +35,7 @@ use crate::{EaseMethod, Lens, RepeatCount, RepeatStrategy, TweeningDirection}; /// # fn elapsed(&self) -> Duration { unimplemented!() } /// # fn set_progress(&mut self, progress: f32) { unimplemented!() } /// # fn progress(&self) -> f32 { unimplemented!() } -/// # fn tick(&mut self, delta: Duration, target: &mut Transform, entity: Entity, event_writer: &mut EventWriter<TweenCompleted>) -> TweenState { unimplemented!() } +/// # fn tick<'a>(&mut self, delta: Duration, target: &'a mut dyn Targetable<Transform>, entity: Entity, events: &mut Mut<Events<TweenCompleted>>) -> TweenState { unimplemented!() } /// # fn times_completed(&self) -> u32 { unimplemented!() } /// # fn rewind(&mut self) { unimplemented!() } /// # } @@ -203,6 +206,60 @@ fn compute_total_duration(duration: Duration, count: RepeatCount) -> TotalDurati } } +// TODO - Targetable et al. should be replaced with Mut->Mut from Bevy 0.9 +// https://github.com/bevyengine/bevy/pull/6199 + +/// Trait to workaround the discrepancies of the change detection mechanisms of +/// assets and components. +pub trait Targetable<T> { + /// Dereference the target, triggering any change detection, and return a + /// mutable reference. + fn target_mut(&mut self) -> &mut T; +} + +pub struct ComponentTarget<'a, T: Component> { + target: Mut<'a, T>, +} + +impl<'a, T: Component> ComponentTarget<'a, T> { + pub fn new(target: Mut<'a, T>) -> Self { + Self { target } + } +} + +impl<'a, T: Component> Targetable<T> for ComponentTarget<'a, T> { + fn target_mut(&mut self) -> &mut T { + self.target.deref_mut() + } +} + +#[cfg(feature = "bevy_asset")] +pub struct AssetTarget<'a, T: Asset> { + assets: ResMut<'a, Assets<T>>, + pub handle: Handle<T>, +} + +#[cfg(feature = "bevy_asset")] +impl<'a, T: Asset> AssetTarget<'a, T> { + pub fn new(assets: ResMut<'a, Assets<T>>) -> Self { + Self { + assets, + handle: Handle::weak(HandleId::default::<T>()), + } + } + + pub fn is_valid(&self) -> bool { + self.assets.contains(&self.handle) + } +} + +#[cfg(feature = "bevy_asset")] +impl<'a, T: Asset> Targetable<T> for AssetTarget<'a, T> { + fn target_mut(&mut self) -> &mut T { + self.assets.get_mut(&self.handle).unwrap() + } +} + /// An animatable entity, either a single [`Tween`] or a collection of them. pub trait Tweenable<T>: Send + Sync { /// Get the total duration of the animation. @@ -276,12 +333,12 @@ pub trait Tweenable<T>: Send + Sync { /// /// [`rewind()`]: Tweenable::rewind /// [`set_progress()`]: Tweenable::set_progress - fn tick( + fn tick<'a>( &mut self, delta: Duration, - target: &mut T, + target: &'a mut dyn Targetable<T>, entity: Entity, - event_writer: &mut EventWriter<TweenCompleted>, + events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState; /// Get the number of times this tweenable completed. @@ -550,12 +607,12 @@ impl<T> Tweenable<T> for Tween<T> { self.clock.progress() } - fn tick( + fn tick<'a>( &mut self, delta: Duration, - target: &mut T, + target: &'a mut dyn Targetable<T>, entity: Entity, - event_writer: &mut EventWriter<TweenCompleted>, + events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState { if self.clock.state() == TweenState::Completed { return TweenState::Completed; @@ -580,12 +637,13 @@ impl<T> Tweenable<T> for Tween<T> { factor = 1. - factor; } let factor = self.ease_function.sample(factor); + let target = target.target_mut(); self.lens.lerp(target, factor); // If completed at least once this frame, notify the user if times_completed > 0 { if let Some(user_data) = &self.event_data { - event_writer.send(TweenCompleted { + events.send(TweenCompleted { entity, user_data: *user_data, }); @@ -745,18 +803,18 @@ impl<T> Tweenable<T> for Sequence<T> { } } - fn tick( + fn tick<'a>( &mut self, mut delta: Duration, - target: &mut T, + target: &'a mut dyn Targetable<T>, entity: Entity, - event_writer: &mut EventWriter<TweenCompleted>, + events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState { self.elapsed = self.elapsed.saturating_add(delta).min(self.duration); while self.index < self.tweens.len() { let tween = &mut self.tweens[self.index]; let tween_remaining = tween.duration().mul_f32(1.0 - tween.progress()); - if let TweenState::Active = tween.tick(delta, target, entity, event_writer) { + if let TweenState::Active = tween.tick(delta, target, entity, events) { return TweenState::Active; } @@ -843,17 +901,17 @@ impl<T> Tweenable<T> for Tracks<T> { } } - fn tick( + fn tick<'a>( &mut self, delta: Duration, - target: &mut T, + target: &'a mut dyn Targetable<T>, entity: Entity, - event_writer: &mut EventWriter<TweenCompleted>, + events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState { self.elapsed = self.elapsed.saturating_add(delta).min(self.duration); let mut any_active = false; for tweenable in &mut self.tracks { - let state = tweenable.tick(delta, target, entity, event_writer); + let state = tweenable.tick(delta, target, entity, events); any_active = any_active || (state == TweenState::Active); } if any_active { @@ -934,12 +992,12 @@ impl<T> Tweenable<T> for Delay { self.timer.percent() } - fn tick( + fn tick<'a>( &mut self, delta: Duration, - _target: &mut T, + _target: &'a mut dyn Targetable<T>, _entity: Entity, - _event_writer: &mut EventWriter<TweenCompleted>, + _events: &mut Mut<Events<TweenCompleted>>, ) -> TweenState { self.timer.tick(delta); if self.timer.finished() { @@ -993,12 +1051,27 @@ mod tests { } /// Utility to create a test environment to tick a tween. - fn make_test_env() -> (World, Entity, Transform) { + fn make_test_env() -> (World, Entity) { let mut world = World::new(); - world.insert_resource(Events::<TweenCompleted>::default()); - let entity = Entity::from_raw(0); - let transform = Transform::default(); - (world, entity, transform) + world.init_resource::<Events<TweenCompleted>>(); + let entity = world.spawn().insert(Transform::default()).id(); + (world, entity) + } + + /// Manually tick a test tweenable targeting a component. + fn manual_tick_component<T: Component>( + duration: Duration, + tween: &mut dyn Tweenable<T>, + world: &mut World, + entity: Entity, + ) -> TweenState { + world.resource_scope( + |world: &mut World, mut events: Mut<Events<TweenCompleted>>| { + let transform = world.get_mut::<T>(entity).unwrap(); + let mut target = ComponentTarget::new(transform); + tween.tick(duration, &mut target, entity, &mut events) + }, + ) } #[test] @@ -1056,9 +1129,7 @@ mod tests { assert!(tween.on_completed.is_none()); assert!(tween.event_data.is_none()); - let (mut world, entity, mut transform) = make_test_env(); - let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); + let (mut world, entity) = make_test_env(); let mut event_reader_system_state: SystemState<EventReader<TweenCompleted>> = SystemState::new(&mut world); @@ -1199,15 +1270,12 @@ mod tests { ); // Tick the tween - let actual_state = { - let mut event_writer = event_writer_system_state.get_mut(&mut world); - tween.tick(tick_duration, &mut transform, entity, &mut event_writer) - }; + let actual_state = + manual_tick_component(tick_duration, &mut tween, &mut world, entity); // Propagate events { - let mut events = - world.get_resource_mut::<Events<TweenCompleted>>().unwrap(); + let mut events = world.resource_mut::<Events<TweenCompleted>>(); events.update(); } @@ -1216,6 +1284,7 @@ mod tests { assert_eq!(actual_state, expected_state); assert_approx_eq!(tween.progress(), progress); assert_eq!(tween.times_completed(), times_completed); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform .translation .abs_diff_eq(expected_translation, 1e-5)); @@ -1245,21 +1314,15 @@ mod tests { assert_eq!(tween.times_completed(), 0); // Dummy tick to update target - let actual_state = { - let mut event_writer = event_writer_system_state.get_mut(&mut world); - tween.tick( - Duration::ZERO, - &mut transform, - Entity::from_raw(0), - &mut event_writer, - ) - }; + let actual_state = + manual_tick_component(Duration::ZERO, &mut tween, &mut world, entity); assert_eq!(actual_state, TweenState::Active); let expected_translation = if tweening_direction.is_backward() { Vec3::ONE } else { Vec3::ZERO }; + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform .translation .abs_diff_eq(expected_translation, 1e-5)); @@ -1299,21 +1362,14 @@ mod tests { // progress is independent of direction assert_approx_eq!(tween.progress(), 0.3); - let (mut world, entity, mut transform) = make_test_env(); - let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = event_writer_system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); // Progress always increases alongside the current direction tween.set_direction(TweeningDirection::Backward); assert_approx_eq!(tween.progress(), 0.3); - tween.tick( - Duration::from_millis(100), - &mut transform, - entity, - &mut event_writer, - ); + manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity); assert_approx_eq!(tween.progress(), 0.4); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.6), 1e-5)); } @@ -1361,22 +1417,16 @@ mod tests { ); let mut seq = tween1.then(tween2); - let (mut world, entity, mut transform) = make_test_env(); - let mut system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); for i in 1..=16 { - let state = seq.tick( - Duration::from_millis(200), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(200), &mut seq, &mut world, entity); + let transform = world.entity(entity).get::<Transform>().unwrap(); if i < 5 { assert_eq!(state, TweenState::Active); let r = i as f32 * 0.2; - assert_eq!(transform, Transform::from_translation(Vec3::splat(r))); + assert_eq!(*transform, Transform::from_translation(Vec3::splat(r))); } else if i < 10 { assert_eq!(state, TweenState::Active); let alpha_deg = (18 * (i - 5)) as f32; @@ -1409,24 +1459,22 @@ mod tests { .with_repeat_count(RepeatCount::Finite(1)) })); - let (mut world, entity, mut transform) = make_test_env(); - let mut system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); // Tick halfway through the first tween, then in one tick: // - Finish the first tween // - Start and finish the second tween // - Start the third tween for delta_ms in [500, 2000] { - seq.tick( + manual_tick_component( Duration::from_millis(delta_ms), - &mut transform, + &mut seq, + &mut world, entity, - &mut event_writer, ); } assert_eq!(seq.index(), 2); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(2.5), 1e-5)); } @@ -1523,18 +1571,12 @@ mod tests { let mut tracks = Tracks::new([tween1, tween2]); assert_eq!(tracks.duration(), Duration::from_secs(1)); // max(1., 0.8) - let (mut world, entity, mut transform) = make_test_env(); - let mut system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); for i in 1..=6 { - let state = tracks.tick( - Duration::from_millis(200), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(200), &mut tracks, &mut world, entity); + let transform = world.entity(entity).get::<Transform>().unwrap(); if i < 5 { assert_eq!(state, TweenState::Active); assert_eq!(tracks.times_completed(), 0); @@ -1563,36 +1605,21 @@ mod tests { tracks.set_progress(0.9); assert_approx_eq!(tracks.progress(), 0.9); // tick to udpate state (set_progress() does not update state) - let state = tracks.tick( - Duration::ZERO, - &mut transform, - Entity::from_raw(0), - &mut event_writer, - ); + let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity); assert_eq!(state, TweenState::Active); assert_eq!(tracks.times_completed(), 0); tracks.set_progress(3.2); assert_approx_eq!(tracks.progress(), 1.); // tick to udpate state (set_progress() does not update state) - let state = tracks.tick( - Duration::ZERO, - &mut transform, - Entity::from_raw(0), - &mut event_writer, - ); + let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity); assert_eq!(state, TweenState::Completed); assert_eq!(tracks.times_completed(), 1); // no looping tracks.set_progress(-0.5); assert_approx_eq!(tracks.progress(), 0.); // tick to udpate state (set_progress() does not update state) - let state = tracks.tick( - Duration::ZERO, - &mut transform, - Entity::from_raw(0), - &mut event_writer, - ); + let state = manual_tick_component(Duration::ZERO, &mut tracks, &mut world, entity); assert_eq!(state, TweenState::Active); assert_eq!(tracks.times_completed(), 0); // no looping } @@ -1621,17 +1648,14 @@ mod tests { } // Dummy world and event writer - let (mut world, entity, mut transform) = make_test_env(); - let mut system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); for i in 1..=6 { - let state = delay.tick( + let state = manual_tick_component::<Transform>( Duration::from_millis(200), - &mut transform, + &mut delay, + &mut world, entity, - &mut event_writer, ); { let tweenable: &dyn Tweenable<Transform> = &delay; @@ -1653,7 +1677,7 @@ mod tests { tweenable.rewind(); assert_eq!(tweenable.times_completed(), 0); assert_approx_eq!(tweenable.progress(), 0.); - let state = tweenable.tick(Duration::ZERO, &mut transform, entity, &mut event_writer); + let state = manual_tick_component(Duration::ZERO, tweenable, &mut world, entity); assert_eq!(state, TweenState::Active); tweenable.set_progress(0.3); @@ -1696,57 +1720,42 @@ mod tests { assert_approx_eq!(tween.progress(), 0.); - let (mut world, entity, mut transform) = make_test_env(); - let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = event_writer_system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); // 10% - let state = tween.tick( - Duration::from_millis(100), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity); assert_eq!(TweenState::Active, state); assert_eq!(0, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.1); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // 130% - let state = tween.tick( - Duration::from_millis(1200), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(1200), &mut tween, &mut world, entity); assert_eq!(TweenState::Active, state); assert_eq!(1, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.3); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.3), 1e-5)); // 480% - let state = tween.tick( - Duration::from_millis(3500), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(3500), &mut tween, &mut world, entity); assert_eq!(TweenState::Active, state); assert_eq!(4, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.8); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // 500% - done - let state = tween.tick( - Duration::from_millis(200), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(200), &mut tween, &mut world, entity); assert_eq!(TweenState::Completed, state); assert_eq!(5, tween.times_completed()); assert_approx_eq!(tween.progress(), 1.0); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::ONE, 1e-5)); } @@ -1758,22 +1767,16 @@ mod tests { assert_approx_eq!(tween.progress(), 0.); - let (mut world, entity, mut transform) = make_test_env(); - let mut event_writer_system_state: SystemState<EventWriter<TweenCompleted>> = - SystemState::new(&mut world); - let mut event_writer = event_writer_system_state.get_mut(&mut world); + let (mut world, entity) = make_test_env(); // 10% - let state = tween.tick( - Duration::from_millis(100), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(100), &mut tween, &mut world, entity); assert_eq!(TweenState::Active, state); assert_eq!(TweeningDirection::Forward, tween.direction()); assert_eq!(0, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.1); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // rewind @@ -1781,19 +1784,17 @@ mod tests { assert_eq!(TweeningDirection::Forward, tween.direction()); assert_eq!(0, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.1), 1e-5)); // no-op, rewind doesn't apply Lens // 120% - mirror - let state = tween.tick( - Duration::from_millis(1200), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(1200), &mut tween, &mut world, entity); assert_eq!(TweeningDirection::Backward, tween.direction()); assert_eq!(TweenState::Active, state); assert_eq!(1, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.2); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // rewind @@ -1801,19 +1802,17 @@ mod tests { assert_eq!(TweeningDirection::Forward, tween.direction()); // restored assert_eq!(0, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::splat(0.8), 1e-5)); // no-op, rewind doesn't apply Lens // 400% - done mirror (because Completed freezes the state) - let state = tween.tick( - Duration::from_millis(4000), - &mut transform, - entity, - &mut event_writer, - ); + let state = + manual_tick_component(Duration::from_millis(4000), &mut tween, &mut world, entity); assert_eq!(TweenState::Completed, state); assert_eq!(TweeningDirection::Backward, tween.direction()); // frozen from last loop assert_eq!(4, tween.times_completed()); assert_approx_eq!(tween.progress(), 1.); // Completed + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // rewind @@ -1821,6 +1820,7 @@ mod tests { assert_eq!(TweeningDirection::Forward, tween.direction()); // restored assert_eq!(0, tween.times_completed()); assert_approx_eq!(tween.progress(), 0.); + let transform = world.entity(entity).get::<Transform>().unwrap(); assert!(transform.translation.abs_diff_eq(Vec3::ZERO, 1e-5)); // no-op, rewind doesn't apply Lens } } -- GitLab