Skip to content

Commit a7ab30c

Browse files
authored
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.
1 parent fa782cf commit a7ab30c

File tree

4 files changed

+99
-59
lines changed

4 files changed

+99
-59
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- 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.
1212
- Support dynamically changing an animation's speed with `Animator::set_speed`
1313
- Add `AnimationSystem` label to tweening tick systems
14+
- A `BoxedTweenable` type to make working with `Box<dyn Tweenable + ...>` easier
15+
16+
### Changed
17+
18+
- Double boxing in `Sequence` and `Tracks` was fixed. As a result, any custom tweenables
19+
should implement `From` for `BoxedTweenable` to make those APIs easier to use.
1420

1521
## [0.4.0] - 2022-04-16
1622

examples/sequence.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
167167
let tracks = Tracks::new([tween_rotate, tween_scale]);
168168
// Build a sequence from an heterogeneous list of tweenables by casting them manually
169169
// to a boxed Tweenable<Transform> : first move, then { rotate + scale }.
170-
let seq2 = Sequence::new([
171-
Box::new(tween_move) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
172-
Box::new(tracks) as Box<dyn Tweenable<Transform> + Send + Sync + 'static>,
173-
]);
170+
let seq2 = Sequence::new([Box::new(tween_move) as BoxedTweenable<_>, tracks.into()]);
174171

175172
commands
176173
.spawn_bundle(SpriteBundle {

src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ pub use lens::Lens;
161161
pub use plugin::{
162162
asset_animator_system, component_animator_system, AnimationSystem, TweeningPlugin,
163163
};
164-
pub use tweenable::{Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable};
164+
pub use tweenable::{
165+
BoxedTweenable, Delay, Sequence, Tracks, Tween, TweenCompleted, TweenState, Tweenable,
166+
};
165167

166168
/// Type of looping for a tween animation.
167169
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -427,7 +429,7 @@ macro_rules! animator_impl {
427429
pub struct Animator<T: Component> {
428430
/// Control if this animation is played or not.
429431
pub state: AnimatorState,
430-
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
432+
tweenable: Option<BoxedTweenable<T>>,
431433
speed: f32,
432434
}
433435

@@ -467,7 +469,7 @@ impl<T: Component> Animator<T> {
467469
pub struct AssetAnimator<T: Asset> {
468470
/// Control if this animation is played or not.
469471
pub state: AnimatorState,
470-
tweenable: Option<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
472+
tweenable: Option<BoxedTweenable<T>>,
471473
handle: Handle<T>,
472474
speed: f32,
473475
}

src/tweenable.rs

Lines changed: 87 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,54 @@
1-
use bevy::prelude::*;
21
use std::time::Duration;
32

3+
use bevy::prelude::*;
4+
45
use crate::{EaseMethod, Lens, TweeningDirection, TweeningType};
56

7+
/// The dynamic tweenable type.
8+
///
9+
/// When creating lists of tweenables, you will need to box them to create a homogeneous
10+
/// array like so:
11+
/// ```no_run
12+
/// # use bevy::prelude::Transform;
13+
/// # use bevy_tweening::{BoxedTweenable, Delay, Sequence, Tween};
14+
/// #
15+
/// # let delay: Delay = unimplemented!();
16+
/// # let tween: Tween<Transform> = unimplemented!();
17+
///
18+
/// Sequence::new([Box::new(delay) as BoxedTweenable<Transform>, tween.into()]);
19+
/// ```
20+
///
21+
/// When using your own [`Tweenable`] types, APIs will be easier to use if you implement [`From`]:
22+
/// ```no_run
23+
/// # use std::time::Duration;
24+
/// # use bevy::prelude::{Entity, EventWriter, Transform};
25+
/// # use bevy_tweening::{BoxedTweenable, Sequence, Tweenable, TweenCompleted, TweenState};
26+
/// #
27+
/// # struct MyTweenable;
28+
/// # impl Tweenable<Transform> for MyTweenable {
29+
/// # fn duration(&self) -> Duration { unimplemented!() }
30+
/// # fn is_looping(&self) -> bool { unimplemented!() }
31+
/// # fn set_progress(&mut self, progress: f32) { unimplemented!() }
32+
/// # fn progress(&self) -> f32 { unimplemented!() }
33+
/// # fn tick(&mut self, delta: Duration, target: &mut Transform, entity: Entity, event_writer: &mut EventWriter<TweenCompleted>) -> TweenState { unimplemented!() }
34+
/// # fn times_completed(&self) -> u32 { unimplemented!() }
35+
/// # fn rewind(&mut self) { unimplemented!() }
36+
/// # }
37+
///
38+
/// Sequence::new([Box::new(MyTweenable) as BoxedTweenable<_>]);
39+
///
40+
/// // OR
41+
///
42+
/// Sequence::new([MyTweenable]);
43+
///
44+
/// impl From<MyTweenable> for BoxedTweenable<Transform> {
45+
/// fn from(t: MyTweenable) -> Self {
46+
/// Box::new(t)
47+
/// }
48+
/// }
49+
/// ```
50+
pub type BoxedTweenable<T> = Box<dyn Tweenable<T> + Send + Sync + 'static>;
51+
652
/// Playback state of a [`Tweenable`].
753
///
854
/// 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 {
169215
fn rewind(&mut self);
170216
}
171217

172-
impl<T> Tweenable<T> for Box<dyn Tweenable<T> + Send + Sync + 'static> {
173-
fn duration(&self) -> Duration {
174-
self.as_ref().duration()
175-
}
176-
fn is_looping(&self) -> bool {
177-
self.as_ref().is_looping()
178-
}
179-
fn set_progress(&mut self, progress: f32) {
180-
self.as_mut().set_progress(progress);
218+
impl<T> From<Delay> for BoxedTweenable<T> {
219+
fn from(d: Delay) -> Self {
220+
Box::new(d)
181221
}
182-
fn progress(&self) -> f32 {
183-
self.as_ref().progress()
184-
}
185-
fn tick(
186-
&mut self,
187-
delta: Duration,
188-
target: &mut T,
189-
entity: Entity,
190-
event_writer: &mut EventWriter<TweenCompleted>,
191-
) -> TweenState {
192-
self.as_mut().tick(delta, target, entity, event_writer)
193-
}
194-
fn times_completed(&self) -> u32 {
195-
self.as_ref().times_completed()
196-
}
197-
fn rewind(&mut self) {
198-
self.as_mut().rewind();
222+
}
223+
224+
impl<T: 'static> From<Sequence<T>> for BoxedTweenable<T> {
225+
fn from(s: Sequence<T>) -> Self {
226+
Box::new(s)
199227
}
200228
}
201229

202-
/// Trait for boxing a [`Tweenable`] trait object.
203-
pub trait IntoBoxDynTweenable<T> {
204-
/// Convert the current object into a boxed [`Tweenable`].
205-
fn into_box_dyn(this: Self) -> Box<dyn Tweenable<T> + Send + Sync + 'static>;
230+
impl<T: 'static> From<Tracks<T>> for BoxedTweenable<T> {
231+
fn from(t: Tracks<T>) -> Self {
232+
Box::new(t)
233+
}
206234
}
207235

208-
impl<T, U: Tweenable<T> + Send + Sync + 'static> IntoBoxDynTweenable<T> for U {
209-
fn into_box_dyn(this: U) -> Box<dyn Tweenable<T> + Send + Sync + 'static> {
210-
Box::new(this)
236+
impl<T: 'static> From<Tween<T>> for BoxedTweenable<T> {
237+
fn from(t: Tween<T>) -> Self {
238+
Box::new(t)
211239
}
212240
}
213241

@@ -480,7 +508,7 @@ impl<T> Tweenable<T> for Tween<T> {
480508

481509
/// A sequence of tweens played back in order one after the other.
482510
pub struct Sequence<T> {
483-
tweens: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
511+
tweens: Vec<BoxedTweenable<T>>,
484512
index: usize,
485513
duration: Duration,
486514
time: Duration,
@@ -492,13 +520,14 @@ impl<T> Sequence<T> {
492520
///
493521
/// This method panics if the input collection is empty.
494522
#[must_use]
495-
pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
496-
let tweens: Vec<_> = items
497-
.into_iter()
498-
.map(IntoBoxDynTweenable::into_box_dyn)
499-
.collect();
523+
pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
524+
let tweens: Vec<_> = items.into_iter().map(Into::into).collect();
500525
assert!(!tweens.is_empty());
501-
let duration = tweens.iter().map(Tweenable::duration).sum();
526+
let duration = tweens
527+
.iter()
528+
.map(AsRef::as_ref)
529+
.map(Tweenable::duration)
530+
.sum();
502531
Self {
503532
tweens,
504533
index: 0,
@@ -512,8 +541,9 @@ impl<T> Sequence<T> {
512541
#[must_use]
513542
pub fn from_single(tween: impl Tweenable<T> + Send + Sync + 'static) -> Self {
514543
let duration = tween.duration();
544+
let boxed: BoxedTweenable<T> = Box::new(tween);
515545
Self {
516-
tweens: vec![Box::new(tween)],
546+
tweens: vec![boxed],
517547
index: 0,
518548
duration,
519549
time: Duration::ZERO,
@@ -637,7 +667,7 @@ impl<T> Tweenable<T> for Sequence<T> {
637667

638668
/// A collection of [`Tweenable`] executing in parallel.
639669
pub struct Tracks<T> {
640-
tracks: Vec<Box<dyn Tweenable<T> + Send + Sync + 'static>>,
670+
tracks: Vec<BoxedTweenable<T>>,
641671
duration: Duration,
642672
time: Duration,
643673
times_completed: u32,
@@ -646,12 +676,14 @@ pub struct Tracks<T> {
646676
impl<T> Tracks<T> {
647677
/// Create a new [`Tracks`] from an iterator over a collection of [`Tweenable`].
648678
#[must_use]
649-
pub fn new(items: impl IntoIterator<Item = impl IntoBoxDynTweenable<T>>) -> Self {
650-
let tracks: Vec<_> = items
651-
.into_iter()
652-
.map(IntoBoxDynTweenable::into_box_dyn)
653-
.collect();
654-
let duration = tracks.iter().map(Tweenable::duration).max().unwrap();
679+
pub fn new(items: impl IntoIterator<Item = impl Into<BoxedTweenable<T>>>) -> Self {
680+
let tracks: Vec<_> = items.into_iter().map(Into::into).collect();
681+
let duration = tracks
682+
.iter()
683+
.map(AsRef::as_ref)
684+
.map(Tweenable::duration)
685+
.max()
686+
.unwrap();
655687
Self {
656688
tracks,
657689
duration,
@@ -797,12 +829,15 @@ impl<T> Tweenable<T> for Delay {
797829

798830
#[cfg(test)]
799831
mod tests {
800-
use super::*;
801-
use crate::lens::*;
802-
use bevy::ecs::{event::Events, system::SystemState};
803832
use std::sync::{Arc, Mutex};
804833
use std::time::Duration;
805834

835+
use bevy::ecs::{event::Events, system::SystemState};
836+
837+
use crate::lens::*;
838+
839+
use super::*;
840+
806841
/// Utility to compare floating-point values with a tolerance.
807842
fn abs_diff_eq(a: f32, b: f32, tol: f32) -> bool {
808843
(a - b).abs() < tol

0 commit comments

Comments
 (0)