Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify FixedTime and Time (ver. B) (proof-of-concept) #8946

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{Diagnostic, DiagnosticId, Diagnostics, RegisterDiagnostic};
use bevy_app::prelude::*;
use bevy_core::FrameCount;
use bevy_ecs::prelude::*;
use bevy_time::Time;
use bevy_time::RealTime;

/// Adds "frame time" diagnostic to an App, specifically "frame time", "fps" and "frame count"
#[derive(Default)]
Expand Down Expand Up @@ -30,12 +30,12 @@ impl FrameTimeDiagnosticsPlugin {

pub fn diagnostic_system(
mut diagnostics: Diagnostics,
time: Res<Time>,
real_time: Res<RealTime>,
frame_count: Res<FrameCount>,
) {
diagnostics.add_measurement(Self::FRAME_COUNT, || frame_count.0 as f64);

let delta_seconds = time.raw_delta_seconds_f64();
let delta_seconds = real_time.delta_seconds_f64();
if delta_seconds == 0.0 {
return;
}
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_diagnostic/src/log_diagnostics_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{Diagnostic, DiagnosticId, DiagnosticsStore};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_log::{debug, info};
use bevy_time::{Time, Timer, TimerMode};
use bevy_time::{RealTime, Timer, TimerMode};
use bevy_utils::Duration;

/// An App Plugin that logs diagnostics to the console
Expand Down Expand Up @@ -82,10 +82,10 @@ impl LogDiagnosticsPlugin {

fn log_diagnostics_system(
mut state: ResMut<LogDiagnosticsState>,
time: Res<Time>,
real_time: Res<RealTime>,
diagnostics: Res<DiagnosticsStore>,
) {
if state.timer.tick(time.raw_delta()).finished() {
if state.timer.tick(real_time.delta()).finished() {
if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().flat_map(|id| {
diagnostics
Expand All @@ -107,10 +107,10 @@ impl LogDiagnosticsPlugin {

fn log_diagnostics_debug_system(
mut state: ResMut<LogDiagnosticsState>,
time: Res<Time>,
real_time: Res<RealTime>,
diagnostics: Res<DiagnosticsStore>,
) {
if state.timer.tick(time.raw_delta()).finished() {
if state.timer.tick(real_time.delta()).finished() {
if let Some(ref filter) = state.filter {
for diagnostic in filter.iter().flat_map(|id| {
diagnostics
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_gilrs/src/rumble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bevy_ecs::{
};
use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest};
use bevy_log::{debug, warn};
use bevy_time::Time;
use bevy_time::RealTime;
use bevy_utils::{Duration, HashMap};
use gilrs::{
ff::{self, BaseEffect, BaseEffectType, Repeat, Replay},
Expand Down Expand Up @@ -120,12 +120,12 @@ fn handle_rumble_request(
Ok(())
}
pub(crate) fn play_gilrs_rumble(
time: Res<Time>,
real_time: Res<RealTime>,
mut gilrs: NonSendMut<Gilrs>,
mut requests: EventReader<GamepadRumbleRequest>,
mut running_rumbles: NonSendMut<RunningRumbleEffects>,
) {
let current_time = time.raw_elapsed();
let current_time = real_time.elapsed();
// Remove outdated rumble effects.
for rumbles in running_rumbles.rumbles.values_mut() {
// `ff::Effect` uses RAII, dropping = deactivating
Expand Down
183 changes: 183 additions & 0 deletions crates/bevy_time/src/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use bevy_reflect::{FromReflect, Reflect};
use bevy_utils::{default, Duration, Instant};

fn duration_div_rem(dividend: Duration, divisor: Duration) -> (u32, Duration) {
// `Duration` does not have a built-in modulo operation
let quotient = (dividend.as_nanos() / divisor.as_nanos()) as u32;
let remainder = dividend - (quotient * divisor);
(quotient, remainder)
}

/// A stopwatch that tracks the time since last update and the time since creation.
#[derive(Debug, Clone, Copy, Reflect, FromReflect, PartialEq)]
pub struct Clock {
startup: Instant,
first_update: Option<Instant>,
last_update: Option<Instant>,
delta: Duration,
delta_seconds: f32,
delta_seconds_f64: f64,
elapsed: Duration,
elapsed_seconds: f32,
elapsed_seconds_f64: f64,
wrap_period: Duration,
elapsed_wrapped: Duration,
elapsed_seconds_wrapped: f32,
elapsed_seconds_wrapped_f64: f64,
}

impl Default for Clock {
fn default() -> Self {
Self {
startup: Instant::now(),
first_update: None,
last_update: None,
delta: Duration::ZERO,
delta_seconds: 0.0,
delta_seconds_f64: 0.0,
elapsed: Duration::ZERO,
elapsed_seconds: 0.0,
elapsed_seconds_f64: 0.0,
wrap_period: Duration::from_secs(3600), // 1 hour
elapsed_wrapped: Duration::ZERO,
elapsed_seconds_wrapped: 0.0,
elapsed_seconds_wrapped_f64: 0.0,
}
}
}

impl Clock {
/// Constructs a new `Clock` instance with a specific startup `Instant`.
pub fn new(startup: Instant) -> Self {
Self {
startup,
..default()
}
}

/// Advances the clock by `dt` and records it happening at `instant`.
pub fn update(&mut self, dt: Duration, instant: Instant) {
self.delta = dt;
self.delta_seconds = self.delta.as_secs_f32();
self.delta_seconds_f64 = self.delta.as_secs_f64();

self.elapsed += dt;
self.elapsed_seconds = self.elapsed.as_secs_f32();
self.elapsed_seconds_f64 = self.elapsed.as_secs_f64();

self.elapsed_wrapped = duration_div_rem(self.elapsed, self.wrap_period).1;
self.elapsed_seconds_wrapped = self.elapsed_wrapped.as_secs_f32();
self.elapsed_seconds_wrapped_f64 = self.elapsed_wrapped.as_secs_f64();

if self.last_update.is_none() {
self.first_update = Some(instant);
}

self.last_update = Some(instant);
}

/// Returns the [`Instant`] the clock was created.
#[inline]
pub fn startup(&self) -> Instant {
self.startup
}

/// Returns the [`Instant`] when [`update`](#method.update) was first called, if it exists.
#[inline]
pub fn first_update(&self) -> Option<Instant> {
self.first_update
}

/// Returns the [`Instant`] when [`update`](#method.update) was last called, if it exists.
#[inline]
pub fn last_update(&self) -> Option<Instant> {
self.last_update
}

/// Returns how much time has advanced since the last [`update`](#method.update), as a [`Duration`].
#[inline]
pub fn delta(&self) -> Duration {
self.delta
}

/// Returns how much time has advanced since the last [`update`](#method.update), as [`f32`] seconds.
#[inline]
pub fn delta_seconds(&self) -> f32 {
self.delta_seconds
}

/// Returns how much time has advanced since the last [`update`](#method.update), as [`f64`] seconds.
#[inline]
pub fn delta_seconds_f64(&self) -> f64 {
self.delta_seconds_f64
}

/// Returns how much time has advanced since [`startup`](#method.startup), as [`Duration`].
#[inline]
pub fn elapsed(&self) -> Duration {
self.elapsed
}

/// Returns how much time has advanced since [`startup`](#method.startup), as [`f32`] seconds.
///
/// **Note:** This is a monotonically increasing value. It's precision will degrade over time.
/// If you need an `f32` but that precision loss is unacceptable,
/// use [`elapsed_seconds_wrapped`](#method.elapsed_seconds_wrapped).
#[inline]
pub fn elapsed_seconds(&self) -> f32 {
self.elapsed_seconds
}

/// Returns how much time has advanced since [`startup`](#method.startup), as [`f64`] seconds.
#[inline]
pub fn elapsed_seconds_f64(&self) -> f64 {
self.elapsed_seconds_f64
}

/// Returns how much time has advanced since [`startup`](#method.startup) modulo
/// the [`wrap_period`](#method.wrap_period), as [`Duration`].
#[inline]
pub fn elapsed_wrapped(&self) -> Duration {
self.elapsed_wrapped
}

/// Returns how much time has advanced since [`startup`](#method.startup) modulo
/// the [`wrap_period`](#method.wrap_period), as [`f32`] seconds.
///
/// This method is intended for applications (e.g. shaders) that require an [`f32`] value but
/// suffer from the gradual precision loss of [`elapsed_seconds`](#method.elapsed_seconds).
#[inline]
pub fn elapsed_seconds_wrapped(&self) -> f32 {
self.elapsed_seconds_wrapped
}

/// Returns how much time has advanced since [`startup`](#method.startup) modulo
/// the [`wrap_period`](#method.wrap_period), as [`f64`] seconds.
#[inline]
pub fn elapsed_seconds_wrapped_f64(&self) -> f64 {
self.elapsed_seconds_wrapped_f64
}

/// Returns the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped) and
/// [`elapsed_wrapped`](#method.elapsed_wrapped).
///
/// **Note:** The default modulus is one hour.
#[inline]
pub fn wrap_period(&self) -> Duration {
self.wrap_period
}

/// Sets the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped) and
/// [`elapsed_wrapped`](#method.elapsed_wrapped).
///
/// **Note:** This will not take effect until the next update.
///
/// # Panics
///
/// Panics if `wrap_period` is a zero-length duration.
#[inline]
pub fn set_wrap_period(&mut self, wrap_period: Duration) {
assert!(!wrap_period.is_zero(), "division by zero");
self.wrap_period = wrap_period;
}
}
47 changes: 4 additions & 43 deletions crates/bevy_time/src/common_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use crate::{fixed_timestep::FixedTime, Time, Timer, TimerMode};
use crate::{Time, Timer, TimerMode};
use bevy_ecs::system::Res;
use bevy_utils::Duration;

/// Run condition that is active on a regular time interval, using [`Time`] to advance
/// the timer.
///
/// If used for a fixed timestep system, use [`on_fixed_timer`] instead.
///
/// ```rust,no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, Update};
/// # use bevy_ecs::schedule::IntoSystemConfigs;
Expand All @@ -29,9 +27,8 @@ use bevy_utils::Duration;
/// times. This condition should only be used with large time durations (relative to
/// delta time).
///
/// For more accurate timers, use the [`Timer`] class directly (see
/// [`Timer::times_finished_this_tick`] to address the problem mentioned above), or
/// use fixed timesteps that allow systems to run multiple times per frame.
/// To create a more precise timer, use the [`Timer`] class directly (see
/// [`Timer::times_finished_this_tick`], which can address the issue described above).
pub fn on_timer(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
let mut timer = Timer::new(duration, TimerMode::Repeating);
move |time: Res<Time>| {
Expand All @@ -40,40 +37,6 @@ pub fn on_timer(duration: Duration) -> impl FnMut(Res<Time>) -> bool + Clone {
}
}

/// Run condition that is active on a regular time interval, using [`FixedTime`] to
/// advance the timer.
///
/// If used for a non-fixed timestep system, use [`on_timer`] instead.
///
/// ```rust,no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, FixedUpdate};
/// # use bevy_ecs::schedule::IntoSystemConfigs;
/// # use bevy_utils::Duration;
/// # use bevy_time::common_conditions::on_fixed_timer;
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .add_systems(FixedUpdate,
/// tick.run_if(on_fixed_timer(Duration::from_secs(1))),
/// )
/// .run();
/// }
/// fn tick() {
/// // ran once a second
/// }
/// ```
///
/// Note that this run condition may not behave as expected if `duration` is smaller
/// than the fixed timestep period, since the timer may complete multiple times in
/// one fixed update.
pub fn on_fixed_timer(duration: Duration) -> impl FnMut(Res<FixedTime>) -> bool + Clone {
let mut timer = Timer::new(duration, TimerMode::Repeating);
move |time: Res<FixedTime>| {
timer.tick(time.period);
timer.just_finished()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -85,9 +48,7 @@ mod tests {
#[test]
fn distributive_run_if_compiles() {
Schedule::default().add_systems(
(test_system, test_system)
.distributive_run_if(on_timer(Duration::new(1, 0)))
.distributive_run_if(on_fixed_timer(Duration::new(1, 0))),
(test_system, test_system).distributive_run_if(on_timer(Duration::new(1, 0))),
);
}
}
Loading