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, SystemChain, 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 fn add_systems_to(schedules: &mut BTreeMap<SystemStage, Schedule>, stage: SystemStage, chain: SystemChain) {
111 schedules
112 .entry(stage)
113 .or_insert_with(Schedule::default)
114 .add_systems(chain);
115 }
116
117 pub fn add_system<S, Params>(mut self, stage: SystemStage, system: impl Into<SystemConfig<S, Params>>) -> Self
124 where
125 Params: 'static,
126 S: IntoSystem<Params> + 'static,
127 {
128 Self::add_system_to(&mut self.schedules, stage, system);
129 self
130 }
131
132 pub fn add_systems(mut self, stage: SystemStage, chain: SystemChain) -> Self {
136 Self::add_systems_to(&mut self.schedules, stage, chain);
137 self
138 }
139
140 pub(crate) fn add_gpu_system<S, Params>(mut self, stage: SystemStage, system: impl Into<SystemConfig<S, Params>>) -> Self
141 where
142 Params: 'static,
143 S: IntoSystem<Params> + 'static,
144 {
145 Self::add_system_to(&mut self.gpu_schedules, stage, system);
146 self
147 }
148
149 pub fn add_event<T: 'static + Send + Sync>(mut self) -> Self {
155 if !self.resources.contains::<Events<T>>() {
156 self.resources.insert(Events::<T>::default());
157 self = self.add_system(SystemStage::PreUpdate, age_events::<T>);
158 }
159 self
160 }
161
162 pub fn add_observer<E: 'static + Send + Sync, Params: 'static>(
167 mut self,
168 observer: impl IntoObserverSystem<E, Params> + 'static,
169 ) -> Self {
170 if !self.resources.contains::<Observers<E>>() {
171 self.resources.insert(Observers::<E>::default());
172 }
173 self.resources.get_mut::<Observers<E>>().0.push(Box::new(observer.into_observer_system()));
174 self
175 }
176
177 pub fn set_runner(mut self, runner: impl FnOnce(App) + 'static) -> Self {
181 self.runner = Some(Box::new(runner));
182 self
183 }
184
185 pub fn with_logging(self) -> Self {
189 tracing_subscriber::fmt().init();
190 self
191 }
192
193 pub fn update(&mut self) {
201 if !self.resources.get::<BackendReady>().0 {
202 println!("Backend Pending");
203 for (_, schedule) in self.gpu_schedules.iter_mut() {
204 schedule.run(&mut self.world, &mut self.resources);
205 }
206 return;
207 }
208
209 println!("Backend Obtained, Running Systems");
210 if let Some(mut ready) = self.schedules.remove(&SystemStage::Ready) {
211 ready.run(&mut self.world, &mut self.resources);
212 }
213 for (_, schedule) in self.schedules.iter_mut() {
214 schedule.run(&mut self.world, &mut self.resources);
215 }
216 }
217
218 pub fn should_exit(&self) -> bool {
221 self.resources.get::<AppExit>().0
222 }
223
224 pub fn run(mut self) {
231 if let Some(mut startup) = self.schedules.remove(&SystemStage::Startup) {
233 startup.run(&mut self.world, &mut self.resources);
234 }
235
236 if let Some(runner) = self.runner.take() {
239 runner(self);
240 return;
241 }
242
243 loop {
244 let was_ready = self.resources.get::<BackendReady>().0;
245 self.update();
246
247 if !was_ready {
248 #[cfg(not(target_arch = "wasm32"))]
252 std::thread::sleep(std::time::Duration::from_millis(16));
253 continue;
254 }
255
256 if self.should_exit() {
257 break;
258 }
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::ecs::{
267 commands::Commands,
268 events::{EventReader, EventWriter},
269 local::Local,
270 observers::Trigger,
271 resources::{Read, Write},
272 };
273
274 struct Damage(u32);
275
276 #[derive(Default)]
277 struct Seen(Vec<u32>);
278
279 fn send_once(mut writer: EventWriter<Damage>, mut sent: Local<bool>) {
280 if !*sent {
281 writer.send(Damage(7));
282 *sent = true;
283 }
284 }
285
286 fn record(mut reader: EventReader<Damage>, mut seen: Write<Seen>) {
287 for event in reader.iter() {
288 seen.0.push(event.0);
289 }
290 }
291
292 #[test]
293 fn add_event_called_twice_still_delivers_exactly_once_one_tick_later() {
294 let mut app = App::new()
301 .add_event::<Damage>()
302 .add_event::<Damage>()
303 .insert_resource(Seen::default())
304 .add_system(SystemStage::PreUpdate, record)
305 .add_system(SystemStage::Update, send_once);
306 app.resources.get_mut::<BackendReady>().0 = true;
307
308 app.update(); assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
310
311 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
313
314 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![7]);
316 }
317
318 struct Ping(u32);
319
320 fn fire_once(mut commands: Commands, mut sent: Local<bool>) {
321 if !*sent {
322 commands.trigger(Ping(3));
323 *sent = true;
324 }
325 }
326
327 fn on_ping(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
328 seen.0.push(trigger.0);
329 }
330
331 fn on_ping_doubled(trigger: Trigger<Ping>, mut seen: Write<Seen>) {
332 seen.0.push(trigger.0 * 2);
333 }
334
335 #[test]
336 fn observer_fires_the_same_tick_it_is_triggered() {
337 let mut app = App::new()
338 .add_observer(on_ping)
339 .insert_resource(Seen::default())
340 .add_system(SystemStage::Update, fire_once);
341 app.resources.get_mut::<BackendReady>().0 = true;
342
343 app.update();
344 assert_eq!(app.resources.get::<Seen>().0, vec![3]);
345
346 app.update(); assert_eq!(app.resources.get::<Seen>().0, vec![3]);
348 }
349
350 #[test]
351 fn ready_stage_runs_exactly_once_before_the_regular_schedules_that_same_tick() {
352 struct SetupRan;
353
354 fn setup(mut commands: Commands, mut seen: Write<Seen>) {
355 seen.0.push(1);
356 commands.insert_resource(SetupRan);
357 }
358
359 fn depends_on_setup(ran: Option<Read<SetupRan>>, mut seen: Write<Seen>) {
360 if ran.is_some() {
361 seen.0.push(2);
362 }
363 }
364
365 let mut app = App::new()
366 .add_system(SystemStage::Ready, setup)
367 .insert_resource(Seen::default())
368 .add_system(SystemStage::PreUpdate, depends_on_setup);
369
370 app.update();
372 assert_eq!(app.resources.get::<Seen>().0, Vec::<u32>::new());
373
374 app.resources.get_mut::<BackendReady>().0 = true;
375
376 app.update();
379 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2]);
380
381 app.update();
383 assert_eq!(app.resources.get::<Seen>().0, vec![1, 2, 2]);
384 }
385
386 #[test]
387 fn multiple_observers_for_the_same_event_all_fire() {
388 let mut app = App::new()
389 .add_observer(on_ping)
390 .add_observer(on_ping_doubled)
391 .insert_resource(Seen::default())
392 .add_system(SystemStage::Update, fire_once);
393 app.resources.get_mut::<BackendReady>().0 = true;
394
395 app.update();
396
397 let seen = app.resources.get::<Seen>().0.clone();
398 assert_eq!(seen.len(), 2);
399 assert!(seen.contains(&3));
400 assert!(seen.contains(&6));
401 }
402}