Skip to main content

pebble/ecs/
system.rs

1use std::cell::RefMut;
2use std::ops::{Deref, DerefMut};
3
4use crate::ecs::resources::Resources;
5
6/// Immutable borrow of a singleton resource `T`.
7///
8/// Obtained as a system parameter; derefs to `T`.
9pub struct Res<'a, T: hecs::Component> {
10    pub(crate) data: hecs::Ref<'a, T>,
11}
12
13impl<'a, T: hecs::Component> Deref for Res<'a, T> {
14    type Target = T;
15    fn deref(&self) -> &Self::Target {
16        &self.data
17    }
18}
19
20/// Mutable borrow of a singleton resource `T`.
21///
22/// Obtained as a system parameter; derefs to `T`.
23pub struct ResMut<'a, T: hecs::Component> {
24    data: hecs::RefMut<'a, T>,
25}
26
27impl<'a, T: hecs::Component> Deref for ResMut<'a, T> {
28    type Target = T;
29    fn deref(&self) -> &Self::Target {
30        &self.data
31    }
32}
33
34impl<'a, T: hecs::Component> DerefMut for ResMut<'a, T> {
35    fn deref_mut(&mut self) -> &mut Self::Target {
36        &mut self.data
37    }
38}
39
40/// Borrow of an ECS query result.
41///
42/// Obtained as a system parameter; derefs to [`hecs::QueryBorrow`].
43///
44/// # Iterating
45///
46/// `Query` implements `IntoIterator` for `&mut Query`, so you can iterate it
47/// directly without going through `Deref`:
48///
49/// ```ignore
50/// fn move_system(mut q: Query<(&mut Position, &Velocity)>) {
51///     for (entity, (pos, vel)) in &mut q {
52///         pos.x += vel.x;
53///         pos.y += vel.y;
54///     }
55/// }
56/// ```
57///
58/// # Single-entity lookups
59///
60/// Use [`Query::get`] to fetch components for one known `Entity` without
61/// scanning the whole result set, and [`Query::single`] /
62/// [`Query::get_single`] when you expect exactly one match (e.g. "the
63/// player", "the active camera").
64pub struct Query<'a, Q: hecs::Query> {
65    world: &'a hecs::World,
66    borrow: hecs::QueryBorrow<'a, Q>,
67}
68
69impl<'a, Q: hecs::Query> Deref for Query<'a, Q> {
70    type Target = hecs::QueryBorrow<'a, Q>;
71    fn deref(&self) -> &Self::Target {
72        &self.borrow
73    }
74}
75
76impl<'a, Q: hecs::Query> DerefMut for Query<'a, Q> {
77    fn deref_mut(&mut self) -> &mut Self::Target {
78        &mut self.borrow
79    }
80}
81
82impl<'q, Q: hecs::Query> IntoIterator for &'q mut Query<'_, Q> {
83    type Item = Q::Item<'q>;
84    type IntoIter = hecs::QueryIter<'q, Q>;
85
86    fn into_iter(self) -> Self::IntoIter {
87        (&mut self.borrow).into_iter()
88    }
89}
90
91impl<'a, Q: hecs::Query> Query<'a, Q> {
92    /// Look up a single entity's components for this query. Returns
93    /// `None` if the entity doesn't exist or doesn't match `Q`.
94    pub fn get(&self, entity: hecs::Entity) -> hecs::QueryOne<'_, Q> {
95        self.world.query_one::<Q>(entity)
96    }
97
98    /// Filter this query to only entities that ALSO have component `R`,
99    /// without `R` itself being part of the yielded items. Consumes
100    /// `self` — matches hecs's own `QueryBorrow::with` signature.
101    pub fn with<R: hecs::Query>(self) -> hecs::QueryBorrow<'a, hecs::With<Q, R>> {
102        self.borrow.with::<R>()
103    }
104
105    /// Filter this query to only entities that do NOT have component `R`.
106    /// Consumes `self`, same reasoning as `with`.
107    pub fn without<R: hecs::Query>(self) -> hecs::QueryBorrow<'a, hecs::Without<Q, R>> {
108        self.borrow.without::<R>()
109    }
110
111    /// Return the single entity's components for this query.
112    ///
113    /// Panics if there isn't exactly one match. Intended for singleton-style
114    /// queries (the player, the active camera, ...) where zero or multiple
115    /// matches indicate a bug. See [`Query::get_single`] for a
116    /// non-panicking version. Include `hecs::Entity` in `Q` if you need the
117    /// id alongside the components.
118    pub fn single(&mut self) -> Q::Item<'_> {
119        self.get_single()
120            .expect("Query::single: expected exactly one matching entity")
121    }
122
123    /// Like [`Query::single`], but returns `None` instead of panicking when
124    /// there isn't exactly one match.
125    pub fn get_single(&mut self) -> Option<Q::Item<'_>> {
126        let mut iter = self.borrow.iter();
127        let first = iter.next()?;
128        if iter.next().is_some() {
129            return None;
130        }
131        Some(first)
132    }
133}
134
135/// Deferred world-mutation commands available as a system parameter.
136///
137/// Mutations are buffered and applied to the world after all systems in the
138/// current stage have finished running.
139pub struct Commands<'a> {
140    buffer: RefMut<'a, hecs::CommandBuffer>,
141    resource_entity: hecs::Entity,
142}
143
144impl<'a> Commands<'a> {
145    /// Queue a resource insertion. Applied after the current stage finishes.
146    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
147        self.buffer.insert_one(self.resource_entity, res);
148    }
149
150    /// Queue a resource removal. Applied after the current stage finishes.
151    pub fn remove_resource<T: hecs::Component>(&mut self) {
152        self.buffer.remove_one::<T>(self.resource_entity);
153    }
154}
155
156impl<'a> Deref for Commands<'a> {
157    type Target = hecs::CommandBuffer;
158    fn deref(&self) -> &Self::Target {
159        &self.buffer
160    }
161}
162
163impl<'a> DerefMut for Commands<'a> {
164    fn deref_mut(&mut self) -> &mut Self::Target {
165        &mut self.buffer
166    }
167}
168
169/// Per-system persistent local state.
170///
171/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
172/// [`Resources`] — each system gets its own private `T`, initialized with
173/// [`Default::default`] the first time the system is registered, and
174/// preserved across every subsequent run of that system.
175///
176/// Useful for counters, caches, or any state a single system needs to
177/// remember without polluting the global resource set.
178pub struct Local<'a, T: Default + Send + Sync + 'static> {
179    data: &'a mut T,
180}
181
182impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
183    type Target = T;
184    fn deref(&self) -> &Self::Target {
185        self.data
186    }
187}
188
189impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
190    fn deref_mut(&mut self) -> &mut Self::Target {
191        self.data
192    }
193}
194
195/// Trait implemented for each valid system parameter type.
196///
197/// The macro-generated [`impl_system!`] blanket implementations use this to
198/// fetch each parameter from the world and resources before calling the system
199/// function. `State` is per-system storage owned by the [`FunctionSystem`]
200/// itself (as opposed to `Item`, which only lives for the duration of one
201/// call) — this is what lets [`Local`] persist between runs.
202pub trait SystemParam {
203    type Item<'a>;
204    type State: Default + 'static;
205    fn fetch<'a>(
206        state: &'a mut Self::State,
207        world: &'a hecs::World,
208        resources: &'a Resources,
209    ) -> Self::Item<'a>;
210}
211
212impl<T> SystemParam for Res<'static, T>
213where
214    T: 'static + Sync + Send,
215{
216    type Item<'a> = Res<'a, T>;
217    type State = ();
218
219    fn fetch<'a>(
220        _state: &'a mut Self::State,
221        world: &'a hecs::World,
222        resource: &'a Resources,
223    ) -> Self::Item<'a> {
224        Res {
225            data: resource.get_resource(world),
226        }
227    }
228}
229
230impl<T> SystemParam for Option<Res<'static, T>>
231where
232    T: 'static + Sync + Send,
233{
234    type Item<'a> = Option<Res<'a, T>>;
235    type State = ();
236
237    fn fetch<'a>(
238        _state: &'a mut Self::State,
239        world: &'a hecs::World,
240        resource: &'a Resources,
241    ) -> Self::Item<'a> {
242        if resource.has_resource::<T>(world) {
243            return Some(Res {
244                data: resource.get_resource(world),
245            });
246        }
247
248        None
249    }
250}
251
252impl<T> SystemParam for ResMut<'static, T>
253where
254    T: 'static + Sync + Send,
255{
256    type Item<'a> = ResMut<'a, T>;
257    type State = ();
258
259    fn fetch<'a>(
260        _state: &'a mut Self::State,
261        world: &'a hecs::World,
262        resource: &'a Resources,
263    ) -> Self::Item<'a> {
264        ResMut {
265            data: resource.get_resource_mut(world),
266        }
267    }
268}
269
270impl<T> SystemParam for Option<ResMut<'static, T>>
271where
272    T: 'static + Sync + Send,
273{
274    type Item<'a> = Option<ResMut<'a, T>>;
275    type State = ();
276
277    fn fetch<'a>(
278        _state: &'a mut Self::State,
279        world: &'a hecs::World,
280        resource: &'a Resources,
281    ) -> Self::Item<'a> {
282        if resource.has_resource::<T>(world) {
283            return Some(ResMut {
284                data: resource.get_resource_mut(world),
285            });
286        }
287
288        None
289    }
290}
291
292impl<Q> SystemParam for Query<'static, Q>
293where
294    Q: hecs::Query + 'static,
295{
296    type Item<'a> = Query<'a, Q>;
297    type State = ();
298
299    fn fetch<'a>(
300        _state: &'a mut Self::State,
301        world: &'a hecs::World,
302        _resources: &'a Resources,
303    ) -> Self::Item<'a> {
304        Query {
305            world: world,
306            borrow: world.query::<Q>(),
307        }
308    }
309}
310
311impl SystemParam for Commands<'static> {
312    type Item<'a> = Commands<'a>;
313    type State = ();
314
315    fn fetch<'a>(
316        _state: &'a mut Self::State,
317        _world: &'a hecs::World,
318        resources: &'a Resources,
319    ) -> Self::Item<'a> {
320        Commands {
321            buffer: resources.get_command_buffer(),
322            resource_entity: resources.resource_entity,
323        }
324    }
325}
326
327impl SystemParam for &'static hecs::World {
328    type Item<'a> = &'a hecs::World;
329    type State = ();
330
331    fn fetch<'a>(
332        _state: &'a mut Self::State,
333        world: &'a hecs::World,
334        _resources: &'a Resources,
335    ) -> Self::Item<'a> {
336        world
337    }
338}
339
340impl SystemParam for &'static Resources {
341    type Item<'a> = &'a Resources;
342    type State = ();
343
344    fn fetch<'a>(
345        _state: &'a mut Self::State,
346        _world: &'a hecs::World,
347        resources: &'a Resources,
348    ) -> Self::Item<'a> {
349        resources
350    }
351}
352
353impl<T> SystemParam for Local<'static, T>
354where
355    T: Default + Send + Sync + 'static,
356{
357    type Item<'a> = Local<'a, T>;
358    type State = T;
359
360    fn fetch<'a>(
361        state: &'a mut Self::State,
362        _world: &'a hecs::World,
363        _resources: &'a Resources,
364    ) -> Self::Item<'a> {
365        Local { data: state }
366    }
367}
368
369/// A type-erased, executable system.
370pub trait System: 'static {
371    fn run(&mut self, world: &hecs::World, resources: &Resources);
372}
373
374/// Type-erased wrapper around a system function, created by [`IntoSystem`].
375///
376/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
377/// is where [`Local`] values actually live between calls to `run`.
378pub struct FunctionSystem<F, Marker, State = ()> {
379    pub func: F,
380    state: State,
381    _marker: std::marker::PhantomData<Marker>,
382}
383
384/// Converts a function (or closure) with valid system parameters into a
385/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
386///
387/// Implemented via the [`impl_system!`] macro for function arities 0–8.
388pub trait IntoSystem<Marker> {
389    type System: System;
390
391    fn into_system(self) -> Self::System;
392}
393
394macro_rules! impl_system {
395    ($($param:ident),*) => {
396        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
397        where
398            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
399            for<'a> &'a mut T: FnMut($($param),*),
400            $($param: SystemParam + 'static),*
401        {
402            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
403
404            fn into_system(self) -> Self::System {
405                FunctionSystem {
406                    func: self,
407                    state: Default::default(),
408                    _marker: std::marker::PhantomData,
409                }
410            }
411        }
412
413        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
414        where
415            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
416            $($param: SystemParam + 'static),*
417        {
418            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
419                #[allow(non_snake_case)]
420                let ($($param,)*) = &mut self.state;
421                (self.func)($($param::fetch($param, _world, _resources)),*);
422            }
423        }
424    };
425}
426
427impl_system!();
428impl_system!(A);
429impl_system!(A, B);
430impl_system!(A, B, C);
431impl_system!(A, B, C, D);
432impl_system!(A, B, C, D, E);
433impl_system!(A, B, C, D, E, F);
434impl_system!(A, B, C, D, E, F, G);
435impl_system!(A, B, C, D, E, F, G, H);