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