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/// A resource requirement declared by a [`SystemParam`]/[`System`], carrying
7/// both a human-readable name and a way to check presence dynamically
8/// (needed because [`System::requires`] is type-erased — the concrete `T`
9/// is only known where the check is constructed, inside each `SystemParam`
10/// impl).
11#[derive(Clone, Copy)]
12pub struct RequiredResource {
13    pub name: &'static str,
14    pub present: fn(&hecs::World, &Resources) -> bool,
15}
16
17/// Immutable borrow of a singleton resource `T`.
18///
19/// Obtained as a system parameter; derefs to `T`.
20pub struct Res<'a, T: hecs::Component> {
21    pub(crate) data: hecs::Ref<'a, T>,
22}
23
24impl<'a, T: hecs::Component> Deref for Res<'a, T> {
25    type Target = T;
26    fn deref(&self) -> &Self::Target {
27        &self.data
28    }
29}
30
31/// Mutable borrow of a singleton resource `T`.
32///
33/// Obtained as a system parameter; derefs to `T`.
34pub struct ResMut<'a, T: hecs::Component> {
35    data: hecs::RefMut<'a, T>,
36}
37
38impl<'a, T: hecs::Component> Deref for ResMut<'a, T> {
39    type Target = T;
40    fn deref(&self) -> &Self::Target {
41        &self.data
42    }
43}
44
45impl<'a, T: hecs::Component> DerefMut for ResMut<'a, T> {
46    fn deref_mut(&mut self) -> &mut Self::Target {
47        &mut self.data
48    }
49}
50
51/// Borrow of an ECS query result.
52///
53/// Obtained as a system parameter; derefs to [`hecs::QueryBorrow`].
54///
55/// # Iterating
56///
57/// `Query` implements `IntoIterator` for `&mut Query`, so you can iterate it
58/// directly without going through `Deref`:
59///
60/// ```ignore
61/// fn move_system(mut q: Query<(&mut Position, &Velocity)>) {
62///     for (entity, (pos, vel)) in &mut q {
63///         pos.x += vel.x;
64///         pos.y += vel.y;
65///     }
66/// }
67/// ```
68///
69/// # Single-entity lookups
70///
71/// Use [`Query::get`] to fetch components for one known `Entity` without
72/// scanning the whole result set, and [`Query::single`] /
73/// [`Query::get_single`] when you expect exactly one match (e.g. "the
74/// player", "the active camera").
75pub struct Query<'a, Q: hecs::Query> {
76    world: &'a hecs::World,
77    borrow: hecs::QueryBorrow<'a, Q>,
78}
79
80impl<'a, Q: hecs::Query> Deref for Query<'a, Q> {
81    type Target = hecs::QueryBorrow<'a, Q>;
82    fn deref(&self) -> &Self::Target {
83        &self.borrow
84    }
85}
86
87impl<'a, Q: hecs::Query> DerefMut for Query<'a, Q> {
88    fn deref_mut(&mut self) -> &mut Self::Target {
89        &mut self.borrow
90    }
91}
92
93impl<'q, Q: hecs::Query> IntoIterator for &'q mut Query<'_, Q> {
94    type Item = Q::Item<'q>;
95    type IntoIter = hecs::QueryIter<'q, Q>;
96
97    fn into_iter(self) -> Self::IntoIter {
98        (&mut self.borrow).into_iter()
99    }
100}
101
102impl<'a, Q: hecs::Query> Query<'a, Q> {
103    /// Look up a single entity's components for this query. Returns
104    /// `None` if the entity doesn't exist or doesn't match `Q`.
105    pub fn get(&self, entity: hecs::Entity) -> hecs::QueryOne<'_, Q> {
106        self.world.query_one::<Q>(entity)
107    }
108
109    /// Filter this query to only entities that ALSO have component `R`,
110    /// without `R` itself being part of the yielded items. Consumes
111    /// `self` — matches hecs's own `QueryBorrow::with` signature.
112    pub fn with<R: hecs::Query>(self) -> hecs::QueryBorrow<'a, hecs::With<Q, R>> {
113        self.borrow.with::<R>()
114    }
115
116    /// Filter this query to only entities that do NOT have component `R`.
117    /// Consumes `self`, same reasoning as `with`.
118    pub fn without<R: hecs::Query>(self) -> hecs::QueryBorrow<'a, hecs::Without<Q, R>> {
119        self.borrow.without::<R>()
120    }
121
122    /// Return the single entity's components for this query.
123    ///
124    /// Panics if there isn't exactly one match. Intended for singleton-style
125    /// queries (the player, the active camera, ...) where zero or multiple
126    /// matches indicate a bug. See [`Query::get_single`] for a
127    /// non-panicking version. Include `hecs::Entity` in `Q` if you need the
128    /// id alongside the components.
129    pub fn single(&mut self) -> Q::Item<'_> {
130        self.get_single()
131            .expect("Query::single: expected exactly one matching entity")
132    }
133
134    /// Like [`Query::single`], but returns `None` instead of panicking when
135    /// there isn't exactly one match.
136    pub fn get_single(&mut self) -> Option<Q::Item<'_>> {
137        let mut iter = self.borrow.iter();
138        let first = iter.next()?;
139        if iter.next().is_some() {
140            return None;
141        }
142        Some(first)
143    }
144}
145
146/// Deferred world-mutation commands available as a system parameter.
147///
148/// Mutations are buffered and applied to the world after all systems in the
149/// current stage have finished running.
150///
151/// Resource insertions immediately bump the [`Resources`] generation counter so
152/// that the convergence loop in [`App`](crate::app::App) can detect them without
153/// needing to inspect the world after every flush.
154pub struct Commands<'a> {
155    buffer: RefMut<'a, hecs::CommandBuffer>,
156    resource_entity: hecs::Entity,
157    /// Held so `insert_resource` can bump the generation counter at queue time.
158    resources: &'a Resources,
159}
160
161impl<'a> Commands<'a> {
162    /// Queue a resource insertion. Applied after the current stage finishes.
163    ///
164    /// Bumps the [`Resources`] generation counter immediately so the
165    /// convergence loop knows another pass is needed even before the command
166    /// buffer is flushed.
167    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
168        self.buffer.insert_one(self.resource_entity, res);
169        self.resources.bump_generation();
170    }
171
172    /// Queue a resource removal. Applied after the current stage finishes.
173    pub fn remove_resource<T: hecs::Component>(&mut self) {
174        self.buffer.remove_one::<T>(self.resource_entity);
175    }
176}
177
178impl<'a> Deref for Commands<'a> {
179    type Target = hecs::CommandBuffer;
180    fn deref(&self) -> &Self::Target {
181        &self.buffer
182    }
183}
184
185impl<'a> DerefMut for Commands<'a> {
186    fn deref_mut(&mut self) -> &mut Self::Target {
187        &mut self.buffer
188    }
189}
190
191/// Per-system persistent local state.
192///
193/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
194/// [`Resources`] — each system gets its own private `T`, initialized with
195/// [`Default::default`] the first time the system is registered, and
196/// preserved across every subsequent run of that system.
197///
198/// Useful for counters, caches, or any state a single system needs to
199/// remember without polluting the global resource set.
200pub struct Local<'a, T: Default + Send + Sync + 'static> {
201    data: &'a mut T,
202}
203
204impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
205    type Target = T;
206    fn deref(&self) -> &Self::Target {
207        self.data
208    }
209}
210
211impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
212    fn deref_mut(&mut self) -> &mut Self::Target {
213        self.data
214    }
215}
216
217/// Trait implemented for each valid system parameter type.
218///
219/// The macro-generated [`impl_system!`] blanket implementations use this to
220/// fetch each parameter from the world and resources before calling the system
221/// function. `State` is per-system storage owned by the [`FunctionSystem`]
222/// itself (as opposed to `Item`, which only lives for the duration of one
223/// call) — this is what lets [`Local`] persist between runs.
224pub trait SystemParam {
225    type Item<'a>;
226    type State: Default + 'static;
227    fn fetch<'a>(
228        state: &'a mut Self::State,
229        world: &'a hecs::World,
230        resources: &'a Resources,
231    ) -> Self::Item<'a>;
232
233    /// Resource types this parameter unconditionally needs present to avoid
234    /// panicking. Used by [`App`](crate::app::App) to validate — before
235    /// running a non-convergent stage's systems — that every hard
236    /// requirement is already satisfied, failing fast with a clear message
237    /// instead of panicking deep inside whichever system happens to run
238    /// first.
239    ///
240    /// Empty by default; only hard requirements (bare [`Res`]/[`ResMut`])
241    /// contribute an entry. `Option<Res<T>>`/`Option<ResMut<T>>` tolerate
242    /// absence and deliberately opt out of this check.
243    fn requires() -> Vec<RequiredResource> {
244        Vec::new()
245    }
246}
247
248impl<T> SystemParam for Res<'static, T>
249where
250    T: 'static + Sync + Send,
251{
252    type Item<'a> = Res<'a, T>;
253    type State = ();
254
255    fn fetch<'a>(
256        _state: &'a mut Self::State,
257        world: &'a hecs::World,
258        resource: &'a Resources,
259    ) -> Self::Item<'a> {
260        Res {
261            data: resource.get_resource(world),
262        }
263    }
264
265    fn requires() -> Vec<RequiredResource> {
266        vec![RequiredResource {
267            name: std::any::type_name::<T>(),
268            present: |world, resources| resources.has_resource::<T>(world),
269        }]
270    }
271}
272
273impl<T> SystemParam for Option<Res<'static, T>>
274where
275    T: 'static + Sync + Send,
276{
277    type Item<'a> = Option<Res<'a, T>>;
278    type State = ();
279
280    fn fetch<'a>(
281        _state: &'a mut Self::State,
282        world: &'a hecs::World,
283        resource: &'a Resources,
284    ) -> Self::Item<'a> {
285        if resource.has_resource::<T>(world) {
286            return Some(Res {
287                data: resource.get_resource(world),
288            });
289        }
290
291        None
292    }
293}
294
295impl<T> SystemParam for ResMut<'static, T>
296where
297    T: 'static + Sync + Send,
298{
299    type Item<'a> = ResMut<'a, T>;
300    type State = ();
301
302    fn fetch<'a>(
303        _state: &'a mut Self::State,
304        world: &'a hecs::World,
305        resource: &'a Resources,
306    ) -> Self::Item<'a> {
307        ResMut {
308            data: resource.get_resource_mut(world),
309        }
310    }
311
312    fn requires() -> Vec<RequiredResource> {
313        vec![RequiredResource {
314            name: std::any::type_name::<T>(),
315            present: |world, resources| resources.has_resource::<T>(world),
316        }]
317    }
318}
319
320impl<T> SystemParam for Option<ResMut<'static, T>>
321where
322    T: 'static + Sync + Send,
323{
324    type Item<'a> = Option<ResMut<'a, T>>;
325    type State = ();
326
327    fn fetch<'a>(
328        _state: &'a mut Self::State,
329        world: &'a hecs::World,
330        resource: &'a Resources,
331    ) -> Self::Item<'a> {
332        if resource.has_resource::<T>(world) {
333            return Some(ResMut {
334                data: resource.get_resource_mut(world),
335            });
336        }
337
338        None
339    }
340}
341
342impl<Q> SystemParam for Query<'static, Q>
343where
344    Q: hecs::Query + 'static,
345{
346    type Item<'a> = Query<'a, Q>;
347    type State = ();
348
349    fn fetch<'a>(
350        _state: &'a mut Self::State,
351        world: &'a hecs::World,
352        _resources: &'a Resources,
353    ) -> Self::Item<'a> {
354        Query {
355            world: world,
356            borrow: world.query::<Q>(),
357        }
358    }
359}
360
361impl SystemParam for Commands<'static> {
362    type Item<'a> = Commands<'a>;
363    type State = ();
364
365    fn fetch<'a>(
366        _state: &'a mut Self::State,
367        _world: &'a hecs::World,
368        resources: &'a Resources,
369    ) -> Self::Item<'a> {
370        Commands {
371            buffer: resources.get_command_buffer(),
372            resource_entity: resources.resource_entity,
373            resources,
374        }
375    }
376}
377
378impl SystemParam for &'static hecs::World {
379    type Item<'a> = &'a hecs::World;
380    type State = ();
381
382    fn fetch<'a>(
383        _state: &'a mut Self::State,
384        world: &'a hecs::World,
385        _resources: &'a Resources,
386    ) -> Self::Item<'a> {
387        world
388    }
389}
390
391impl SystemParam for &'static Resources {
392    type Item<'a> = &'a Resources;
393    type State = ();
394
395    fn fetch<'a>(
396        _state: &'a mut Self::State,
397        _world: &'a hecs::World,
398        resources: &'a Resources,
399    ) -> Self::Item<'a> {
400        resources
401    }
402}
403
404impl<T> SystemParam for Local<'static, T>
405where
406    T: Default + Send + Sync + 'static,
407{
408    type Item<'a> = Local<'a, T>;
409    type State = T;
410
411    fn fetch<'a>(
412        state: &'a mut Self::State,
413        _world: &'a hecs::World,
414        _resources: &'a Resources,
415    ) -> Self::Item<'a> {
416        Local { data: state }
417    }
418}
419
420/// A type-erased, executable system.
421pub trait System: 'static {
422    fn run(&mut self, world: &hecs::World, resources: &Resources);
423
424    /// Resource types this system needs present, derived automatically from
425    /// its bare [`Res`]/[`ResMut`] parameters. [`App`](crate::app::App)
426    /// checks these before running a non-convergent stage's systems and
427    /// panics with a clear message naming the missing resource(s) rather
428    /// than letting a param fetch panic deep inside whichever system happens
429    /// to run first.
430    fn requires(&self) -> Vec<RequiredResource> {
431        Vec::new()
432    }
433
434    /// Human-readable identifier for this system, used in error/trace output
435    /// so a missing-resource failure can be pinned to the system that needs
436    /// it instead of just the resource name. Defaults to the type name of
437    /// the [`System`] impl; [`FunctionSystem`] overrides this with the name
438    /// of the wrapped function/closure, which is far more legible.
439    fn name(&self) -> &'static str {
440        std::any::type_name::<Self>()
441    }
442}
443
444/// Type-erased wrapper around a system function, created by [`IntoSystem`].
445///
446/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
447/// is where [`Local`] values actually live between calls to `run`.
448pub struct FunctionSystem<F, Marker, State = ()> {
449    pub func: F,
450    state: State,
451    _marker: std::marker::PhantomData<Marker>,
452}
453
454/// Converts a function (or closure) with valid system parameters into a
455/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
456///
457/// Implemented via the [`impl_system!`] macro for function arities 0–8.
458pub trait IntoSystem<Marker> {
459    type System: System;
460
461    fn into_system(self) -> Self::System;
462}
463
464macro_rules! impl_system {
465    ($($param:ident),*) => {
466        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
467        where
468            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
469            for<'a> &'a mut T: FnMut($($param),*),
470            $($param: SystemParam + 'static),*
471        {
472            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
473
474            fn into_system(self) -> Self::System {
475                FunctionSystem {
476                    func: self,
477                    state: Default::default(),
478                    _marker: std::marker::PhantomData,
479                }
480            }
481        }
482
483        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
484        where
485            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
486            $($param: SystemParam + 'static),*
487        {
488            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
489                #[allow(non_snake_case)]
490                let ($($param,)*) = &mut self.state;
491                (self.func)($($param::fetch($param, _world, _resources)),*);
492            }
493
494            fn requires(&self) -> Vec<RequiredResource> {
495                let mut _v = Vec::new();
496                $(_v.extend($param::requires());)*
497                _v
498            }
499
500            fn name(&self) -> &'static str {
501                std::any::type_name::<T>()
502            }
503        }
504    };
505}
506
507impl_system!();
508impl_system!(A);
509impl_system!(A, B);
510impl_system!(A, B, C);
511impl_system!(A, B, C, D);
512impl_system!(A, B, C, D, E);
513impl_system!(A, B, C, D, E, F);
514impl_system!(A, B, C, D, E, F, G);
515impl_system!(A, B, C, D, E, F, G, H);
516impl_system!(A, B, C, D, E, F, G, H, I);
517impl_system!(A, B, C, D, E, F, G, H, I, J);
518impl_system!(A, B, C, D, E, F, G, H, I, J, K);
519impl_system!(A, B, C, D, E, F, G, H, I, J, K, L);