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/// and a `.priority(...)`, produced by [`IntoSystemConfig`] and consumed by
86/// [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system).
87pub struct SystemConfig<S, Params> {
88    system: S,
89    priority: i32,
90    constraints: Vec<OrderConstraint>,
91    _marker: std::marker::PhantomData<fn() -> Params>,
92}
93
94impl<S, Params> SystemConfig<S, Params>
95where
96    S: IntoSystem<Params> + 'static,
97{
98    /// Adds a constraint that this system must run after `other`. `other`
99    /// need not be added to the schedule yet — only its type is used, to
100    /// look it up when the schedule's order is next computed.
101    pub fn after<S2, P2>(mut self, _other: S2) -> Self
102    where
103        S2: IntoSystem<P2> + 'static,
104    {
105        self.constraints.push(OrderConstraint::After(TypeId::of::<S2>()));
106        self
107    }
108
109    /// Adds a constraint that this system must run before `other`. `other`
110    /// need not be added to the schedule yet — only its type is used, to
111    /// look it up when the schedule's order is next computed.
112    pub fn before<S2, P2>(mut self, _other: S2) -> Self
113    where
114        S2: IntoSystem<P2> + 'static,
115    {
116        self.constraints.push(OrderConstraint::Before(TypeId::of::<S2>()));
117        self
118    }
119
120    /// Sets this system's priority, used to break ties between systems that
121    /// have no `after`/`before` relationship to each other — higher runs
122    /// first. Defaults to 0. An explicit `after`/`before` constraint always
123    /// takes precedence over priority: priority only decides ordering
124    /// between systems the schedule would otherwise be free to run in any
125    /// order.
126    pub fn priority(mut self, priority: i32) -> Self {
127        self.priority = priority;
128        self
129    }
130
131    /// Unpacks this config into what [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system)
132    /// actually stores: the system's identity, its boxed runnable, its
133    /// priority, and any ordering constraints to register against that
134    /// identity.
135    #[doc(hidden)]
136    pub fn into_parts(self) -> (TypeId, Box<dyn System>, i32, Vec<(TypeId, TypeId)>) {
137        let id = TypeId::of::<S>();
138        // `(dependent, dependency)` — dependent must run after dependency.
139        let constraints = self
140            .constraints
141            .into_iter()
142            .map(|constraint| match constraint {
143                OrderConstraint::After(dependency) => (id, dependency),
144                OrderConstraint::Before(dependent) => (dependent, id),
145            })
146            .collect();
147        (id, Box::new(self.system.into_system()), self.priority, constraints)
148    }
149}
150
151/// A bare system is trivially "configured" with no ordering constraints and
152/// priority 0 — this is what lets [`Schedule::add_system`](crate::ecs::schedule::Schedule::add_system)
153/// accept either a plain system or one built via `.after(...)`/`.before(...)`/`.priority(...)`.
154impl<S, Params> From<S> for SystemConfig<S, Params>
155where
156    S: IntoSystem<Params> + 'static,
157{
158    fn from(system: S) -> Self {
159        SystemConfig {
160            system,
161            priority: 0,
162            constraints: Vec::new(),
163            _marker: std::marker::PhantomData,
164        }
165    }
166}
167
168/// Lets `.after(...)`/`.before(...)`/`.priority(...)` be called directly on
169/// a system — a plain function, closure, or anything else [`IntoSystem`] is
170/// implemented for — to declare where it must run relative to another
171/// system in the same [`Schedule`](crate::ecs::schedule::Schedule), or how
172/// it should be prioritized against unconstrained systems:
173///
174/// ```ignore
175/// schedule
176///     .add_system(spawn_enemies)
177///     .add_system(move_enemies.after(spawn_enemies))
178///     .add_system(render.after(move_enemies))
179///     .add_system(hud.priority(10)); // runs before other unconstrained systems
180/// ```
181pub trait IntoSystemConfig<Params>: IntoSystem<Params> + Sized {
182    /// Wraps this system with a constraint that it must run after `other`.
183    fn after<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
184    where
185        S2: IntoSystem<P2> + 'static;
186
187    /// Wraps this system with a constraint that it must run before `other`.
188    fn before<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
189    where
190        S2: IntoSystem<P2> + 'static;
191
192    /// Wraps this system with a priority — see [`SystemConfig::priority`].
193    fn priority(self, priority: i32) -> SystemConfig<Self, Params>;
194}
195
196impl<T, Params> IntoSystemConfig<Params> for T
197where
198    T: IntoSystem<Params> + 'static,
199{
200    fn after<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
201    where
202        S2: IntoSystem<P2> + 'static,
203    {
204        SystemConfig::from(self).after(other)
205    }
206
207    fn before<S2, P2>(self, other: S2) -> SystemConfig<Self, Params>
208    where
209        S2: IntoSystem<P2> + 'static,
210    {
211        SystemConfig::from(self).before(other)
212    }
213
214    fn priority(self, priority: i32) -> SystemConfig<Self, Params> {
215        SystemConfig::from(self).priority(priority)
216    }
217}
218
219/// A sequence of systems built with [`Chain::chain`] — e.g.
220/// `(spawn_enemies, move_enemies, render).chain()` — that forces each
221/// system to run strictly after the one before it in the tuple, on top of
222/// whatever `.after(...)`/`.before(...)`/`.priority(...)` is applied to the
223/// chain as a whole. Register it with
224/// [`Schedule::add_systems`](crate::ecs::schedule::Schedule::add_systems)
225/// (not `add_system` — a chain is more than one system).
226///
227/// `.after`/`.before` on a chain only need to constrain its first/last
228/// system respectively — every other member already transitively depends on
229/// that one through the chain's own internal ordering. `.priority` instead
230/// applies to every member, since each one competes for its own slot in the
231/// schedule as it individually becomes eligible to run, not just the first.
232pub struct SystemChain {
233    systems: Vec<(TypeId, Box<dyn System>, i32)>,
234    constraints: Vec<(TypeId, TypeId)>,
235}
236
237impl SystemChain {
238    fn first_id(&self) -> TypeId {
239        self.systems[0].0
240    }
241
242    fn last_id(&self) -> TypeId {
243        self.systems[self.systems.len() - 1].0
244    }
245
246    /// Constrains the whole chain to run after `other` — see
247    /// [`SystemConfig::after`].
248    pub fn after<S2, P2>(mut self, _other: S2) -> Self
249    where
250        S2: IntoSystem<P2> + 'static,
251    {
252        let first = self.first_id();
253        self.constraints.push((first, TypeId::of::<S2>()));
254        self
255    }
256
257    /// Constrains the whole chain to run before `other` — see
258    /// [`SystemConfig::before`].
259    pub fn before<S2, P2>(mut self, _other: S2) -> Self
260    where
261        S2: IntoSystem<P2> + 'static,
262    {
263        let last = self.last_id();
264        self.constraints.push((TypeId::of::<S2>(), last));
265        self
266    }
267
268    /// Sets every system in the chain to this priority — see
269    /// [`SystemConfig::priority`].
270    pub fn priority(mut self, priority: i32) -> Self {
271        for (_, _, p) in &mut self.systems {
272            *p = priority;
273        }
274        self
275    }
276
277    /// Unpacks this chain into what
278    /// [`Schedule::add_systems`](crate::ecs::schedule::Schedule::add_systems)
279    /// actually stores: each system's identity, boxed runnable, and
280    /// priority, plus every ordering constraint (the chain's own internal
281    /// links and any external `.after`/`.before`).
282    #[doc(hidden)]
283    pub fn into_parts(self) -> (Vec<(TypeId, Box<dyn System>, i32)>, Vec<(TypeId, TypeId)>) {
284        (self.systems, self.constraints)
285    }
286}
287
288/// Lets `.chain()` be called on a tuple of 2 or more systems to force them
289/// to run in that exact relative order within a
290/// [`Schedule`](crate::ecs::schedule::Schedule), regardless of the order
291/// they (or other unrelated systems) are added in:
292///
293/// ```ignore
294/// schedule.add_systems(
295///     (spawn_enemies, move_enemies, render)
296///         .chain()
297///         .after(setup)
298///         .priority(10),
299/// );
300/// ```
301pub trait Chain<Marker> {
302    fn chain(self) -> SystemChain;
303}
304
305macro_rules! impl_chain {
306    ($($S:ident : $P:ident),+) => {
307        impl<$($S, $P),+> Chain<($($P,)+)> for ($($S,)+)
308        where
309            $($S: IntoSystem<$P> + 'static,)+
310        {
311            #[allow(non_snake_case)]
312            fn chain(self) -> SystemChain {
313                let ($($S,)+) = self;
314                let systems: Vec<(TypeId, Box<dyn System>, i32)> = vec![
315                    $((TypeId::of::<$S>(), Box::new($S.into_system()) as Box<dyn System>, 0),)+
316                ];
317                // consecutive pairs: the later system must run after the earlier one.
318                let constraints = systems.windows(2).map(|w| (w[1].0, w[0].0)).collect();
319                SystemChain { systems, constraints }
320            }
321        }
322    };
323}
324
325impl_chain!(A: PA, B: PB);
326impl_chain!(A: PA, B: PB, C: PC);
327impl_chain!(A: PA, B: PB, C: PC, D: PD);
328impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE);
329impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF);
330impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG);
331impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG, H: PH);
332impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG, H: PH, I: PI);
333impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG, H: PH, I: PI, J: PJ);
334impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG, H: PH, I: PI, J: PJ, K: PK);
335impl_chain!(A: PA, B: PB, C: PC, D: PD, E: PE, F: PF, G: PG, H: PH, I: PI, J: PJ, K: PK, L: PL);
336
337macro_rules! impl_system {
338    ($($param:ident),*) => {
339        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
340        where
341            T: FnMut($($param),*) + for<'a> FnMut($($param::Item<'a>),*) + 'static,
342            $($param: SystemParam + 'static),*
343        {
344            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
345            fn into_system(self) -> Self::System {
346                FunctionSystem {
347                    func: self,
348                    state: Default::default(),
349                    _marker: std::marker::PhantomData,
350                }
351            }
352        }
353        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
354        where
355            T: FnMut($($param),*) + for<'a> FnMut($($param::Item<'a>),*) + 'static,
356            $($param: SystemParam + 'static),*
357        {
358            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
359                #[allow(non_snake_case)]
360                let ($($param,)*) = &mut self.state;
361                (self.func)($($param::fetch(_world, _resources, $param)),*);
362            }
363        }
364    };
365}
366
367impl_system!();
368impl_system!(A);
369impl_system!(A, B);
370impl_system!(A, B, C);
371impl_system!(A, B, C, D);
372impl_system!(A, B, C, D, E);
373impl_system!(A, B, C, D, E, F);
374impl_system!(A, B, C, D, E, F, G);
375impl_system!(A, B, C, D, E, F, G, H);
376impl_system!(A, B, C, D, E, F, G, H, I);
377impl_system!(A, B, C, D, E, F, G, H, I, J);
378impl_system!(A, B, C, D, E, F, G, H, I, J, K);
379impl_system!(A, B, C, D, E, F, G, H, I, J, K, L);