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    /// Fetch components for a single known entity, without iterating the
93    /// rest of the query.
94    ///
95    /// Returns `None` if the entity doesn't exist or doesn't match `Q`.
96    /// The result is handed to `f` rather than returned directly, since the
97    /// borrow can only live as long as the lookup itself.
98    ///
99    /// Include `hecs::Entity` in `Q` if you need the id back out, e.g.
100    /// `Query<(hecs::Entity, &Health)>`.
101    ///
102    /// ```ignore
103    /// let hp = q.get(player, |health| health.current);
104    /// ```
105    pub fn get<T>(
106        &self,
107        entity: hecs::Entity,
108        f: impl for<'r> FnOnce(Q::Item<'r>) -> T,
109    ) -> Option<T> {
110        self.world.query_one::<Q>(entity).get().ok().map(f)
111    }
112
113    /// Return the single entity's components for this query.
114    ///
115    /// Panics if there isn't exactly one match. Intended for singleton-style
116    /// queries (the player, the active camera, ...) where zero or multiple
117    /// matches indicate a bug. See [`Query::get_single`] for a
118    /// non-panicking version. Include `hecs::Entity` in `Q` if you need the
119    /// id alongside the components.
120    pub fn single(&mut self) -> Q::Item<'_> {
121        self.get_single()
122            .expect("Query::single: expected exactly one matching entity")
123    }
124
125    /// Like [`Query::single`], but returns `None` instead of panicking when
126    /// there isn't exactly one match.
127    pub fn get_single(&mut self) -> Option<Q::Item<'_>> {
128        let mut iter = self.borrow.iter();
129        let first = iter.next()?;
130        if iter.next().is_some() {
131            return None;
132        }
133        Some(first)
134    }
135}
136
137/// Deferred world-mutation commands available as a system parameter.
138///
139/// Mutations are buffered and applied to the world after all systems in the
140/// current stage have finished running.
141pub struct Commands<'a> {
142    buffer: RefMut<'a, hecs::CommandBuffer>,
143    resource_entity: hecs::Entity,
144}
145
146impl<'a> Commands<'a> {
147    /// Queue a resource insertion. Applied after the current stage finishes.
148    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
149        self.buffer.insert_one(self.resource_entity, res);
150    }
151
152    /// Queue a resource removal. Applied after the current stage finishes.
153    pub fn remove_resource<T: hecs::Component>(&mut self) {
154        self.buffer.remove_one::<T>(self.resource_entity);
155    }
156}
157
158impl<'a> Deref for Commands<'a> {
159    type Target = hecs::CommandBuffer;
160    fn deref(&self) -> &Self::Target {
161        &self.buffer
162    }
163}
164
165impl<'a> DerefMut for Commands<'a> {
166    fn deref_mut(&mut self) -> &mut Self::Target {
167        &mut self.buffer
168    }
169}
170
171/// Per-system persistent local state.
172///
173/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
174/// [`Resources`] — each system gets its own private `T`, initialized with
175/// [`Default::default`] the first time the system is registered, and
176/// preserved across every subsequent run of that system.
177///
178/// Useful for counters, caches, or any state a single system needs to
179/// remember without polluting the global resource set.
180pub struct Local<'a, T: Default + Send + Sync + 'static> {
181    data: &'a mut T,
182}
183
184impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
185    type Target = T;
186    fn deref(&self) -> &Self::Target {
187        self.data
188    }
189}
190
191impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
192    fn deref_mut(&mut self) -> &mut Self::Target {
193        self.data
194    }
195}
196
197/// Trait implemented for each valid system parameter type.
198///
199/// The macro-generated [`impl_system!`] blanket implementations use this to
200/// fetch each parameter from the world and resources before calling the system
201/// function. `State` is per-system storage owned by the [`FunctionSystem`]
202/// itself (as opposed to `Item`, which only lives for the duration of one
203/// call) — this is what lets [`Local`] persist between runs.
204pub trait SystemParam {
205    type Item<'a>;
206    type State: Default + 'static;
207    fn fetch<'a>(
208        state: &'a mut Self::State,
209        world: &'a hecs::World,
210        resources: &'a Resources,
211    ) -> Self::Item<'a>;
212}
213
214impl<T> SystemParam for Res<'static, T>
215where
216    T: 'static + Sync + Send,
217{
218    type Item<'a> = Res<'a, T>;
219    type State = ();
220
221    fn fetch<'a>(
222        _state: &'a mut Self::State,
223        world: &'a hecs::World,
224        resource: &'a Resources,
225    ) -> Self::Item<'a> {
226        Res {
227            data: resource.get_resource(world),
228        }
229    }
230}
231
232impl<T> SystemParam for Option<Res<'static, T>>
233where
234    T: 'static + Sync + Send,
235{
236    type Item<'a> = Option<Res<'a, T>>;
237    type State = ();
238
239    fn fetch<'a>(
240        _state: &'a mut Self::State,
241        world: &'a hecs::World,
242        resource: &'a Resources,
243    ) -> Self::Item<'a> {
244        if resource.has_resource::<T>(world) {
245            return Some(Res {
246                data: resource.get_resource(world),
247            });
248        }
249
250        None
251    }
252}
253
254impl<T> SystemParam for ResMut<'static, T>
255where
256    T: 'static + Sync + Send,
257{
258    type Item<'a> = ResMut<'a, T>;
259    type State = ();
260
261    fn fetch<'a>(
262        _state: &'a mut Self::State,
263        world: &'a hecs::World,
264        resource: &'a Resources,
265    ) -> Self::Item<'a> {
266        ResMut {
267            data: resource.get_resource_mut(world),
268        }
269    }
270}
271
272impl<T> SystemParam for Option<ResMut<'static, T>>
273where
274    T: 'static + Sync + Send,
275{
276    type Item<'a> = Option<ResMut<'a, T>>;
277    type State = ();
278
279    fn fetch<'a>(
280        _state: &'a mut Self::State,
281        world: &'a hecs::World,
282        resource: &'a Resources,
283    ) -> Self::Item<'a> {
284        if resource.has_resource::<T>(world) {
285            return Some(ResMut {
286                data: resource.get_resource_mut(world),
287            });
288        }
289
290        None
291    }
292}
293
294impl<Q> SystemParam for Query<'static, Q>
295where
296    Q: hecs::Query + 'static,
297{
298    type Item<'a> = Query<'a, Q>;
299    type State = ();
300
301    fn fetch<'a>(
302        _state: &'a mut Self::State,
303        world: &'a hecs::World,
304        _resources: &'a Resources,
305    ) -> Self::Item<'a> {
306        Query {
307            world: world,
308            borrow: world.query::<Q>(),
309        }
310    }
311}
312
313impl SystemParam for Commands<'static> {
314    type Item<'a> = Commands<'a>;
315    type State = ();
316
317    fn fetch<'a>(
318        _state: &'a mut Self::State,
319        _world: &'a hecs::World,
320        resources: &'a Resources,
321    ) -> Self::Item<'a> {
322        Commands {
323            buffer: resources.get_command_buffer(),
324            resource_entity: resources.resource_entity,
325        }
326    }
327}
328
329impl SystemParam for &'static hecs::World {
330    type Item<'a> = &'a hecs::World;
331    type State = ();
332
333    fn fetch<'a>(
334        _state: &'a mut Self::State,
335        world: &'a hecs::World,
336        _resources: &'a Resources,
337    ) -> Self::Item<'a> {
338        world
339    }
340}
341
342impl SystemParam for &'static Resources {
343    type Item<'a> = &'a Resources;
344    type State = ();
345
346    fn fetch<'a>(
347        _state: &'a mut Self::State,
348        _world: &'a hecs::World,
349        resources: &'a Resources,
350    ) -> Self::Item<'a> {
351        resources
352    }
353}
354
355impl<T> SystemParam for Local<'static, T>
356where
357    T: Default + Send + Sync + 'static,
358{
359    type Item<'a> = Local<'a, T>;
360    type State = T;
361
362    fn fetch<'a>(
363        state: &'a mut Self::State,
364        _world: &'a hecs::World,
365        _resources: &'a Resources,
366    ) -> Self::Item<'a> {
367        Local { data: state }
368    }
369}
370
371/// A type-erased, executable system.
372pub trait System: 'static {
373    fn run(&mut self, world: &hecs::World, resources: &Resources);
374}
375
376/// Type-erased wrapper around a system function, created by [`IntoSystem`].
377///
378/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
379/// is where [`Local`] values actually live between calls to `run`.
380pub struct FunctionSystem<F, Marker, State = ()> {
381    pub func: F,
382    state: State,
383    _marker: std::marker::PhantomData<Marker>,
384}
385
386/// Converts a function (or closure) with valid system parameters into a
387/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
388///
389/// Implemented via the [`impl_system!`] macro for function arities 0–8.
390pub trait IntoSystem<Marker> {
391    type System: System;
392
393    fn into_system(self) -> Self::System;
394}
395
396macro_rules! impl_system {
397    ($($param:ident),*) => {
398        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
399        where
400            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
401            for<'a> &'a mut T: FnMut($($param),*),
402            $($param: SystemParam + 'static),*
403        {
404            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
405
406            fn into_system(self) -> Self::System {
407                FunctionSystem {
408                    func: self,
409                    state: Default::default(),
410                    _marker: std::marker::PhantomData,
411                }
412            }
413        }
414
415        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
416        where
417            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
418            $($param: SystemParam + 'static),*
419        {
420            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
421                #[allow(non_snake_case)]
422                let ($($param,)*) = &mut self.state;
423                (self.func)($($param::fetch($param, _world, _resources)),*);
424            }
425        }
426    };
427}
428
429impl_system!();
430impl_system!(A);
431impl_system!(A, B);
432impl_system!(A, B, C);
433impl_system!(A, B, C, D);
434impl_system!(A, B, C, D, E);
435impl_system!(A, B, C, D, E, F);
436impl_system!(A, B, C, D, E, F, G);
437impl_system!(A, B, C, D, E, F, G, H);