Skip to main content

goud_engine/ecs/app/
mod.rs

1//! Application framework for organizing ECS systems and plugins.
2//!
3//! The [`App`] struct is the top-level container that holds a [`World`], manages
4//! system stages, and supports plugin-based composition.
5//!
6//! # Example
7//!
8//! ```rust
9//! use goud_engine::ecs::app::App;
10//! use goud_engine::ecs::schedule::CoreStage;
11//! use goud_engine::ecs::system::System;
12//! use goud_engine::ecs::World;
13//!
14//! struct MySystem;
15//! impl System for MySystem {
16//!     fn name(&self) -> &'static str { "MySystem" }
17//!     fn run(&mut self, _world: &mut World) {}
18//! }
19//!
20//! let mut app = App::new();
21//! app.add_system(CoreStage::Update, MySystem);
22//! app.run_once();
23//! ```
24
25pub mod builtin_plugins;
26pub mod physics_plugins;
27pub mod plugin;
28
29pub use builtin_plugins::{DefaultPlugins, TransformPropagationPlugin};
30pub use physics_plugins::{PhysicsPlugin2D, PhysicsPlugin3D};
31pub use plugin::{Plugin, PluginGroup};
32
33use std::any::TypeId;
34use std::collections::HashSet;
35
36use crate::ecs::resource::{NonSendResource, Resource};
37use crate::ecs::schedule::{CoreStage, Stage, SystemSetConfig, SystemStage};
38use crate::ecs::system::IntoSystem;
39use crate::ecs::World;
40
41/// The main application container for ECS-based games.
42///
43/// `App` holds a [`World`] and a set of [`SystemStage`]s organized by
44/// [`CoreStage`]. Systems are added to stages and executed in stage order.
45///
46/// # Stage Execution Order
47///
48/// Stages run in [`CoreStage`] order: PreUpdate, Update, PostUpdate,
49/// PreRender, Render, PostRender.
50pub struct App {
51    /// The ECS world containing all entities, components, and resources.
52    world: World,
53    /// Stages indexed by CoreStage variant (in execution order).
54    stages: Vec<(CoreStage, SystemStage)>,
55    /// Tracks which plugins have been initialized (by TypeId).
56    initialized_plugins: HashSet<TypeId>,
57}
58
59impl App {
60    /// Creates a new App with default stages and all [`DefaultPlugins`] applied.
61    pub fn new_with_defaults() -> Self {
62        let mut app = Self::new();
63        app.add_plugin_group(DefaultPlugins);
64        app
65    }
66
67    /// Creates a new App with default stages for each [`CoreStage`] variant.
68    pub fn new() -> Self {
69        let stages = CoreStage::all()
70            .iter()
71            .map(|&stage| (stage, SystemStage::from_core(stage)))
72            .collect();
73
74        Self {
75            world: World::new(),
76            stages,
77            initialized_plugins: HashSet::new(),
78        }
79    }
80
81    /// Adds a plugin to the app.
82    ///
83    /// Each plugin type can only be added once. Duplicate additions are
84    /// silently ignored.
85    ///
86    /// # Panics
87    ///
88    /// Panics if the plugin declares dependencies that have not been added.
89    pub fn add_plugin<P: Plugin>(&mut self, plugin: P) -> &mut Self {
90        let plugin_type_id = TypeId::of::<P>();
91
92        if self.initialized_plugins.contains(&plugin_type_id) {
93            log::warn!(
94                "Plugin '{}' already added, skipping duplicate",
95                plugin.name()
96            );
97            return self;
98        }
99
100        // Check dependencies
101        for dep in plugin.dependencies() {
102            assert!(
103                self.initialized_plugins.contains(&dep),
104                "Plugin '{}' has an unmet dependency (TypeId: {:?}). \
105                 Add the dependency plugin first.",
106                plugin.name(),
107                dep
108            );
109        }
110
111        plugin.build(self);
112        self.initialized_plugins.insert(plugin_type_id);
113        self
114    }
115
116    /// Adds a plugin group to the app.
117    pub fn add_plugin_group<G: PluginGroup>(&mut self, group: G) -> &mut Self {
118        group.build(self);
119        self
120    }
121
122    /// Inserts a non-send resource into the world.
123    pub fn insert_non_send_resource<T: NonSendResource>(&mut self, resource: T) -> &mut Self {
124        self.world.insert_non_send_resource(resource);
125        self
126    }
127
128    /// Adds a system to the specified stage.
129    pub fn add_system<S, Marker>(&mut self, stage: CoreStage, system: S) -> &mut Self
130    where
131        S: IntoSystem<Marker>,
132    {
133        for (core_stage, system_stage) in &mut self.stages {
134            if *core_stage == stage {
135                system_stage.add_system(system);
136                return self;
137            }
138        }
139        self
140    }
141
142    /// Inserts a resource into the world.
143    pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
144        self.world.insert_resource(resource);
145        self
146    }
147
148    /// Returns an immutable reference to the world.
149    pub fn world(&self) -> &World {
150        &self.world
151    }
152
153    /// Returns a mutable reference to the world.
154    pub fn world_mut(&mut self) -> &mut World {
155        &mut self.world
156    }
157
158    /// Executes all stages once in order.
159    ///
160    /// This runs every stage's systems on the world, from PreUpdate
161    /// through PostRender.
162    pub fn run_once(&mut self) {
163        for (_stage_label, stage) in &mut self.stages {
164            stage.run(&mut self.world);
165        }
166    }
167
168    /// Executes all stages once, designed for per-frame use.
169    ///
170    /// Currently identical to [`run_once`](Self::run_once). In the future,
171    /// this may include frame-specific logic such as delta time updates.
172    pub fn update(&mut self) {
173        self.run_once();
174    }
175
176    // =====================================================================
177    // Named System Sets API
178    // =====================================================================
179
180    /// Registers a named system set in the specified stage.
181    pub fn register_set(&mut self, stage: CoreStage, name: &str) -> &mut Self {
182        for (core_stage, system_stage) in &mut self.stages {
183            if *core_stage == stage {
184                system_stage.register_set(name);
185                return self;
186            }
187        }
188        self
189    }
190
191    /// Adds a system to a named set, returning its [`SystemId`].
192    ///
193    /// The system is added to the stage and simultaneously placed in the
194    /// named set.
195    pub fn add_system_to_set<S, Marker>(
196        &mut self,
197        stage: CoreStage,
198        set_name: &str,
199        system: S,
200    ) -> &mut Self
201    where
202        S: IntoSystem<Marker>,
203    {
204        for (core_stage, system_stage) in &mut self.stages {
205            if *core_stage == stage {
206                assert!(
207                    system_stage.get_set(set_name).is_some(),
208                    "System set '{set_name}' is not registered in stage {stage:?}"
209                );
210                let id = system_stage.add_system(system);
211                system_stage.add_system_to_set(set_name, id);
212                return self;
213            }
214        }
215        self
216    }
217
218    /// Configures ordering for a named set in the specified stage.
219    pub fn configure_set(
220        &mut self,
221        stage: CoreStage,
222        name: &str,
223        config: SystemSetConfig,
224    ) -> &mut Self {
225        for (core_stage, system_stage) in &mut self.stages {
226            if *core_stage == stage {
227                system_stage.configure_named_set(name, config);
228                return self;
229            }
230        }
231        self
232    }
233}
234
235impl Default for App {
236    fn default() -> Self {
237        Self::new()
238    }
239}
240
241impl std::fmt::Debug for App {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        let total_systems: usize = self.stages.iter().map(|(_, s)| s.system_count()).sum();
244        f.debug_struct("App")
245            .field("stage_count", &self.stages.len())
246            .field("total_systems", &total_systems)
247            .field("plugin_count", &self.initialized_plugins.len())
248            .finish()
249    }
250}
251
252#[cfg(test)]
253#[path = "tests.rs"]
254mod tests;
255
256#[cfg(test)]
257#[path = "tests_extended.rs"]
258mod tests_extended;