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