diff --git a/CHANGELOG.md b/CHANGELOG.md index 46a96d910a1f1efb4158b171f01b1cd1f95a1a1e..141a3fd12cd2f8c5488355581939b17d9a09c4b0 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 f5cb2f11926c248055b3c33b95c6fdeccdb7f68a..6e833615fb268be178c742a9c02c1b8949b15281 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 bdbf3dc4a001c1ec6d7ec6305de1b1a092eb76bf..0061b4d04346f71f8ef951a9d3a2c563a8d88218 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 ad1e02dfc22b8acd18b10fadf07c1b9b81f11e46..c067aae72ae6222cdf5480136d7db83d66bdd91f 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 } }