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 {}