Skip to main content

concinnity_core/ecs/
system.rs

1// src/ecs/system.rs
2//
3// The runtime behavior trait every engine system implements, plus its per-step
4// control signal. Renderer-free: `System` names only `PipelineContext` (which is
5// core), so it lives here where the physics / audio subsystem crates can name it
6// without depending on the renderer. The client `ecs` module re-exports both
7// under the historical `crate::ecs::*` paths, and its `define_systems!` table
8// names each system's gate; a world holds the built systems as trait objects.
9
10use crate::ecs::{Access, PipelineContext};
11
12/// What a system asks the world to do after its step.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum StepResult {
15    /// Keep running.
16    Continue,
17    /// This system is finished -- remove it from the active set.
18    /// The world exits naturally when no systems remain.
19    Done,
20    /// Hard stop -- halt everything immediately.
21    Stop,
22}
23
24/// System -- has behavior, receives a PipelineContext each tick. Every system
25/// is internal engine code: `World::start` constructs it from world components
26/// (via the system's own `new(..)`), so a system is never loaded from or
27/// written to a blob. `init` runs once at `World::start`; `step` runs every
28/// tick.
29///
30/// A world holds its systems as `dyn System`, so the trait is object-safe.
31/// `Send` is what lets a built world move to the simulation thread, and `Any`
32/// is what lets a caller holding the world reach one system as its own type
33/// (the `cn debug` / `cn editor` hot-reload drive).
34pub trait System: core::any::Any + core::fmt::Debug + Send {
35    /// Run once at `World::start`, before the first step.
36    fn init(&mut self, _ctx: &mut PipelineContext) {}
37
38    /// Run once per tick.
39    fn step(&mut self, ctx: &mut PipelineContext) -> StepResult;
40
41    /// The data `step` may touch, consulted once when the schedule is built
42    /// (after `init`, so a data-dependent system can compute it from its
43    /// compiled state). The default claims everything: an undeclared system is
44    /// ordered against all others and never runs concurrently, which is always
45    /// safe. Declaring narrower access is what admits a system to shared waves
46    /// and to debug-build access validation.
47    fn access(&self) -> Access {
48        Access::new().exclusive()
49    }
50}