1use std::collections::BTreeMap;
2
3use crate::ecs::{
4 commands::{ResourceCommandQueue, TriggerQueue},
5 events::{Events, age_events},
6 observers::{IntoObserverSystem, Observers},
7 plugin::Plugin,
8 resources::Resources,
9 schedule::Schedule,
10 system::SystemStage,
11 system_param::IntoSystem,
12};
13
14#[derive(Default)]
19pub struct AppExit(pub bool);
20
21#[derive(Default)]
25pub struct BackendReady(pub bool);
26
27pub struct App {
42 world: hecs::World,
43 resources: Resources,
44 schedules: BTreeMap<SystemStage, Schedule>,
45 pub(crate) gpu_schedules: BTreeMap<SystemStage, Schedule>,
46 runner: Option<Box<dyn FnOnce(App)>>,
47}
48
49impl Default for App {
50 fn default() -> Self {
51 let mut resources = Resources::default();
52 resources.insert(hecs::CommandBuffer::default());
53 resources.insert(ResourceCommandQueue::default());
54 resources.insert(TriggerQueue::default());
55 resources.insert(AppExit::default());
56 resources.insert(BackendReady::default());
57
58 Self {
59 world: hecs::World::default(),
60 schedules: BTreeMap::new(),
61 gpu_schedules: BTreeMap::new(),
62 resources,
63 runner: None,
64 }
65 }
66}
67
68impl App {
69 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn insert_resource<T: 'static>(mut self, resource: T) -> Self {
80 self.resources.insert(resource);
81 self
82 }
83
84 pub fn remove_resource<T: 'static>(mut self) -> Self {
86 self.resources.remove::<T>();
87 self
88 }
89
90 pub fn add_plugin<P: Plugin>(self, plugin: P) -> Self {
93 plugin.build(self)
94 }
95
96 fn add_system_to<Params: 'static>(
97 schedules: &mut BTreeMap<SystemStage, Schedule>,
98 stage: SystemStage,
99 system: impl IntoSystem<Params> + 'static,
100 ) {
101 schedules
102 .entry(stage)
103 .or_insert_with(Schedule::default)
104 .add_system(system);
105 }
106
107 pub fn add_system<Params: 'static>(
110 mut self,
111 stage: SystemStage,
112 system: impl IntoSystem<Params> + 'static,
113 ) -> Self {
114 Self::add_system_to(&mut self.schedules, stage, system);
115 self
116 }
117
118 pub(crate) fn add_gpu_system<Params: 'static>(
119 mut self,
120 stage: SystemStage,
121 system: impl IntoSystem<Params> + 'static,
122 ) -> Self {
123 Self::add_system_to(&mut self.gpu_schedules, stage, system);
124 self
125 }
126
127 pub fn add_event<T: 'static + Send + Sync>(mut self) -> Self {
133 if !self.resources.contains::<Events<T>>() {
134 self.resources.insert(Events::<T>::default());
135 self = self.add_system(SystemStage::PreUpdate, age_events::<T>);
136 }
137 self
138 }
139
140 pub fn add_observer<E: 'static + Send + Sync, Params: 'static>(
145 mut self,
146 observer: impl IntoObserverSystem<E, Params> + 'static,
147 ) -> Self {
148 if !self.resources.contains::<Observers<E>>() {
149 self.resources.insert(Observers::<E>::default());
150 }
151 self.resources.get_mut::<Observers<E>>().0.push(Box::new(observer.into_observer_system()));
152 self
153 }
154
155 pub fn set_runner(mut self, runner: impl FnOnce(App) + 'static) -> Self {
159 self.runner = Some(Box::new(runner));
160 self
161 }
162
163 pub fn with_logging(self) -> Self {
167 tracing_subscriber::fmt().init();
168 self
169 }
170
171 pub fn update(&mut self) {
179 if !self.resources.get::<BackendReady>().0 {
180 println!("Backend Pending");
181 for (_, schedule) in self.gpu_schedules.iter_mut() {
182 schedule.run(&mut self.world, &mut self.resources);
183 }
184 return;
185 }
186
187 println!("Backend Obtained, Running Systems");
188 if let Some(mut ready) = self.schedules.remove(&SystemStage::Ready) {
189 ready.run(&mut self.world, &mut self.resources);
190 }
191 for (_, schedule) in self.schedules.iter_mut() {
192 schedule.run(&mut self.world, &mut self.resources);
193 }
194 }
195
196 pub fn should_exit(&self) -> bool {
199 self.resources.get::<AppExit>().0
200 }
201
202 pub fn run(mut self) {
209 if let Some(mut startup) = self.schedules.remove(&SystemStage::Startup) {
211 startup.run(&mut self.world, &mut self.resources);
212 }
213
214 if let Some(runner) = self.runner.take() {
217 runner(self);
218 return;
219 }
220
221 loop {
222 let was_ready = self.resources.get::<BackendReady>().0;
223 self.update();
224
225 if !was_ready {
226 #[cfg(not(target_arch = "wasm32"))]
230 std::thread::sleep(std::time::Duration::from_millis(16));
231 continue;
232 }
233
234 if self.should_exit() {
235 break;
236 }
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::ecs::{
245 commands::Commands,
246 events::{EventReader, EventWriter},
247 local::Local,
248 observers::Trigger,
249 resources::{Read, Write},
250 };
251
252 struct Damage(u32);
253
254 #[derive(Default)]
255 struct Seen(Vec<u32>);
256
257 fn send_once(mut writer: EventWriter<Damage>, mut sent: Local<bool>) {
258 if !*sent {
259 writer.send(Damage(7));
260 *sent = true;
261 }
262 }
263
264 fn record(mut reader: EventReader<Damage>, mut seen: Write<Seen>) {
265 for event in reader.iter() {
266 seen.0.push(event.0);
267 }
268 }
269
270 #[test]
271 fn add_event_called_twice_still_delivers_exactly_once_one_tick_later() {
272 let mut app = App::new()
279 .add_event::<Damage>()
280 .add_event::<Damage>()
281 .insert_resource(Seen::default())
282 .add_system(SystemStage::PreUpdate, record)
283 .add_system(SystemStage::Update, send_once);
284 app.resources.get_mut::<BackendReady>().0 = true;
285
286 app.update(); assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
288
289 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
291
292 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
294 }
295
296 struct Ping(u32);
297
298 fn fire_once(mut commands: Commands, mut sent: Local<bool>) {
299 if !*sent {
300 commands.trigger(Ping(3));
301 *sent = true;
302 }
303 }
304
305 fn on_ping(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
306 seen.0.push(trigger.0);
307 }
308
309 fn on_ping_doubled(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
310 seen.0.push(trigger.0 * 2);
311 }
312
313 #[test]
314 fn observer_fires_the_same_tick_it_is_triggered() {
315 let mut app = App::new()
316 .add_observer(on_ping)
317 .insert_resource(Seen::default())
318 .add_system(SystemStage::Update, fire_once);
319 app.resources.get_mut::<BackendReady>().0 = true;
320
321 app.update();
322 assert_eq!(app.resources.get::<Seen>().0, vec![3]);
323
324 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![3]);
326 }
327
328 #[test]
329 fn ready_stage_runs_exactly_once_before_the_regular_schedules_that_same_tick() {
330 struct SetupRan;
331
332 fn setup(mut commands: Commands, mut seen: Write<Seen>) {
333 seen.0.push(1);
334 commands.insert_resource(SetupRan);
335 }
336
337 fn depends_on_setup(ran: Option<Read<SetupRan>>, mut seen: Write<Seen>) {
338 if ran.is_some() {
339 seen.0.push(2);
340 }
341 }
342
343 let mut app = App::new()
344 .add_system(SystemStage::Ready, setup)
345 .insert_resource(Seen::default())
346 .add_system(SystemStage::PreUpdate, depends_on_setup);
347
348 app.update();
350 assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
351
352 app.resources.get_mut::<BackendReady>().0 = true;
353
354 app.update();
357 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2]);
358
359 app.update();
361 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2, 2]);
362 }
363
364 #[test]
365 fn multiple_observers_for_the_same_event_all_fire() {
366 let mut app = App::new()
367 .add_observer(on_ping)
368 .add_observer(on_ping_doubled)
369 .insert_resource(Seen::default())
370 .add_system(SystemStage::Update, fire_once);
371 app.resources.get_mut::<BackendReady>().0 = true;
372
373 app.update();
374
375 let seen = app.resources.get::<Seen>().0.clone();
376 assert_eq!(seen.len(), 2);
377 assert!(seen.contains(&3));
378 assert!(seen.contains(&6));
379 }
380}