gravitron_plugin/
app.rs

1use std::{
2  any::{type_name, Any, TypeId},
3  collections::HashMap,
4  marker::PhantomData,
5  time::{Duration, Instant},
6};
7
8use gravitron_ecs::{
9  scheduler::{Scheduler, SchedulerBuilder},
10  systems::{IntoSystem, System},
11  world::World,
12};
13use log::debug;
14#[cfg(feature = "debug")]
15use log::trace;
16
17use crate::{
18  config::AppConfig,
19  ecs::resources::{engine_commands::EngineCommands, engine_info::EngineInfo},
20  stages::{CleanupSystemStage, InitSystemStage, MainSystemStage},
21};
22
23pub struct AppBuilder<S: Stage> {
24  world: World,
25  init_scheduler: SchedulerBuilder<InitSystemStage>,
26  main_scheduler: SchedulerBuilder<MainSystemStage>,
27  cleanup_scheduler: SchedulerBuilder<CleanupSystemStage>,
28  config: HashMap<TypeId, Box<dyn Any>>,
29  marker: PhantomData<S>,
30}
31
32pub struct App<S: Status> {
33  world: World,
34  init_scheduler: Scheduler,
35  main_scheduler: Scheduler,
36  cleanup_scheduler: Scheduler,
37  config: HashMap<TypeId, Box<dyn Any>>,
38  marker: PhantomData<S>,
39}
40
41impl<S: Status> App<S> {
42  #[inline]
43  pub fn get_resource<R: 'static>(&self) -> Option<&R> {
44    self.world.get_resource()
45  }
46
47  #[inline]
48  pub fn get_resource_mut<R: 'static>(&mut self) -> Option<&mut R> {
49    self.world.get_resource_mut()
50  }
51}
52
53impl App<Running> {
54  #[inline]
55  pub fn set_resource<R: 'static>(&mut self, res: R) {
56    self.world.set_resource(res);
57  }
58
59  #[inline]
60  pub fn run_init(&mut self) {
61    self.init_scheduler.run(&mut self.world);
62  }
63
64  #[inline]
65  pub fn config<C: 'static>(&self) -> Option<&C> {
66    self
67      .config
68      .get(&TypeId::of::<C>())
69      .and_then(|c| c.downcast_ref())
70  }
71
72  pub fn run_main(&mut self) {
73    let fps = self.config::<AppConfig>().unwrap().fps;
74
75    let mut last_frame = Instant::now();
76    let frame_time = Duration::from_secs(1) / fps;
77
78    loop {
79      let elapsed = last_frame.elapsed();
80
81      if elapsed > frame_time {
82        self.set_resource(EngineInfo {
83          delta_time: elapsed.as_secs_f32(),
84        });
85
86        last_frame = Instant::now();
87
88        self.main_scheduler.run(&mut self.world);
89
90        let cmds = self
91          .get_resource::<EngineCommands>()
92          .expect("Failed to get Engine Commands");
93        if cmds.is_shutdown() {
94          debug!("Exiting game loop");
95          break;
96        }
97
98        self.world.next_tick();
99
100        #[cfg(feature = "debug")]
101        trace!("Frame took {:?}", last_frame.elapsed());
102      }
103    }
104  }
105
106  #[inline]
107  pub fn run_cleanup(mut self) -> App<Cleanup> {
108    self.cleanup_scheduler.run(&mut self.world);
109
110    App {
111      world: self.world,
112      init_scheduler: self.init_scheduler,
113      main_scheduler: self.main_scheduler,
114      cleanup_scheduler: self.cleanup_scheduler,
115      config: self.config,
116      marker: PhantomData,
117    }
118  }
119}
120
121impl<S: Stage> AppBuilder<S> {
122  #[inline]
123  pub fn add_init_system<I, Sy: System + 'static>(
124    &mut self,
125    system: impl IntoSystem<I, System = Sy>,
126  ) {
127    self.init_scheduler.add_system(system);
128  }
129
130  #[inline]
131  pub fn add_init_system_at_stage<I, Sy: System + 'static>(
132    &mut self,
133    system: impl IntoSystem<I, System = Sy>,
134    stage: InitSystemStage,
135  ) {
136    self.init_scheduler.add_system_at_stage(system, stage);
137  }
138
139  #[inline]
140  pub fn add_main_system<I, Sy: System + 'static>(
141    &mut self,
142    system: impl IntoSystem<I, System = Sy>,
143  ) {
144    self.main_scheduler.add_system(system);
145  }
146
147  #[inline]
148  pub fn add_main_system_at_stage<I, Sy: System + 'static>(
149    &mut self,
150    system: impl IntoSystem<I, System = Sy>,
151    stage: MainSystemStage,
152  ) {
153    self.main_scheduler.add_system_at_stage(system, stage);
154  }
155
156  #[inline]
157  pub fn add_cleanup_system<I, Sy: System + 'static>(
158    &mut self,
159    system: impl IntoSystem<I, System = Sy>,
160  ) {
161    self.cleanup_scheduler.add_system(system);
162  }
163
164  #[inline]
165  pub fn add_cleanup_system_at_stage<I, Sy: System + 'static>(
166    &mut self,
167    system: impl IntoSystem<I, System = Sy>,
168    stage: CleanupSystemStage,
169  ) {
170    self.cleanup_scheduler.add_system_at_stage(system, stage);
171  }
172
173  #[inline]
174  pub fn add_resource<R: 'static>(&mut self, res: R) {
175    self.world.add_resource(res);
176  }
177
178  #[inline]
179  pub fn config<C: 'static>(&self) -> Option<&C> {
180    self
181      .config
182      .get(&TypeId::of::<C>())
183      .and_then(|c| c.downcast_ref())
184  }
185
186  pub(crate) fn build(mut self) -> App<Running> {
187    self.world.add_resource(EngineCommands::default());
188    let parallel = self.config::<AppConfig>().unwrap().parallel_systems;
189
190    App {
191      world: self.world,
192      init_scheduler: self.init_scheduler.build(parallel),
193      main_scheduler: self.main_scheduler.build(parallel),
194      cleanup_scheduler: self.cleanup_scheduler.build(parallel),
195      config: self.config,
196      marker: PhantomData,
197    }
198  }
199}
200
201impl AppBuilder<Build> {
202  #[inline]
203  pub(crate) fn new() -> Self {
204    Self::default()
205  }
206
207  #[inline]
208  pub fn config_mut<C: 'static>(&mut self) -> Option<&mut C> {
209    self
210      .config
211      .get_mut(&TypeId::of::<C>())
212      .and_then(|c| c.downcast_mut())
213  }
214
215  #[inline]
216  pub fn add_config<C: 'static>(&mut self, config: C) {
217    debug!("Adding Config {}", type_name::<C>());
218    self.config.insert(TypeId::of::<C>(), Box::new(config));
219  }
220
221  pub(crate) fn finalize(self) -> AppBuilder<Finalize> {
222    AppBuilder {
223      world: self.world,
224      init_scheduler: self.init_scheduler,
225      main_scheduler: self.main_scheduler,
226      cleanup_scheduler: self.cleanup_scheduler,
227      config: self.config,
228      marker: PhantomData,
229    }
230  }
231}
232
233impl AppBuilder<Finalize> {
234  #[inline]
235  pub fn get_resource<R: 'static>(&self) -> Option<&R> {
236    self.world.get_resource()
237  }
238
239  #[inline]
240  pub fn get_resource_mut<R: 'static>(&mut self) -> Option<&mut R> {
241    self.world.get_resource_mut()
242  }
243}
244
245impl Default for AppBuilder<Build> {
246  fn default() -> Self {
247    let orig_hook = std::panic::take_hook();
248    std::panic::set_hook(Box::new(move |panic_info| {
249      orig_hook(panic_info);
250      std::process::exit(1);
251    }));
252
253    let mut builder = Self {
254      world: Default::default(),
255      init_scheduler: Default::default(),
256      main_scheduler: Default::default(),
257      cleanup_scheduler: Default::default(),
258      config: Default::default(),
259      marker: PhantomData,
260    };
261
262    builder.add_config(AppConfig::default());
263
264    builder
265  }
266}
267
268pub trait Stage {}
269
270pub struct Build {}
271impl Stage for Build {}
272
273pub struct Finalize {}
274impl Stage for Finalize {}
275
276pub trait Status {}
277
278pub struct Running {}
279impl Status for Running {}
280
281pub struct Cleanup {}
282impl Status for Cleanup {}