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, SystemConfig},
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<S, Params>(
97 schedules: &mut BTreeMap<SystemStage, Schedule>,
98 stage: SystemStage,
99 system: impl Into<SystemConfig<S, Params>>,
100 ) where
101 Params: 'static,
102 S: IntoSystem<Params> + 'static,
103 {
104 schedules
105 .entry(stage)
106 .or_insert_with(Schedule::default)
107 .add_system(system);
108 }
109
110 pub fn add_system<S, Params>(
117 mut self,
118 stage: SystemStage,
119 system: impl Into<SystemConfig<S, Params>>,
120 ) -> Self
121 where
122 Params: 'static,
123 S: IntoSystem<Params> + 'static,
124 {
125 Self::add_system_to(&mut self.schedules, stage, system);
126 self
127 }
128
129 pub(crate) fn add_gpu_system<S, Params>(
130 mut self,
131 stage: SystemStage,
132 system: impl Into<SystemConfig<S, Params>>,
133 ) -> Self
134 where
135 Params: 'static,
136 S: IntoSystem<Params> + 'static,
137 {
138 Self::add_system_to(&mut self.gpu_schedules, stage, system);
139 self
140 }
141
142 pub fn add_event<T: 'static + Send + Sync>(mut self) -> Self {
148 if !self.resources.contains::<Events<T>>() {
149 self.resources.insert(Events::<T>::default());
150 self = self.add_system(SystemStage::PreUpdate, age_events::<T>);
151 }
152 self
153 }
154
155 pub fn add_observer<E: 'static + Send + Sync, Params: 'static>(
160 mut self,
161 observer: impl IntoObserverSystem<E, Params> + 'static,
162 ) -> Self {
163 if !self.resources.contains::<Observers<E>>() {
164 self.resources.insert(Observers::<E>::default());
165 }
166 self.resources.get_mut::<Observers<E>>().0.push(Box::new(observer.into_observer_system()));
167 self
168 }
169
170 pub fn set_runner(mut self, runner: impl FnOnce(App) + 'static) -> Self {
174 self.runner = Some(Box::new(runner));
175 self
176 }
177
178 pub fn with_logging(self) -> Self {
182 tracing_subscriber::fmt().init();
183 self
184 }
185
186 pub fn update(&mut self) {
194 if !self.resources.get::<BackendReady>().0 {
195 println!("Backend Pending");
196 for (_, schedule) in self.gpu_schedules.iter_mut() {
197 schedule.run(&mut self.world, &mut self.resources);
198 }
199 return;
200 }
201
202 println!("Backend Obtained, Running Systems");
203 if let Some(mut ready) = self.schedules.remove(&SystemStage::Ready) {
204 ready.run(&mut self.world, &mut self.resources);
205 }
206 for (_, schedule) in self.schedules.iter_mut() {
207 schedule.run(&mut self.world, &mut self.resources);
208 }
209 }
210
211 pub fn should_exit(&self) -> bool {
214 self.resources.get::<AppExit>().0
215 }
216
217 pub fn run(mut self) {
224 if let Some(mut startup) = self.schedules.remove(&SystemStage::Startup) {
226 startup.run(&mut self.world, &mut self.resources);
227 }
228
229 if let Some(runner) = self.runner.take() {
232 runner(self);
233 return;
234 }
235
236 loop {
237 let was_ready = self.resources.get::<BackendReady>().0;
238 self.update();
239
240 if !was_ready {
241 #[cfg(not(target_arch = "wasm32"))]
245 std::thread::sleep(std::time::Duration::from_millis(16));
246 continue;
247 }
248
249 if self.should_exit() {
250 break;
251 }
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::ecs::{
260 commands::Commands,
261 events::{EventReader, EventWriter},
262 local::Local,
263 observers::Trigger,
264 resources::{Read, Write},
265 };
266
267 struct Damage(u32);
268
269 #[derive(Default)]
270 struct Seen(Vec<u32>);
271
272 fn send_once(mut writer: EventWriter<Damage>, mut sent: Local<bool>) {
273 if !*sent {
274 writer.send(Damage(7));
275 *sent = true;
276 }
277 }
278
279 fn record(mut reader: EventReader<Damage>, mut seen: Write<Seen>) {
280 for event in reader.iter() {
281 seen.0.push(event.0);
282 }
283 }
284
285 #[test]
286 fn add_event_called_twice_still_delivers_exactly_once_one_tick_later() {
287 let mut app = App::new()
294 .add_event::<Damage>()
295 .add_event::<Damage>()
296 .insert_resource(Seen::default())
297 .add_system(SystemStage::PreUpdate, record)
298 .add_system(SystemStage::Update, send_once);
299 app.resources.get_mut::<BackendReady>().0 = true;
300
301 app.update(); assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
303
304 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
306
307 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
309 }
310
311 struct Ping(u32);
312
313 fn fire_once(mut commands: Commands, mut sent: Local<bool>) {
314 if !*sent {
315 commands.trigger(Ping(3));
316 *sent = true;
317 }
318 }
319
320 fn on_ping(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
321 seen.0.push(trigger.0);
322 }
323
324 fn on_ping_doubled(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
325 seen.0.push(trigger.0 * 2);
326 }
327
328 #[test]
329 fn observer_fires_the_same_tick_it_is_triggered() {
330 let mut app = App::new()
331 .add_observer(on_ping)
332 .insert_resource(Seen::default())
333 .add_system(SystemStage::Update, fire_once);
334 app.resources.get_mut::<BackendReady>().0 = true;
335
336 app.update();
337 assert_eq!(app.resources.get::<Seen>().0, vec![3]);
338
339 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![3]);
341 }
342
343 #[test]
344 fn ready_stage_runs_exactly_once_before_the_regular_schedules_that_same_tick() {
345 struct SetupRan;
346
347 fn setup(mut commands: Commands, mut seen: Write<Seen>) {
348 seen.0.push(1);
349 commands.insert_resource(SetupRan);
350 }
351
352 fn depends_on_setup(ran: Option<Read<SetupRan>>, mut seen: Write<Seen>) {
353 if ran.is_some() {
354 seen.0.push(2);
355 }
356 }
357
358 let mut app = App::new()
359 .add_system(SystemStage::Ready, setup)
360 .insert_resource(Seen::default())
361 .add_system(SystemStage::PreUpdate, depends_on_setup);
362
363 app.update();
365 assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
366
367 app.resources.get_mut::<BackendReady>().0 = true;
368
369 app.update();
372 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2]);
373
374 app.update();
376 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2, 2]);
377 }
378
379 #[test]
380 fn multiple_observers_for_the_same_event_all_fire() {
381 let mut app = App::new()
382 .add_observer(on_ping)
383 .add_observer(on_ping_doubled)
384 .insert_resource(Seen::default())
385 .add_system(SystemStage::Update, fire_once);
386 app.resources.get_mut::<BackendReady>().0 = true;
387
388 app.update();
389
390 let seen = app.resources.get::<Seen>().0.clone();
391 assert_eq!(seen.len(), 2);
392 assert!(seen.contains(&3));
393 assert!(seen.contains(&6));
394 }
395}