Skip to content

Commit

Permalink
Fix timer with zero duration (bevyengine#8467)
Browse files Browse the repository at this point in the history
# Objective

Timer with zero `Duration` panics at `tick()` because of division by
zero. This PR Fixes bevyengine#8463 .

## Solution

- Handle division by zero separately with `checked_div` and
`checked_rem`.

---

## Changelog


- Replace division with `checked_div`. Set `times_finished_this_tick` to
u32::MAX when duration is zero.
- Set `elapsed` to `Duration::ZERO` when timer duration is zero.
- Set `percent` to `1.0` when duration is zero.
- `times_finished_this_tick` is [not used
anywhere](https://github.com/bevyengine/bevy/search?q=times_finished_this_tick),
that's why this change will not affect other parts of the project.
- `times_finished_this_tick` is set to `0` after `reset()` and before
first `tick()` call.
  • Loading branch information
CrazyRoka authored Apr 24, 2023
1 parent f336093 commit e2531b2
Showing 1 changed file with 36 additions and 5 deletions.
41 changes: 36 additions & 5 deletions crates/bevy_time/src/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,17 @@ impl Timer {

if self.finished() {
if self.mode == TimerMode::Repeating {
self.times_finished_this_tick =
(self.elapsed().as_nanos() / self.duration().as_nanos()) as u32;
// Duration does not have a modulo
self.set_elapsed(self.elapsed() - self.duration() * self.times_finished_this_tick);
self.times_finished_this_tick = self
.elapsed()
.as_nanos()
.checked_div(self.duration().as_nanos())
.map_or(u32::MAX, |x| x as u32);
self.set_elapsed(
self.elapsed()
.as_nanos()
.checked_rem(self.duration().as_nanos())
.map_or(Duration::ZERO, |x| Duration::from_nanos(x as u64)),
);
} else {
self.times_finished_this_tick = 1;
self.set_elapsed(self.duration());
Expand Down Expand Up @@ -329,7 +336,11 @@ impl Timer {
/// ```
#[inline]
pub fn percent(&self) -> f32 {
self.elapsed().as_secs_f32() / self.duration().as_secs_f32()
if self.duration == Duration::ZERO {
1.0
} else {
self.elapsed().as_secs_f32() / self.duration().as_secs_f32()
}
}

/// Returns the percentage of the timer remaining time (goes from 1.0 to 0.0).
Expand Down Expand Up @@ -517,6 +528,26 @@ mod tests {
assert_eq!(t.times_finished_this_tick(), 0);
}

#[test]
fn times_finished_this_tick_repeating_zero_duration() {
let mut t = Timer::from_seconds(0.0, TimerMode::Repeating);
assert_eq!(t.times_finished_this_tick(), 0);
assert_eq!(t.elapsed(), Duration::ZERO);
assert_eq!(t.percent(), 1.0);
t.tick(Duration::from_secs(1));
assert_eq!(t.times_finished_this_tick(), u32::MAX);
assert_eq!(t.elapsed(), Duration::ZERO);
assert_eq!(t.percent(), 1.0);
t.tick(Duration::from_secs(2));
assert_eq!(t.times_finished_this_tick(), u32::MAX);
assert_eq!(t.elapsed(), Duration::ZERO);
assert_eq!(t.percent(), 1.0);
t.reset();
assert_eq!(t.times_finished_this_tick(), 0);
assert_eq!(t.elapsed(), Duration::ZERO);
assert_eq!(t.percent(), 1.0);
}

#[test]
fn times_finished_this_tick_precise() {
let mut t = Timer::from_seconds(0.01, TimerMode::Repeating);
Expand Down

0 comments on commit e2531b2

Please sign in to comment.