Skip to content

Commit ab1e792

Browse files
authored
Fix loss of precision in AnimClock and improve performance (#24)
1 parent 64f7460 commit ab1e792

File tree

1 file changed

+52
-30
lines changed

1 file changed

+52
-30
lines changed

src/tweenable.rs

Lines changed: 52 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -45,60 +45,52 @@ pub struct TweenCompleted {
4545
#[derive(Debug, Default, Clone, Copy)]
4646
struct AnimClock {
4747
elapsed: Duration,
48-
total: Duration,
48+
duration: Duration,
4949
is_looping: bool,
5050
}
5151

5252
impl AnimClock {
5353
fn new(duration: Duration, is_looping: bool) -> Self {
5454
AnimClock {
5555
elapsed: Duration::ZERO,
56-
total: duration,
56+
duration,
5757
is_looping,
5858
}
5959
}
6060

61-
#[allow(dead_code)] // TEMP
62-
fn elapsed(&self) -> Duration {
63-
self.elapsed
64-
}
61+
fn tick(&mut self, duration: Duration) -> u32 {
62+
self.elapsed = self.elapsed.saturating_add(duration);
6563

66-
fn total(&self) -> Duration {
67-
self.total
68-
}
64+
if self.elapsed < self.duration {
65+
0
66+
} else if self.is_looping {
67+
let elapsed = self.elapsed.as_nanos();
68+
let duration = self.duration.as_nanos();
6969

70-
fn tick(&mut self, duration: Duration) -> u32 {
71-
let new_elapsed = self.elapsed.saturating_add(duration);
72-
let progress = new_elapsed.as_secs_f64() / self.total.as_secs_f64();
73-
let times_completed = progress as u32;
74-
let progress = if self.is_looping {
75-
progress.fract()
70+
self.elapsed = Duration::from_nanos((elapsed % duration) as u64);
71+
(elapsed / duration) as u32
7672
} else {
77-
progress.min(1.)
78-
};
79-
self.elapsed = self.total.mul_f64(progress);
80-
times_completed
73+
self.elapsed = self.duration;
74+
1
75+
}
8176
}
8277

83-
fn set_progress(&mut self, progress: f32) -> u32 {
84-
let progress = progress.max(0.);
85-
let times_completed = progress as u32;
78+
fn set_progress(&mut self, progress: f32) {
8679
let progress = if self.is_looping {
87-
progress.fract()
80+
progress.max(0.).fract()
8881
} else {
89-
progress.min(1.)
82+
progress.clamp(0., 1.)
9083
};
91-
self.elapsed = self.total.mul_f32(progress);
92-
times_completed
84+
85+
self.elapsed = self.duration.mul_f32(progress);
9386
}
9487

9588
fn progress(&self) -> f32 {
96-
//self.elapsed.div_duration_f32(self.total) // TODO: unstable
97-
(self.elapsed.as_secs_f64() / self.total.as_secs_f64()) as f32
89+
self.elapsed.as_secs_f32() / self.duration.as_secs_f32()
9890
}
9991

10092
fn completed(&self) -> bool {
101-
self.elapsed >= self.total
93+
self.elapsed >= self.duration
10294
}
10395

10496
fn reset(&mut self) {
@@ -408,7 +400,7 @@ impl<T> Tween<T> {
408400

409401
impl<T> Tweenable<T> for Tween<T> {
410402
fn duration(&self) -> Duration {
411-
self.clock.total()
403+
self.clock.duration
412404
}
413405

414406
fn is_looping(&self) -> bool {
@@ -808,6 +800,36 @@ mod tests {
808800
last_reported_count: u32,
809801
}
810802

803+
#[test]
804+
fn anim_clock_precision() {
805+
let duration = Duration::from_millis(1);
806+
let mut clock = AnimClock::new(duration, true);
807+
808+
let test_ticks = [
809+
Duration::from_micros(123),
810+
Duration::from_millis(1),
811+
Duration::from_secs_f32(1. / 24.),
812+
Duration::from_secs_f32(1. / 30.),
813+
Duration::from_secs_f32(1. / 60.),
814+
Duration::from_secs_f32(1. / 120.),
815+
Duration::from_secs_f32(1. / 144.),
816+
Duration::from_secs_f32(1. / 240.),
817+
];
818+
819+
let mut times_completed = 0;
820+
let mut total_duration = Duration::ZERO;
821+
for i in 0..10_000_000 {
822+
let tick = test_ticks[i % test_ticks.len()];
823+
times_completed += clock.tick(tick);
824+
total_duration += tick;
825+
}
826+
827+
assert_eq!(
828+
(total_duration.as_secs_f64() / duration.as_secs_f64()) as u32,
829+
times_completed
830+
);
831+
}
832+
811833
/// Test ticking of a single tween in isolation.
812834
#[test]
813835
fn tween_tick() {

0 commit comments

Comments
 (0)