1use crate::{
2 ecs::{
3 events::{AsyncEventChannel, Events, drain_async_events},
4 plugin::Plugin,
5 resources::Resources,
6 system::{IntoSystem, System},
7 system_set::IntoSystemSet,
8 },
9};
10use std::collections::{BTreeMap, BinaryHeap, HashMap};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub enum SystemStage {
19 Startup,
23 AssetSync,
25 AssetSyncDeps,
28 PreUpdate,
30 Update,
32 PostUpdate,
34 PreRender,
36 Render,
38 PostRender,
40}
41
42const ALL_STAGES: [SystemStage; 9] = [
44 SystemStage::Startup,
45 SystemStage::AssetSync,
46 SystemStage::AssetSyncDeps,
47 SystemStage::PreUpdate,
48 SystemStage::Update,
49 SystemStage::PostUpdate,
50 SystemStage::PreRender,
51 SystemStage::Render,
52 SystemStage::PostRender,
53];
54
55pub type AppRunner = Box<dyn FnOnce(App)>;
60
61pub struct App {
71 pub(crate) world: hecs::World,
72 pub(crate) resources: Resources,
73 plugins: Vec<Box<dyn Plugin>>,
74 systems: BTreeMap<SystemStage, Vec<Box<dyn System>>>,
75 runner: Option<AppRunner>,
76 event_updaters: Vec<Box<dyn FnMut(&hecs::World, &Resources)>>,
81 ready_gate: Option<Box<dyn FnMut(&mut hecs::World, &mut Resources) -> bool>>,
90}
91
92impl Default for App {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl App {
99 pub fn new() -> Self {
107 let mut world = hecs::World::default();
108 let mut resources = Resources::new(&mut world);
109 resources.insert_resource(&mut world, ());
110
111 let mut app = Self {
112 world: world,
113 resources: resources,
114 plugins: Vec::new(),
115 systems: BTreeMap::new(),
116 runner: Some(Box::new(|mut app| {
117 loop {
118 app.update();
119 }
120 })),
121 event_updaters: Vec::new(),
122 ready_gate: None,
123 };
124 app.add_plugin(crate::time::TimePlugin);
125 #[cfg(not(target_arch = "wasm32"))]
126 app.add_plugin(crate::gamepad::GamepadPlugin);
127 app.add_plugin(crate::audio::AudioPlugin);
128 app
129 }
130
131 fn run_stage_once(&mut self, stage: SystemStage) {
133 if let Some(systems) = self.systems.get_mut(&stage) {
134 for system in systems.iter_mut() {
135 let _guard = crate::ecs::resources::set_current_system(system.name());
136 system.run(&self.world, &self.resources);
137 }
138 }
139 self.resources.get_command_buffer().run_on(&mut self.world);
140 }
141
142 pub fn add_plugin(&mut self, plugin: impl Plugin) -> &mut Self {
144 self.plugins.push(Box::new(plugin));
145 self
146 }
147
148 pub fn set_ready_gate<F>(&mut self, gate: F) -> &mut Self
164 where
165 F: FnMut(&mut hecs::World, &mut Resources) -> bool + 'static,
166 {
167 self.ready_gate = Some(Box::new(gate));
168 self
169 }
170
171 pub fn add_resource(&mut self, res: impl hecs::Component) -> &mut Self {
173 self.resources.insert_resource(&mut self.world, res);
174 self
175 }
176
177 pub fn get_resource<'a, T: hecs::Component>(&'a self) -> hecs::Ref<'a, T> {
179 self.resources.get_resource(&self.world)
180 }
181
182 pub fn get_resource_mut<'a, T: hecs::Component>(&'a self) -> hecs::RefMut<'a, T> {
184 self.resources.get_resource_mut(&self.world)
185 }
186
187 pub fn try_insert_resource<T: hecs::Component>(&mut self, res: T) -> bool {
191 self.resources.try_insert(&mut self.world, res)
192 }
193
194 pub fn add_event<T: hecs::Component>(&mut self) -> &mut Self {
198 self.try_insert_resource(Events::<T>::default());
199 self.event_updaters.push(Box::new(|world, resources| {
200 resources.get_resource_mut::<Events<T>>(world).update();
201 }));
202 self
203 }
204
205 pub fn add_async_event<T: hecs::Component>(&mut self) -> &mut Self {
209 self.add_event::<T>();
210 self.try_insert_resource(AsyncEventChannel::<T>::new());
211 self.add_system(SystemStage::PreUpdate, drain_async_events::<T>);
212 self
213 }
214
215 pub fn add_system<Marker>(
217 &mut self,
218 stage: SystemStage,
219 system: impl IntoSystem<Marker> + 'static,
220 ) -> &mut Self {
221 self.systems
222 .entry(stage)
223 .or_default()
224 .push(Box::new(system.into_system()));
225 self
226 }
227
228 pub fn add_systems<Marker>(
232 &mut self,
233 stage: SystemStage,
234 systems: impl IntoSystemSet<Marker>,
235 ) -> &mut Self {
236 let entry = self.systems.entry(stage).or_default();
237 entry.extend(systems.into_system_set());
238 self
239 }
240
241 fn sort_stage(stage: SystemStage, systems: &mut Vec<Box<dyn System>>) {
246 let id_index: HashMap<std::any::TypeId, usize> = systems
247 .iter()
248 .enumerate()
249 .map(|(i, s)| (s.ordering_id(), i))
250 .collect();
251
252 let n = systems.len();
253 let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); n];
254 let mut in_degree = vec![0usize; n];
255
256 for (i, system) in systems.iter().enumerate() {
257 for id in system.after_ids() {
258 if let Some(&dep) = id_index.get(id) {
259 adjacency[dep].push(i);
260 in_degree[i] += 1;
261 }
262 }
263 for id in system.before_ids() {
264 if let Some(&dependent) = id_index.get(id) {
265 adjacency[i].push(dependent);
266 in_degree[dependent] += 1;
267 }
268 }
269 }
270
271 let mut ready: BinaryHeap<std::cmp::Reverse<usize>> = in_degree
273 .iter()
274 .enumerate()
275 .filter(|(_, d)| **d == 0)
276 .map(|(i, _)| std::cmp::Reverse(i))
277 .collect();
278
279 let mut order = Vec::with_capacity(n);
280 while let Some(std::cmp::Reverse(u)) = ready.pop() {
281 order.push(u);
282 for &v in &adjacency[u] {
283 in_degree[v] -= 1;
284 if in_degree[v] == 0 {
285 ready.push(std::cmp::Reverse(v));
286 }
287 }
288 }
289
290 if order.len() != n {
291 let stuck: Vec<&'static str> = (0..n)
292 .filter(|i| in_degree[*i] > 0)
293 .map(|i| systems[i].name())
294 .collect();
295 panic!(
296 "{stage:?}: system ordering constraints form a cycle among: {stuck:?}"
297 );
298 }
299
300 let mut taken: Vec<Option<Box<dyn System>>> = systems.drain(..).map(Some).collect();
301 for i in order {
302 systems.push(taken[i].take().unwrap());
303 }
304 }
305
306 pub fn build(&mut self) -> &mut Self {
312 let mut iterations = 0;
313 const MAX_PLUGIN_BUILD_ITERATIONS: u32 = 64;
314
315 while !self.plugins.is_empty() {
316 iterations += 1;
317 if iterations > MAX_PLUGIN_BUILD_ITERATIONS {
318 panic!(
319 "App::build() exceeded {MAX_PLUGIN_BUILD_ITERATIONS} plugin-registration passes — \
320 likely a cycle where plugins keep registering each other. Check for a plugin whose \
321 build() unconditionally re-adds itself or another plugin that re-adds it."
322 );
323 }
324 let plugins: Vec<_> = self.plugins.drain(..).collect();
325 for plugin in plugins {
326 plugin.build(self);
327 }
328 }
329
330 for (stage, systems) in self.systems.iter_mut() {
331 Self::sort_stage(*stage, systems);
332 }
333
334 self
335 }
336
337 pub fn update(&mut self) {
341 if let Some(gate) = &mut self.ready_gate {
342 if !gate(&mut self.world, &mut self.resources) {
343 return;
344 }
345 self.ready_gate = None;
346 }
347
348 for updater in self.event_updaters.iter_mut() {
349 updater(&self.world, &self.resources);
350 }
351 for stage in ALL_STAGES {
352 self.run_stage_once(stage);
353 }
354 }
355
356 pub fn set_runner<F>(&mut self, runner: F) -> &mut Self
362 where
363 F: FnOnce(App) + 'static,
364 {
365 self.runner = Some(Box::new(runner));
366 self
367 }
368
369 pub fn run(&mut self) {
373 let mut owned_app = std::mem::take(self);
374 let runner = owned_app.runner.take().expect("No runner found!");
375 runner(owned_app);
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::ecs::system::{ResMut, SystemOrderingExt};
383
384 struct Order(Vec<&'static str>);
385
386 fn sys_a(mut o: ResMut<Order>) {
387 o.0.push("a");
388 }
389 fn sys_b(mut o: ResMut<Order>) {
390 o.0.push("b");
391 }
392 fn sys_c(mut o: ResMut<Order>) {
393 o.0.push("c");
394 }
395
396 #[test]
397 fn systems_run_in_declared_order() {
398 let mut app = App::new();
399 app.add_resource(Order(Vec::new()));
400
401 app.add_system(SystemStage::Update, sys_a.after(sys_c));
405 app.add_system(SystemStage::Update, sys_b.after(sys_c));
406 app.add_system(SystemStage::Update, sys_c);
407
408 app.build();
409 app.update();
410
411 let order = app.get_resource::<Order>();
412 assert_eq!(order.0, vec!["c", "a", "b"]);
413 }
414
415 #[test]
416 #[should_panic(expected = "cycle")]
417 fn cyclic_ordering_constraints_panic() {
418 let mut app = App::new();
419 app.add_resource(Order(Vec::new()));
420
421 app.add_system(SystemStage::Update, sys_a.after(sys_b));
422 app.add_system(SystemStage::Update, sys_b.after(sys_a));
423
424 app.build();
425 }
426
427 #[test]
428 fn time_plugin_is_already_built_into_a_fresh_app() {
429 let mut app = App::new();
430 app.build();
431
432 let _ = app.get_resource::<crate::time::Time>();
435 }
436
437 #[test]
438 fn registering_time_plugin_again_does_not_double_register_its_system() {
439 let mut baseline = App::new();
443 baseline.build();
444 let baseline_count = baseline.systems.get(&SystemStage::PreUpdate).map_or(0, Vec::len);
445
446 let mut app = App::new();
447 app.add_plugin(crate::time::TimePlugin); app.build();
449
450 let tick_systems = app.systems.get(&SystemStage::PreUpdate).map_or(0, Vec::len);
452 assert_eq!(tick_systems, baseline_count);
453 }
454}