Skip to main content

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}