Skip to content

Commit b6e327a

Browse files
committed
add max delta
1 parent ea32e48 commit b6e327a

File tree

4 files changed

+65
-27
lines changed

4 files changed

+65
-27
lines changed

crates/bevy_time/src/clock.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ pub struct Clock {
2929
impl Default for Clock {
3030
fn default() -> Self {
3131
Self {
32+
startup: Instant::now(),
33+
first_update: None,
34+
last_update: None,
3235
delta: Duration::ZERO,
3336
delta_seconds: 0.0,
3437
delta_seconds_f64: 0.0,
3538
elapsed: Duration::ZERO,
3639
elapsed_seconds: 0.0,
3740
elapsed_seconds_f64: 0.0,
38-
startup: Instant::now(),
39-
first_update: None,
40-
last_update: None,
4141
wrap_period: Duration::from_secs(3600), // 1 hour
4242
elapsed_wrapped: Duration::ZERO,
4343
elapsed_seconds_wrapped: 0.0,
@@ -55,7 +55,8 @@ impl Clock {
5555
}
5656
}
5757

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

crates/bevy_time/src/fixed_timestep.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use bevy_utils::{default, Duration, Instant};
1616

1717
use crate::{Time, TimeContext};
1818

19-
/// The step size (and some metadata) for the `FixedUpdate` schedule .
19+
/// The step size (and some metadata) for the `FixedUpdate` schedule.
2020
#[derive(Resource, Debug, Clone, Copy)]
2121
pub struct FixedTimestep {
2222
size: Duration,

crates/bevy_time/src/lib.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,17 @@ impl Plugin for TimePlugin {
6767
}
6868
}
6969

70-
/// Configuration resource used to determine how the time system should run.
70+
/// Determines how the duration of app updates are measured.
7171
///
72-
/// For most cases, [`TimeUpdateStrategy::Automatic`] is fine. When writing tests, dealing with networking, or similar
73-
/// you may prefer to set the next [`Time`] value manually.
74-
#[derive(Resource, Default)]
72+
/// Most users should leave this as [`TimeUpdateStrategy::Automatic`]. The other variants are provided to
73+
/// make writing tests and similar logic easier.
74+
#[derive(Resource, Default, Clone, Copy)]
7575
pub enum TimeUpdateStrategy {
7676
#[default]
7777
Automatic,
78-
// Update [`Time`] with an exact `Instant` value
78+
/// The duration of the update will be the duration between its last update and the given [`Instant`].
7979
ManualInstant(Instant),
80-
// Update [`Time`] with the last update time + a specified `Duration`
80+
/// The duration of the update will be the given [`Duration`].
8181
ManualDuration(Duration),
8282
}
8383

@@ -104,11 +104,12 @@ fn time_system(
104104
mut time: ResMut<Time>,
105105
mut real_time: ResMut<RealTime>,
106106
mut fixed_timestep: ResMut<FixedTimestep>,
107-
update_strategy: Res<TimeUpdateStrategy>,
107+
strategy: Res<TimeUpdateStrategy>,
108108
time_recv: Option<Res<TimeReceiver>>,
109109
mut has_received_time: Local<bool>,
110110
) {
111-
let real_frame_start = if let Some(time_recv) = time_recv {
111+
assert!(matches!(time.context(), TimeContext::Update));
112+
let frame_start = if let Some(time_recv) = time_recv {
112113
// TODO: Figure out how to handle this when using pipelined rendering.
113114
if let Ok(instant) = time_recv.0.try_recv() {
114115
*has_received_time = true;
@@ -123,22 +124,21 @@ fn time_system(
123124
Instant::now()
124125
};
125126

126-
// update real time clock
127-
real_time.update_with_instant(real_frame_start);
128-
129-
let virtual_frame_start = match update_strategy.as_ref() {
130-
TimeUpdateStrategy::Automatic => real_frame_start,
127+
let frame_start = match strategy.as_ref() {
128+
TimeUpdateStrategy::Automatic => frame_start,
131129
TimeUpdateStrategy::ManualInstant(instant) => *instant,
132130
TimeUpdateStrategy::ManualDuration(duration) => {
133131
let last_update = time.last_update().unwrap_or(time.startup());
134132
last_update.checked_add(*duration).unwrap()
135133
}
136134
};
137135

136+
// update real time clock
137+
real_time.update_with_instant(frame_start);
138+
138139
// update virtual time clock
139-
// TODO: limit how much time can be advanced in a single frame (e.g. Unity's maximumDeltaTime)
140-
time.update_with_instant(virtual_frame_start);
140+
time.update_with_instant(frame_start);
141141

142-
// accumulate virtual time delta
142+
// accumulate
143143
fixed_timestep.accumulate(time.delta());
144144
}

crates/bevy_time/src/time.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ pub struct Time {
1515
context: TimeContext,
1616
update: Clock,
1717
fixed_update: Clock,
18-
// settings
1918
paused: bool,
2019
next_paused: Option<bool>,
2120
relative_speed: f64, // using `f64` instead of `f32` to minimize drift from rounding errors
2221
next_relative_speed: Option<f64>,
22+
max_delta: Duration,
2323
}
2424

2525
/// [`Time`] stores two clocks that are synchronized but advance at different rates.
@@ -41,6 +41,7 @@ impl Default for Time {
4141
next_paused: None,
4242
relative_speed: 1.0,
4343
next_relative_speed: None,
44+
max_delta: Duration::from_millis(500),
4445
}
4546
}
4647
}
@@ -156,8 +157,17 @@ impl Time {
156157
self.apply_pending_changes();
157158

158159
// zero for first update
159-
let dt = instant - self.current_clock().last_update().unwrap_or(instant);
160-
let dt = if self.paused {
160+
let mut dt = instant - self.update.last_update().unwrap_or(instant);
161+
162+
if dt > self.max_delta {
163+
warn!(
164+
"update delta time {:?} is larger than the maximum {:?}",
165+
dt, self.max_delta
166+
);
167+
dt = self.max_delta;
168+
}
169+
170+
dt = if self.paused {
161171
Duration::ZERO
162172
} else if self.relative_speed != 1.0 {
163173
dt.mul_f64(self.relative_speed)
@@ -166,7 +176,7 @@ impl Time {
166176
dt
167177
};
168178

169-
self.update.tick(dt, instant);
179+
self.update.update(dt, instant);
170180
}
171181
TimeContext::FixedUpdate => {
172182
warn!("In the `FixedUpdate` context, `Time` can only be advanced via `tick`.");
@@ -175,7 +185,7 @@ impl Time {
175185
}
176186

177187
pub(crate) fn tick(&mut self, dt: Duration, instant: Instant) {
178-
self.current_clock_mut().tick(dt, instant);
188+
self.current_clock_mut().update(dt, instant);
179189
}
180190

181191
/// Applies pending pause or relative speed changes.
@@ -376,6 +386,27 @@ impl Time {
376386
pub fn is_paused(&self) -> bool {
377387
self.paused
378388
}
389+
390+
/// Returns the maximum value of [`delta`](#method.delta) (*before* scaling) for any given update.
391+
#[inline]
392+
pub fn max_delta(&self) -> Duration {
393+
self.max_delta
394+
}
395+
396+
/// Sets the maximum value of [`delta`](#method.delta) (*before* scaling) for any given update.
397+
///
398+
/// After an unusually long update, this value limits the value of [`delta`](#method.delta)
399+
/// in the next update to avoid the app falling into a "death spiral" where each update has an
400+
/// ever-increasing number of `FixedUpdate` steps to run.
401+
///
402+
/// Without this setting, anything that results in an exceptionally long time passing between
403+
/// app updates (e.g. the program was suspended) would cause time to jump forward a lot and
404+
/// rack up a large simulation debt.
405+
#[inline]
406+
pub fn set_max_delta(&mut self, max_delta: Duration) {
407+
assert!(!max_delta.is_zero());
408+
self.max_delta = max_delta;
409+
}
379410
}
380411

381412
/// A clock that tracks how much actual time has elasped since the last update and since startup.
@@ -407,7 +438,7 @@ impl RealTime {
407438
pub fn update_with_instant(&mut self, instant: Instant) {
408439
// zero for first update
409440
let dt = instant - self.0.last_update().unwrap_or(instant);
410-
self.0.tick(dt, instant);
441+
self.0.update(dt, instant);
411442
}
412443

413444
/// Returns the [`Instant`] the clock was created.
@@ -608,6 +639,9 @@ mod tests {
608639
let mut time = Time::new(startup);
609640
time.set_wrap_period(Duration::from_secs(3));
610641

642+
// disable maximum delta for this test
643+
time.set_max_delta(Duration::MAX);
644+
611645
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
612646

613647
// time starts counting from first update
@@ -633,6 +667,9 @@ mod tests {
633667
let mut time = Time::new(startup);
634668
let mut real_time = RealTime::new(startup);
635669

670+
// disable maximum delta for this test
671+
time.set_max_delta(Duration::MAX);
672+
636673
let first_update = Instant::now();
637674
time.update_with_instant(first_update);
638675
real_time.update_with_instant(first_update);

0 commit comments

Comments
 (0)