Skip to content
Snippets Groups Projects
Unverified Commit a7ab30c3 authored by Alex Saveau's avatar Alex Saveau Committed by GitHub
Browse files

Optimize out allocations as much as possible (#27)

Fix double-boxing by removing the `IntoBoxDynTweenable` trait and the impl of
`Tweenable<T>` for `Box<dyn Tweenable>`, and instead using some `From`
conversion implemented per concrete type.
parent fa782cf0
No related branches found
No related tags found
No related merge requests found
...@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `Tween::set_direction()` and `Tween::with_direction()` which allow configuring the playback direction of a tween, allowing to play it backward from end to start. - Add `Tween::set_direction()` and `Tween::with_direction()` which allow configuring the playback direction of a tween, allowing to play it backward from end to start.
- Support dynamically changing an animation's speed with `Animator::set_speed` - Support dynamically changing an animation's speed with `Animator::set_speed`
- Add `AnimationSystem` label to tweening tick systems - Add `AnimationSystem` label to tweening tick systems
- A `BoxedTweenable` type to make working with `Box<dyn Tweenable + ...>` easier
### Changed
- Double boxing in `Sequence` and `Tracks` was fixed. As a result, any custom tweenables
should implement `From` for `BoxedTweenable` to make those APIs easier to use.
## [0.4.0] - 2022-04-16 ## [0.4.0] - 2022-04-16
......
...@@ -167,10 +167,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { ...@@ -167,10 +167,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let tracks = Tracks::new([tween_rotate, tween_scale]); let tracks = Tracks::new([tween_rotate, tween_scale]);
// Build a sequence from an heterogeneous list of tweenables by casting them manually // Build a sequence from an heterogeneous list of tweenables by casting them manually
// to a boxed Tweenable<Transform> : first move, then { rotate + scale }. // to a boxed Tweenable<Transform> : first move, then { rotate + scale }.
let seq2 = Sequence::new([ let seq2 = Sequence::new([Box::new(tween_move) as BoxedTweenable<_>, tracks.into()]);
Box::new(tween_move) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
Box::new(tracks) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
]);
commands commands
.spawn_bundle(SpriteBundle { .spawn_bundle(SpriteBundle {
......
...@@ -161,7 +161,9 @@ pub use lens::Lens; ...@@ -161,7 +161,9 @@ pub use lens::Lens;
pub use plugin::{ pub use plugin::{
asset_animator_system, component_animator_system, AnimationSystem, TweeningPlugin, asset_animator_system, component_animator_system, AnimationSystem, TweeningPlugin,
}; };
pub use tweenable::{Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable}; pub use tweenable::{
BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable,
};
/// Type of looping for a tween animation. /// Type of looping for a tween animation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
...@@ -427,7 +429,7 @@ macro_rules! animator_impl { ...@@ -427,7 +429,7 @@ macro_rules! animator_impl {
pub struct Animator<T: Component> { pub struct Animator<T: Component> {
/// Control if this animation is played or not. /// Control if this animation is played or not.
pub state: AnimatorState, pub state: AnimatorState,
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>, tweenable: Option<BoxedTweenable<T>>,
speed: f32, speed: f32,
} }
...@@ -467,7 +469,7 @@ impl<T: Component> Animator<T> { ...@@ -467,7 +469,7 @@ impl<T: Component> Animator<T> {
pub struct AssetAnimator<T: Asset> { pub struct AssetAnimator<T: Asset> {
/// Control if this animation is played or not. /// Control if this animation is played or not.
pub state: AnimatorState, pub state: AnimatorState,
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>, tweenable: Option<BoxedTweenable<T>>,
handle: Handle<T>, handle: Handle<T>,
speed: f32, speed: f32,
} }
......
use bevy::prelude::*;
use std::time::Duration; use std::time::Duration;
use bevy::prelude::*;
use crate::{EaseMethod, Lens, TweeningDirection, TweeningType}; use crate::{EaseMethod, Lens, TweeningDirection, TweeningType};
/// The dynamic tweenable type.
///
/// When creating lists of tweenables, you will need to box them to create a homogeneous
/// array like so:
/// ```no_run
/// # use bevy::prelude::Transform;
/// # use bevy_tweening::{BoxedTweenable, Delay, Sequence, Tween};
/// #
/// # let delay: Delay = unimplemented!();
/// # let tween: Tween<Transform> = unimplemented!();
///
/// Sequence::new([Box::new(delay) as BoxedTweenable<Transform>, tween.into()]);
/// ```
///
/// When using your own [`Tweenable`] types, APIs will be easier to use if you implement [`From`]:
/// ```no_run
/// # use std::time::Duration;
/// # use bevy::prelude::{Entity, EventWriter, Transform};
/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState};
/// #
/// # struct MyTweenable;
/// # impl Tweenable<Transform> for MyTweenable {
/// # fn duration(&self) -> Duration { unimplemented!() }
/// # fn is_looping(&self) -> bool { 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 times_completed(&self) -> u32 { unimplemented!() }
/// # fn rewind(&mut self) { unimplemented!() }
/// # }
///
/// Sequence::new([Box::new(MyTweenable) as BoxedTweenable<_>]);
///
/// // OR
///
/// Sequence::new([MyTweenable]);
///
/// impl From<MyTweenable> for BoxedTweenable<Transform> {
/// fn from(t: MyTweenable) -> Self {
/// Box::new(t)
/// }
/// }
/// ```
pub type BoxedTweenable<T> = Box<dyn Tweenable<T> + Send + Sync + 'static>;
/// Playback state of a [`Tweenable`]. /// Playback state of a [`Tweenable`].
/// ///
/// This is returned by [`Tweenable::tick()`] to allow the caller to execute some logic based on the /// This is returned by [`Tweenable::tick()`] to allow the caller to execute some logic based on the
...@@ -169,45 +215,27 @@ pub trait Tweenable<T>: Send + Sync { ...@@ -169,45 +215,27 @@ pub trait Tweenable<T>: Send + Sync {
fn rewind(&mut self); fn rewind(&mut self);
} }
impl<T> Tweenable<T> for Box<dyn Tweenable<T> + Send + Sync + 'static> { impl<T> From<Delay> for BoxedTweenable<T> {
fn duration(&self) -> Duration { fn from(d: Delay) -> Self {
self.as_ref().duration() Box::new(d)
}
fn is_looping(&self) -> bool {
self.as_ref().is_looping()
}
fn set_progress(&mut self, progress: f32) {
self.as_mut().set_progress(progress);
} }
fn progress(&self) -> f32 { }
self.as_ref().progress()
} impl<T: 'static> From<Sequence<T>> for BoxedTweenable<T> {
fn tick( fn from(s: Sequence<T>) -> Self {
&mut self, Box::new(s)
delta: Duration,
target: &mut T,
entity: Entity,
event_writer: &mut EventWriter<TweenCompleted>,
) -> TweenState {
self.as_mut().tick(delta, target, entity, event_writer)
}
fn times_completed(&self) -> u32 {
self.as_ref().times_completed()
}
fn rewind(&mut self) {
self.as_mut().rewind();
} }
} }
/// Trait for boxing a [`Tweenable`] trait object. impl<T: 'static> From<Tracks<T>> for BoxedTweenable<T> {
pub trait IntoBoxDynTweenable<T> { fn from(t: Tracks<T>) -> Self {
/// Convert the current object into a boxed [`Tweenable`]. Box::new(t)
fn into_box_dyn(this: Self) -> Box<dyn Tweenable<T> + Send + Sync + 'static>; }
} }
impl<T, U: Tweenable<T> + Send + Sync + 'static> IntoBoxDynTweenable<T> for U { impl<T: 'static> From<Tween<T>> for BoxedTweenable<T> {
fn into_box_dyn(this: U) -> Box<dyn Tweenable<T> + Send + Sync + 'static> { fn from(t: Tween<T>) -> Self {
Box::new(this) Box::new(t)
} }
} }
...@@ -480,7 +508,7 @@ impl<T> Tweenable<T> for Tween<T> { ...@@ -480,7 +508,7 @@ impl<T> Tweenable<T> for Tween<T> {
/// A sequence of tweens played back in order one after the other. /// A sequence of tweens played back in order one after the other.
pub struct Sequence<T> { pub struct Sequence<T> {
tweens: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>, tweens: Vec<BoxedTweenable<T>>,
index: usize, index: usize,
duration: Duration, duration: Duration,
time: Duration, time: Duration,
...@@ -492,13 +520,14 @@ impl<T> Sequence<T> { ...@@ -492,13 +520,14 @@ impl<T> Sequence<T> {
/// ///
/// This method panics if the input collection is empty. /// This method panics if the input collection is empty.
#[must_use] #[must_use]
pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self { pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
let tweens: Vec<_> = items let tweens: Vec<_> = items.into_iter().map(Into::into).collect();
.into_iter()
.map(IntoBoxDynTweenable::into_box_dyn)
.collect();
assert!(!tweens.is_empty()); assert!(!tweens.is_empty());
let duration = tweens.iter().map(Tweenable::duration).sum(); let duration = tweens
.iter()
.map(AsRef::as_ref)
.map(Tweenable::duration)
.sum();
Self { Self {
tweens, tweens,
index: 0, index: 0,
...@@ -512,8 +541,9 @@ impl<T> Sequence<T> { ...@@ -512,8 +541,9 @@ impl<T> Sequence<T> {
#[must_use] #[must_use]
pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self { pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
let duration = tween.duration(); let duration = tween.duration();
let boxed: BoxedTweenable<T> = Box::new(tween);
Self { Self {
tweens: vec![Box::new(tween)], tweens: vec![boxed],
index: 0, index: 0,
duration, duration,
time: Duration::ZERO, time: Duration::ZERO,
...@@ -637,7 +667,7 @@ impl<T> Tweenable<T> for Sequence<T> { ...@@ -637,7 +667,7 @@ impl<T> Tweenable<T> for Sequence<T> {
/// A collection of [`Tweenable`] executing in parallel. /// A collection of [`Tweenable`] executing in parallel.
pub struct Tracks<T> { pub struct Tracks<T> {
tracks: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>, tracks: Vec<BoxedTweenable<T>>,
duration: Duration, duration: Duration,
time: Duration, time: Duration,
times_completed: u32, times_completed: u32,
...@@ -646,12 +676,14 @@ pub struct Tracks<T> { ...@@ -646,12 +676,14 @@ pub struct Tracks<T> {
impl<T> Tracks<T> { impl<T> Tracks<T> {
/// Create a new [`Tracks`] from an iterator over a collection of [`Tweenable`]. /// Create a new [`Tracks`] from an iterator over a collection of [`Tweenable`].
#[must_use] #[must_use]
pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self { pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
let tracks: Vec<_> = items let tracks: Vec<_> = items.into_iter().map(Into::into).collect();
.into_iter() let duration = tracks
.map(IntoBoxDynTweenable::into_box_dyn) .iter()
.collect(); .map(AsRef::as_ref)
let duration = tracks.iter().map(Tweenable::duration).max().unwrap(); .map(Tweenable::duration)
.max()
.unwrap();
Self { Self {
tracks, tracks,
duration, duration,
...@@ -797,12 +829,15 @@ impl<T> Tweenable<T> for Delay { ...@@ -797,12 +829,15 @@ impl<T> Tweenable<T> for Delay {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::lens::*;
use bevy::ecs::{event::Events, system::SystemState};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use bevy::ecs::{event::Events, system::SystemState};
use crate::lens::*;
use super::*;
/// Utility to compare floating-point values with a tolerance. /// Utility to compare floating-point values with a tolerance.
fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool { fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
(a - b).abs() < tol (a - b).abs() < tol
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment