Skip to content

Commit 002279c

Browse files
committed
Add a lot of documentation
1 parent 2a602c0 commit 002279c

File tree

4 files changed

+242
-11
lines changed

4 files changed

+242
-11
lines changed

crates/bevy_time/src/fixed.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,107 @@ use crate::FixedUpdate;
66
use crate::time::Time;
77
use crate::virt::Virtual;
88

9+
/// The fixed timestep game clock following virtual time.
10+
///
11+
/// Normally used as `Time<Fixed>`. It is automatically inserted as a resource
12+
/// by `TimePlugin` and updated based on `Time<Virtual>`. The fixed clock is
13+
/// automatically set as the generic `Time` resource during `FixedUpdate`
14+
/// schedule processing.
15+
///
16+
/// The fixed timestep game clock runs at a regular interval and is extremely
17+
/// useful for steady, frame-rate independent gameplay logic and physics.
18+
///
19+
/// To run a system on a fixed timestep, add it to the `FixedUpdate` schedule.
20+
/// This schedule is run a number of times between `PreUpdate` and `Update`
21+
/// according to the accumulated `overstep()` time divided by the `timestep()`.
22+
/// This means the schedule may run 0, 1 or more times during a single update.
23+
///
24+
/// `Time<Fixed>` and the generic `Time` resource will report a `delta()` equal
25+
/// to `timestep()` and always grow `elapsed()` by one `timestep()` per
26+
/// iteration.
27+
///
28+
/// The fixed timestep clock follows the `Time<Virtual>` clock, which means it
29+
/// is affected by `paused()`, `relative_speed()` and `max_delta()` from virtual
30+
/// time. If the virtual clock is paused, the `FixedUpdate` schedule will not
31+
/// run. It is guaranteed that the `elapsed()` time in `Time<Fixed>` is always
32+
/// between the previous `elapsed()` and the next `elapsed()` value in
33+
/// `Time<Virtual>`, so the values are compatible.
34+
///
35+
/// Changing the timestep size while the game is running should not normally be
36+
/// done, as having a regular interval is the point of this schedule, but it may
37+
/// be necessary for effects like "bullet-time" if the normal granularity of the
38+
/// fixed timestep is too big for the slowed down time. In this case,
39+
/// `set_timestep()` and be called to set a new value. The new value will be
40+
/// used immediately for the next run of the `FixedUpdate` schedule, meaning
41+
/// that it will affect the `delta()` value for the very next `FixedUpdate`,
42+
/// even if it is still during the same frame. Any `overstep()` present in the
43+
/// accumulator will be processed according to the new `timestep()` value.
44+
945
#[derive(Debug, Copy, Clone, Reflect, FromReflect)]
1046
pub struct Fixed {
1147
timestep: Duration,
1248
overstep: Duration,
1349
}
1450

1551
impl Time<Fixed> {
52+
/// Returns the amount of virtual time that must pass before the fixed
53+
/// timestep schedule is run again.
1654
pub fn timestep(&self) -> Duration {
1755
self.context().timestep
1856
}
1957

58+
/// Sets the amount of virtual time that must pass before the fixed timestep
59+
/// schedule is run again, as [`Duration`].
60+
///
61+
/// Takes effect immediately on the next run of the schedule, respecting
62+
/// what is currently in `overstep()`.
2063
pub fn set_timestep(&mut self, timestep: Duration) {
2164
assert_ne!(timestep, Duration::ZERO, "attempted to set fixed timestep to zero");
2265
self.context_mut().timestep = timestep;
2366
}
2467

68+
/// Sets the amount of virtual time that must pass before the fixed timestep
69+
/// schedule is run again, as seconds.
70+
///
71+
/// Timestep is stored as a `Duration`, which has fixed nanosecond
72+
/// resolution and will be converted from the floating point number.
73+
///
74+
/// Takes effect immediately on the next run of the schedule, respecting
75+
/// what is currently in `overstep()`.
2576
pub fn set_timestep_seconds(&mut self, seconds: f64) {
77+
assert!(seconds.is_sign_positive(), "seconds less than or equal to zero");
78+
assert!(seconds.is_finite(), "seconds is infinite");
2679
self.set_timestep(Duration::from_secs_f64(seconds));
2780
}
2881

82+
/// Sets the amount of virtual time that must pass before the fixed timestep
83+
/// schedule is run again, as frequency.
84+
///
85+
/// The timestep value is set to `1 / hz`, converted to a `Duration` which
86+
/// has fixed nanosecond resolution.
87+
///
88+
/// Takes effect immediately on the next run of the schedule, respecting
89+
/// what is currently in `overstep()`.
2990
pub fn set_timestep_hz(&mut self, hz: f64) {
3091
assert!(hz.is_sign_positive(), "Hz less than or equal to zero");
3192
assert!(hz.is_finite(), "Hz is infinite");
3293
self.set_timestep_seconds(1.0 / hz);
3394
}
3495

96+
/// Returns the amount of overstep time accumulated toward new steps, as
97+
/// `Duration`.
3598
pub fn overstep(&self) -> Duration {
3699
self.context().overstep
37100
}
38101

102+
/// Returns the amount of overstep time accumulated toward new steps, as an
103+
/// [`f32`] fraction of the timestep.
39104
pub fn overstep_percentage(&self) -> f32 {
40105
self.context().overstep.as_secs_f32() / self.context().timestep.as_secs_f32()
41106
}
42107

108+
/// Returns the amount of overstep time accumulated toward new steps, as an
109+
/// [`f64`] fraction of the timestep.
43110
pub fn overstep_percentage_f64(&self) -> f64 {
44111
self.context().overstep.as_secs_f64() / self.context().timestep.as_secs_f64()
45112
}

crates/bevy_time/src/real.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ use bevy_reflect::{FromReflect, Reflect};
33

44
use crate::time::Time;
55

6+
/// Real time clock representing elapsed wall clock time.
7+
///
8+
/// Normally used as `Time<Real>`. It is automatically inserted as a resource by
9+
/// `TimePlugin` and updated with time instants based on `TimeUpdateStrategy`.
10+
///
11+
/// The `delta()` and `elapsed()` values of this clock should be used for
12+
/// anything which deals specifically with real time (wall clock time). It will
13+
/// not be affected by relative game speed adjustments, pausing or other
14+
/// adjustments.
15+
///
16+
/// The clock does not count time from `startup()` to `first_update()` into
17+
/// elapsed, but instead will start counting time from the first update call.
18+
/// `delta()` and `elapsed()` will report zero on the first update as there is
19+
/// no previous update instant. This means that a `delta()` of zero must be
20+
/// handled without errors in application logic, as it may theoretically also
21+
/// happen at other times.
22+
///
23+
/// `Instant`s for `startup()`, `first_update()` and `last_update()` are
24+
/// recorded and accessible.
625
#[derive(Debug, Copy, Clone, Reflect, FromReflect)]
726
pub struct Real {
827
startup: Instant,
@@ -21,6 +40,7 @@ impl Default for Real {
2140
}
2241

2342
impl Time<Real> {
43+
/// Constructs a new `Time<Real>` instance with a specific startup `Instant`.
2444
pub fn new(startup: Instant) -> Self {
2545
Self::new_with(Real {
2646
startup,

crates/bevy_time/src/time.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,83 @@ use bevy_ecs::{reflect::ReflectResource, system::Resource};
22
use bevy_reflect::{FromReflect, Reflect};
33
use bevy_utils::Duration;
44

5-
/// A generic clock that tracks how much it has advanced its previous update and
6-
/// since its creation.
5+
/// A generic clock resource that tracks how much it has advanced its previous
6+
/// update and since its creation.
7+
///
8+
/// Multiple instances of this resource are inserted automatically by
9+
/// `TimePlugin`:
10+
///
11+
/// - `Time<Real>` tracks real wall-clock time.
12+
/// - `Time<Virtual>` tracks virtual game time that may be paused or scaled.
13+
/// - `Time<Fixed` tracks fixed timesteps based on virtual time.
14+
/// - `Time` is a generic clock that corresponds to "current" or "default" time
15+
/// for systems. It contains `Time<Virtual>` except inside the `FixedUpdate`
16+
/// schedule when it contains `Time<Fixed`.
17+
///
18+
/// A clock tracks the time elapsed since the previous time this clock was
19+
/// advanced as `delta()` and the total amount of time the clock has been
20+
/// advanced with as `elapsed()`. Both are represented as exact `Duration`
21+
/// values with fixed nanosecond precision. The clock does not support time
22+
/// moving backwards, but it can be updated with `Duration::ZERO` which will set
23+
/// `delta()` to zero.
24+
///
25+
/// These values are also available in seconds as `f32` via `delta_seconds()`
26+
/// and `elapsed_seconds()`, and also in seconds as `f64` via
27+
/// `delta_seconds_f64()` and `elapsed_seconds_f64()`.
28+
///
29+
/// Since `elapsed_seconds()` will grow constantly and is `f32`, it will exhibit
30+
/// gradual precision loss. For applications that require an `f32` value but
31+
/// suffer from gradual precision loss there is `wrapped_elapsed_seconds()`
32+
/// available. The same wrapped value is also available as `Duration` and `f64`
33+
/// for consistency. The wrap period is by default 1 hour, and can be set by
34+
/// `set_wrap_period()`.
35+
///
36+
/// # Accessing clocks
37+
///
38+
/// By default, any systems requiring current `delta()` or `elapsed()` should
39+
/// use `Res<Time>` to access the default time configured for the program. By
40+
/// default, this refers to `Time<Virtual>` except during `FixedUpdate` when it
41+
/// refers to `Time<Fixed>`. This makes your system compatible to be used either
42+
/// in `Update` or `FixedUpdate` schedule depending on what is needed.
43+
///
44+
/// If your system needs to react based on real time (wall clock time), like for
45+
/// user interfaces, it should use `Res<Time<Real>>`. The `delta()` and
46+
/// `elapsed()` values will always correspond to real time and will not be
47+
/// affected by pause, time scaling or other tweaks.
48+
///
49+
/// If your system specifically needs to access fixed timestep clock, even when
50+
/// placed in `Update` schedule, you should use `Res<Time<Fixed>>`. The
51+
/// `delta()` and `elapsed()` values will correspond to the latest fixed
52+
/// timestep that has been run.
53+
///
54+
/// Finally, if your system specifically needs to know the current virtual game
55+
/// time, even if placed inside `FixedUpdate`, for example to know if the game
56+
/// is `paused()` or to use `relative_speed()`, you can use
57+
/// `Res<Time<Virtual>>`. However, if the system is placed in `FixedUpdate`,
58+
/// extra care must be used because your system might be run multiple times with
59+
/// the same `delta()` and `elapsed()` values as the virtual game time has not
60+
/// changed between the iterations.
61+
///
62+
/// If you need to change the settings for any of the clocks, for example to
63+
/// `pause()` the game, you should use `ResMut<Time<Virtual>>`.
64+
///
65+
/// # Adding custom clocks
66+
///
67+
/// New custom clocks can be created by creating your own struct as a context
68+
/// and passing it to `new_from()`. These clocks can be inserted as resources as
69+
/// normal and then accessed by systems. You can use the `advance_by()` or
70+
/// `advance_to()` methods to move the clock forwards based on your own logic.
71+
///
72+
/// Your context struct will need to implement the `Default` trait because
73+
/// `Time` structures support reflection. It also makes initialization trivial
74+
/// by being able to call `app.init_resource::<Time<Custom>>()`.
75+
///
76+
/// You can also replace the "generic" `Time` clock resource if the "default"
77+
/// time for your game should not be the default virtual time provided. You can
78+
/// get a "generic" snapshot of your clock by calling `as_generic()` and then
79+
/// overwrite the `Time` resource with it. The default systems added by
80+
/// `TimePlugin` will overwrite the `Time` clock during `First` and
81+
/// `FixedUpdate` schedules.
782
#[derive(Resource, Debug, Copy, Clone, Reflect, FromReflect)]
883
#[reflect(Resource)]
984
pub struct Time<T: Default = ()> {
@@ -23,7 +98,7 @@ pub struct Time<T: Default = ()> {
2398
impl<T: Default> Time<T> {
2499
const DEFAULT_WRAP_SECONDS: u64 = 3600; // 1 hour
25100

26-
/// Create a new clock with `delta` and `elapsed` starting from zero.
101+
/// Create a new clock from context with `delta` and `elapsed` starting from zero.
27102
pub fn new_with(context: T) -> Self {
28103
Self {
29104
context,

crates/bevy_time/src/virt.rs

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,60 @@ use bevy_reflect::{FromReflect, Reflect};
55
use crate::time::Time;
66
use crate::real::Real;
77

8+
/// The virtual game clock representing game time.
9+
///
10+
/// Normally used as `Time<Virtual>`. It is automatically inserted as a resource
11+
/// by `TimePlugin` and updated based on `Time<Real>`. The virtual clock is
12+
/// automatically set as the default generic `Time` resource for the update.
13+
///
14+
/// Virtual clock differs from real time clock in that it can be paused, sped up
15+
/// and slowed down. It also limits the amount it will move forward based on a
16+
/// single update, to prevent bugs and bad behaviour in game logic if the
17+
/// updates do not always come at regular intervals.
18+
///
19+
/// The virtual clock can be paused by calling `pause()` and unpaused by calling
20+
/// `unpause()`. When the game clock is paused `delta()` will be zero on each
21+
/// update, and `elapsed()` will not grow. `effective_relative_speed()` will
22+
/// return `0.0`. Calling `pause()` will not affect value the `delta()` value
23+
/// for the update currently being processed.
24+
///
25+
/// The speed of the virtual clock can be changed by calling
26+
/// `set_relative_speed()`. A value of `2.0` means that virtual clock should
27+
/// advance twice as fast as real time, meaning that `delta()` values will be
28+
/// double of what `Time<Real>::delta()` reports and `elapsed()` will go twice
29+
/// as fast as `Time<Real>::elapsed()`. Calling `set_relative_speed()` will not
30+
/// affect the `delta()` value for the update currently being processed.
31+
///
32+
/// The maximum amount of delta time that can be added by a single update can be
33+
/// set by `set_max_delta()`. This value serves a dual purpose in the virtual
34+
/// clock.
35+
///
36+
/// If the game temporarily freezes due to any reason, such as disk access, a
37+
/// blocking system call, or operating system level suspend, reporting the full
38+
/// elapsed delta time is likely to cause bugs in game logic. Usually if a
39+
/// laptop is suspended for an hour, it doesn't make sense to try to simulate
40+
/// the game logic for the elapsed hour when resuming. Instead it is better to
41+
/// lose the extra time and pretend a shorter duration of time passed. Setting
42+
/// `max_delta` to a relatively short time means that the impact on game logic
43+
/// will be minimal.
44+
///
45+
/// If the game lags for some reason, meaning that it will take a longer time to
46+
/// compute a frame than the real time that passes during the computation, then
47+
/// we would fall behind in processing virtual time. If this situation persists,
48+
/// and computing a frame takes longer depending on how much virtual time has
49+
/// passed, the game would enter a "death spiral" where computing each frame
50+
/// takes longer and longer and the game will appear to freeze. By limiting the
51+
/// maximum time that can be added at once, we also limit the amount of virtual
52+
/// time the game needs to compute for each frame. This means that the game will
53+
/// run slow, and it will run slower than real time, but it will not freeze and
54+
/// it will recover as soon as computation becomes fast again.
55+
///
56+
/// You should set `max_delta()` to a value that is approximately the minimum
57+
/// FPS your game should have even if heavily lagged for a moment. The actual
58+
/// FPS when lagged will be somewhat lower than this, depending on how much more
59+
/// time it takes to compute a frame compared to real time. You should also
60+
/// consider how stable your FPS is, as the limit will also dictate how big of
61+
/// an FPS drop you can accept without losing time and falling behind real time.
862
#[derive(Debug, Copy, Clone, Reflect, FromReflect)]
963
pub struct Virtual {
1064
max_delta: Duration,
@@ -13,26 +67,41 @@ pub struct Virtual {
1367
}
1468

1569
impl Time<Virtual> {
16-
/// Default max_delta value.
1770
const DEFAULT_MAX_DELTA: Duration = Duration::from_millis(333); // XXX: better value
1871

19-
/// Returns the maximum amount of time that can be added to the clock by a
72+
/// Returns the maximum amount of time that can be added to this clock by a
2073
/// single update, as [`std::time::Duration`].
2174
///
2275
/// This is the maximum value [`Self::delta()`] will return and also to
23-
/// maximum time [`Self::elapsed()`] will be increased by in a single update.
76+
/// maximum time [`Self::elapsed()`] will be increased by in a single
77+
/// update.
78+
///
79+
/// This ensures that even if there are no updates scheduled for an extended
80+
/// amount of time, there won't be a huge update at once for the clock. This
81+
/// also limits the number of fixed time ticks that will be evaluated at
82+
/// maximum per a single update.
83+
///
84+
/// The default value is [`Self::DEFAULT_MAX_DELTA`].
2485
#[inline]
2586
pub fn max_delta(&self) -> Duration {
2687
self.context().max_delta
2788
}
2889

29-
/// Sets the maximum amount of time that can be added to the clock by a
90+
/// Sets the maximum amount of time that can be added to this clock by a
3091
/// single update, as [`std::time::Duration`].
3192
///
32-
/// This ensures that even if there are no updates scheduled for an extended
33-
/// amount of time, there won't be a huge update at once for the clock. This
34-
/// also limits the number of fixed time ticks that will be evaluated at
35-
/// maximum per a single update.
93+
/// This is the maximum value [`Self::delta()`] will return and also to
94+
/// maximum time [`Self::elapsed()`] will be increased by in a single
95+
/// update.
96+
///
97+
/// This is used to ensure that even if the game freezes for a few seconds,
98+
/// or is suspended for hours or even days, the virtual clock doesn't
99+
/// suddenly jump forward for that full amount, which would likely cause
100+
/// gameplay bugs or having to suddenly simulate all the intervening time.
101+
///
102+
/// re are no updates scheduled for an extended amount of time, there won't
103+
/// be a huge update at once for the clock. This also limits the number of
104+
/// fixed time ticks that will be evaluated at maximum per a single update.
36105
///
37106
/// The default value is [`Self::DEFAULT_MAX_DELTA`]. If you want to disable
38107
/// this feature, set the value to [`std::time::Duration::MAX`]

0 commit comments

Comments
 (0)