Replace the bool argument of Timer with TimerMode (#6247)

As mentioned in #2926, it's better to have an explicit type that clearly communicates the intent of the timer mode rather than an opaque boolean, which can be only understood when knowing the signature or having to look up the documentation.

This also opens up a way to merge different timers, such as `Stopwatch`, and possibly future ones, such as `DiscreteStopwatch` and `DiscreteTimer` from #2683, into one struct.

Signed-off-by: Lena Milizé <me@lvmn.org>

# Objective

Fixes #2926.

## Solution

Introduce `TimerMode` which replaces the `bool` argument of `Timer` constructors. A `Default` value for `TimerMode` is `Once`.

---

## Changelog

### Added

- `TimerMode` enum, along with variants `TimerMode::Once` and `TimerMode::Repeating`

### Changed

- Replace `bool` argument of `Timer::new` and `Timer::from_seconds` with `TimerMode`
- Change `repeating: bool` field of `Timer` with `mode: TimerMode`

## Migration Guide

- Replace `Timer::new(duration, false)` with `Timer::new(duration, TimerMode::Once)`.
- Replace `Timer::new(duration, true)` with `Timer::new(duration, TimerMode::Repeating)`.
- Replace `Timer::from_seconds(seconds, false)` with `Timer::from_seconds(seconds, TimerMode::Once)`.
- Replace `Timer::from_seconds(seconds, true)` with `Timer::from_seconds(seconds, TimerMode::Repeating)`.
- Change `timer.repeating()` to `timer.mode() == TimerMode::Repeating`.
This commit is contained in:
Lena Milizé 2022-10-17 13:47:01 +00:00
parent a0f1468108
commit 73605f43b6
17 changed files with 86 additions and 69 deletions

View file

@ -2,7 +2,7 @@ use super::{Diagnostic, DiagnosticId, Diagnostics};
use bevy_app::prelude::*;
use bevy_ecs::system::{Res, ResMut, Resource};
use bevy_log::{debug, info};
use bevy_time::{Time, Timer};
use bevy_time::{Time, Timer, TimerMode};
use bevy_utils::Duration;
/// An App Plugin that logs diagnostics to the console
@ -32,7 +32,7 @@ impl Default for LogDiagnosticsPlugin {
impl Plugin for LogDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(LogDiagnosticsState {
timer: Timer::new(self.wait_duration, true),
timer: Timer::new(self.wait_duration, TimerMode::Repeating),
filter: self.filter.clone(),
});

View file

@ -16,7 +16,7 @@ use crossbeam_channel::{Receiver, Sender};
pub mod prelude {
//! The Bevy Time Prelude.
#[doc(hidden)]
pub use crate::{Time, Timer};
pub use crate::{Time, Timer, TimerMode};
}
use bevy_app::prelude::*;

View file

@ -14,7 +14,7 @@ use bevy_utils::Duration;
pub struct Timer {
stopwatch: Stopwatch,
duration: Duration,
repeating: bool,
mode: TimerMode,
finished: bool,
times_finished_this_tick: u32,
}
@ -23,10 +23,10 @@ impl Timer {
/// Creates a new timer with a given duration.
///
/// See also [`Timer::from_seconds`](Timer::from_seconds).
pub fn new(duration: Duration, repeating: bool) -> Self {
pub fn new(duration: Duration, mode: TimerMode) -> Self {
Self {
duration,
repeating,
mode,
..Default::default()
}
}
@ -36,12 +36,12 @@ impl Timer {
/// # Example
/// ```
/// # use bevy_time::*;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// ```
pub fn from_seconds(duration: f32, repeating: bool) -> Self {
pub fn from_seconds(duration: f32, mode: TimerMode) -> Self {
Self {
duration: Duration::from_secs_f32(duration),
repeating,
mode,
..Default::default()
}
}
@ -52,7 +52,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(1.5));
/// assert!(timer.finished());
/// timer.tick(Duration::from_secs_f32(0.5));
@ -69,7 +69,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(1.5));
/// assert!(timer.just_finished());
/// timer.tick(Duration::from_secs_f32(0.5));
@ -89,7 +89,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(0.5));
/// assert_eq!(timer.elapsed(), Duration::from_secs_f32(0.5));
/// ```
@ -113,7 +113,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.set_elapsed(Duration::from_secs(2));
/// assert_eq!(timer.elapsed(), Duration::from_secs(2));
/// // the timer is not finished even if the elapsed time is greater than the duration.
@ -130,7 +130,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let timer = Timer::new(Duration::from_secs(1), false);
/// let timer = Timer::new(Duration::from_secs(1), TimerMode::Once);
/// assert_eq!(timer.duration(), Duration::from_secs(1));
/// ```
#[inline]
@ -144,7 +144,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.5, false);
/// let mut timer = Timer::from_seconds(1.5, TimerMode::Once);
/// timer.set_duration(Duration::from_secs(1));
/// assert_eq!(timer.duration(), Duration::from_secs(1));
/// ```
@ -158,12 +158,12 @@ impl Timer {
/// # Examples
/// ```
/// # use bevy_time::*;
/// let mut timer = Timer::from_seconds(1.0, true);
/// assert!(timer.repeating());
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
/// assert_eq!(timer.mode(), TimerMode::Repeating);
/// ```
#[inline]
pub fn repeating(&self) -> bool {
self.repeating
pub fn mode(&self) -> TimerMode {
self.mode
}
/// Sets whether the timer is repeating or not.
@ -171,17 +171,17 @@ impl Timer {
/// # Examples
/// ```
/// # use bevy_time::*;
/// let mut timer = Timer::from_seconds(1.0, true);
/// timer.set_repeating(false);
/// assert!(!timer.repeating());
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
/// timer.set_mode(TimerMode::Once);
/// assert_eq!(timer.mode(), TimerMode::Once);
/// ```
#[inline]
pub fn set_repeating(&mut self, repeating: bool) {
if !self.repeating && repeating && self.finished {
pub fn set_mode(&mut self, mode: TimerMode) {
if self.mode != TimerMode::Repeating && mode == TimerMode::Repeating && self.finished {
self.stopwatch.reset();
self.finished = self.just_finished();
}
self.repeating = repeating;
self.mode = mode;
}
/// Advance the timer by `delta` seconds.
@ -194,8 +194,8 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut repeating = Timer::from_seconds(1.0, true);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// let mut repeating = Timer::from_seconds(1.0, TimerMode::Repeating);
/// timer.tick(Duration::from_secs_f32(1.5));
/// repeating.tick(Duration::from_secs_f32(1.5));
/// assert_eq!(timer.elapsed_secs(), 1.0);
@ -204,13 +204,13 @@ impl Timer {
pub fn tick(&mut self, delta: Duration) -> &Self {
if self.paused() {
self.times_finished_this_tick = 0;
if self.repeating() {
if self.mode == TimerMode::Repeating {
self.finished = false;
}
return self;
}
if !self.repeating() && self.finished() {
if self.mode != TimerMode::Repeating && self.finished() {
self.times_finished_this_tick = 0;
return self;
}
@ -219,7 +219,7 @@ impl Timer {
self.finished = self.elapsed() >= self.duration();
if self.finished() {
if self.repeating() {
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
@ -243,7 +243,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.pause();
/// timer.tick(Duration::from_secs_f32(0.5));
/// assert_eq!(timer.elapsed_secs(), 0.0);
@ -261,7 +261,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.pause();
/// timer.tick(Duration::from_secs_f32(0.5));
/// timer.unpause();
@ -280,7 +280,7 @@ impl Timer {
/// # Examples
/// ```
/// # use bevy_time::*;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// assert!(!timer.paused());
/// timer.pause();
/// assert!(timer.paused());
@ -300,7 +300,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, false);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(1.5));
/// timer.reset();
/// assert!(!timer.finished());
@ -319,7 +319,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(2.0, false);
/// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(0.5));
/// assert_eq!(timer.percent(), 0.25);
/// ```
@ -334,7 +334,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(2.0, false);
/// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(0.5));
/// assert_eq!(timer.percent_left(), 0.75);
/// ```
@ -350,7 +350,7 @@ impl Timer {
/// # use bevy_time::*;
/// use std::cmp::Ordering;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(2.0, false);
/// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(0.5));
/// let result = timer.remaining_secs().total_cmp(&1.5);
/// assert_eq!(Ordering::Equal, result);
@ -366,7 +366,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(2.0, false);
/// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
/// timer.tick(Duration::from_secs_f32(0.5));
/// assert_eq!(timer.remaining(), Duration::from_secs_f32(1.5));
/// ```
@ -385,7 +385,7 @@ impl Timer {
/// ```
/// # use bevy_time::*;
/// use std::time::Duration;
/// let mut timer = Timer::from_seconds(1.0, true);
/// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
/// timer.tick(Duration::from_secs_f32(6.0));
/// assert_eq!(timer.times_finished_this_tick(), 6);
/// timer.tick(Duration::from_secs_f32(2.0));
@ -399,6 +399,17 @@ impl Timer {
}
}
/// Specifies [`Timer`] behavior.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, Reflect)]
#[reflect(Default)]
pub enum TimerMode {
/// Run once and stop.
#[default]
Once,
/// Reset when finished.
Repeating,
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
@ -406,7 +417,7 @@ mod tests {
#[test]
fn non_repeating_timer() {
let mut t = Timer::from_seconds(10.0, false);
let mut t = Timer::from_seconds(10.0, TimerMode::Once);
// Tick once, check all attributes
t.tick(Duration::from_secs_f32(0.25));
assert_eq!(t.elapsed_secs(), 0.25);
@ -414,7 +425,7 @@ mod tests {
assert!(!t.finished());
assert!(!t.just_finished());
assert_eq!(t.times_finished_this_tick(), 0);
assert!(!t.repeating());
assert_eq!(t.mode(), TimerMode::Once);
assert_eq!(t.percent(), 0.025);
assert_eq!(t.percent_left(), 0.975);
// Ticking while paused changes nothing
@ -425,7 +436,7 @@ mod tests {
assert!(!t.finished());
assert!(!t.just_finished());
assert_eq!(t.times_finished_this_tick(), 0);
assert!(!t.repeating());
assert_eq!(t.mode(), TimerMode::Once);
assert_eq!(t.percent(), 0.025);
assert_eq!(t.percent_left(), 0.975);
// Tick past the end and make sure elapsed doesn't go past 0.0 and other things update
@ -449,7 +460,7 @@ mod tests {
#[test]
fn repeating_timer() {
let mut t = Timer::from_seconds(2.0, true);
let mut t = Timer::from_seconds(2.0, TimerMode::Repeating);
// Tick once, check all attributes
t.tick(Duration::from_secs_f32(0.75));
assert_eq!(t.elapsed_secs(), 0.75);
@ -457,7 +468,7 @@ mod tests {
assert!(!t.finished());
assert!(!t.just_finished());
assert_eq!(t.times_finished_this_tick(), 0);
assert!(t.repeating());
assert_eq!(t.mode(), TimerMode::Repeating);
assert_eq!(t.percent(), 0.375);
assert_eq!(t.percent_left(), 0.625);
// Tick past the end and make sure elapsed wraps
@ -480,7 +491,7 @@ mod tests {
#[test]
fn times_finished_repeating() {
let mut t = Timer::from_seconds(1.0, true);
let mut t = Timer::from_seconds(1.0, TimerMode::Repeating);
assert_eq!(t.times_finished_this_tick(), 0);
t.tick(Duration::from_secs_f32(3.5));
assert_eq!(t.times_finished_this_tick(), 3);
@ -493,7 +504,7 @@ mod tests {
#[test]
fn times_finished_this_tick() {
let mut t = Timer::from_seconds(1.0, false);
let mut t = Timer::from_seconds(1.0, TimerMode::Once);
assert_eq!(t.times_finished_this_tick(), 0);
t.tick(Duration::from_secs_f32(1.5));
assert_eq!(t.times_finished_this_tick(), 1);
@ -503,7 +514,7 @@ mod tests {
#[test]
fn times_finished_this_tick_precise() {
let mut t = Timer::from_seconds(0.01, true);
let mut t = Timer::from_seconds(0.01, TimerMode::Repeating);
let duration = Duration::from_secs_f64(0.333);
// total duration: 0.333 => 33 times finished
@ -522,7 +533,7 @@ mod tests {
#[test]
fn paused() {
let mut t = Timer::from_seconds(10.0, false);
let mut t = Timer::from_seconds(10.0, TimerMode::Once);
t.tick(Duration::from_secs_f32(10.0));
assert!(t.just_finished());
@ -536,7 +547,7 @@ mod tests {
#[test]
fn paused_repeating() {
let mut t = Timer::from_seconds(10.0, true);
let mut t = Timer::from_seconds(10.0, TimerMode::Repeating);
t.tick(Duration::from_secs_f32(10.0));
assert!(t.just_finished());

View file

@ -49,6 +49,6 @@ fn setup(
transform: Transform::from_scale(Vec3::splat(6.0)),
..default()
},
AnimationTimer(Timer::from_seconds(0.1, true)),
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
));
}

View file

@ -29,7 +29,7 @@ impl Plugin for PrintMessagePlugin {
fn build(&self, app: &mut App) {
let state = PrintMessageState {
message: self.message.clone(),
timer: Timer::new(self.wait_duration, true),
timer: Timer::new(self.wait_duration, TimerMode::Repeating),
};
app.insert_resource(state).add_system(print_message_system);
}

View file

@ -30,7 +30,7 @@ struct EventTriggerState {
impl Default for EventTriggerState {
fn default() -> Self {
EventTriggerState {
event_timer: Timer::from_seconds(1.0, true),
event_timer: Timer::from_seconds(1.0, TimerMode::Repeating),
}
}
}

View file

@ -51,13 +51,13 @@ fn main() {
fn setup_system(mut commands: Commands) {
commands.spawn((
PrinterTick(Timer::from_seconds(1.0, true)),
PrinterTick(Timer::from_seconds(1.0, TimerMode::Repeating)),
TextToPrint("I will print until you press space.".to_string()),
MenuClose,
));
commands.spawn((
PrinterTick(Timer::from_seconds(1.0, true)),
PrinterTick(Timer::from_seconds(1.0, TimerMode::Repeating)),
TextToPrint("I will always print".to_string()),
LevelUnload,
));

View file

@ -24,8 +24,8 @@ pub struct Countdown {
impl Countdown {
pub fn new() -> Self {
Self {
percent_trigger: Timer::from_seconds(4.0, true),
main_timer: Timer::from_seconds(20.0, false),
percent_trigger: Timer::from_seconds(4.0, TimerMode::Repeating),
main_timer: Timer::from_seconds(20.0, TimerMode::Once),
}
}
}
@ -38,7 +38,10 @@ impl Default for Countdown {
fn setup(mut commands: Commands) {
// Add an entity to the world with a timer
commands.spawn(PrintOnCompletionTimer(Timer::from_seconds(5.0, false)));
commands.spawn(PrintOnCompletionTimer(Timer::from_seconds(
5.0,
TimerMode::Once,
)));
}
/// This system ticks all the `Timer` components on entities within the scene

View file

@ -17,7 +17,10 @@ struct BonusSpawnTimer(Timer);
fn main() {
App::new()
.init_resource::<Game>()
.insert_resource(BonusSpawnTimer(Timer::from_seconds(5.0, true)))
.insert_resource(BonusSpawnTimer(Timer::from_seconds(
5.0,
TimerMode::Repeating,
)))
.add_plugins(DefaultPlugins)
.add_state(GameState::Playing)
.add_startup_system(setup_cameras)
@ -98,7 +101,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
game.score = 0;
game.player.i = BOARD_SIZE_I / 2;
game.player.j = BOARD_SIZE_J / 2;
game.player.move_cooldown = Timer::from_seconds(0.3, false);
game.player.move_cooldown = Timer::from_seconds(0.3, TimerMode::Once);
commands.spawn(PointLightBundle {
transform: Transform::from_xyz(4.0, 10.0, 4.0),

View file

@ -39,7 +39,7 @@ struct SelectionState {
impl Default for SelectionState {
fn default() -> Self {
Self {
timer: Timer::from_seconds(SHOWCASE_TIMER_SECS, true),
timer: Timer::from_seconds(SHOWCASE_TIMER_SECS, TimerMode::Repeating),
has_triggered: false,
}
}

View file

@ -96,7 +96,7 @@ mod splash {
OnSplashScreen,
));
// Insert the timer as a resource
commands.insert_resource(SplashTimer(Timer::from_seconds(1.0, false)));
commands.insert_resource(SplashTimer(Timer::from_seconds(1.0, TimerMode::Once)));
}
// Tick the timer, and change state when finished
@ -215,7 +215,7 @@ mod game {
);
});
// Spawn a 5 seconds timer to trigger going back to the menu
commands.insert_resource(GameTimer(Timer::from_seconds(5.0, false)));
commands.insert_resource(GameTimer(Timer::from_seconds(5.0, TimerMode::Once)));
}
// Tick the timer, and change state when finished

View file

@ -61,7 +61,7 @@ fn setup(
let translation = (position * tile_size).extend(rng.gen::<f32>());
let rotation = Quat::from_rotation_z(rng.gen::<f32>());
let scale = Vec3::splat(rng.gen::<f32>() * 2.0);
let mut timer = Timer::from_seconds(0.1, true);
let mut timer = Timer::from_seconds(0.1, TimerMode::Repeating);
timer.set_elapsed(Duration::from_secs_f32(rng.gen::<f32>()));
commands.spawn((
@ -118,7 +118,7 @@ struct PrintingTimer(Timer);
impl Default for PrintingTimer {
fn default() -> Self {
Self(Timer::from_seconds(1.0, true))
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
}
}

View file

@ -185,6 +185,6 @@ struct PrintingTimer(Timer);
impl Default for PrintingTimer {
fn default() -> Self {
Self(Timer::from_seconds(1.0, true))
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
}
}

View file

@ -187,6 +187,6 @@ struct PrintingTimer(Timer);
impl Default for PrintingTimer {
fn default() -> Self {
Self(Timer::from_seconds(1.0, true))
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
}
}

View file

@ -103,7 +103,7 @@ struct PrintingTimer(Timer);
impl Default for PrintingTimer {
fn default() -> Self {
Self(Timer::from_seconds(1.0, true))
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
}
}

View file

@ -26,7 +26,7 @@ impl Default for State {
Self {
atlas_count: 0,
handle: Handle::default(),
timer: Timer::from_seconds(0.05, true),
timer: Timer::from_seconds(0.05, TimerMode::Repeating),
}
}
}

View file

@ -13,7 +13,7 @@ fn main() {
.insert_resource(TargetScale {
start_scale: 1.0,
target_scale: 1.0,
target_time: Timer::new(Duration::from_millis(SCALE_TIME), false),
target_time: Timer::new(Duration::from_millis(SCALE_TIME), TimerMode::Once),
})
.add_startup_system(setup)
.add_system(apply_scaling.label(ApplyScaling))