goud_engine/ecs/system/system_trait/core.rs
1//! Core `System` trait, `BoxedSystem` wrapper, and `IntoSystem` conversion trait.
2
3use crate::ecs::query::Access;
4use crate::ecs::World;
5
6use super::system_id::SystemId;
7
8// =============================================================================
9// System Trait
10// =============================================================================
11
12/// A trait for types that can be run as systems on a [`World`].
13///
14/// Systems are the behavior layer of the ECS. They operate on entities and
15/// components, implementing game logic, physics, rendering, and more.
16///
17/// # Requirements
18///
19/// Systems must be `Send` to enable future parallel execution. This means
20/// all data accessed by the system must be thread-safe.
21///
22/// # Implementing System
23///
24/// For simple systems, implement this trait directly:
25///
26/// ```
27/// use goud_engine::ecs::{World};
28/// use goud_engine::ecs::system::System;
29///
30/// struct CountEntities {
31/// count: u32,
32/// }
33///
34/// impl System for CountEntities {
35/// fn name(&self) -> &'static str {
36/// "CountEntities"
37/// }
38///
39/// fn run(&mut self, world: &mut World) {
40/// self.count = world.entity_count() as u32;
41/// }
42/// }
43/// ```
44///
45/// # Access Tracking
46///
47/// Override `component_access()` to declare what components your system
48/// reads and writes. This enables the scheduler to detect conflicts and
49/// run non-conflicting systems in parallel.
50///
51/// ```
52/// use goud_engine::ecs::{World, Component, ComponentId};
53/// use goud_engine::ecs::system::System;
54/// use goud_engine::ecs::query::Access;
55///
56/// #[derive(Clone, Copy)]
57/// struct Position { x: f32, y: f32 }
58/// impl Component for Position {}
59///
60/// #[derive(Clone, Copy)]
61/// struct Velocity { x: f32, y: f32 }
62/// impl Component for Velocity {}
63///
64/// struct MovementSystem;
65///
66/// impl System for MovementSystem {
67/// fn name(&self) -> &'static str {
68/// "MovementSystem"
69/// }
70///
71/// fn component_access(&self) -> Access {
72/// let mut access = Access::new();
73/// access.add_write(ComponentId::of::<Position>());
74/// access.add_read(ComponentId::of::<Velocity>());
75/// access
76/// }
77///
78/// fn run(&mut self, world: &mut World) {
79/// // Movement logic here
80/// }
81/// }
82/// ```
83///
84/// # Lifecycle
85///
86/// Systems have optional lifecycle hooks:
87///
88/// - `initialize()`: Called once when the system is first added
89/// - `run()`: Called each time the system executes
90///
91/// # Thread Safety
92///
93/// The `Send` bound ensures systems can be sent between threads. However,
94/// the scheduler ensures only one system runs on a World at a time (for now).
95pub trait System: Send {
96 /// Returns the name of this system.
97 ///
98 /// Used for debugging, logging, and profiling. Should be a short,
99 /// descriptive name.
100 fn name(&self) -> &'static str;
101
102 /// Returns the component access pattern for this system.
103 ///
104 /// Override this to declare which components your system reads and writes.
105 /// The default implementation returns empty access (no components).
106 ///
107 /// Accurate access information enables:
108 /// - Parallel execution of non-conflicting systems
109 /// - Compile-time verification of system ordering
110 /// - Runtime conflict detection
111 #[inline]
112 fn component_access(&self) -> Access {
113 Access::new()
114 }
115
116 /// Called once when the system is first added to a scheduler.
117 ///
118 /// Use this for one-time initialization that requires world access.
119 /// The default implementation does nothing.
120 ///
121 /// # Arguments
122 ///
123 /// * `world` - Mutable reference to the world
124 #[inline]
125 fn initialize(&mut self, _world: &mut World) {
126 // Default: no initialization needed
127 }
128
129 /// Runs the system on the given world.
130 ///
131 /// This is called each frame (or each time the system's stage runs).
132 /// Implement your system logic here.
133 ///
134 /// # Arguments
135 ///
136 /// * `world` - Mutable reference to the world containing all ECS data
137 fn run(&mut self, world: &mut World);
138
139 /// Returns whether this system should run.
140 ///
141 /// Override this to conditionally skip system execution.
142 /// The default implementation always returns `true`.
143 ///
144 /// # Arguments
145 ///
146 /// * `world` - Reference to the world (for checking conditions)
147 ///
148 /// # Example
149 ///
150 /// ```
151 /// use goud_engine::ecs::{World, Component};
152 /// use goud_engine::ecs::system::System;
153 ///
154 /// struct GamePaused;
155 /// impl Component for GamePaused {}
156 ///
157 /// struct PhysicsSystem;
158 ///
159 /// impl System for PhysicsSystem {
160 /// fn name(&self) -> &'static str { "PhysicsSystem" }
161 ///
162 /// fn should_run(&self, world: &World) -> bool {
163 /// // Skip physics when game is paused
164 /// // (In a real implementation, you'd check a resource)
165 /// true
166 /// }
167 ///
168 /// fn run(&mut self, world: &mut World) {
169 /// // Physics logic
170 /// }
171 /// }
172 /// ```
173 #[inline]
174 fn should_run(&self, _world: &World) -> bool {
175 true
176 }
177
178 /// Returns true if this system only reads data.
179 ///
180 /// Read-only systems can potentially run in parallel with other
181 /// read-only systems. The default implementation delegates to
182 /// `component_access().is_read_only()`.
183 #[inline]
184 fn is_read_only(&self) -> bool {
185 self.component_access().is_read_only()
186 }
187}
188
189// =============================================================================
190// BoxedSystem
191// =============================================================================
192
193/// A type-erased, boxed system.
194///
195/// `BoxedSystem` wraps any `System` implementor in a `Box`, enabling
196/// dynamic dispatch and storage in collections.
197///
198/// # When to Use
199///
200/// Use `BoxedSystem` when you need to:
201/// - Store systems of different types in a single collection
202/// - Pass systems around without knowing their concrete type
203/// - Build dynamic system pipelines
204///
205/// # Example
206///
207/// ```
208/// use goud_engine::ecs::{World};
209/// use goud_engine::ecs::system::{System, BoxedSystem};
210///
211/// struct SystemA;
212/// impl System for SystemA {
213/// fn name(&self) -> &'static str { "SystemA" }
214/// fn run(&mut self, _world: &mut World) {}
215/// }
216///
217/// struct SystemB;
218/// impl System for SystemB {
219/// fn name(&self) -> &'static str { "SystemB" }
220/// fn run(&mut self, _world: &mut World) {}
221/// }
222///
223/// // Store different system types in a Vec
224/// let mut systems: Vec<BoxedSystem> = vec![
225/// BoxedSystem::new(SystemA),
226/// BoxedSystem::new(SystemB),
227/// ];
228///
229/// // Run all systems
230/// let mut world = World::new();
231/// for system in &mut systems {
232/// system.run(&mut world);
233/// }
234/// ```
235pub struct BoxedSystem {
236 /// The boxed system.
237 inner: Box<dyn System>,
238 /// Cached system ID.
239 id: SystemId,
240}
241
242impl BoxedSystem {
243 /// Creates a new boxed system from any System implementor.
244 #[inline]
245 pub fn new<S: System + 'static>(system: S) -> Self {
246 Self {
247 inner: Box::new(system),
248 id: SystemId::new(),
249 }
250 }
251
252 /// Returns the system's unique ID.
253 #[inline]
254 pub fn id(&self) -> SystemId {
255 self.id
256 }
257
258 /// Returns the system's name.
259 #[inline]
260 pub fn name(&self) -> &'static str {
261 self.inner.name()
262 }
263
264 /// Returns the system's component access pattern.
265 #[inline]
266 pub fn component_access(&self) -> Access {
267 self.inner.component_access()
268 }
269
270 /// Initializes the system.
271 #[inline]
272 pub fn initialize(&mut self, world: &mut World) {
273 self.inner.initialize(world);
274 }
275
276 /// Runs the system.
277 #[inline]
278 pub fn run(&mut self, world: &mut World) {
279 self.inner.run(world);
280 }
281
282 /// Checks if the system should run.
283 #[inline]
284 pub fn should_run(&self, world: &World) -> bool {
285 self.inner.should_run(world)
286 }
287
288 /// Returns true if this system only reads data.
289 #[inline]
290 pub fn is_read_only(&self) -> bool {
291 self.inner.is_read_only()
292 }
293
294 /// Checks if this system conflicts with another.
295 #[inline]
296 pub fn conflicts_with(&self, other: &BoxedSystem) -> bool {
297 self.component_access()
298 .conflicts_with(&other.component_access())
299 }
300}
301
302impl std::fmt::Debug for BoxedSystem {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("BoxedSystem")
305 .field("id", &self.id)
306 .field("name", &self.name())
307 .field("is_read_only", &self.is_read_only())
308 .finish()
309 }
310}
311
312// =============================================================================
313// IntoSystem Trait
314// =============================================================================
315
316/// A trait for types that can be converted into a [`System`].
317///
318/// This trait enables ergonomic system registration by allowing functions
319/// and other types to be automatically converted into boxed systems.
320///
321/// # Implemented For
322///
323/// - Any type implementing [`System`]
324/// - Functions with valid system parameters (future, Step 3.1.5)
325///
326/// # Example
327///
328/// ```
329/// use goud_engine::ecs::{World};
330/// use goud_engine::ecs::system::{System, BoxedSystem, IntoSystem};
331///
332/// struct MySystem;
333///
334/// impl System for MySystem {
335/// fn name(&self) -> &'static str { "MySystem" }
336/// fn run(&mut self, _world: &mut World) {}
337/// }
338///
339/// // Convert to BoxedSystem using IntoSystem
340/// let boxed: BoxedSystem = MySystem.into_system();
341/// assert_eq!(boxed.name(), "MySystem");
342/// ```
343pub trait IntoSystem<Marker = ()> {
344 /// The concrete system type this converts to.
345 type System: System + 'static;
346
347 /// Converts this into a system.
348 fn into_system(self) -> BoxedSystem;
349}
350
351// Blanket implementation for any System
352impl<S: System + 'static> IntoSystem for S {
353 type System = S;
354
355 #[inline]
356 fn into_system(self) -> BoxedSystem {
357 BoxedSystem::new(self)
358 }
359}