bevy_time/
real.rs

1use bevy_platform::time::Instant;
2#[cfg(feature = "bevy_reflect")]
3use bevy_reflect::{std_traits::ReflectDefault, Reflect};
4use core::time::Duration;
5
6use crate::time::Time;
7
8/// Real time clock representing elapsed wall clock time.
9///
10/// A specialization of the [`Time`] structure. **For method documentation, see
11/// [`Time<Real>#impl-Time<Real>`].**
12///
13/// It is automatically inserted as a resource by
14/// [`TimePlugin`](crate::TimePlugin) and updated with time instants according
15/// to [`TimeUpdateStrategy`](crate::TimeUpdateStrategy).[^disclaimer]
16///
17/// Note:
18/// Using [`TimeUpdateStrategy::ManualDuration`](crate::TimeUpdateStrategy::ManualDuration)
19/// allows for mocking the wall clock for testing purposes.
20/// Besides this use case, it is not recommended to do this, as it will no longer
21/// represent "wall clock" time as intended.
22///
23/// The [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values of this
24/// clock should be used for anything which deals specifically with real time
25/// (wall clock time). It will not be affected by relative game speed
26/// adjustments, pausing or other adjustments.[^disclaimer]
27///
28/// The clock does not count time from [`startup()`](Time::startup) to
29/// [`first_update()`](Time::first_update()) into elapsed, but instead will
30/// start counting time from the first update call. [`delta()`](Time::delta) and
31/// [`elapsed()`](Time::elapsed) will report zero on the first update as there
32/// is no previous update instant. This means that a [`delta()`](Time::delta) of
33/// zero must be handled without errors in application logic, as it may
34/// theoretically also happen at other times.
35///
36/// [`Instant`]s for [`startup()`](Time::startup),
37/// [`first_update()`](Time::first_update) and
38/// [`last_update()`](Time::last_update) are recorded and accessible.
39///
40/// [^disclaimer]: When using [`TimeUpdateStrategy::ManualDuration`](crate::TimeUpdateStrategy::ManualDuration),
41///     [`Time<Real>#impl-Time<Real>`] is only a *mock* of wall clock time.
42///
43#[derive(Debug, Copy, Clone)]
44#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone, Default))]
45pub struct Real {
46    startup: Instant,
47    first_update: Option<Instant>,
48    last_update: Option<Instant>,
49}
50
51impl Default for Real {
52    fn default() -> Self {
53        Self {
54            startup: Instant::now(),
55            first_update: None,
56            last_update: None,
57        }
58    }
59}
60
61impl Time<Real> {
62    /// Constructs a new `Time<Real>` instance with a specific startup
63    /// [`Instant`].
64    pub fn new(startup: Instant) -> Self {
65        Self::new_with(Real {
66            startup,
67            ..Default::default()
68        })
69    }
70
71    /// Updates the internal time measurements.
72    ///
73    /// Calling this method as part of your app will most likely result in
74    /// inaccurate timekeeping, as the [`Time`] resource is ordinarily managed
75    /// by the [`TimePlugin`](crate::TimePlugin).
76    pub fn update(&mut self) {
77        let instant = Instant::now();
78        self.update_with_instant(instant);
79    }
80
81    /// Updates time with a specified [`Duration`].
82    ///
83    /// This method is provided for use in tests.
84    ///
85    /// Calling this method as part of your app will most likely result in
86    /// inaccurate timekeeping, as the [`Time`] resource is ordinarily managed
87    /// by the [`TimePlugin`](crate::TimePlugin).
88    pub fn update_with_duration(&mut self, duration: Duration) {
89        let last_update = self.context().last_update.unwrap_or(self.context().startup);
90        self.update_with_instant(last_update + duration);
91    }
92
93    /// Updates time with a specified [`Instant`].
94    ///
95    /// This method is provided for use in tests.
96    ///
97    /// Calling this method as part of your app will most likely result in inaccurate timekeeping,
98    /// as the [`Time`] resource is ordinarily managed by the [`TimePlugin`](crate::TimePlugin).
99    pub fn update_with_instant(&mut self, instant: Instant) {
100        let Some(last_update) = self.context().last_update else {
101            let context = self.context_mut();
102            context.first_update = Some(instant);
103            context.last_update = Some(instant);
104            return;
105        };
106        let delta = instant - last_update;
107        self.advance_by(delta);
108        self.context_mut().last_update = Some(instant);
109    }
110
111    /// Returns the [`Instant`] the clock was created.
112    ///
113    /// This usually represents when the app was started.
114    #[inline]
115    pub fn startup(&self) -> Instant {
116        self.context().startup
117    }
118
119    /// Returns the [`Instant`] when [`Self::update`] was first called, if it
120    /// exists.
121    ///
122    /// This usually represents when the first app update started.
123    #[inline]
124    pub fn first_update(&self) -> Option<Instant> {
125        self.context().first_update
126    }
127
128    /// Returns the [`Instant`] when [`Self::update`] was last called, if it
129    /// exists.
130    ///
131    /// This usually represents when the current app update started.
132    #[inline]
133    pub fn last_update(&self) -> Option<Instant> {
134        self.context().last_update
135    }
136}
137
138#[cfg(test)]
139mod test {
140    use super::*;
141
142    // Waits until Instant::now() has increased.
143    //
144    // ```
145    // let previous = Instant::now();
146    // wait();
147    // assert!(Instant::now() > previous);
148    // ```
149    fn wait() {
150        let start = Instant::now();
151        while Instant::now() <= start {}
152    }
153
154    #[test]
155    fn test_update() {
156        let startup = Instant::now();
157        let mut time = Time::<Real>::new(startup);
158
159        assert_eq!(time.startup(), startup);
160        assert_eq!(time.first_update(), None);
161        assert_eq!(time.last_update(), None);
162        assert_eq!(time.delta(), Duration::ZERO);
163        assert_eq!(time.elapsed(), Duration::ZERO);
164
165        wait();
166        time.update();
167
168        assert_ne!(time.first_update(), None);
169        assert_ne!(time.last_update(), None);
170        assert_eq!(time.delta(), Duration::ZERO);
171        assert_eq!(time.elapsed(), Duration::ZERO);
172
173        wait();
174        time.update();
175
176        assert_ne!(time.first_update(), None);
177        assert_ne!(time.last_update(), None);
178        assert_ne!(time.last_update(), time.first_update());
179        assert_ne!(time.delta(), Duration::ZERO);
180        assert_eq!(time.elapsed(), time.delta());
181
182        wait();
183        let prev_elapsed = time.elapsed();
184        time.update();
185
186        assert_ne!(time.delta(), Duration::ZERO);
187        assert_eq!(time.elapsed(), prev_elapsed + time.delta());
188    }
189
190    #[test]
191    fn test_update_with_instant() {
192        let startup = Instant::now();
193        let mut time = Time::<Real>::new(startup);
194
195        wait();
196        let first_update = Instant::now();
197        time.update_with_instant(first_update);
198
199        assert_eq!(time.startup(), startup);
200        assert_eq!(time.first_update(), Some(first_update));
201        assert_eq!(time.last_update(), Some(first_update));
202        assert_eq!(time.delta(), Duration::ZERO);
203        assert_eq!(time.elapsed(), Duration::ZERO);
204
205        wait();
206        let second_update = Instant::now();
207        time.update_with_instant(second_update);
208
209        assert_eq!(time.first_update(), Some(first_update));
210        assert_eq!(time.last_update(), Some(second_update));
211        assert_eq!(time.delta(), second_update - first_update);
212        assert_eq!(time.elapsed(), second_update - first_update);
213
214        wait();
215        let third_update = Instant::now();
216        time.update_with_instant(third_update);
217
218        assert_eq!(time.first_update(), Some(first_update));
219        assert_eq!(time.last_update(), Some(third_update));
220        assert_eq!(time.delta(), third_update - second_update);
221        assert_eq!(time.elapsed(), third_update - first_update);
222    }
223
224    #[test]
225    fn test_update_with_duration() {
226        let startup = Instant::now();
227        let mut time = Time::<Real>::new(startup);
228
229        time.update_with_duration(Duration::from_secs(1));
230
231        assert_eq!(time.startup(), startup);
232        assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
233        assert_eq!(time.last_update(), Some(startup + Duration::from_secs(1)));
234        assert_eq!(time.delta(), Duration::ZERO);
235        assert_eq!(time.elapsed(), Duration::ZERO);
236
237        time.update_with_duration(Duration::from_secs(1));
238
239        assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
240        assert_eq!(time.last_update(), Some(startup + Duration::from_secs(2)));
241        assert_eq!(time.delta(), Duration::from_secs(1));
242        assert_eq!(time.elapsed(), Duration::from_secs(1));
243
244        time.update_with_duration(Duration::from_secs(1));
245
246        assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
247        assert_eq!(time.last_update(), Some(startup + Duration::from_secs(3)));
248        assert_eq!(time.delta(), Duration::from_secs(1));
249        assert_eq!(time.elapsed(), Duration::from_secs(2));
250    }
251}