Skip to content

Commit

Permalink
Split Time into three separate clocks
Browse files Browse the repository at this point in the history
  • Loading branch information
nakedible committed Jun 26, 2023
1 parent 910f984 commit 03d2c1b
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 846 deletions.
36 changes: 1 addition & 35 deletions crates/bevy_time/src/common_conditions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{fixed_timestep::FixedTime, Time, Timer, TimerMode};
use crate::{Time, Timer, TimerMode};
use bevy_ecs::system::Res;
use bevy_utils::Duration;

Expand Down Expand Up @@ -40,40 +40,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 Down
64 changes: 64 additions & 0 deletions crates/bevy_time/src/fixed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use bevy_utils::Duration;
use bevy_ecs::world::World;
use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{FromReflect, Reflect};

use crate::FixedUpdate;
use crate::time::Time;
use crate::virt::Virtual;

#[derive(Resource, Debug, Copy, Clone, Reflect, FromReflect)]
#[reflect(Resource)]
pub struct Fixed {
period: Duration,
accumulated: Duration,
}

impl Time<Fixed> {
pub fn period(&self) -> Duration {
self.context().period
}

pub fn set_period(&mut self, period: Duration) {
assert_ne!(period, Duration::ZERO, "attempted to set fixed time period to zero");
self.context_mut().period = period;
}

fn expend(&mut self) -> bool {
let period = self.period();
if let Some(new_value) = self.context_mut().accumulated.checked_sub(period) {
// reduce accumulated and increase elapsed by period
self.context_mut().accumulated = new_value;
self.advance_by(period);
true
} else {
// no more periods left in accumulated
false
}
}
}

impl Default for Fixed {
fn default() -> Self {
Self {
period: Duration::from_secs_f64(1. / 60.),
accumulated: Duration::ZERO,
}
}
}

pub fn run_fixed_update_schedule(
world: &mut World
) {
let delta = world.resource::<Time<Virtual>>().delta();
world.resource_mut::<Time<Fixed>>().context_mut().accumulated += delta;

// Run the schedule until we run out of accumulated time
let _ = world.try_schedule_scope(FixedUpdate, |world, schedule| {
while world.resource_mut::<Time<Fixed>>().expend() {
world.resource::<Time<Fixed>>().as_current().clone_into(world.resource_mut::<Time>().as_mut());
schedule.run(world);
}
});
world.resource::<Time<Virtual>>().as_current().clone_into(world.resource_mut::<Time>().as_mut());
}
158 changes: 0 additions & 158 deletions crates/bevy_time/src/fixed_timestep.rs

This file was deleted.

31 changes: 19 additions & 12 deletions crates/bevy_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

/// Common run conditions
pub mod common_conditions;
pub mod fixed_timestep;
mod stopwatch;
#[allow(clippy::module_inception)]
mod time;
mod timer;
mod real;
mod fixed;
mod virt;

use fixed_timestep::FixedTime;
pub use stopwatch::*;
pub use time::*;
pub use timer::*;
Expand All @@ -20,13 +21,17 @@ use crossbeam_channel::{Receiver, Sender};
pub mod prelude {
//! The Bevy Time Prelude.
#[doc(hidden)]
pub use crate::{fixed_timestep::FixedTime, Time, Timer, TimerMode};
pub use crate::{Time, Timer, TimerMode};
}

use bevy_app::{prelude::*, RunFixedUpdateLoop};
use bevy_ecs::prelude::*;

use crate::fixed_timestep::run_fixed_update_schedule;
use crate::real::Real;
use crate::virt::Virtual;
use crate::virt::virtual_time_system;
use crate::fixed::Fixed;
use crate::fixed::run_fixed_update_schedule;

/// Adds time functionality to Apps.
#[derive(Default)]
Expand All @@ -40,12 +45,17 @@ pub struct TimeSystem;
impl Plugin for TimePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Time>()
.init_resource::<Time<Real>>()
.init_resource::<Time<Virtual>>()
.init_resource::<Time<Fixed>>()
.init_resource::<TimeUpdateStrategy>()
.register_type::<Timer>()
.register_type::<Time>()
.register_type::<Time<Real>>()
.register_type::<Time<Virtual>>()
.register_type::<Time<Fixed>>()
.register_type::<Timer>()
.register_type::<Stopwatch>()
.init_resource::<FixedTime>()
.add_systems(First, time_system.in_set(TimeSystem))
.add_systems(First, (time_system, virtual_time_system.after(time_system)).in_set(TimeSystem))
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule);

#[cfg(feature = "bevy_ci_testing")]
Expand Down Expand Up @@ -96,7 +106,7 @@ pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
/// The system used to update the [`Time`] used by app logic. If there is a render world the time is sent from
/// there to this system through channels. Otherwise the time is updated in this system.
fn time_system(
mut time: ResMut<Time>,
mut time: ResMut<Time<Real>>,
update_strategy: Res<TimeUpdateStrategy>,
time_recv: Option<Res<TimeReceiver>>,
mut has_received_time: Local<bool>,
Expand All @@ -119,9 +129,6 @@ fn time_system(
match update_strategy.as_ref() {
TimeUpdateStrategy::Automatic => time.update_with_instant(new_time),
TimeUpdateStrategy::ManualInstant(instant) => time.update_with_instant(*instant),
TimeUpdateStrategy::ManualDuration(duration) => {
let last_update = time.last_update().unwrap_or_else(|| time.startup());
time.update_with_instant(last_update + *duration);
}
TimeUpdateStrategy::ManualDuration(duration) => time.update_with_duration(*duration),
}
}
46 changes: 46 additions & 0 deletions crates/bevy_time/src/real.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use bevy_utils::{Duration, Instant};
use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{FromReflect, Reflect};

use crate::time::Time;

#[derive(Resource, Debug, Copy, Clone, Reflect, FromReflect)]
#[reflect(Resource)]
pub struct Real {
startup: Instant,
first_update: Option<Instant>,
last_update: Option<Instant>,
}

impl Default for Real {
fn default() -> Self {
Self {
startup: Instant::now(),
first_update: None,
last_update: None,
}
}
}

impl Time<Real> {
pub fn update(&mut self) {
let instant = Instant::now();
self.update_with_instant(instant);
}

pub fn update_with_duration(&mut self, duration: Duration) {
let last_update = self.context().last_update.unwrap_or(self.context().startup);
self.update_with_instant(last_update + duration);
}

pub fn update_with_instant(&mut self, instant: Instant) {
let Some(last_update) = self.context().last_update else {
let mut context = self.context_mut();
context.first_update = Some(instant);
context.last_update = Some(instant);
return;
};
let delta = instant - last_update;
self.advance_by(delta);
}
}
Loading

0 comments on commit 03d2c1b

Please sign in to comment.