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 — a curated wrapper around `hecs`'s query types, not a
41/// transparent passthrough to them: no `hecs::*` type appears in any method here, and nothing
42/// leaks through beyond what's listed below.
43///
44/// # Iterating
45///
46/// [`iter`](Self::iter) returns a plain `Iterator`, so the usual adapters (`.filter(...)`,
47/// `.map(...)`, `.take(...)`, `.collect()`, ...) all compose directly — reach for `.filter(...)`
48/// for a predicate over the yielded *values* (health below a threshold, say); [`with`](Self::with)/
49/// [`without`](Self::without) below are for filtering by which *components* an entity has, not
50/// their values.
51///
52/// ```ignore
53/// fn move_system(mut q: Query<(&mut Position, &Velocity)>) {
54///     for (pos, vel) in q.iter() {
55///         pos.x += vel.x;
56///         pos.y += vel.y;
57///     }
58/// }
59/// ```
60///
61/// `Query` also implements `IntoIterator` for `&mut Query`, so `for item in &mut q` works too,
62/// identically to `for item in q.iter()`.
63///
64/// Include `hecs::Entity` in `Q` (e.g. `Query<(Entity, &Position)>`) if you need the entity id
65/// back alongside its components — it's a query term like any other, not a separate mechanism.
66///
67/// # Narrowing by component (`with`/`without`)
68///
69/// [`with`](Self::with)/[`without`](Self::without) narrow which entities match, without adding
70/// to (or needing) the yielded item type, and — unlike calling straight through to `hecs` —
71/// chain: each returns another `Query`, so `.with::<&Enemy>().without::<&Dead>().iter()` keeps
72/// every method on this page available at each step, no `hecs::With`/`hecs::Without` naming
73/// required on your end.
74///
75/// # Single-entity lookups
76///
77/// Use [`Query::get`] to fetch components for one known `Entity` without scanning the whole
78/// result set, and [`Query::single`]/[`Query::get_single`] when you expect exactly one match
79/// (e.g. "the player", "the active camera").
80pub struct Query<'a, Q: hecs::Query> {
81    world: &'a hecs::World,
82    borrow: hecs::QueryBorrow<'a, Q>,
83    /// Scratch storage for [`get`](Self::get) — a fresh one-shot lookup is built into this slot
84    /// on every call (dropping whatever was there from the last call) so the item it returns
85    /// can borrow from `self` (stable, caller-controlled) instead of a temporary that would be
86    /// gone by the time the caller could use it.
87    scratch: Option<hecs::QueryOne<'a, Q>>,
88}
89
90impl<'q, Q: hecs::Query> IntoIterator for &'q mut Query<'_, Q> {
91    type Item = Q::Item<'q>;
92    type IntoIter = hecs::QueryIter<'q, Q>;
93
94    fn into_iter(self) -> Self::IntoIter {
95        (&mut self.borrow).into_iter()
96    }
97}
98
99impl<'a, Q: hecs::Query> Query<'a, Q> {
100    /// Iterate every entity matching this query. An ordinary `Iterator` — `.filter(...)`,
101    /// `.map(...)`, `.count()`, `.collect()`, and every other standard adapter work directly on
102    /// the result, no `hecs` types involved.
103    pub fn iter(&mut self) -> impl Iterator<Item = Q::Item<'_>> {
104        self.borrow.iter()
105    }
106
107    /// Look up a single entity's components for this query. `None` if the entity doesn't exist
108    /// or doesn't match `Q`.
109    pub fn get(&mut self, entity: hecs::Entity) -> Option<Q::Item<'_>> {
110        self.scratch = Some(self.world.query_one::<Q>(entity));
111        self.scratch.as_mut().unwrap().get().ok()
112    }
113
114    /// Narrow this query to only entities that ALSO have component(s) `R`, without `R` itself
115    /// being part of the yielded items. Consumes `self` and returns another `Query` — chain
116    /// further `.with`/`.without`, or call `.iter`/`.get`/`.single` directly on the result.
117    pub fn with<R: hecs::Query>(self) -> Query<'a, hecs::With<Q, R>> {
118        Query { world: self.world, borrow: self.borrow.with::<R>(), scratch: None }
119    }
120
121    /// Narrow this query to only entities that do NOT have component(s) `R`. Same shape as
122    /// [`with`](Self::with).
123    pub fn without<R: hecs::Query>(self) -> Query<'a, hecs::Without<Q, R>> {
124        Query { world: self.world, borrow: self.borrow.without::<R>(), scratch: None }
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.
155pub struct Commands<'a> {
156    buffer: RefMut<'a, hecs::CommandBuffer>,
157    resource_entity: hecs::Entity,
158}
159
160impl<'a> Commands<'a> {
161    /// Queue a resource insertion. Applied after the current stage finishes.
162    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
163        self.buffer.insert_one(self.resource_entity, res);
164    }
165
166    /// Queue a resource removal. Applied after the current stage finishes.
167    pub fn remove_resource<T: hecs::Component>(&mut self) {
168        self.buffer.remove_one::<T>(self.resource_entity);
169    }
170}
171
172impl<'a> Deref for Commands<'a> {
173    type Target = hecs::CommandBuffer;
174    fn deref(&self) -> &Self::Target {
175        &self.buffer
176    }
177}
178
179impl<'a> DerefMut for Commands<'a> {
180    fn deref_mut(&mut self) -> &mut Self::Target {
181        &mut self.buffer
182    }
183}
184
185/// Per-system persistent local state.
186///
187/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
188/// [`Resources`] — each system gets its own private `T`, initialized with
189/// [`Default::default`] the first time the system is registered, and
190/// preserved across every subsequent run of that system.
191///
192/// Useful for counters, caches, or any state a single system needs to
193/// remember without polluting the global resource set.
194pub struct Local<'a, T: Default + Send + Sync + 'static> {
195    data: &'a mut T,
196}
197
198impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
199    type Target = T;
200    fn deref(&self) -> &Self::Target {
201        self.data
202    }
203}
204
205impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
206    fn deref_mut(&mut self) -> &mut Self::Target {
207        self.data
208    }
209}
210
211/// Trait implemented for each valid system parameter type.
212///
213/// The macro-generated [`impl_system!`] blanket implementations use this to
214/// fetch each parameter from the world and resources before calling the system
215/// function. `State` is per-system storage owned by the [`FunctionSystem`]
216/// itself (as opposed to `Item`, which only lives for the duration of one
217/// call) — this is what lets [`Local`] persist between runs.
218pub trait SystemParam {
219    type Item<'a>;
220    type State: Default + 'static;
221    fn fetch<'a>(
222        state: &'a mut Self::State,
223        world: &'a hecs::World,
224        resources: &'a Resources,
225    ) -> Self::Item<'a>;
226}
227
228impl<T> SystemParam for Res<'static, T>
229where
230    T: 'static + Sync + Send,
231{
232    type Item<'a> = Res<'a, T>;
233    type State = ();
234
235    fn fetch<'a>(
236        _state: &'a mut Self::State,
237        world: &'a hecs::World,
238        resource: &'a Resources,
239    ) -> Self::Item<'a> {
240        Res {
241            data: resource.get_resource(world),
242        }
243    }
244}
245
246impl<T> SystemParam for Option<Res<'static, T>>
247where
248    T: 'static + Sync + Send,
249{
250    type Item<'a> = Option<Res<'a, T>>;
251    type State = ();
252
253    fn fetch<'a>(
254        _state: &'a mut Self::State,
255        world: &'a hecs::World,
256        resource: &'a Resources,
257    ) -> Self::Item<'a> {
258        if resource.has_resource::<T>(world) {
259            return Some(Res {
260                data: resource.get_resource(world),
261            });
262        }
263
264        None
265    }
266}
267
268impl<T> SystemParam for ResMut<'static, T>
269where
270    T: 'static + Sync + Send,
271{
272    type Item<'a> = ResMut<'a, T>;
273    type State = ();
274
275    fn fetch<'a>(
276        _state: &'a mut Self::State,
277        world: &'a hecs::World,
278        resource: &'a Resources,
279    ) -> Self::Item<'a> {
280        ResMut {
281            data: resource.get_resource_mut(world),
282        }
283    }
284}
285
286impl<T> SystemParam for Option<ResMut<'static, T>>
287where
288    T: 'static + Sync + Send,
289{
290    type Item<'a> = Option<ResMut<'a, T>>;
291    type State = ();
292
293    fn fetch<'a>(
294        _state: &'a mut Self::State,
295        world: &'a hecs::World,
296        resource: &'a Resources,
297    ) -> Self::Item<'a> {
298        if resource.has_resource::<T>(world) {
299            return Some(ResMut {
300                data: resource.get_resource_mut(world),
301            });
302        }
303
304        None
305    }
306}
307
308impl<Q> SystemParam for Query<'static, Q>
309where
310    Q: hecs::Query + 'static,
311{
312    type Item<'a> = Query<'a, Q>;
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        Query {
321            world,
322            borrow: world.query::<Q>(),
323            scratch: None,
324        }
325    }
326}
327
328impl SystemParam for Commands<'static> {
329    type Item<'a> = Commands<'a>;
330    type State = ();
331
332    fn fetch<'a>(
333        _state: &'a mut Self::State,
334        _world: &'a hecs::World,
335        resources: &'a Resources,
336    ) -> Self::Item<'a> {
337        Commands {
338            buffer: resources.get_command_buffer(),
339            resource_entity: resources.resource_entity,
340        }
341    }
342}
343
344impl SystemParam for &'static hecs::World {
345    type Item<'a> = &'a hecs::World;
346    type State = ();
347
348    fn fetch<'a>(
349        _state: &'a mut Self::State,
350        world: &'a hecs::World,
351        _resources: &'a Resources,
352    ) -> Self::Item<'a> {
353        world
354    }
355}
356
357impl SystemParam for &'static Resources {
358    type Item<'a> = &'a Resources;
359    type State = ();
360
361    fn fetch<'a>(
362        _state: &'a mut Self::State,
363        _world: &'a hecs::World,
364        resources: &'a Resources,
365    ) -> Self::Item<'a> {
366        resources
367    }
368}
369
370impl<T> SystemParam for Local<'static, T>
371where
372    T: Default + Send + Sync + 'static,
373{
374    type Item<'a> = Local<'a, T>;
375    type State = T;
376
377    fn fetch<'a>(
378        state: &'a mut Self::State,
379        _world: &'a hecs::World,
380        _resources: &'a Resources,
381    ) -> Self::Item<'a> {
382        Local { data: state }
383    }
384}
385
386/// A type-erased, executable system.
387pub trait System: 'static {
388    fn run(&mut self, world: &hecs::World, resources: &Resources);
389
390    /// Human-readable identifier for this system, used in error/trace output.
391    /// Defaults to the type name of the [`System`] impl; [`FunctionSystem`]
392    /// overrides this with the name of the wrapped function/closure.
393    fn name(&self) -> &'static str {
394        std::any::type_name::<Self>()
395    }
396
397    /// This system's identity for ordering purposes — the [`TypeId`](std::any::TypeId)
398    /// of the function/closure it wraps. Automatic: every distinct function
399    /// or closure has a distinct type, so no manual labeling is needed to
400    /// make a system a valid target for another system's
401    /// [`after`](SystemOrderingExt::after)/[`before`](SystemOrderingExt::before).
402    /// Defaults to `Self`'s own `TypeId`; [`FunctionSystem`]/[`OnceFunctionSystem`]
403    /// override it with the wrapped function's `TypeId` instead of the
404    /// wrapper's, so ordering constraints referencing the bare function match.
405    fn ordering_id(&self) -> std::any::TypeId {
406        std::any::TypeId::of::<Self>()
407    }
408
409    /// Other systems in the same stage that must run before this one. Set
410    /// via [`SystemOrderingExt::after`]. A referenced system that isn't
411    /// registered in the same stage is silently ignored.
412    fn after_ids(&self) -> &[std::any::TypeId] {
413        &[]
414    }
415
416    /// Other systems in the same stage that must run after this one. Set
417    /// via [`SystemOrderingExt::before`]. A referenced system that isn't
418    /// registered in the same stage is silently ignored.
419    fn before_ids(&self) -> &[std::any::TypeId] {
420        &[]
421    }
422}
423
424/// Wraps a [`System`] with ordering constraints relative to other systems in
425/// the same stage, added via [`SystemOrderingExt`].
426///
427/// Constraints only take effect within the stage the system is registered
428/// to — there's no cross-stage ordering, since stage order is already fixed
429/// by [`SystemStage`](crate::app::SystemStage). [`App::build`](crate::app::App::build)
430/// topologically sorts each stage's systems by these constraints, breaking
431/// ties by registration order, and panics if constraints form a cycle.
432pub struct Labeled<S: System> {
433    inner: S,
434    after: Vec<std::any::TypeId>,
435    before: Vec<std::any::TypeId>,
436}
437
438impl<S: System> Labeled<S> {
439    /// Require that `system` runs before this one, within the same stage.
440    /// Chainable — call multiple times to depend on multiple systems.
441    pub fn after<F: 'static, Marker>(mut self, system: F) -> Self
442    where
443        F: IntoSystem<Marker>,
444    {
445        let _ = system;
446        self.after.push(std::any::TypeId::of::<F>());
447        self
448    }
449
450    /// Require that `system` runs after this one, within the same stage.
451    /// Chainable — call multiple times to constrain multiple systems.
452    pub fn before<F: 'static, Marker>(mut self, system: F) -> Self
453    where
454        F: IntoSystem<Marker>,
455    {
456        let _ = system;
457        self.before.push(std::any::TypeId::of::<F>());
458        self
459    }
460}
461
462impl<S: System> System for Labeled<S> {
463    fn run(&mut self, world: &hecs::World, resources: &Resources) {
464        self.inner.run(world, resources)
465    }
466
467    fn name(&self) -> &'static str {
468        self.inner.name()
469    }
470
471    fn ordering_id(&self) -> std::any::TypeId {
472        self.inner.ordering_id()
473    }
474
475    fn after_ids(&self) -> &[std::any::TypeId] {
476        &self.after
477    }
478
479    fn before_ids(&self) -> &[std::any::TypeId] {
480        &self.before
481    }
482}
483
484impl<S: System> IntoSystem<()> for Labeled<S> {
485    type System = Self;
486
487    fn into_system(self) -> Self::System {
488        self
489    }
490}
491
492/// Adds [`.after()`](SystemOrderingExt::after)/[`.before()`](SystemOrderingExt::before)
493/// to any system, for declaring run-order constraints relative to other
494/// systems in the same stage — referenced directly by their function/closure,
495/// no string labels needed.
496///
497/// ```ignore
498/// app.add_system(SystemStage::Update, physics_step);
499/// app.add_system(SystemStage::Update, apply_damage.after(physics_step));
500/// ```
501pub trait SystemOrderingExt<Marker>: IntoSystem<Marker> + Sized {
502    /// Require that `system` runs before this one, within the same stage.
503    fn after<F: 'static, Marker2>(self, system: F) -> Labeled<Self::System>
504    where
505        F: IntoSystem<Marker2>,
506    {
507        let _ = system;
508        Labeled {
509            inner: self.into_system(),
510            after: vec![std::any::TypeId::of::<F>()],
511            before: Vec::new(),
512        }
513    }
514
515    /// Require that `system` runs after this one, within the same stage.
516    fn before<F: 'static, Marker2>(self, system: F) -> Labeled<Self::System>
517    where
518        F: IntoSystem<Marker2>,
519    {
520        let _ = system;
521        Labeled {
522            inner: self.into_system(),
523            after: Vec::new(),
524            before: vec![std::any::TypeId::of::<F>()],
525        }
526    }
527}
528
529impl<T, Marker> SystemOrderingExt<Marker> for T where T: IntoSystem<Marker> {}
530
531/// Type-erased wrapper around a system function, created by [`IntoSystem`].
532///
533/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
534/// is where [`Local`] values actually live between calls to `run`.
535pub struct FunctionSystem<F, Marker, State = ()> {
536    pub func: F,
537    state: State,
538    _marker: std::marker::PhantomData<Marker>,
539}
540
541/// Converts a function (or closure) with valid system parameters into a
542/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
543///
544/// Implemented via the [`impl_system!`] macro for function arities 0–12.
545pub trait IntoSystem<Marker> {
546    type System: System;
547
548    fn into_system(self) -> Self::System;
549}
550
551macro_rules! impl_system {
552    ($($param:ident),*) => {
553        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
554        where
555            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
556            for<'a> &'a mut T: FnMut($($param),*),
557            $($param: SystemParam + 'static),*
558        {
559            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
560
561            fn into_system(self) -> Self::System {
562                FunctionSystem {
563                    func: self,
564                    state: Default::default(),
565                    _marker: std::marker::PhantomData,
566                }
567            }
568        }
569
570        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
571        where
572            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
573            $($param: SystemParam + 'static),*
574        {
575            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
576                #[allow(non_snake_case)]
577                let ($($param,)*) = &mut self.state;
578                (self.func)($($param::fetch($param, _world, _resources)),*);
579            }
580
581            fn name(&self) -> &'static str {
582                std::any::type_name::<T>()
583            }
584
585            fn ordering_id(&self) -> std::any::TypeId {
586                std::any::TypeId::of::<T>()
587            }
588        }
589    };
590}
591
592impl_system!();
593impl_system!(A);
594impl_system!(A, B);
595impl_system!(A, B, C);
596impl_system!(A, B, C, D);
597impl_system!(A, B, C, D, E);
598impl_system!(A, B, C, D, E, F);
599impl_system!(A, B, C, D, E, F, G);
600impl_system!(A, B, C, D, E, F, G, H);
601impl_system!(A, B, C, D, E, F, G, H, I);
602impl_system!(A, B, C, D, E, F, G, H, I, J);
603impl_system!(A, B, C, D, E, F, G, H, I, J, K);
604impl_system!(A, B, C, D, E, F, G, H, I, J, K, L);
605
606/// Marker type used as the first element of the `Marker` tuple in
607/// [`IntoSystem`] for functions that return `Option<()>`. This distinguishes
608/// their [`IntoSystem`] impl from the regular void-function impl so that both
609/// can coexist without conflicting — Rust's coherence checker sees different
610/// marker tuples `(OnceMark, A, B, ...)` vs `(A, B, ...)` and never confuses
611/// them.
612pub struct OnceMark;
613
614/// Type-erased wrapper for functions returning `Option<()>`. Runs `func`
615/// every tick until `func` returns `Some(())`, at which point it is
616/// permanently retired — every subsequent invocation is a no-op. The "have I
617/// already succeeded" bookkeeping lives entirely in `done`, hidden inside
618/// this wrapper; the wrapped function itself just returns `None` ("not ready,
619/// call me again") or `Some(())` ("done").
620pub struct OnceFunctionSystem<F, Marker, State = ()> {
621    func: F,
622    state: State,
623    done: bool,
624    _marker: std::marker::PhantomData<Marker>,
625}
626
627macro_rules! impl_auto_once_system {
628    ($($param:ident),*) => {
629        impl<T, $($param),*> IntoSystem<(OnceMark, $($param,)*)> for T
630        where
631            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
632            for<'a> &'a mut T: FnMut($($param),*) -> Option<()>,
633            $($param: SystemParam + 'static),*
634        {
635            type System = OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
636
637            fn into_system(self) -> Self::System {
638                OnceFunctionSystem {
639                    func: self,
640                    state: Default::default(),
641                    done: false,
642                    _marker: std::marker::PhantomData,
643                }
644            }
645        }
646
647        impl<T, $($param),*> IntoSystem<(OnceMark, $($param,)*)> for OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
648        where
649            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
650            $($param: SystemParam + 'static),*
651        {
652            type System = Self;
653
654            fn into_system(self) -> Self::System {
655                self
656            }
657        }
658
659        impl<T, $($param),*> System for OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
660        where
661            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
662            $($param: SystemParam + 'static),*
663        {
664            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
665                if self.done {
666                    return;
667                }
668                #[allow(non_snake_case)]
669                let ($($param,)*) = &mut self.state;
670                let result = (self.func)($($param::fetch($param, _world, _resources)),*);
671                if result.is_some() {
672                    self.done = true;
673                }
674            }
675
676            fn name(&self) -> &'static str {
677                std::any::type_name::<T>()
678            }
679
680            fn ordering_id(&self) -> std::any::TypeId {
681                std::any::TypeId::of::<T>()
682            }
683        }
684    };
685}
686
687impl_auto_once_system!();
688impl_auto_once_system!(A);
689impl_auto_once_system!(A, B);
690impl_auto_once_system!(A, B, C);
691impl_auto_once_system!(A, B, C, D);
692impl_auto_once_system!(A, B, C, D, E);
693impl_auto_once_system!(A, B, C, D, E, F);
694impl_auto_once_system!(A, B, C, D, E, F, G);
695impl_auto_once_system!(A, B, C, D, E, F, G, H);
696impl_auto_once_system!(A, B, C, D, E, F, G, H, I);
697impl_auto_once_system!(A, B, C, D, E, F, G, H, I, J);
698impl_auto_once_system!(A, B, C, D, E, F, G, H, I, J, K);
699impl_auto_once_system!(A, B, C, D, E, F, G, H, I, J, K, L);
700
701#[cfg(test)]
702mod tests {
703    use super::*;
704
705    struct Health(i32);
706    struct Enemy;
707    struct Dead;
708
709    fn make_query<Q: hecs::Query>(world: &hecs::World) -> Query<'_, Q> {
710        Query { world, borrow: world.query::<Q>(), scratch: None }
711    }
712
713    #[test]
714    fn iter_yields_every_matching_entity() {
715        let mut world = hecs::World::new();
716        world.spawn((Health(10),));
717        world.spawn((Health(20),));
718
719        let mut query = make_query::<&Health>(&world);
720        let mut totals: Vec<i32> = query.iter().map(|h| h.0).collect();
721        totals.sort();
722        assert_eq!(totals, vec![10, 20]);
723    }
724
725    #[test]
726    fn iter_composes_with_standard_iterator_adapters() {
727        let mut world = hecs::World::new();
728        world.spawn((Health(5),));
729        world.spawn((Health(50),));
730
731        let mut query = make_query::<&Health>(&world);
732        let low_health_count = query.iter().filter(|h| h.0 < 10).count();
733        assert_eq!(low_health_count, 1);
734    }
735
736    #[test]
737    fn get_returns_some_for_a_matching_entity_and_none_otherwise() {
738        let mut world = hecs::World::new();
739        let matching = world.spawn((Health(7),));
740        let non_matching = world.spawn(()); // no Health
741
742        let mut query = make_query::<&Health>(&world);
743        assert_eq!(query.get(matching).map(|h| h.0), Some(7));
744        assert!(query.get(non_matching).is_none());
745    }
746
747    #[test]
748    fn get_can_be_called_more_than_once_on_the_same_query() {
749        let mut world = hecs::World::new();
750        let a = world.spawn((Health(1),));
751        let b = world.spawn((Health(2),));
752
753        let mut query = make_query::<&Health>(&world);
754        assert_eq!(query.get(a).map(|h| h.0), Some(1));
755        assert_eq!(query.get(b).map(|h| h.0), Some(2));
756    }
757
758    #[test]
759    fn with_and_without_chain_and_narrow_by_component_presence() {
760        let mut world = hecs::World::new();
761        let alive_enemy = world.spawn((Health(1), Enemy));
762        world.spawn((Health(1), Enemy, Dead));
763        world.spawn((Health(1),));
764
765        let query = make_query::<&Health>(&world);
766        let mut narrowed = query.with::<&Enemy>().without::<&Dead>();
767
768        assert_eq!(narrowed.iter().count(), 1);
769        assert!(narrowed.get(alive_enemy).is_some());
770    }
771
772    #[test]
773    fn single_panics_on_zero_or_multiple_matches_get_single_does_not() {
774        let mut world = hecs::World::new();
775
776        assert!(make_query::<&Health>(&world).get_single().is_none());
777
778        world.spawn((Health(1),));
779        assert_eq!(make_query::<&Health>(&world).single().0, 1);
780
781        world.spawn((Health(2),));
782        assert!(make_query::<&Health>(&world).get_single().is_none());
783    }
784}