Skip to main content

cougr_core/
system.rs

1use crate::commands::CommandQueue;
2use crate::scheduler::{ScheduleStage, SystemConfig};
3use crate::simple_world::SimpleWorld;
4
5/// Execution context for the Soroban-first `SimpleWorld` system API.
6pub struct SystemContext<'w, 'e, 'c> {
7    world: &'w mut SimpleWorld,
8    env: &'e soroban_sdk::Env,
9    commands: &'c mut CommandQueue,
10}
11
12impl<'w, 'e, 'c> SystemContext<'w, 'e, 'c> {
13    pub fn new(
14        world: &'w mut SimpleWorld,
15        env: &'e soroban_sdk::Env,
16        commands: &'c mut CommandQueue,
17    ) -> Self {
18        Self {
19            world,
20            env,
21            commands,
22        }
23    }
24
25    pub fn world(&self) -> &SimpleWorld {
26        self.world
27    }
28
29    pub fn world_mut(&mut self) -> &mut SimpleWorld {
30        self.world
31    }
32
33    pub fn env(&self) -> &soroban_sdk::Env {
34        self.env
35    }
36
37    pub fn commands(&mut self) -> &mut CommandQueue {
38        self.commands
39    }
40}
41
42/// System trait for the `SimpleWorld`/Soroban runtime.
43pub trait SimpleSystem {
44    fn run(&mut self, context: &mut SystemContext<'_, '_, '_>);
45}
46
47/// Marker trait for systems that participate in the Soroban-first app runtime.
48///
49/// This is the preferred system model for `GameApp` and `SimpleScheduler`.
50pub trait AppSystem: SimpleSystem {}
51
52impl<T: SimpleSystem + ?Sized> AppSystem for T {}
53
54/// Declarative registration spec for the Soroban-first runtime system APIs.
55///
56/// This is the preferred way to package a system together with its scheduler
57/// metadata before handing it to `GameApp::add_systems()` or
58/// `SimpleScheduler::add_systems()`.
59pub struct SystemSpec<S> {
60    name: &'static str,
61    system: S,
62    config: SystemConfig,
63}
64
65impl<S> SystemSpec<S> {
66    pub fn new(name: &'static str, system: S) -> Self {
67        Self {
68            name,
69            system,
70            config: SystemConfig::default(),
71        }
72    }
73
74    pub fn in_stage(mut self, stage: ScheduleStage) -> Self {
75        self.config = self.config.in_stage(stage);
76        self
77    }
78
79    pub fn after(mut self, system_name: impl Into<alloc::string::String>) -> Self {
80        self.config = self.config.after(system_name);
81        self
82    }
83
84    pub fn before(mut self, system_name: impl Into<alloc::string::String>) -> Self {
85        self.config = self.config.before(system_name);
86        self
87    }
88
89    pub fn in_set(mut self, set_name: impl Into<alloc::string::String>) -> Self {
90        self.config = self.config.in_set(set_name);
91        self
92    }
93
94    pub fn after_set(mut self, set_name: impl Into<alloc::string::String>) -> Self {
95        self.config = self.config.after_set(set_name);
96        self
97    }
98
99    pub fn before_set(mut self, set_name: impl Into<alloc::string::String>) -> Self {
100        self.config = self.config.before_set(set_name);
101        self
102    }
103
104    pub fn with_config(mut self, config: SystemConfig) -> Self {
105        self.config = config;
106        self
107    }
108
109    pub fn name(&self) -> &'static str {
110        self.name
111    }
112
113    pub fn config(&self) -> &SystemConfig {
114        &self.config
115    }
116
117    pub(crate) fn into_parts(self) -> (&'static str, SystemConfig, S) {
118        (self.name, self.config, self.system)
119    }
120}
121
122/// Adapter for context-aware closures.
123pub struct ContextSystem<F> {
124    function: F,
125}
126
127impl<F> ContextSystem<F> {
128    pub fn new(function: F) -> Self {
129        Self { function }
130    }
131}
132
133impl<F> SimpleSystem for ContextSystem<F>
134where
135    F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>),
136{
137    fn run(&mut self, context: &mut SystemContext<'_, '_, '_>) {
138        (self.function)(context);
139    }
140}
141
142/// Adapter for world/env systems to preserve the original onboarding path.
143pub struct WorldSystem<F> {
144    function: F,
145}
146
147impl<F> WorldSystem<F> {
148    pub fn new(function: F) -> Self {
149        Self { function }
150    }
151}
152
153impl<F> SimpleSystem for WorldSystem<F>
154where
155    F: FnMut(&mut SimpleWorld, &soroban_sdk::Env),
156{
157    fn run(&mut self, context: &mut SystemContext<'_, '_, '_>) {
158        let env = context.env().clone();
159        (self.function)(context.world_mut(), &env);
160    }
161}
162
163/// Wrap a `FnMut(&mut SystemContext)` closure as a runtime system.
164pub fn context_system<F>(function: F) -> ContextSystem<F>
165where
166    F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>),
167{
168    ContextSystem::new(function)
169}
170
171/// Wrap a `FnMut(&mut SimpleWorld, &Env)` closure as a runtime system.
172pub fn world_system<F>(function: F) -> WorldSystem<F>
173where
174    F: FnMut(&mut SimpleWorld, &soroban_sdk::Env),
175{
176    WorldSystem::new(function)
177}
178
179/// Wrap a world/env closure together with its registration name.
180pub fn named_system<F>(name: &'static str, function: F) -> SystemSpec<WorldSystem<F>>
181where
182    F: FnMut(&mut SimpleWorld, &soroban_sdk::Env),
183{
184    SystemSpec::new(name, world_system(function))
185}
186
187/// Wrap a context-aware closure together with its registration name.
188pub fn named_context_system<F>(name: &'static str, function: F) -> SystemSpec<ContextSystem<F>>
189where
190    F: for<'w, 'e, 'c> FnMut(&mut SystemContext<'w, 'e, 'c>),
191{
192    SystemSpec::new(name, context_system(function))
193}
194
195/// Wrap a pre-built runtime system together with its registration name.
196pub fn named_app_system<S>(name: &'static str, system: S) -> SystemSpec<S>
197where
198    S: AppSystem,
199{
200    SystemSpec::new(name, system)
201}
202
203#[cfg(test)]
204mod tests {
205    use super::{
206        named_context_system, named_system, AppSystem, ContextSystem, SimpleSystem, SystemContext,
207        WorldSystem,
208    };
209    use crate::commands::CommandQueue;
210    use crate::simple_world::SimpleWorld;
211    use soroban_sdk::{symbol_short, Bytes, Env};
212
213    #[test]
214    fn world_system_wraps_world_and_env_closure() {
215        let env = Env::default();
216        let mut world = SimpleWorld::new(&env);
217        let mut commands = CommandQueue::new();
218        let mut system = WorldSystem::new(|world: &mut SimpleWorld, env: &Env| {
219            let entity = world.spawn_entity();
220            world.add_component(entity, symbol_short!("tag"), Bytes::from_array(env, &[1]));
221        });
222        let mut context = SystemContext::new(&mut world, &env, &mut commands);
223
224        system.run(&mut context);
225
226        assert!(world.has_component(1, &symbol_short!("tag")));
227    }
228
229    #[test]
230    fn context_system_wraps_context_closure() {
231        let env = Env::default();
232        let mut world = SimpleWorld::new(&env);
233        let mut commands = CommandQueue::new();
234        let mut system = ContextSystem::new(|context: &mut SystemContext<'_, '_, '_>| {
235            context.commands().spawn();
236        });
237        let mut context = SystemContext::new(&mut world, &env, &mut commands);
238
239        system.run(&mut context);
240        let spawned = commands.apply(&mut world);
241
242        assert_eq!(spawned.len(), 1);
243    }
244
245    #[test]
246    fn named_helpers_preserve_registration_name() {
247        let world_spec = named_system("tick", |_world: &mut SimpleWorld, _env: &Env| {});
248        let context_spec = named_context_system("ctx", |_context| {});
249
250        assert_eq!(world_spec.name(), "tick");
251        assert_eq!(context_spec.name(), "ctx");
252    }
253
254    fn accepts_app_system<S: AppSystem>(_system: &S) {}
255
256    #[test]
257    fn runtime_adapters_implement_app_system() {
258        let world_system = WorldSystem::new(|_world: &mut SimpleWorld, _env: &Env| {});
259        let context_system = ContextSystem::new(|_context: &mut SystemContext<'_, '_, '_>| {});
260
261        accepts_app_system(&world_system);
262        accepts_app_system(&context_system);
263    }
264}