1use bevy_ecs::prelude::*;
7use bevy_ecs::schedule::IntoScheduleConfigs;
8use bevy_ecs::system::ScheduleSystem;
9
10use crate::engine::Engine;
11use crate::game_loop::{GameLoop, TickRate};
12use crate::schedule::UpdateStage;
13use crate::state::EngineState;
14
15#[derive(Resource, Clone, Copy, PartialEq, Eq)]
20pub struct TickRateConfig {
21 pub rate: TickRate,
22}
23
24#[derive(Clone, Copy, PartialEq, Eq)]
30pub struct LoopConfig {
31 pub frame_cap: u32,
33 pub tick_rate: TickRate,
35}
36
37impl Default for LoopConfig {
38 fn default() -> Self {
39 Self {
40 frame_cap: 0,
41 tick_rate: TickRate::Hz60,
42 }
43 }
44}
45
46#[derive(Resource)]
50pub struct Time {
51 delta_seconds: f32,
54 raw_delta_seconds: f32,
56 real_delta_seconds: f32,
59 elapsed_seconds: f32,
61 scale: f32,
63 frame_count: u64,
65 interp_alpha: f32,
68}
69
70impl Time {
71 #[must_use]
73 pub fn new() -> Self {
74 Self {
75 delta_seconds: 0.0,
76 raw_delta_seconds: 0.0,
77 real_delta_seconds: 0.0,
78 elapsed_seconds: 0.0,
79 scale: 1.0,
80 frame_count: 0,
81 interp_alpha: 0.0,
82 }
83 }
84
85 #[must_use]
87 pub const fn delta_seconds(&self) -> f32 {
88 self.delta_seconds
89 }
90
91 #[must_use]
94 pub const fn raw_delta_seconds(&self) -> f32 {
95 self.raw_delta_seconds
96 }
97
98 #[must_use]
101 pub const fn real_delta_seconds(&self) -> f32 {
102 self.real_delta_seconds
103 }
104
105 #[must_use]
107 pub const fn elapsed_seconds(&self) -> f32 {
108 self.elapsed_seconds
109 }
110
111 #[must_use]
113 pub const fn time_scale(&self) -> f32 {
114 self.scale
115 }
116
117 pub fn set_time_scale(&mut self, scale: f32) {
119 self.scale = scale.max(0.0);
120 }
121
122 #[must_use]
124 pub const fn frame_count(&self) -> u64 {
125 self.frame_count
126 }
127
128 pub fn set_delta_seconds(&mut self, delta: f32) {
130 self.delta_seconds = delta;
131 self.raw_delta_seconds = delta;
132 }
133
134 pub fn advance(&mut self, fixed_delta: f32) {
139 self.raw_delta_seconds = fixed_delta;
140 self.delta_seconds = fixed_delta * self.scale;
141 self.elapsed_seconds += self.delta_seconds;
142 self.frame_count += 1;
143 }
144
145 pub fn set_real_delta(&mut self, real_delta: f32) {
147 self.real_delta_seconds = real_delta;
148 }
149
150 #[must_use]
153 pub const fn interp_alpha(&self) -> f32 {
154 self.interp_alpha
155 }
156
157 pub fn set_interp_alpha(&mut self, alpha: f32) {
159 self.interp_alpha = alpha;
160 }
161}
162
163impl Default for Time {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169pub struct App {
173 engine: Engine,
175 pending_plugins: Vec<Box<dyn GamePlugin>>,
177 built_plugins: Vec<String>,
179 startup_run: bool,
181}
182
183impl App {
184 #[must_use]
186 pub fn new() -> Self {
187 let mut engine = Engine::new();
188 engine.world_mut().insert_resource(Time::new());
190 engine.world_mut().insert_resource(EngineState::Running);
192 Self {
193 engine,
194 pending_plugins: Vec::new(),
195 built_plugins: Vec::new(),
196 startup_run: false,
197 }
198 }
199
200 pub const fn world_mut(&mut self) -> &mut World {
202 self.engine.world_mut()
203 }
204
205 pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
207 self.engine.world_mut().insert_resource(resource);
208 self
209 }
210
211 pub fn add_system<M>(
215 &mut self,
216 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
217 ) -> &mut Self {
218 self.add_system_to_stage(UpdateStage::Update, systems)
219 }
220
221 pub fn add_system_to_stage<M>(
226 &mut self,
227 stage: UpdateStage,
228 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
229 ) -> &mut Self {
230 self.engine.stage_schedule_mut(stage).add_systems(systems);
231 self
232 }
233
234 pub fn add_ordered_systems<M>(
238 &mut self,
239 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
240 ) -> &mut Self {
241 self.add_system(systems.chain())
242 }
243
244 pub fn add_ordered_systems_to_stage<M>(
248 &mut self,
249 stage: UpdateStage,
250 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
251 ) -> &mut Self {
252 self.add_system_to_stage(stage, systems.chain())
253 }
254
255 pub fn add_startup_system<M>(
257 &mut self,
258 systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
259 ) -> &mut Self {
260 self.engine.startup_schedule_mut().add_systems(systems);
261 self
262 }
263
264 pub fn add_plugin(&mut self, plugin: impl GamePlugin + 'static) -> &mut Self {
268 self.pending_plugins.push(Box::new(plugin));
269 self
270 }
271
272 fn build_plugins(&mut self) {
274 let mut built = std::mem::take(&mut self.built_plugins);
280 let mut pending = std::mem::take(&mut self.pending_plugins);
281 let mut ready: Vec<Box<dyn GamePlugin>> = Vec::new();
282
283 loop {
284 pending.append(&mut self.pending_plugins);
286 if pending.is_empty() {
287 break;
288 }
289
290 let mut progressed = false;
291 for mut plugin in std::mem::take(&mut pending) {
292 let deps_met = plugin
293 .dependencies()
294 .iter()
295 .all(|dep| built.iter().any(|name| name.as_str() == *dep));
296 if deps_met {
297 plugin.build(self);
298 built.push(plugin.name().to_string());
299 ready.push(plugin);
300 progressed = true;
301 } else {
302 pending.push(plugin);
303 }
304 }
305
306 if !progressed && self.pending_plugins.is_empty() {
308 break;
309 }
310 }
311
312 self.pending_plugins = pending;
314 self.built_plugins = built;
315
316 if !self.pending_plugins.is_empty() {
317 log::warn!(
318 "{} plugins could not be built (missing dependencies or circular deps)",
319 self.pending_plugins.len()
320 );
321 }
322
323 for mut plugin in ready {
325 plugin.finish(self);
326 }
327 }
328
329 pub const fn engine(&self) -> &Engine {
331 &self.engine
332 }
333
334 pub const fn engine_mut(&mut self) -> &mut Engine {
336 &mut self.engine
337 }
338
339 pub fn run(&mut self, config: LoopConfig) {
341 self.run_with_events(config, |_| {});
342 }
343
344 pub fn run_with_events<F>(&mut self, config: LoopConfig, mut process_events: F)
349 where
350 F: FnMut(&mut World),
351 {
352 self.build_plugins();
353 if !self.startup_run {
354 self.engine.run_startup();
355 self.startup_run = true;
356 }
357
358 self.engine.world_mut().insert_resource(TickRateConfig {
360 rate: config.tick_rate,
361 });
362
363 let mut fixed_delta = config.tick_rate.delta_seconds();
364 let mut game_loop = GameLoop::new(config.frame_cap, config.tick_rate);
365
366 while game_loop.is_running() {
367 if let Some(cfg) = self.engine.world().get_resource::<TickRateConfig>()
369 && cfg.rate != game_loop.tick_rate()
370 {
371 game_loop.set_tick_rate(cfg.rate);
372 fixed_delta = cfg.rate.delta_seconds();
373 }
374
375 let (ticks, frame_delta) = game_loop.tick();
376 let alpha = game_loop.interpolation_alpha();
377
378 if let Some(mut time) = self.engine.world_mut().get_resource_mut::<Time>() {
379 time.set_real_delta(frame_delta);
380 time.set_interp_alpha(alpha);
381 }
382
383 for _ in 0..ticks {
385 if let Some(mut time) = self.engine.world_mut().get_resource_mut::<Time>() {
386 time.advance(fixed_delta);
387 }
388 self.engine.run_logic_tick();
389 }
390 self.engine.run_render_and_post();
394
395 if let Some(state) = self.engine.world().get_resource::<EngineState>()
396 && state.is_stopping()
397 {
398 break;
399 }
400
401 game_loop.apply_frame_cap();
402 process_events(self.engine.world_mut());
403 }
404 }
405
406 pub fn tick(&mut self, fixed_delta: f32) {
409 if !self.pending_plugins.is_empty() {
410 self.build_plugins();
411 }
412 if !self.startup_run {
413 self.engine.run_startup();
414 self.startup_run = true;
415 }
416 if let Some(mut time) = self.engine.world_mut().get_resource_mut::<Time>() {
417 time.advance(fixed_delta);
418 }
419 self.engine.run_stages();
420 }
421}
422
423impl Default for App {
424 fn default() -> Self {
425 Self::new()
426 }
427}
428
429pub trait GamePlugin: Send {
433 fn name(&self) -> &str;
435
436 fn dependencies(&self) -> &[&str] {
438 &[]
439 }
440
441 fn build(&mut self, _app: &mut App) {}
443
444 fn finish(&mut self, _app: &mut App) {}
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use std::sync::{Arc, Mutex};
452
453 type Log = Arc<Mutex<Vec<String>>>;
454 type Spawn = Box<dyn FnOnce(&mut App) + Send>;
456
457 struct Recorder {
461 name: &'static str,
462 deps: Vec<&'static str>,
463 log: Log,
464 spawn: Option<Spawn>,
465 }
466
467 impl GamePlugin for Recorder {
468 fn name(&self) -> &str {
469 self.name
470 }
471 fn dependencies(&self) -> &[&str] {
472 &self.deps
473 }
474 fn build(&mut self, app: &mut App) {
475 self.log
476 .lock()
477 .unwrap()
478 .push(format!("build:{}", self.name));
479 if let Some(spawn) = self.spawn.take() {
480 spawn(app);
481 }
482 }
483 fn finish(&mut self, _app: &mut App) {
484 self.log
485 .lock()
486 .unwrap()
487 .push(format!("finish:{}", self.name));
488 }
489 }
490
491 fn calls(log: &Log) -> Vec<String> {
492 log.lock().unwrap().clone()
493 }
494
495 #[test]
496 fn builds_dependencies_before_dependents() {
497 let log: Log = Arc::new(Mutex::new(Vec::new()));
498 let mut app = App::new();
499 app.add_plugin(Recorder {
501 name: "b",
502 deps: vec!["a"],
503 log: log.clone(),
504 spawn: None,
505 });
506 app.add_plugin(Recorder {
507 name: "a",
508 deps: vec![],
509 log: log.clone(),
510 spawn: None,
511 });
512 app.build_plugins();
513
514 let c = calls(&log);
515 let build_a = c.iter().position(|x| x == "build:a").expect("a built");
516 let build_b = c.iter().position(|x| x == "build:b").expect("b built");
517 assert!(build_a < build_b, "a must build before b: {c:?}");
518 let last_build = c.iter().rposition(|x| x.starts_with("build:")).unwrap();
520 let first_finish = c.iter().position(|x| x.starts_with("finish:")).unwrap();
521 assert!(
522 last_build < first_finish,
523 "all builds precede finishes: {c:?}"
524 );
525 }
526
527 #[test]
528 fn absorbs_plugin_registered_during_build() {
529 let log: Log = Arc::new(Mutex::new(Vec::new()));
530 let log_for_child = log.clone();
531 let mut app = App::new();
532 app.add_plugin(Recorder {
533 name: "parent",
534 deps: vec![],
535 log: log.clone(),
536 spawn: Some(Box::new(move |app| {
537 app.add_plugin(Recorder {
538 name: "child",
539 deps: vec!["parent"],
540 log: log_for_child.clone(),
541 spawn: None,
542 });
543 })),
544 });
545 app.build_plugins();
546
547 let c = calls(&log);
548 assert!(
549 c.contains(&"build:parent".to_string()),
550 "parent built: {c:?}"
551 );
552 assert!(
553 c.contains(&"build:child".to_string()),
554 "child registered during build must be built: {c:?}"
555 );
556 assert!(app.pending_plugins.is_empty(), "no plugins left pending");
557 }
558
559 #[test]
560 fn leaves_unresolved_dependency_unbuilt() {
561 let log: Log = Arc::new(Mutex::new(Vec::new()));
562 let mut app = App::new();
563 app.add_plugin(Recorder {
564 name: "needy",
565 deps: vec!["missing"],
566 log: log.clone(),
567 spawn: None,
568 });
569 app.build_plugins();
570
571 assert!(
572 !calls(&log).iter().any(|x| x == "build:needy"),
573 "plugin with a missing dep must not build"
574 );
575 assert_eq!(
576 app.pending_plugins.len(),
577 1,
578 "the unresolved plugin stays pending"
579 );
580 }
581}