Fix timer with zero duration (#8467)

# Objective

Timer with zero `Duration` panics at `tick()` because of division by
zero. This PR Fixes #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.
This commit is contained in:
Rostyslav Toch 2023-04-24 15:32:42 +01:00 committed by GitHub
parent f3360938eb
commit e2531b2273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -224,10 +224,17 @@ impl Timer {
if self.finished() { if self.finished() {
if self.mode == TimerMode::Repeating { if self.mode == TimerMode::Repeating {
self.times_finished_this_tick = self.times_finished_this_tick = self
(self.elapsed().as_nanos() / self.duration().as_nanos()) as u32; .elapsed()
// Duration does not have a modulo .as_nanos()
self.set_elapsed(self.elapsed() - self.duration() * self.times_finished_this_tick); .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 { } else {
self.times_finished_this_tick = 1; self.times_finished_this_tick = 1;
self.set_elapsed(self.duration()); self.set_elapsed(self.duration());
@ -329,7 +336,11 @@ impl Timer {
/// ``` /// ```
#[inline] #[inline]
pub fn percent(&self) -> f32 { 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). /// Returns the percentage of the timer remaining time (goes from 1.0 to 0.0).
@ -517,6 +528,26 @@ mod tests {
assert_eq!(t.times_finished_this_tick(), 0); 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] #[test]
fn times_finished_this_tick_precise() { fn times_finished_this_tick_precise() {
let mut t = Timer::from_seconds(0.01, TimerMode::Repeating); let mut t = Timer::from_seconds(0.01, TimerMode::Repeating);