1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![forbid(unsafe_code)]
4#![doc(
5 html_logo_url = "https://bevyengine.org/assets/icon.png",
6 html_favicon_url = "https://bevyengine.org/assets/icon.png"
7)]
8
9pub mod common_conditions;
11mod fixed;
12mod real;
13mod stopwatch;
14#[allow(clippy::module_inception)]
15mod time;
16mod timer;
17mod virt;
18
19pub use fixed::*;
20pub use real::*;
21pub use stopwatch::*;
22pub use time::*;
23pub use timer::*;
24pub use virt::*;
25
26pub mod prelude {
27 #[doc(hidden)]
29 pub use crate::{Fixed, Real, Time, Timer, TimerMode, Virtual};
30}
31
32use bevy_app::{prelude::*, RunFixedMainLoop};
33use bevy_ecs::event::{signal_event_update_system, EventRegistry, ShouldUpdateEvents};
34use bevy_ecs::prelude::*;
35use bevy_utils::{tracing::warn, Duration, Instant};
36pub use crossbeam_channel::TrySendError;
37use crossbeam_channel::{Receiver, Sender};
38
39#[derive(Default)]
41pub struct TimePlugin;
42
43#[derive(Debug, PartialEq, Eq, Clone, Hash, SystemSet)]
44pub struct TimeSystem;
47
48impl Plugin for TimePlugin {
49 fn build(&self, app: &mut App) {
50 app.init_resource::<Time>()
51 .init_resource::<Time<Real>>()
52 .init_resource::<Time<Virtual>>()
53 .init_resource::<Time<Fixed>>()
54 .init_resource::<TimeUpdateStrategy>();
55
56 #[cfg(feature = "bevy_reflect")]
57 {
58 app.register_type::<Time>()
59 .register_type::<Time<Real>>()
60 .register_type::<Time<Virtual>>()
61 .register_type::<Time<Fixed>>()
62 .register_type::<Timer>();
63 }
64
65 app.add_systems(First, time_system.in_set(TimeSystem))
66 .add_systems(RunFixedMainLoop, run_fixed_main_schedule);
67
68 app.add_systems(FixedPostUpdate, signal_event_update_system);
70 let mut event_registry = app.world_mut().resource_mut::<EventRegistry>();
71 event_registry.should_update = ShouldUpdateEvents::Waiting;
73 }
74}
75
76#[derive(Resource, Default)]
81pub enum TimeUpdateStrategy {
82 #[default]
85 Automatic,
86 ManualInstant(Instant),
91 ManualDuration(Duration),
93}
94
95#[derive(Resource)]
97pub struct TimeReceiver(pub Receiver<Instant>);
98
99#[derive(Resource)]
101pub struct TimeSender(pub Sender<Instant>);
102
103pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
105 let (s, r) = crossbeam_channel::bounded::<Instant>(2);
108 (TimeSender(s), TimeReceiver(r))
109}
110
111pub fn time_system(
114 mut real_time: ResMut<Time<Real>>,
115 mut virtual_time: ResMut<Time<Virtual>>,
116 mut time: ResMut<Time>,
117 update_strategy: Res<TimeUpdateStrategy>,
118 time_recv: Option<Res<TimeReceiver>>,
119 mut has_received_time: Local<bool>,
120) {
121 let new_time = if let Some(time_recv) = time_recv {
122 if let Ok(new_time) = time_recv.0.try_recv() {
124 *has_received_time = true;
125 new_time
126 } else {
127 if *has_received_time {
128 warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
129 }
130 Instant::now()
131 }
132 } else {
133 Instant::now()
134 };
135
136 match update_strategy.as_ref() {
137 TimeUpdateStrategy::Automatic => real_time.update_with_instant(new_time),
138 TimeUpdateStrategy::ManualInstant(instant) => real_time.update_with_instant(*instant),
139 TimeUpdateStrategy::ManualDuration(duration) => real_time.update_with_duration(*duration),
140 }
141
142 update_virtual_time(&mut time, &mut virtual_time, &real_time);
143}
144
145#[cfg(test)]
146mod tests {
147 use crate::{Fixed, Time, TimePlugin, TimeUpdateStrategy, Virtual};
148 use bevy_app::{App, FixedUpdate, Startup, Update};
149 use bevy_ecs::{
150 event::{Event, EventReader, EventRegistry, EventWriter, Events, ShouldUpdateEvents},
151 system::{Local, Res, ResMut, Resource},
152 };
153 use bevy_utils::Duration;
154 use std::error::Error;
155
156 #[derive(Event)]
157 struct TestEvent<T: Default> {
158 sender: std::sync::mpsc::Sender<T>,
159 }
160
161 impl<T: Default> Drop for TestEvent<T> {
162 fn drop(&mut self) {
163 self.sender
164 .send(T::default())
165 .expect("Failed to send drop signal");
166 }
167 }
168
169 #[derive(Event)]
170 struct DummyEvent;
171
172 #[derive(Resource, Default)]
173 struct FixedUpdateCounter(u8);
174
175 fn count_fixed_updates(mut counter: ResMut<FixedUpdateCounter>) {
176 counter.0 += 1;
177 }
178
179 fn report_time(
180 mut frame_count: Local<u64>,
181 virtual_time: Res<Time<Virtual>>,
182 fixed_time: Res<Time<Fixed>>,
183 ) {
184 println!(
185 "Virtual time on frame {}: {:?}",
186 *frame_count,
187 virtual_time.elapsed()
188 );
189 println!(
190 "Fixed time on frame {}: {:?}",
191 *frame_count,
192 fixed_time.elapsed()
193 );
194
195 *frame_count += 1;
196 }
197
198 #[test]
199 fn fixed_main_schedule_should_run_with_time_plugin_enabled() {
200 let fixed_update_timestep = Time::<Fixed>::default().timestep();
204 let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
205
206 let mut app = App::new();
207 app.add_plugins(TimePlugin)
208 .add_systems(FixedUpdate, count_fixed_updates)
209 .add_systems(Update, report_time)
210 .init_resource::<FixedUpdateCounter>()
211 .insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
212
213 app.update();
216
217 assert!(Duration::ZERO < fixed_update_timestep);
218 let counter = app.world().resource::<FixedUpdateCounter>();
219 assert_eq!(counter.0, 0, "Fixed update should not have run yet");
220
221 app.update();
224
225 assert!(time_step < fixed_update_timestep);
226 let counter = app.world().resource::<FixedUpdateCounter>();
227 assert_eq!(counter.0, 0, "Fixed update should not have run yet");
228
229 app.update();
232
233 assert!(2 * time_step > fixed_update_timestep);
234 let counter = app.world().resource::<FixedUpdateCounter>();
235 assert_eq!(counter.0, 1, "Fixed update should have run once");
236
237 app.update();
240
241 assert!(3 * time_step < 2 * fixed_update_timestep);
242 let counter = app.world().resource::<FixedUpdateCounter>();
243 assert_eq!(counter.0, 1, "Fixed update should have run once");
244
245 app.update();
248
249 assert!(4 * time_step > 2 * fixed_update_timestep);
250 let counter = app.world().resource::<FixedUpdateCounter>();
251 assert_eq!(counter.0, 2, "Fixed update should have run twice");
252 }
253
254 #[test]
255 fn events_get_dropped_regression_test_11528() -> Result<(), impl Error> {
256 let (tx1, rx1) = std::sync::mpsc::channel();
257 let (tx2, rx2) = std::sync::mpsc::channel();
258 let mut app = App::new();
259 app.add_plugins(TimePlugin)
260 .add_event::<TestEvent<i32>>()
261 .add_event::<TestEvent<()>>()
262 .add_systems(Startup, move |mut ev2: EventWriter<TestEvent<()>>| {
263 ev2.send(TestEvent {
264 sender: tx2.clone(),
265 });
266 })
267 .add_systems(Update, move |mut ev1: EventWriter<TestEvent<i32>>| {
268 ev1.send(TestEvent {
270 sender: tx1.clone(),
271 });
272 })
273 .add_systems(
274 Update,
275 |mut ev1: EventReader<TestEvent<i32>>, mut ev2: EventReader<TestEvent<()>>| {
276 for _ in ev1.read() {}
278 for _ in ev2.read() {}
279 },
280 )
281 .insert_resource(TimeUpdateStrategy::ManualDuration(
282 Time::<Fixed>::default().timestep(),
283 ));
284
285 for _ in 0..10 {
286 app.update();
287 }
288
289 let _drop_signal = rx1.try_recv()?;
291 rx2.try_recv()
293 }
294
295 #[test]
296 fn event_update_should_wait_for_fixed_main() {
297 let fixed_update_timestep = Time::<Fixed>::default().timestep();
301 let time_step = fixed_update_timestep / 2 + Duration::from_millis(1);
302
303 fn send_event(mut events: ResMut<Events<DummyEvent>>) {
304 events.send(DummyEvent);
305 }
306
307 let mut app = App::new();
308 app.add_plugins(TimePlugin)
309 .add_event::<DummyEvent>()
310 .init_resource::<FixedUpdateCounter>()
311 .add_systems(Startup, send_event)
312 .add_systems(FixedUpdate, count_fixed_updates)
313 .insert_resource(TimeUpdateStrategy::ManualDuration(time_step));
314
315 for frame in 0..10 {
316 app.update();
317 let fixed_updates_seen = app.world().resource::<FixedUpdateCounter>().0;
318 let events = app.world().resource::<Events<DummyEvent>>();
319 let n_total_events = events.len();
320 let n_current_events = events.iter_current_update_events().count();
321 let event_registry = app.world().resource::<EventRegistry>();
322 let should_update = event_registry.should_update;
323
324 println!("Frame {frame}, {fixed_updates_seen} fixed updates seen. Should update: {should_update:?}");
325 println!("Total events: {n_total_events} | Current events: {n_current_events}",);
326
327 match frame {
328 0 | 1 => {
329 assert_eq!(fixed_updates_seen, 0);
330 assert_eq!(n_total_events, 1);
331 assert_eq!(n_current_events, 1);
332 assert_eq!(should_update, ShouldUpdateEvents::Waiting);
333 }
334 2 => {
335 assert_eq!(fixed_updates_seen, 1); assert_eq!(n_total_events, 1);
337 assert_eq!(n_current_events, 1);
338 assert_eq!(should_update, ShouldUpdateEvents::Ready); }
340 3 => {
341 assert_eq!(fixed_updates_seen, 1);
342 assert_eq!(n_total_events, 1);
343 assert_eq!(n_current_events, 0); assert_eq!(should_update, ShouldUpdateEvents::Waiting);
345 }
346 4 => {
347 assert_eq!(fixed_updates_seen, 2); assert_eq!(n_total_events, 1);
349 assert_eq!(n_current_events, 0);
350 assert_eq!(should_update, ShouldUpdateEvents::Ready); }
352 5 => {
353 assert_eq!(fixed_updates_seen, 2);
354 assert_eq!(n_total_events, 0); assert_eq!(n_current_events, 0);
356 assert_eq!(should_update, ShouldUpdateEvents::Waiting);
357 }
358 _ => {
359 assert_eq!(n_total_events, 0); assert_eq!(n_current_events, 0);
361 }
362 }
363 }
364 }
365}