Skip to main content

bevy_time/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![forbid(unsafe_code)]
4#![doc(
5    html_logo_url = "https://bevy.org/assets/icon.png",
6    html_favicon_url = "https://bevy.org/assets/icon.png"
7)]
8#![no_std]
9
10#[cfg(feature = "std")]
11extern crate std;
12
13extern crate alloc;
14
15/// Common run conditions
16pub mod common_conditions;
17mod delayed_commands;
18mod fixed;
19mod real;
20mod stopwatch;
21mod time;
22mod timer;
23mod virt;
24
25pub use delayed_commands::*;
26pub use fixed::*;
27pub use real::*;
28pub use stopwatch::*;
29pub use time::*;
30pub use timer::*;
31pub use virt::*;
32
33/// The time prelude.
34///
35/// This includes the most common types in this crate, re-exported for your convenience.
36pub mod prelude {
37    #[doc(hidden)]
38    pub use crate::{DelayedCommandsExt, Fixed, Real, Time, Timer, TimerMode, Virtual};
39}
40
41use bevy_app::{prelude::*, OnAppExitSystems, RunFixedMainLoop};
42use bevy_ecs::{
43    message::{
44        message_update_system, signal_message_update_system, MessageRegistry, ShouldUpdateMessages,
45    },
46    prelude::*,
47};
48use bevy_platform::time::Instant;
49use core::time::Duration;
50
51#[cfg(feature = "std")]
52pub use crossbeam_channel::TrySendError;
53
54#[cfg(feature = "std")]
55use crossbeam_channel::{Receiver, Sender};
56
57/// Adds time functionality to Apps.
58#[derive(Default)]
59pub struct TimePlugin;
60
61/// Updates the elapsed time. Any system that interacts with [`Time`] component should run after
62/// this.
63#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
64pub struct TimeSystems;
65
66impl Plugin for TimePlugin {
67    fn build(&self, app: &mut App) {
68        app.init_resource::<Time>()
69            .init_resource::<Time<Real>>()
70            .init_resource::<Time<Virtual>>()
71            .init_resource::<Time<Fixed>>()
72            .init_resource::<TimeUpdateStrategy>();
73
74        #[cfg(feature = "bevy_reflect")]
75        {
76            app.register_type::<Time>()
77                .register_type::<Time<Real>>()
78                .register_type::<Time<Virtual>>()
79                .register_type::<Time<Fixed>>();
80        }
81
82        app.add_systems(
83            First,
84            time_system
85                .in_set(TimeSystems)
86                .ambiguous_with(message_update_system),
87        )
88        .add_systems(PreUpdate, check_delayed_command_queues)
89        .add_systems(
90            RunFixedMainLoop,
91            run_fixed_main_schedule.in_set(RunFixedMainLoopSystems::FixedMainLoop),
92        )
93        .add_systems(
94            Last,
95            silence_delayed_command_queues_on_exit
96                .in_set(OnAppExitSystems)
97                .run_if(|messages: Res<Messages<AppExit>>| !messages.is_empty()),
98        );
99
100        // Ensure the messages are not dropped until `FixedMain` systems can observe them
101        app.add_systems(FixedPostUpdate, signal_message_update_system);
102        let mut message_registry = app.world_mut().resource_mut::<MessageRegistry>();
103        // We need to start in a waiting state so that the messages are not updated until the first fixed update
104        message_registry.should_update = ShouldUpdateMessages::Waiting;
105    }
106}
107
108/// Configuration resource used to determine how the time system should run.
109///
110/// For most cases, [`TimeUpdateStrategy::Automatic`] is fine. When writing tests, dealing with
111/// networking or similar, you may prefer to set the next [`Time`] value manually.
112#[derive(Resource, Default)]
113pub enum TimeUpdateStrategy {
114    /// [`Time`] will be automatically updated each frame using an [`Instant`] sent from the render world.
115    /// If nothing is sent, the system clock will be used instead.
116    #[cfg_attr(feature = "std", doc = "See [`TimeSender`] for more details.")]
117    #[default]
118    Automatic,
119    /// [`Time`] will be updated to the specified [`Instant`] value each frame.
120    /// In order for time to progress, this value must be manually updated each frame.
121    ///
122    /// Note that the `Time` resource will not be updated until [`TimeSystems`] runs.
123    ManualInstant(Instant),
124    /// [`Time`] will be incremented by the specified [`Duration`] each frame.
125    ManualDuration(Duration),
126    /// [`Time`] will be incremented by the fixed timestep each frame, multiplied by the specified factor `n`.
127    /// This means that a call to [`App::update`] will always run the fixed loop exactly n times.
128    FixedTimesteps(u32),
129}
130
131/// Channel resource used to receive time from the render world.
132#[cfg(feature = "std")]
133#[derive(Resource)]
134pub struct TimeReceiver(pub Receiver<Instant>);
135
136/// Channel resource used to send time from the render world.
137#[cfg(feature = "std")]
138#[derive(Resource)]
139pub struct TimeSender(pub Sender<Instant>);
140
141/// Creates channels used for sending time between the render world and the main world.
142#[cfg(feature = "std")]
143pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
144    // bound the channel to 2 since when pipelined the render phase can finish before
145    // the time system runs.
146    let (s, r) = crossbeam_channel::bounded::<Instant>(2);
147    (TimeSender(s), TimeReceiver(r))
148}
149
150/// The system used to update the [`Time`] used by app logic. If there is a render world the time is
151/// sent from there to this system through channels. Otherwise the time is updated in this system.
152pub fn time_system(
153    mut real_time: ResMut<Time<Real>>,
154    mut virtual_time: ResMut<Time<Virtual>>,
155    fixed_time: Res<Time<Fixed>>,
156    mut time: ResMut<Time>,
157    update_strategy: Res<TimeUpdateStrategy>,
158    #[cfg(feature = "std")] time_recv: Option<Res<TimeReceiver>>,
159    #[cfg(feature = "std")] mut has_received_time: Local<bool>,
160) {
161    #[cfg(feature = "std")]
162    // TODO: Figure out how to handle this when using pipelined rendering.
163    let sent_time = match time_recv.map(|res| res.0.try_recv()) {
164        Some(Ok(new_time)) => {
165            *has_received_time = true;
166            Some(new_time)
167        }
168        Some(Err(_)) => {
169            if *has_received_time {
170                log::warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
171            }
172            None
173        }
174        None => None,
175    };
176
177    match update_strategy.as_ref() {
178        TimeUpdateStrategy::Automatic => {
179            #[cfg(feature = "std")]
180            real_time.update_with_instant(sent_time.unwrap_or_else(Instant::now));
181
182            #[cfg(not(feature = "std"))]
183            real_time.update_with_instant(Instant::now());
184        }
185        TimeUpdateStrategy::ManualInstant(instant) => real_time.update_with_instant(*instant),
186        TimeUpdateStrategy::ManualDuration(duration) => real_time.update_with_duration(*duration),
187        TimeUpdateStrategy::FixedTimesteps(factor) => {
188            real_time.update_with_duration(fixed_time.timestep() * *factor);
189        }
190    }
191
192    update_virtual_time(&mut time, &mut virtual_time, &real_time);
193}
194
195#[cfg(test)]
196#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
197mod tests {
198    use crate::{Fixed, Time, TimePlugin, TimeUpdateStrategy, Virtual};
199    use bevy_app::{App, FixedUpdate, Startup, Update};
200    use bevy_ecs::{
201        message::{
202            Message, MessageReader, MessageRegistry, MessageWriter, Messages, ShouldUpdateMessages,
203        },
204        resource::Resource,
205        system::{Local, Res, ResMut},
206    };
207    use core::error::Error;
208    use core::time::Duration;
209    use std::println;
210
211    #[derive(Message)]
212    struct TestMessage<T: Default> {
213        sender: std::sync::mpsc::Sender<T>,
214    }
215
216    impl<T: Default> Drop for TestMessage<T> {
217        fn drop(&mut self) {
218            self.sender
219                .send(T::default())
220                .expect("Failed to send drop signal");
221        }
222    }
223
224    #[derive(Message)]
225    struct DummyMessage;
226
227    #[derive(Resource, Default)]
228    struct FixedUpdateCounter(u8);
229
230    fn count_fixed_updates(mut counter: ResMut<FixedUpdateCounter>) {
231        counter.0 += 1;
232    }
233
234    fn report_time(
235        mut frame_count: Local<u64>,
236        virtual_time: Res<Time<Virtual>>,
237        fixed_time: Res<Time<Fixed>>,
238    ) {
239        println!(
240            "Virtual time on frame {}: {:?}",
241            *frame_count,
242            virtual_time.elapsed()
243        );
244        println!(
245            "Fixed time on frame {}: {:?}",
246            *frame_count,
247            fixed_time.elapsed()
248        );
249
250        *frame_count += 1;
251    }
252
253    #[test]
254    fn fixed_main_schedule_should_run_with_time_plugin_enabled() {
255        // Set the time step to just over half the fixed update timestep
256        // This way, it will have not accumulated enough time to run the fixed update after one update
257        // But will definitely have enough time after two updates
258        let fixed_update_timestep = Time::<Fixed>::default().timestep();
259        let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
260
261        let mut app = App::new();
262        app.add_plugins(TimePlugin)
263            .add_systems(FixedUpdate, count_fixed_updates)
264            .add_systems(Update, report_time)
265            .init_resource::<FixedUpdateCounter>()
266            .insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
267
268        // Frame 0
269        // Fixed update should not have run yet
270        app.update();
271
272        assert!(Duration::ZERO < fixed_update_timestep);
273        let counter = app.world().resource::<FixedUpdateCounter>();
274        assert_eq!(counter.0, 0, "Fixed update should not have run yet");
275
276        // Frame 1
277        // Fixed update should not have run yet
278        app.update();
279
280        assert!(time_step < fixed_update_timestep);
281        let counter = app.world().resource::<FixedUpdateCounter>();
282        assert_eq!(counter.0, 0, "Fixed update should not have run yet");
283
284        // Frame 2
285        // Fixed update should have run now
286        app.update();
287
288        assert!(2 * time_step > fixed_update_timestep);
289        let counter = app.world().resource::<FixedUpdateCounter>();
290        assert_eq!(counter.0, 1, "Fixed update should have run once");
291
292        // Frame 3
293        // Fixed update should have run exactly once still
294        app.update();
295
296        assert!(3 * time_step < 2 * fixed_update_timestep);
297        let counter = app.world().resource::<FixedUpdateCounter>();
298        assert_eq!(counter.0, 1, "Fixed update should have run once");
299
300        // Frame 4
301        // Fixed update should have run twice now
302        app.update();
303
304        assert!(4 * time_step > 2 * fixed_update_timestep);
305        let counter = app.world().resource::<FixedUpdateCounter>();
306        assert_eq!(counter.0, 2, "Fixed update should have run twice");
307    }
308
309    #[test]
310    fn events_get_dropped_regression_test_11528() -> Result<(), impl Error> {
311        let (tx1, rx1) = std::sync::mpsc::channel();
312        let (tx2, rx2) = std::sync::mpsc::channel();
313        let mut app = App::new();
314        app.add_plugins(TimePlugin)
315            .add_message::<TestMessage<i32>>()
316            .add_message::<TestMessage<()>>()
317            .add_systems(Startup, move |mut ev2: MessageWriter<TestMessage<()>>| {
318                ev2.write(TestMessage {
319                    sender: tx2.clone(),
320                });
321            })
322            .add_systems(Update, move |mut ev1: MessageWriter<TestMessage<i32>>| {
323                // Keep adding events so this event type is processed every update
324                ev1.write(TestMessage {
325                    sender: tx1.clone(),
326                });
327            })
328            .add_systems(
329                Update,
330                |mut m1: MessageReader<TestMessage<i32>>,
331                 mut m2: MessageReader<TestMessage<()>>| {
332                    // Read events so they can be dropped
333                    for _ in m1.read() {}
334                    for _ in m2.read() {}
335                },
336            )
337            .insert_resource(TimeUpdateStrategy::ManualDuration(
338                Time::<Fixed>::default().timestep(),
339            ));
340
341        for _ in 0..10 {
342            app.update();
343        }
344
345        // Check event type 1 as been dropped at least once
346        let _drop_signal = rx1.try_recv()?;
347        // Check event type 2 has been dropped
348        rx2.try_recv()
349    }
350
351    #[test]
352    fn event_update_should_wait_for_fixed_main() {
353        // Set the time step to just over half the fixed update timestep
354        // This way, it will have not accumulated enough time to run the fixed update after one update
355        // But will definitely have enough time after two updates
356        let fixed_update_timestep = Time::<Fixed>::default().timestep();
357        let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
358
359        fn write_message(mut messages: ResMut<Messages<DummyMessage>>) {
360            messages.write(DummyMessage);
361        }
362
363        let mut app = App::new();
364        app.add_plugins(TimePlugin)
365            .add_message::<DummyMessage>()
366            .init_resource::<FixedUpdateCounter>()
367            .add_systems(Startup, write_message)
368            .add_systems(FixedUpdate, count_fixed_updates)
369            .insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
370
371        for frame in 0..10 {
372            app.update();
373            let fixed_updates_seen = app.world().resource::<FixedUpdateCounter>().0;
374            let messages = app.world().resource::<Messages<DummyMessage>>();
375            let n_total_messages = messages.len();
376            let n_current_messages = messages.iter_current_update_messages().count();
377            let message_registry = app.world().resource::<MessageRegistry>();
378            let should_update = message_registry.should_update;
379
380            println!("Frame {frame}, {fixed_updates_seen} fixed updates seen. Should update: {should_update:?}");
381            println!("Total messages: {n_total_messages} | Current messages: {n_current_messages}",);
382
383            match frame {
384                0 | 1 => {
385                    assert_eq!(fixed_updates_seen, 0);
386                    assert_eq!(n_total_messages, 1);
387                    assert_eq!(n_current_messages, 1);
388                    assert_eq!(should_update, ShouldUpdateMessages::Waiting);
389                }
390                2 => {
391                    assert_eq!(fixed_updates_seen, 1); // Time to trigger event updates
392                    assert_eq!(n_total_messages, 1);
393                    assert_eq!(n_current_messages, 1);
394                    assert_eq!(should_update, ShouldUpdateMessages::Ready); // Prepping first update
395                }
396                3 => {
397                    assert_eq!(fixed_updates_seen, 1);
398                    assert_eq!(n_total_messages, 1);
399                    assert_eq!(n_current_messages, 0); // First update has occurred
400                    assert_eq!(should_update, ShouldUpdateMessages::Waiting);
401                }
402                4 => {
403                    assert_eq!(fixed_updates_seen, 2); // Time to trigger the second update
404                    assert_eq!(n_total_messages, 1);
405                    assert_eq!(n_current_messages, 0);
406                    assert_eq!(should_update, ShouldUpdateMessages::Ready); // Prepping second update
407                }
408                5 => {
409                    assert_eq!(fixed_updates_seen, 2);
410                    assert_eq!(n_total_messages, 0); // Second update has occurred
411                    assert_eq!(n_current_messages, 0);
412                    assert_eq!(should_update, ShouldUpdateMessages::Waiting);
413                }
414                _ => {
415                    assert_eq!(n_total_messages, 0); // No more events are sent
416                    assert_eq!(n_current_messages, 0);
417                }
418            }
419        }
420    }
421}