Skip to main content

concinnity_core/ecs/
built_system.rs

1// src/ecs/built_system.rs
2//
3// What a world holds once a table gate has run: the constructed system behind a
4// `dyn System` pointer, paired with the name of the table entry that built it.
5// A trait object carries no name of its own, and the name is what the profile,
6// the log, and the schedule's ordering edges all key on.
7
8use alloc::boxed::Box;
9
10use crate::ecs::{Access, PipelineContext, StepResult, System};
11
12/// A constructed system and the table entry name it was built from.
13#[derive(Debug)]
14pub struct BuiltSystem {
15    name: &'static str,
16    system: Box<dyn System>,
17}
18
19impl BuiltSystem {
20    // Pair a gate's system with its table entry's name.
21    pub(crate) fn new(name: &'static str, system: Box<dyn System>) -> Self {
22        Self { name, system }
23    }
24
25    /// Stable display name used for profiling and logging: the system's entry
26    /// name in the table, which is also what its ordering edges name.
27    pub fn name(&self) -> &'static str {
28        self.name
29    }
30
31    /// Run the system's `init`.
32    pub fn init(&mut self, ctx: &mut PipelineContext) {
33        self.system.init(ctx);
34    }
35
36    /// Run the system's `step`.
37    pub fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
38        self.system.step(ctx)
39    }
40
41    /// The system's declared data access, consulted at schedule build (after
42    /// init). Defaults to exclusive via the `System` trait.
43    pub fn access(&self) -> Access {
44        self.system.access()
45    }
46
47    /// Borrow the system as `S`, or `None` when it is a different system.
48    pub fn downcast_ref<S: System>(&self) -> Option<&S> {
49        (&*self.system as &dyn core::any::Any).downcast_ref::<S>()
50    }
51
52    /// Mutably borrow the system as `S`, or `None` when it is a different
53    /// system. The `DebugHook::tick` drive reaches the GraphicsSystem's
54    /// hot-reload bookkeeping and the AnimationSystem's clip table through
55    /// this, from outside the per-system step.
56    pub fn downcast_mut<S: System>(&mut self) -> Option<&mut S> {
57        (&mut *self.system as &mut dyn core::any::Any).downcast_mut::<S>()
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[derive(Debug)]
66    struct Tick(u32);
67
68    impl System for Tick {
69        fn step(&mut self, _ctx: &mut PipelineContext) -> StepResult {
70            self.0 += 1;
71            StepResult::Continue
72        }
73    }
74
75    #[derive(Debug)]
76    struct Other;
77
78    impl System for Other {
79        fn step(&mut self, _ctx: &mut PipelineContext) -> StepResult {
80            StepResult::Continue
81        }
82    }
83
84    // The pair carries the table name a trait object cannot.
85    #[test]
86    fn keeps_the_table_name() {
87        let built = BuiltSystem::new("Tick", Box::new(Tick(0)));
88        assert_eq!(built.name(), "Tick");
89    }
90
91    // Stepping runs the boxed system's own body.
92    #[test]
93    fn steps_the_boxed_system() {
94        let mut world = crate::ecs::World::new();
95        let mut ctx = world.context();
96        let mut built = BuiltSystem::new("Tick", Box::new(Tick(0)));
97        assert_eq!(built.step(&mut ctx), StepResult::Continue);
98        assert_eq!(built.step(&mut ctx), StepResult::Continue);
99        assert_eq!(built.downcast_ref::<Tick>().expect("a Tick").0, 2);
100    }
101
102    // A downcast to the wrong system yields nothing rather than the wrong body.
103    #[test]
104    fn downcast_answers_only_for_its_own_type() {
105        let mut built = BuiltSystem::new("Tick", Box::new(Tick(7)));
106        assert!(built.downcast_ref::<Other>().is_none());
107        assert!(built.downcast_mut::<Other>().is_none());
108        built.downcast_mut::<Tick>().expect("a Tick").0 = 9;
109        assert_eq!(built.downcast_ref::<Tick>().expect("a Tick").0, 9);
110    }
111}