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.
139///
140/// Resource insertions immediately bump the [`Resources`] generation counter so
141/// that the convergence loop in [`App`](crate::app::App) can detect them without
142/// needing to inspect the world after every flush.
143pub struct Commands<'a> {
144    buffer: RefMut<'a, hecs::CommandBuffer>,
145    resource_entity: hecs::Entity,
146    /// Held so `insert_resource` can bump the generation counter at queue time.
147    resources: &'a Resources,
148}
149
150impl<'a> Commands<'a> {
151    /// Queue a resource insertion. Applied after the current stage finishes.
152    ///
153    /// Bumps the [`Resources`] generation counter immediately so the
154    /// convergence loop knows another pass is needed even before the command
155    /// buffer is flushed.
156    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
157        self.buffer.insert_one(self.resource_entity, res);
158        self.resources.bump_generation();
159    }
160
161    /// Queue a resource removal. Applied after the current stage finishes.
162    pub fn remove_resource<T: hecs::Component>(&mut self) {
163        self.buffer.remove_one::<T>(self.resource_entity);
164    }
165}
166
167impl<'a> Deref for Commands<'a> {
168    type Target = hecs::CommandBuffer;
169    fn deref(&self) -> &Self::Target {
170        &self.buffer
171    }
172}
173
174impl<'a> DerefMut for Commands<'a> {
175    fn deref_mut(&mut self) -> &mut Self::Target {
176        &mut self.buffer
177    }
178}
179
180/// Per-system persistent local state.
181///
182/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
183/// [`Resources`] — each system gets its own private `T`, initialized with
184/// [`Default::default`] the first time the system is registered, and
185/// preserved across every subsequent run of that system.
186///
187/// Useful for counters, caches, or any state a single system needs to
188/// remember without polluting the global resource set.
189pub struct Local<'a, T: Default + Send + Sync + 'static> {
190    data: &'a mut T,
191}
192
193impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
194    type Target = T;
195    fn deref(&self) -> &Self::Target {
196        self.data
197    }
198}
199
200impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
201    fn deref_mut(&mut self) -> &mut Self::Target {
202        self.data
203    }
204}
205
206/// Trait implemented for each valid system parameter type.
207///
208/// The macro-generated [`impl_system!`] blanket implementations use this to
209/// fetch each parameter from the world and resources before calling the system
210/// function. `State` is per-system storage owned by the [`FunctionSystem`]
211/// itself (as opposed to `Item`, which only lives for the duration of one
212/// call) — this is what lets [`Local`] persist between runs.
213pub trait SystemParam {
214    type Item<'a>;
215    type State: Default + 'static;
216    fn fetch<'a>(
217        state: &'a mut Self::State,
218        world: &'a hecs::World,
219        resources: &'a Resources,
220    ) -> Self::Item<'a>;
221}
222
223impl<T> SystemParam for Res<'static, T>
224where
225    T: 'static + Sync + Send,
226{
227    type Item<'a> = Res<'a, T>;
228    type State = ();
229
230    fn fetch<'a>(
231        _state: &'a mut Self::State,
232        world: &'a hecs::World,
233        resource: &'a Resources,
234    ) -> Self::Item<'a> {
235        Res {
236            data: resource.get_resource(world),
237        }
238    }
239}
240
241impl<T> SystemParam for Option<Res<'static, T>>
242where
243    T: 'static + Sync + Send,
244{
245    type Item<'a> = Option<Res<'a, T>>;
246    type State = ();
247
248    fn fetch<'a>(
249        _state: &'a mut Self::State,
250        world: &'a hecs::World,
251        resource: &'a Resources,
252    ) -> Self::Item<'a> {
253        if resource.has_resource::<T>(world) {
254            return Some(Res {
255                data: resource.get_resource(world),
256            });
257        }
258
259        None
260    }
261}
262
263impl<T> SystemParam for ResMut<'static, T>
264where
265    T: 'static + Sync + Send,
266{
267    type Item<'a> = ResMut<'a, T>;
268    type State = ();
269
270    fn fetch<'a>(
271        _state: &'a mut Self::State,
272        world: &'a hecs::World,
273        resource: &'a Resources,
274    ) -> Self::Item<'a> {
275        ResMut {
276            data: resource.get_resource_mut(world),
277        }
278    }
279}
280
281impl<T> SystemParam for Option<ResMut<'static, T>>
282where
283    T: 'static + Sync + Send,
284{
285    type Item<'a> = Option<ResMut<'a, T>>;
286    type State = ();
287
288    fn fetch<'a>(
289        _state: &'a mut Self::State,
290        world: &'a hecs::World,
291        resource: &'a Resources,
292    ) -> Self::Item<'a> {
293        if resource.has_resource::<T>(world) {
294            return Some(ResMut {
295                data: resource.get_resource_mut(world),
296            });
297        }
298
299        None
300    }
301}
302
303impl<Q> SystemParam for Query<'static, Q>
304where
305    Q: hecs::Query + 'static,
306{
307    type Item<'a> = Query<'a, Q>;
308    type State = ();
309
310    fn fetch<'a>(
311        _state: &'a mut Self::State,
312        world: &'a hecs::World,
313        _resources: &'a Resources,
314    ) -> Self::Item<'a> {
315        Query {
316            world: world,
317            borrow: world.query::<Q>(),
318        }
319    }
320}
321
322impl SystemParam for Commands<'static> {
323    type Item<'a> = Commands<'a>;
324    type State = ();
325
326    fn fetch<'a>(
327        _state: &'a mut Self::State,
328        _world: &'a hecs::World,
329        resources: &'a Resources,
330    ) -> Self::Item<'a> {
331        Commands {
332            buffer: resources.get_command_buffer(),
333            resource_entity: resources.resource_entity,
334            resources,
335        }
336    }
337}
338
339impl SystemParam for &'static hecs::World {
340    type Item<'a> = &'a hecs::World;
341    type State = ();
342
343    fn fetch<'a>(
344        _state: &'a mut Self::State,
345        world: &'a hecs::World,
346        _resources: &'a Resources,
347    ) -> Self::Item<'a> {
348        world
349    }
350}
351
352impl SystemParam for &'static Resources {
353    type Item<'a> = &'a Resources;
354    type State = ();
355
356    fn fetch<'a>(
357        _state: &'a mut Self::State,
358        _world: &'a hecs::World,
359        resources: &'a Resources,
360    ) -> Self::Item<'a> {
361        resources
362    }
363}
364
365impl<T> SystemParam for Local<'static, T>
366where
367    T: Default + Send + Sync + 'static,
368{
369    type Item<'a> = Local<'a, T>;
370    type State = T;
371
372    fn fetch<'a>(
373        state: &'a mut Self::State,
374        _world: &'a hecs::World,
375        _resources: &'a Resources,
376    ) -> Self::Item<'a> {
377        Local { data: state }
378    }
379}
380
381/// A type-erased, executable system.
382pub trait System: 'static {
383    fn run(&mut self, world: &hecs::World, resources: &Resources);
384}
385
386/// Type-erased wrapper around a system function, created by [`IntoSystem`].
387///
388/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
389/// is where [`Local`] values actually live between calls to `run`.
390pub struct FunctionSystem<F, Marker, State = ()> {
391    pub func: F,
392    state: State,
393    _marker: std::marker::PhantomData<Marker>,
394}
395
396/// Converts a function (or closure) with valid system parameters into a
397/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
398///
399/// Implemented via the [`impl_system!`] macro for function arities 0–8.
400pub trait IntoSystem<Marker> {
401    type System: System;
402
403    fn into_system(self) -> Self::System;
404}
405
406macro_rules! impl_system {
407    ($($param:ident),*) => {
408        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
409        where
410            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
411            for<'a> &'a mut T: FnMut($($param),*),
412            $($param: SystemParam + 'static),*
413        {
414            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
415
416            fn into_system(self) -> Self::System {
417                FunctionSystem {
418                    func: self,
419                    state: Default::default(),
420                    _marker: std::marker::PhantomData,
421                }
422            }
423        }
424
425        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
426        where
427            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
428            $($param: SystemParam + 'static),*
429        {
430            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
431                #[allow(non_snake_case)]
432                let ($($param,)*) = &mut self.state;
433                (self.func)($($param::fetch($param, _world, _resources)),*);
434            }
435        }
436    };
437}
438
439impl_system!();
440impl_system!(A);
441impl_system!(A, B);
442impl_system!(A, B, C);
443impl_system!(A, B, C, D);
444impl_system!(A, B, C, D, E);
445impl_system!(A, B, C, D, E, F);
446impl_system!(A, B, C, D, E, F, G);
447impl_system!(A, B, C, D, E, F, G, H);