Skip to main content

pebble/ecs/
system_param.rs

1use std::any::TypeId;
2
3use crate::ecs::resources::Resources;
4
5/// Anything that can be fetched as a system function parameter —
6/// implemented for [`Read`](crate::ecs::resources::Read)/[`Write`](crate::ecs::resources::Write),
7/// [`Query`](crate::ecs::query::Query), [`Local`](crate::ecs::local::Local),
8/// [`Commands`](crate::ecs::commands::Commands), tuples of `SystemParam`s
9/// (so a function can take several), and a few others. You generally don't
10/// implement this yourself unless you're adding a new kind of parameter.
11pub trait SystemParam {
12    /// The value actually handed to the system function.
13    type Item<'a>;
14    /// Per-system persistent state (e.g. [`Local`](crate::ecs::local::Local)'s
15    /// stored value) — `()` for anything stateless.
16    type State: Default + 'static;
17
18    fn fetch<'a>(
19        world: &'a hecs::World,
20        resources: &'a Resources,
21        state: &'a mut Self::State,
22    ) -> Self::Item<'a>;
23}
24
25impl<'a> SystemParam for &'a hecs::World {
26    type Item<'w> = &'w hecs::World;
27    type State = ();
28
29    fn fetch<'w>(
30        world: &'w hecs::World,
31        _resources: &'w Resources,
32        _state: &'w mut Self::State,
33    ) -> Self::Item<'w> {
34        world
35    }
36}
37
38impl<'a> SystemParam for &'a Resources {
39    type Item<'w> = &'w Resources;
40    type State = ();
41
42    fn fetch<'w>(
43        _world: &'w hecs::World,
44        resources: &'w Resources,
45        _state: &'w mut Self::State,
46    ) -> Self::Item<'w> {
47        resources
48    }
49}
50
51/// A runnable system — the type-erased form `IntoSystem` produces, so
52/// different systems (different parameter lists) can live in the same
53/// `Vec<Box<dyn System>>`.
54pub trait System: 'static {
55    fn run(&mut self, world: &hecs::World, resources: &Resources);
56}
57
58/// Wraps a plain function into a [`System`], holding its per-call
59/// [`SystemParam::State`] between runs.
60pub struct FunctionSystem<F, Marker, State = ()> {
61    pub func: F,
62    state: State,
63    _marker: std::marker::PhantomData<Marker>,
64}
65
66/// Implemented for any function whose parameters are all [`SystemParam`]s —
67/// this is what lets a plain `fn my_system(time: Read<Time>)` be passed
68/// directly to `add_system`.
69pub trait IntoSystem<Marker> {
70    type System: System;
71
72    fn into_system(self) -> Self::System;
73}
74
75/// A relative-ordering rule attached to a system via
76/// [`IntoSystemConfig::after`]/[`IntoSystemConfig::before`], keyed by the
77/// other system's own type — no runtime label needed, since a distinct
78/// function or closure is already a distinct type.
79enum OrderConstraint {
80    After(TypeId),
81    Before(TypeId),
82}
83
84/// A system bundled with `.after(...)`/`.before(...)` ordering constraints,
85/// produced by [`IntoSystemConfig::after`]/[`IntoSystemConfig::before`] and
86/// consumed by [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system).
87pub struct SystemConfig<S, Params> {
88    system: S,
89    constraints: Vec<OrderConstraint>,
90    _marker: std::marker::PhantomData<fn() -> Params>,
91}
92
93impl<S, Params> SystemConfig<S, Params>
94where
95    S: IntoSystem<Params> + 'static,
96{
97    /// Adds a constraint that this system must run after `other`. `other`
98    /// need not be added to the schedule yet — only its type is used, to
99    /// look it up when the schedule's order is next computed.
100    pub fn after<S2, P2>(mut self, _other: S2) -> Self
101    where
102        S2: IntoSystem<P2> + 'static,
103    {
104        self.constraints.push(OrderConstraint::After(TypeId::of::<S2>()));
105        self
106    }
107
108    /// Adds a constraint that this system must run before `other`. `other`
109    /// need not be added to the schedule yet — only its type is used, to
110    /// look it up when the schedule's order is next computed.
111    pub fn before<S2, P2>(mut self, _other: S2) -> Self
112    where
113        S2: IntoSystem<P2> + 'static,
114    {
115        self.constraints.push(OrderConstraint::Before(TypeId::of::<S2>()));
116        self
117    }
118
119    /// Unpacks this config into what [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system)
120    /// actually stores: the system's identity, its boxed runnable, and any
121    /// ordering constraints to register against that identity.
122    #[doc(hidden)]
123    pub fn into_parts(self) -> (TypeId, Box<dyn System>, Vec<(TypeId, TypeId)>) {
124        let id = TypeId::of::<S>();
125        // `(dependent, dependency)` — dependent must run after dependency.
126        let constraints = self
127            .constraints
128            .into_iter()
129            .map(|constraint| match constraint {
130                OrderConstraint::After(dependency) => (id, dependency),
131                OrderConstraint::Before(dependent) => (dependent, id),
132            })
133            .collect();
134        (id, Box::new(self.system.into_system()), constraints)
135    }
136}
137
138/// A bare system is trivially "configured" with no ordering constraints —
139/// this is what lets [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system)
140/// accept either a plain system or one built via `.after(...)`/`.before(...)`.
141impl<S, Params> From<S> for SystemConfig<S, Params>
142where
143    S: IntoSystem<Params> + 'static,
144{
145    fn from(system: S) -> Self {
146        SystemConfig {
147            system,
148            constraints: Vec::new(),
149            _marker: std::marker::PhantomData,
150        }
151    }
152}
153
154/// Lets `.after(...)`/`.before(...)` be called directly on a system —
155/// a plain function, closure, or anything else [`IntoSystem`] is
156/// implemented for — to declare where it must run relative to another
157/// system in the same [`Schedule`](crate::ecs::schedule::Schedule):
158///
159/// ```ignore
160/// schedule
161///     .add_system(spawn_enemies)
162///     .add_system(move_enemies.after(spawn_enemies))
163///     .add_system(render.after(move_enemies));
164/// ```
165pub trait IntoSystemConfig<Params>: IntoSystem<Params> + Sized {
166    /// Wraps this system with a constraint that it must run after `other`.
167    fn after<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
168    where
169        S2: IntoSystem<P2> + 'static;
170
171    /// Wraps this system with a constraint that it must run before `other`.
172    fn before<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
173    where
174        S2: IntoSystem<P2> + 'static;
175}
176
177impl<T, Params> IntoSystemConfig<Params> for T
178where
179    T: IntoSystem<Params> + 'static,
180{
181    fn after<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
182    where
183        S2: IntoSystem<P2> + 'static,
184    {
185        SystemConfig::from(self).after(other)
186    }
187
188    fn before<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
189    where
190        S2: IntoSystem<P2> + 'static,
191    {
192        SystemConfig::from(self).before(other)
193    }
194}
195
196macro_rules! impl_system {
197    ($($param:ident),*) => {
198        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
199        where
200            T: FnMut($($param),*) + for<'a> FnMut($($param::Item<'a>),*) + 'static,
201            $($param: SystemParam + 'static),*
202        {
203            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
204            fn into_system(self) -> Self::System {
205                FunctionSystem {
206                    func: self,
207                    state: Default::default(),
208                    _marker: std::marker::PhantomData,
209                }
210            }
211        }
212        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
213        where
214            T: FnMut($($param),*) + for<'a> FnMut($($param::Item<'a>),*) + 'static,
215            $($param: SystemParam + 'static),*
216        {
217            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
218                #[allow(non_snake_case)]
219                let ($($param,)*) = &mut self.state;
220                (self.func)($($param::fetch(_world, _resources, $param)),*);
221            }
222        }
223    };
224}
225
226impl_system!();
227impl_system!(A);
228impl_system!(A, B);
229impl_system!(A, B, C);
230impl_system!(A, B, C, D);
231impl_system!(A, B, C, D, E);
232impl_system!(A, B, C, D, E, F);
233impl_system!(A, B, C, D, E, F, G);
234impl_system!(A, B, C, D, E, F, G, H);
235impl_system!(A, B, C, D, E, F, G, H, I);
236impl_system!(A, B, C, D, E, F, G, H, I, J);
237impl_system!(A, B, C, D, E, F, G, H, I, J, K);
238impl_system!(A, B, C, D, E, F, G, H, I, J, K, L);