From a7ab30c347a2ed1dcd69e38a749a28bcb1422836 Mon Sep 17 00:00:00 2001
From: Alex Saveau <saveau.alexandre@gmail.com>
Date: Wed, 1 Jun 2022 06:03:08 -0700
Subject: [PATCH] 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.
---
 CHANGELOG.md         |   6 ++
 examples/sequence.rs |   5 +-
 src/lib.rs           |   8 ++-
 src/tweenable.rs     | 139 +++++++++++++++++++++++++++----------------
 4 files changed, 99 insertions(+), 59 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a421d5d..b8caeb9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
 - Support dynamically changing an animation's speed with `Animator::set_speed`
 - 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
 
diff --git a/examples/sequence.rs b/examples/sequence.rs
index d79a4bc..161676b 100644
--- a/examples/sequence.rs
+++ b/examples/sequence.rs
@@ -167,10 +167,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
     let tracks = Tracks::new([tween_rotate, tween_scale]);
     // Build a sequence from an heterogeneous list of tweenables by casting them manually
     // to a boxed Tweenable<Transform> : first move, then { rotate + scale }.
-    let seq2 = Sequence::new([
-        Box::new(tween_move) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
-        Box::new(tracks) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
-    ]);
+    let seq2 = Sequence::new([Box::new(tween_move) as BoxedTweenable<_>, tracks.into()]);
 
     commands
         .spawn_bundle(SpriteBundle {
diff --git a/src/lib.rs b/src/lib.rs
index 79b1024..f05d268 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -161,7 +161,9 @@ pub use lens::Lens;
 pub use plugin::{
     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.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -427,7 +429,7 @@ macro_rules! animator_impl {
 pub struct Animator<T: Component> {
     /// Control if this animation is played or not.
     pub state: AnimatorState,
-    tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
+    tweenable: Option<BoxedTweenable<T>>,
     speed: f32,
 }
 
@@ -467,7 +469,7 @@ impl<T: Component> Animator<T> {
 pub struct AssetAnimator<T: Asset> {
     /// Control if this animation is played or not.
     pub state: AnimatorState,
-    tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
+    tweenable: Option<BoxedTweenable<T>>,
     handle: Handle<T>,
     speed: f32,
 }
diff --git a/src/tweenable.rs b/src/tweenable.rs
index 03a868e..2169709 100644
--- a/src/tweenable.rs
+++ b/src/tweenable.rs
@@ -1,8 +1,54 @@
-use bevy::prelude::*;
 use std::time::Duration;
 
+use bevy::prelude::*;
+
 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`].
 ///
 /// 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 {
     fn rewind(&mut self);
 }
 
-impl<T> Tweenable<T> for Box<dyn Tweenable<T> + Send + Sync + 'static> {
-    fn duration(&self) -> Duration {
-        self.as_ref().duration()
-    }
-    fn is_looping(&self) -> bool {
-        self.as_ref().is_looping()
-    }
-    fn set_progress(&mut self, progress: f32) {
-        self.as_mut().set_progress(progress);
+impl<T> From<Delay> for BoxedTweenable<T> {
+    fn from(d: Delay) -> Self {
+        Box::new(d)
     }
-    fn progress(&self) -> f32 {
-        self.as_ref().progress()
-    }
-    fn tick(
-        &mut self,
-        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();
+}
+
+impl<T: 'static> From<Sequence<T>> for BoxedTweenable<T> {
+    fn from(s: Sequence<T>) -> Self {
+        Box::new(s)
     }
 }
 
-/// Trait for boxing a [`Tweenable`] trait object.
-pub trait IntoBoxDynTweenable<T> {
-    /// Convert the current object into a boxed [`Tweenable`].
-    fn into_box_dyn(this: Self) -> Box<dyn Tweenable<T> + Send + Sync + 'static>;
+impl<T: 'static> From<Tracks<T>> for BoxedTweenable<T> {
+    fn from(t: Tracks<T>) -> Self {
+        Box::new(t)
+    }
 }
 
-impl<T, U: Tweenable<T> + Send + Sync + 'static> IntoBoxDynTweenable<T> for U {
-    fn into_box_dyn(this: U) -> Box<dyn Tweenable<T> + Send + Sync + 'static> {
-        Box::new(this)
+impl<T: 'static> From<Tween<T>> for BoxedTweenable<T> {
+    fn from(t: Tween<T>) -> Self {
+        Box::new(t)
     }
 }
 
@@ -480,7 +508,7 @@ impl<T> Tweenable<T> for Tween<T> {
 
 /// A sequence of tweens played back in order one after the other.
 pub struct Sequence<T> {
-    tweens: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
+    tweens: Vec<BoxedTweenable<T>>,
     index: usize,
     duration: Duration,
     time: Duration,
@@ -492,13 +520,14 @@ impl<T> Sequence<T> {
     ///
     /// This method panics if the input collection is empty.
     #[must_use]
-    pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
-        let tweens: Vec<_> = items
-            .into_iter()
-            .map(IntoBoxDynTweenable::into_box_dyn)
-            .collect();
+    pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
+        let tweens: Vec<_> = items.into_iter().map(Into::into).collect();
         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 {
             tweens,
             index: 0,
@@ -512,8 +541,9 @@ impl<T> Sequence<T> {
     #[must_use]
     pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
         let duration = tween.duration();
+        let boxed: BoxedTweenable<T> = Box::new(tween);
         Self {
-            tweens: vec![Box::new(tween)],
+            tweens: vec![boxed],
             index: 0,
             duration,
             time: Duration::ZERO,
@@ -637,7 +667,7 @@ impl<T> Tweenable<T> for Sequence<T> {
 
 /// A collection of [`Tweenable`] executing in parallel.
 pub struct Tracks<T> {
-    tracks: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
+    tracks: Vec<BoxedTweenable<T>>,
     duration: Duration,
     time: Duration,
     times_completed: u32,
@@ -646,12 +676,14 @@ pub struct Tracks<T> {
 impl<T> Tracks<T> {
     /// Create a new [`Tracks`] from an iterator over a collection of [`Tweenable`].
     #[must_use]
-    pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
-        let tracks: Vec<_> = items
-            .into_iter()
-            .map(IntoBoxDynTweenable::into_box_dyn)
-            .collect();
-        let duration = tracks.iter().map(Tweenable::duration).max().unwrap();
+    pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
+        let tracks: Vec<_> = items.into_iter().map(Into::into).collect();
+        let duration = tracks
+            .iter()
+            .map(AsRef::as_ref)
+            .map(Tweenable::duration)
+            .max()
+            .unwrap();
         Self {
             tracks,
             duration,
@@ -797,12 +829,15 @@ impl<T> Tweenable<T> for Delay {
 
 #[cfg(test)]
 mod tests {
-    use super::*;
-    use crate::lens::*;
-    use bevy::ecs::{event::Events, system::SystemState};
     use std::sync::{Arc, Mutex};
     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.
     fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
         (a - b).abs() < tol
-- 
GitLab