Skip to main content

cougr_core/plugin/
mod.rs

1mod groups;
2mod resources;
3#[cfg(test)]
4mod tests;
5
6use crate::hooks::{HookRegistry, OnAddHook, OnRemoveHook};
7use crate::resource::{Resource, ResourceTrait};
8use crate::scheduler::{ScheduleError, ScheduleStage, SimpleScheduler, SystemConfig, SystemGroup};
9use crate::simple_world::SimpleWorld;
10use crate::system::{AppSystem, SystemContext};
11use alloc::vec::Vec;
12use soroban_sdk::{Env, Symbol};
13
14/// A plugin that configures systems, hooks, and initial world state.
15///
16/// Plugins provide a modular way to compose game functionality.
17/// Each plugin gets access to a `GameApp` builder during `build()`.
18///
19/// # Example
20/// ```no_run
21/// # use cougr_core::plugin::{GameApp, Plugin};
22/// # use cougr_core::simple_world::SimpleWorld;
23/// # use soroban_sdk::Env;
24/// # fn gravity_system(_world: &mut SimpleWorld, _env: &Env) {}
25/// # fn collision_system(_world: &mut SimpleWorld, _env: &Env) {}
26/// struct PhysicsPlugin;
27///
28/// impl Plugin for PhysicsPlugin {
29///     fn name(&self) -> &'static str { "physics" }
30///     fn build(&self, app: &mut GameApp) {
31///         app.add_system("gravity", gravity_system);
32///         app.add_system("collision", collision_system);
33///     }
34/// }
35/// ```
36pub trait Plugin {
37    fn name(&self) -> &'static str;
38    fn build(&self, app: &mut GameApp);
39}
40
41/// Composable plugin group abstraction for `GameApp`.
42pub trait PluginGroup {
43    fn build(self, app: &mut GameApp);
44}
45
46/// Soroban-first application builder and runtime.
47///
48/// `GameApp` is the recommended entrypoint for new Cougr projects:
49/// it owns the `SimpleWorld`, the validated `SimpleScheduler`, and hook
50/// registration in one place.
51///
52/// # Example
53/// ```no_run
54/// # use cougr_core::plugin::{GameApp, Plugin};
55/// # use cougr_core::scheduler::{ScheduleStage, SystemConfig};
56/// # use cougr_core::simple_world::SimpleWorld;
57/// # use soroban_sdk::Env;
58/// # struct PhysicsPlugin;
59/// # struct ScoringPlugin;
60/// # fn physics_system(_world: &mut SimpleWorld, _env: &Env) {}
61/// # fn scoring_system(_world: &mut SimpleWorld, _env: &Env) {}
62/// # impl Plugin for PhysicsPlugin {
63/// #     fn name(&self) -> &'static str { "physics" }
64/// #     fn build(&self, app: &mut GameApp) { app.add_system("physics", physics_system); }
65/// # }
66/// # impl Plugin for ScoringPlugin {
67/// #     fn name(&self) -> &'static str { "scoring" }
68/// #     fn build(&self, app: &mut GameApp) {
69/// #         app.add_system_with_config(
70/// #             "scoring",
71/// #             scoring_system,
72/// #             SystemConfig::new().in_stage(ScheduleStage::PostUpdate),
73/// #         );
74/// #     }
75/// # }
76/// let env = Env::default();
77/// let mut app = GameApp::new(&env);
78/// app.add_plugin(PhysicsPlugin);
79/// app.add_plugin(ScoringPlugin);
80/// app.run(&env).unwrap();
81/// let world = app.into_world();
82/// assert_eq!(world.version(), 0);
83/// ```
84pub struct GameApp {
85    world: SimpleWorld,
86    scheduler: SimpleScheduler,
87    hooks: HookRegistry,
88    resources: Vec<Resource>,
89    plugins_registered: Vec<&'static str>,
90    startup_ran: bool,
91}
92
93impl GameApp {
94    pub fn new(env: &Env) -> Self {
95        Self {
96            world: SimpleWorld::new(env),
97            scheduler: SimpleScheduler::new(),
98            hooks: HookRegistry::new(),
99            resources: Vec::new(),
100            plugins_registered: Vec::new(),
101            startup_ran: false,
102        }
103    }
104
105    pub fn with_world(world: SimpleWorld) -> Self {
106        Self {
107            world,
108            scheduler: SimpleScheduler::new(),
109            hooks: HookRegistry::new(),
110            resources: Vec::new(),
111            plugins_registered: Vec::new(),
112            startup_ran: false,
113        }
114    }
115
116    pub fn add_plugin<P: Plugin>(&mut self, plugin: P) -> &mut Self {
117        let name = Plugin::name(&plugin);
118        if !self.has_plugin(name) {
119            self.plugins_registered.push(name);
120            Plugin::build(&plugin, self);
121        }
122        self
123    }
124
125    pub fn add_plugins<G: PluginGroup>(&mut self, group: G) -> &mut Self {
126        group.build(self);
127        self
128    }
129
130    /// Add a world/env system to the default `Update` stage.
131    pub fn add_system<F>(&mut self, name: &'static str, system: F) -> &mut Self
132    where
133        F: FnMut(&mut SimpleWorld, &Env) + 'static,
134    {
135        self.scheduler.add_system(name, system);
136        self
137    }
138
139    /// Add a world/env system with explicit scheduling rules.
140    pub fn add_system_with_config<F>(
141        &mut self,
142        name: &'static str,
143        system: F,
144        config: SystemConfig,
145    ) -> &mut Self
146    where
147        F: FnMut(&mut SimpleWorld, &Env) + 'static,
148    {
149        self.scheduler.add_system_with_config(name, system, config);
150        self
151    }
152
153    /// Add a world/env system directly to a specific stage.
154    pub fn add_system_in_stage<F>(
155        &mut self,
156        stage: ScheduleStage,
157        name: &'static str,
158        system: F,
159    ) -> &mut Self
160    where
161        F: FnMut(&mut SimpleWorld, &Env) + 'static,
162    {
163        self.scheduler.add_system_in_stage(stage, name, system);
164        self
165    }
166
167    /// Add a context-aware system to the default `Update` stage.
168    pub fn add_context_system<F>(&mut self, name: &'static str, system: F) -> &mut Self
169    where
170        F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>) + 'static,
171    {
172        self.scheduler.add_context_system(name, system);
173        self
174    }
175
176    /// Add a context-aware system with explicit scheduling rules.
177    pub fn add_context_system_with_config<F>(
178        &mut self,
179        name: &'static str,
180        system: F,
181        config: SystemConfig,
182    ) -> &mut Self
183    where
184        F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>) + 'static,
185    {
186        self.scheduler
187            .add_context_system_with_config(name, system, config);
188        self
189    }
190
191    /// Add a context-aware system directly to a specific stage.
192    pub fn add_context_system_in_stage<F>(
193        &mut self,
194        stage: ScheduleStage,
195        name: &'static str,
196        system: F,
197    ) -> &mut Self
198    where
199        F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>) + 'static,
200    {
201        self.scheduler
202            .add_context_system_in_stage(stage, name, system);
203        self
204    }
205
206    /// Add any pre-built runtime system to the default `Update` stage.
207    pub fn add_simple_system<S>(&mut self, name: &'static str, system: S) -> &mut Self
208    where
209        S: AppSystem + 'static,
210    {
211        self.scheduler.add_simple_system(name, system);
212        self
213    }
214
215    /// Add any pre-built runtime system with explicit scheduling rules.
216    pub fn add_simple_system_with_config<S>(
217        &mut self,
218        name: &'static str,
219        system: S,
220        config: SystemConfig,
221    ) -> &mut Self
222    where
223        S: AppSystem + 'static,
224    {
225        self.scheduler
226            .add_simple_system_with_config(name, system, config);
227        self
228    }
229
230    /// Add any pre-built runtime system directly to a specific stage.
231    pub fn add_simple_system_in_stage<S>(
232        &mut self,
233        stage: ScheduleStage,
234        name: &'static str,
235        system: S,
236    ) -> &mut Self
237    where
238        S: AppSystem + 'static,
239    {
240        self.scheduler
241            .add_simple_system_in_stage(stage, name, system);
242        self
243    }
244
245    /// Add one or more runtime systems using declarative specs.
246    pub fn add_systems<G>(&mut self, systems: G) -> &mut Self
247    where
248        G: SystemGroup,
249    {
250        self.scheduler.add_systems(systems);
251        self
252    }
253
254    /// Add one or more runtime systems while forcing them into a stage.
255    pub fn add_systems_in_stage<G>(&mut self, stage: ScheduleStage, systems: G) -> &mut Self
256    where
257        G: SystemGroup,
258    {
259        self.scheduler.add_systems_in_stage(stage, systems);
260        self
261    }
262
263    /// Convenience wrapper to register a startup-only system.
264    pub fn add_startup_system<F>(&mut self, name: &'static str, system: F) -> &mut Self
265    where
266        F: FnMut(&mut SimpleWorld, &Env) + 'static,
267    {
268        self.add_system_with_config(
269            name,
270            system,
271            SystemConfig::new().in_stage(ScheduleStage::Startup),
272        )
273    }
274
275    pub fn add_hook_on_add(&mut self, component_type: Symbol, hook: OnAddHook) -> &mut Self {
276        self.hooks.on_add(component_type, hook);
277        self
278    }
279
280    pub fn add_hook_on_remove(&mut self, component_type: Symbol, hook: OnRemoveHook) -> &mut Self {
281        self.hooks.on_remove(component_type, hook);
282        self
283    }
284
285    pub fn insert_resource<R: ResourceTrait>(&mut self, env: &Env, resource: &R) -> &mut Self {
286        resources::insert_resource(&mut self.resources, env, resource);
287        self
288    }
289
290    pub fn get_resource<R: ResourceTrait>(&self, env: &Env) -> Option<R> {
291        resources::get_resource(&self.resources, env)
292    }
293
294    pub fn remove_resource<R: ResourceTrait>(&mut self) -> Option<Resource> {
295        resources::remove_resource::<R>(&mut self.resources)
296    }
297
298    pub fn world(&self) -> &SimpleWorld {
299        &self.world
300    }
301
302    pub fn world_mut(&mut self) -> &mut SimpleWorld {
303        &mut self.world
304    }
305
306    pub fn scheduler(&self) -> &SimpleScheduler {
307        &self.scheduler
308    }
309
310    pub fn hooks(&self) -> &HookRegistry {
311        &self.hooks
312    }
313
314    pub fn resources(&self) -> &Vec<Resource> {
315        &self.resources
316    }
317
318    pub fn run_startup(&mut self, env: &Env) -> Result<(), ScheduleError> {
319        if !self.startup_ran {
320            self.scheduler
321                .run_stage(ScheduleStage::Startup, &mut self.world, env)?;
322            self.startup_ran = true;
323        }
324        Ok(())
325    }
326
327    /// Run one gameplay tick.
328    pub fn run(&mut self, env: &Env) -> Result<(), ScheduleError> {
329        self.run_startup(env)?;
330        self.scheduler
331            .run_stage(ScheduleStage::PreUpdate, &mut self.world, env)?;
332        self.scheduler
333            .run_stage(ScheduleStage::Update, &mut self.world, env)?;
334        self.scheduler
335            .run_stage(ScheduleStage::PostUpdate, &mut self.world, env)?;
336        self.scheduler
337            .run_stage(ScheduleStage::Cleanup, &mut self.world, env)?;
338        Ok(())
339    }
340
341    pub fn run_stage(&mut self, stage: ScheduleStage, env: &Env) -> Result<(), ScheduleError> {
342        if stage == ScheduleStage::Startup {
343            return self.run_startup(env);
344        }
345        self.scheduler.run_stage(stage, &mut self.world, env)
346    }
347
348    pub fn configure_system(
349        &mut self,
350        name: &str,
351        config: SystemConfig,
352    ) -> Result<&mut Self, ScheduleError> {
353        self.scheduler.configure_system(name, config)?;
354        Ok(self)
355    }
356
357    pub fn into_world(self) -> SimpleWorld {
358        self.world
359    }
360
361    pub fn plugin_count(&self) -> usize {
362        self.plugins_registered.len()
363    }
364
365    pub fn has_plugin(&self, name: &str) -> bool {
366        self.plugins_registered.contains(&name)
367    }
368
369    pub fn system_count(&self) -> usize {
370        self.scheduler.system_count()
371    }
372}