Skip to main content

pebble/ecs/
system.rs

1use std::cell::RefMut;
2use std::ops::{Deref, DerefMut};
3
4use crate::ecs::resources::Resources;
5// A `.detach()`-ed system's returned future must satisfy this bound —
6// `BackgroundTasks::spawn_async`'s own bound, since that's what ends up
7// driving it.
8use crate::threading::SpawnableFuture;
9
10/// A resource requirement declared by a [`SystemParam`]/[`System`], carrying
11/// a human-readable name, the resource's [`TypeId`](std::any::TypeId) (so
12/// [`App`](crate::app::App) can check it against
13/// [`RequiredResources`](crate::assets::required::RequiredResources) —
14/// resources some plugin has declared it eventually provides, e.g. an async
15/// GPU backend or a [`LazyResource`](crate::assets::singleton_asset::LazyResource) —
16/// and a way to check presence dynamically (needed because
17/// [`System::requires`] is type-erased — the concrete `T` is only known
18/// where the check is constructed, inside each `SystemParam` impl).
19#[derive(Clone, Copy)]
20pub struct RequiredResource {
21    pub name: &'static str,
22    pub type_id: std::any::TypeId,
23    pub present: fn(&hecs::World, &Resources) -> bool,
24    /// Overrides `App`'s generic "call `app.provides::<T>()` or
25    /// `App::add_resource`" advice when this resource has its own, more
26    /// specific registration path (e.g. `Events<T>` — the actual fix is
27    /// `app.add_event::<T>()`, not a manual `provides` call). `None` falls
28    /// back to the generic advice, appropriate for a plain `Res<T>`/`ResMut<T>`
29    /// on an arbitrary user resource type.
30    pub hint: Option<&'static str>,
31}
32
33/// Immutable borrow of a singleton resource `T`.
34///
35/// Obtained as a system parameter; derefs to `T`.
36pub struct Res<'a, T: hecs::Component> {
37    pub(crate) data: hecs::Ref<'a, T>,
38}
39
40impl<'a, T: hecs::Component> Deref for Res<'a, T> {
41    type Target = T;
42    fn deref(&self) -> &Self::Target {
43        &self.data
44    }
45}
46
47/// Mutable borrow of a singleton resource `T`.
48///
49/// Obtained as a system parameter; derefs to `T`.
50pub struct ResMut<'a, T: hecs::Component> {
51    data: hecs::RefMut<'a, T>,
52}
53
54impl<'a, T: hecs::Component> Deref for ResMut<'a, T> {
55    type Target = T;
56    fn deref(&self) -> &Self::Target {
57        &self.data
58    }
59}
60
61impl<'a, T: hecs::Component> DerefMut for ResMut<'a, T> {
62    fn deref_mut(&mut self) -> &mut Self::Target {
63        &mut self.data
64    }
65}
66
67/// Borrow of an ECS query result — a curated wrapper around `hecs`'s query types, not a
68/// transparent passthrough to them: no `hecs::*` type appears in any method here, and nothing
69/// leaks through beyond what's listed below.
70///
71/// # Iterating
72///
73/// [`iter`](Self::iter) returns a plain `Iterator`, so the usual adapters (`.filter(...)`,
74/// `.map(...)`, `.take(...)`, `.collect()`, ...) all compose directly — reach for `.filter(...)`
75/// for a predicate over the yielded *values* (health below a threshold, say); [`with`](Self::with)/
76/// [`without`](Self::without) below are for filtering by which *components* an entity has, not
77/// their values.
78///
79/// ```ignore
80/// fn move_system(mut q: Query<(&mut Position, &Velocity)>) {
81///     for (pos, vel) in q.iter() {
82///         pos.x += vel.x;
83///         pos.y += vel.y;
84///     }
85/// }
86/// ```
87///
88/// `Query` also implements `IntoIterator` for `&mut Query`, so `for item in &mut q` works too,
89/// identically to `for item in q.iter()`.
90///
91/// Include `hecs::Entity` in `Q` (e.g. `Query<(Entity, &Position)>`) if you need the entity id
92/// back alongside its components — it's a query term like any other, not a separate mechanism.
93///
94/// # Narrowing by component (`with`/`without`)
95///
96/// [`with`](Self::with)/[`without`](Self::without) narrow which entities match, without adding
97/// to (or needing) the yielded item type, and — unlike calling straight through to `hecs` —
98/// chain: each returns another `Query`, so `.with::<&Enemy>().without::<&Dead>().iter()` keeps
99/// every method on this page available at each step, no `hecs::With`/`hecs::Without` naming
100/// required on your end.
101///
102/// # Single-entity lookups
103///
104/// Use [`Query::get`] to fetch components for one known `Entity` without scanning the whole
105/// result set, and [`Query::single`]/[`Query::get_single`] when you expect exactly one match
106/// (e.g. "the player", "the active camera").
107pub struct Query<'a, Q: hecs::Query> {
108    world: &'a hecs::World,
109    borrow: hecs::QueryBorrow<'a, Q>,
110    /// Scratch storage for [`get`](Self::get) — a fresh one-shot lookup is built into this slot
111    /// on every call (dropping whatever was there from the last call) so the item it returns
112    /// can borrow from `self` (stable, caller-controlled) instead of a temporary that would be
113    /// gone by the time the caller could use it.
114    scratch: Option<hecs::QueryOne<'a, Q>>,
115}
116
117impl<'q, Q: hecs::Query> IntoIterator for &'q mut Query<'_, Q> {
118    type Item = Q::Item<'q>;
119    type IntoIter = hecs::QueryIter<'q, Q>;
120
121    fn into_iter(self) -> Self::IntoIter {
122        (&mut self.borrow).into_iter()
123    }
124}
125
126impl<'a, Q: hecs::Query> Query<'a, Q> {
127    /// Iterate every entity matching this query. An ordinary `Iterator` — `.filter(...)`,
128    /// `.map(...)`, `.count()`, `.collect()`, and every other standard adapter work directly on
129    /// the result, no `hecs` types involved.
130    pub fn iter(&mut self) -> impl Iterator<Item = Q::Item<'_>> {
131        self.borrow.iter()
132    }
133
134    /// Look up a single entity's components for this query. `None` if the entity doesn't exist
135    /// or doesn't match `Q`.
136    pub fn get(&mut self, entity: hecs::Entity) -> Option<Q::Item<'_>> {
137        self.scratch = Some(self.world.query_one::<Q>(entity));
138        self.scratch.as_mut().unwrap().get().ok()
139    }
140
141    /// Narrow this query to only entities that ALSO have component(s) `R`, without `R` itself
142    /// being part of the yielded items. Consumes `self` and returns another `Query` — chain
143    /// further `.with`/`.without`, or call `.iter`/`.get`/`.single` directly on the result.
144    pub fn with<R: hecs::Query>(self) -> Query<'a, hecs::With<Q, R>> {
145        Query { world: self.world, borrow: self.borrow.with::<R>(), scratch: None }
146    }
147
148    /// Narrow this query to only entities that do NOT have component(s) `R`. Same shape as
149    /// [`with`](Self::with).
150    pub fn without<R: hecs::Query>(self) -> Query<'a, hecs::Without<Q, R>> {
151        Query { world: self.world, borrow: self.borrow.without::<R>(), scratch: None }
152    }
153
154    /// Return the single entity's components for this query.
155    ///
156    /// Panics if there isn't exactly one match. Intended for singleton-style
157    /// queries (the player, the active camera, ...) where zero or multiple
158    /// matches indicate a bug. See [`Query::get_single`] for a
159    /// non-panicking version. Include `hecs::Entity` in `Q` if you need the
160    /// id alongside the components.
161    pub fn single(&mut self) -> Q::Item<'_> {
162        self.get_single()
163            .expect("Query::single: expected exactly one matching entity")
164    }
165
166    /// Like [`Query::single`], but returns `None` instead of panicking when
167    /// there isn't exactly one match.
168    pub fn get_single(&mut self) -> Option<Q::Item<'_>> {
169        let mut iter = self.borrow.iter();
170        let first = iter.next()?;
171        if iter.next().is_some() {
172            return None;
173        }
174        Some(first)
175    }
176}
177
178/// Deferred world-mutation commands available as a system parameter.
179///
180/// Mutations are buffered and applied to the world after all systems in the
181/// current stage have finished running.
182///
183/// Resource insertions immediately bump the [`Resources`] generation counter so
184/// that the convergence loop in [`App`](crate::app::App) can detect them without
185/// needing to inspect the world after every flush.
186pub struct Commands<'a> {
187    buffer: RefMut<'a, hecs::CommandBuffer>,
188    resource_entity: hecs::Entity,
189    /// Held so `insert_resource` can bump the generation counter at queue time.
190    resources: &'a Resources,
191}
192
193impl<'a> Commands<'a> {
194    /// Queue a resource insertion. Applied after the current stage finishes.
195    ///
196    /// Bumps the [`Resources`] generation counter immediately so the
197    /// convergence loop knows another pass is needed even before the command
198    /// buffer is flushed.
199    pub fn insert_resource<T: hecs::Component>(&mut self, res: T) {
200        self.buffer.insert_one(self.resource_entity, res);
201        self.resources.bump_generation();
202    }
203
204    /// Queue a resource removal. Applied after the current stage finishes.
205    pub fn remove_resource<T: hecs::Component>(&mut self) {
206        self.buffer.remove_one::<T>(self.resource_entity);
207    }
208}
209
210impl<'a> Deref for Commands<'a> {
211    type Target = hecs::CommandBuffer;
212    fn deref(&self) -> &Self::Target {
213        &self.buffer
214    }
215}
216
217impl<'a> DerefMut for Commands<'a> {
218    fn deref_mut(&mut self) -> &mut Self::Target {
219        &mut self.buffer
220    }
221}
222
223/// Per-system persistent local state.
224///
225/// Unlike [`Res`]/[`ResMut`], a `Local<T>` is *not* shared through
226/// [`Resources`] — each system gets its own private `T`, initialized with
227/// [`Default::default`] the first time the system is registered, and
228/// preserved across every subsequent run of that system.
229///
230/// Useful for counters, caches, or any state a single system needs to
231/// remember without polluting the global resource set.
232pub struct Local<'a, T: Default + Send + Sync + 'static> {
233    data: &'a mut T,
234}
235
236impl<'a, T: Default + Send + Sync + 'static> Deref for Local<'a, T> {
237    type Target = T;
238    fn deref(&self) -> &Self::Target {
239        self.data
240    }
241}
242
243impl<'a, T: Default + Send + Sync + 'static> DerefMut for Local<'a, T> {
244    fn deref_mut(&mut self) -> &mut Self::Target {
245        self.data
246    }
247}
248
249/// Trait implemented for each valid system parameter type.
250///
251/// The macro-generated [`impl_system!`] blanket implementations use this to
252/// fetch each parameter from the world and resources before calling the system
253/// function. `State` is per-system storage owned by the [`FunctionSystem`]
254/// itself (as opposed to `Item`, which only lives for the duration of one
255/// call) — this is what lets [`Local`] persist between runs.
256pub trait SystemParam {
257    type Item<'a>;
258    type State: Default + 'static;
259    fn fetch<'a>(
260        state: &'a mut Self::State,
261        world: &'a hecs::World,
262        resources: &'a Resources,
263    ) -> Self::Item<'a>;
264
265    /// Resource types this parameter unconditionally needs present to avoid
266    /// panicking. Used by [`App`](crate::app::App) to validate — before
267    /// running a non-convergent stage's systems — that every hard
268    /// requirement is already satisfied, failing fast with a clear message
269    /// instead of panicking deep inside whichever system happens to run
270    /// first.
271    ///
272    /// Empty by default; only hard requirements (bare [`Res`]/[`ResMut`])
273    /// contribute an entry. `Option<Res<T>>`/`Option<ResMut<T>>` tolerate
274    /// absence and deliberately opt out of this check.
275    fn requires() -> Vec<RequiredResource> {
276        Vec::new()
277    }
278}
279
280impl<T> SystemParam for Res<'static, T>
281where
282    T: 'static + Sync + Send,
283{
284    type Item<'a> = Res<'a, T>;
285    type State = ();
286
287    fn fetch<'a>(
288        _state: &'a mut Self::State,
289        world: &'a hecs::World,
290        resource: &'a Resources,
291    ) -> Self::Item<'a> {
292        Res {
293            data: resource.get_resource(world),
294        }
295    }
296
297    fn requires() -> Vec<RequiredResource> {
298        vec![RequiredResource {
299            name: std::any::type_name::<T>(),
300            type_id: std::any::TypeId::of::<T>(),
301            present: |world, resources| resources.has_resource::<T>(world),
302            hint: None,
303        }]
304    }
305}
306
307impl<T> SystemParam for Option<Res<'static, T>>
308where
309    T: 'static + Sync + Send,
310{
311    type Item<'a> = Option<Res<'a, T>>;
312    type State = ();
313
314    fn fetch<'a>(
315        _state: &'a mut Self::State,
316        world: &'a hecs::World,
317        resource: &'a Resources,
318    ) -> Self::Item<'a> {
319        if resource.has_resource::<T>(world) {
320            return Some(Res {
321                data: resource.get_resource(world),
322            });
323        }
324
325        None
326    }
327}
328
329impl<T> SystemParam for ResMut<'static, T>
330where
331    T: 'static + Sync + Send,
332{
333    type Item<'a> = ResMut<'a, T>;
334    type State = ();
335
336    fn fetch<'a>(
337        _state: &'a mut Self::State,
338        world: &'a hecs::World,
339        resource: &'a Resources,
340    ) -> Self::Item<'a> {
341        ResMut {
342            data: resource.get_resource_mut(world),
343        }
344    }
345
346    fn requires() -> Vec<RequiredResource> {
347        vec![RequiredResource {
348            name: std::any::type_name::<T>(),
349            type_id: std::any::TypeId::of::<T>(),
350            present: |world, resources| resources.has_resource::<T>(world),
351            hint: None,
352        }]
353    }
354}
355
356impl<T> SystemParam for Option<ResMut<'static, T>>
357where
358    T: 'static + Sync + Send,
359{
360    type Item<'a> = Option<ResMut<'a, T>>;
361    type State = ();
362
363    fn fetch<'a>(
364        _state: &'a mut Self::State,
365        world: &'a hecs::World,
366        resource: &'a Resources,
367    ) -> Self::Item<'a> {
368        if resource.has_resource::<T>(world) {
369            return Some(ResMut {
370                data: resource.get_resource_mut(world),
371            });
372        }
373
374        None
375    }
376}
377
378impl<Q> SystemParam for Query<'static, Q>
379where
380    Q: hecs::Query + 'static,
381{
382    type Item<'a> = Query<'a, Q>;
383    type State = ();
384
385    fn fetch<'a>(
386        _state: &'a mut Self::State,
387        world: &'a hecs::World,
388        _resources: &'a Resources,
389    ) -> Self::Item<'a> {
390        Query {
391            world,
392            borrow: world.query::<Q>(),
393            scratch: None,
394        }
395    }
396}
397
398impl SystemParam for Commands<'static> {
399    type Item<'a> = Commands<'a>;
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        Commands {
408            buffer: resources.get_command_buffer(),
409            resource_entity: resources.resource_entity,
410            resources,
411        }
412    }
413}
414
415impl SystemParam for &'static hecs::World {
416    type Item<'a> = &'a hecs::World;
417    type State = ();
418
419    fn fetch<'a>(
420        _state: &'a mut Self::State,
421        world: &'a hecs::World,
422        _resources: &'a Resources,
423    ) -> Self::Item<'a> {
424        world
425    }
426}
427
428impl SystemParam for &'static Resources {
429    type Item<'a> = &'a Resources;
430    type State = ();
431
432    fn fetch<'a>(
433        _state: &'a mut Self::State,
434        _world: &'a hecs::World,
435        resources: &'a Resources,
436    ) -> Self::Item<'a> {
437        resources
438    }
439}
440
441impl<T> SystemParam for Local<'static, T>
442where
443    T: Default + Send + Sync + 'static,
444{
445    type Item<'a> = Local<'a, T>;
446    type State = T;
447
448    fn fetch<'a>(
449        state: &'a mut Self::State,
450        _world: &'a hecs::World,
451        _resources: &'a Resources,
452    ) -> Self::Item<'a> {
453        Local { data: state }
454    }
455}
456
457/// A type-erased, executable system.
458pub trait System: 'static {
459    fn run(&mut self, world: &hecs::World, resources: &Resources);
460
461    /// Resource types this system needs present, derived automatically from
462    /// its bare [`Res`]/[`ResMut`] parameters. [`App`](crate::app::App)
463    /// checks these before running a non-convergent stage's systems and
464    /// panics with a clear message naming the missing resource(s) rather
465    /// than letting a param fetch panic deep inside whichever system happens
466    /// to run first.
467    fn requires(&self) -> Vec<RequiredResource> {
468        Vec::new()
469    }
470
471    /// Human-readable identifier for this system, used in error/trace output
472    /// so a missing-resource failure can be pinned to the system that needs
473    /// it instead of just the resource name. Defaults to the type name of
474    /// the [`System`] impl; [`FunctionSystem`] overrides this with the name
475    /// of the wrapped function/closure, which is far more legible.
476    fn name(&self) -> &'static str {
477        std::any::type_name::<Self>()
478    }
479
480    /// This system's identity for ordering purposes — the [`TypeId`](std::any::TypeId)
481    /// of the function/closure it wraps. Automatic: every distinct function
482    /// or closure has a distinct type, so no manual labeling is needed to
483    /// make a system a valid target for another system's
484    /// [`after`](SystemOrderingExt::after)/[`before`](SystemOrderingExt::before).
485    /// Defaults to `Self`'s own `TypeId`; [`FunctionSystem`]/[`OnceFunctionSystem`]
486    /// override it with the wrapped function's `TypeId` instead of the
487    /// wrapper's, so ordering constraints referencing the bare function match.
488    fn ordering_id(&self) -> std::any::TypeId {
489        std::any::TypeId::of::<Self>()
490    }
491
492    /// Other systems in the same stage that must run before this one. Set
493    /// via [`SystemOrderingExt::after`]. A referenced system that isn't
494    /// registered in the same stage is silently ignored.
495    fn after_ids(&self) -> &[std::any::TypeId] {
496        &[]
497    }
498
499    /// Other systems in the same stage that must run after this one. Set
500    /// via [`SystemOrderingExt::before`]. A referenced system that isn't
501    /// registered in the same stage is silently ignored.
502    fn before_ids(&self) -> &[std::any::TypeId] {
503        &[]
504    }
505}
506
507/// Wraps a [`System`] with ordering constraints relative to other systems in
508/// the same stage, added via [`SystemOrderingExt`].
509///
510/// Constraints only take effect within the stage the system is registered
511/// to — there's no cross-stage ordering, since stage order is already fixed
512/// by [`SystemStage`](crate::app::SystemStage). [`App::build`](crate::app::App::build)
513/// topologically sorts each stage's systems by these constraints, breaking
514/// ties by registration order, and panics if constraints form a cycle.
515pub struct Labeled<S: System> {
516    inner: S,
517    after: Vec<std::any::TypeId>,
518    before: Vec<std::any::TypeId>,
519}
520
521impl<S: System> Labeled<S> {
522    /// Require that `system` runs before this one, within the same stage.
523    /// Chainable — call multiple times to depend on multiple systems.
524    pub fn after<F: 'static, Marker>(mut self, system: F) -> Self
525    where
526        F: IntoSystem<Marker>,
527    {
528        let _ = system;
529        self.after.push(std::any::TypeId::of::<F>());
530        self
531    }
532
533    /// Require that `system` runs after this one, within the same stage.
534    /// Chainable — call multiple times to constrain multiple systems.
535    pub fn before<F: 'static, Marker>(mut self, system: F) -> Self
536    where
537        F: IntoSystem<Marker>,
538    {
539        let _ = system;
540        self.before.push(std::any::TypeId::of::<F>());
541        self
542    }
543}
544
545impl<S: System> System for Labeled<S> {
546    fn run(&mut self, world: &hecs::World, resources: &Resources) {
547        self.inner.run(world, resources)
548    }
549
550    fn requires(&self) -> Vec<RequiredResource> {
551        self.inner.requires()
552    }
553
554    fn name(&self) -> &'static str {
555        self.inner.name()
556    }
557
558    fn ordering_id(&self) -> std::any::TypeId {
559        self.inner.ordering_id()
560    }
561
562    fn after_ids(&self) -> &[std::any::TypeId] {
563        &self.after
564    }
565
566    fn before_ids(&self) -> &[std::any::TypeId] {
567        &self.before
568    }
569}
570
571impl<S: System> IntoSystem<()> for Labeled<S> {
572    type System = Self;
573
574    fn into_system(self) -> Self::System {
575        self
576    }
577}
578
579/// Adds [`.after()`](SystemOrderingExt::after)/[`.before()`](SystemOrderingExt::before)
580/// to any system, for declaring run-order constraints relative to other
581/// systems in the same stage — referenced directly by their function/closure,
582/// no string labels needed.
583///
584/// ```ignore
585/// app.add_system(SystemStage::Update, physics_step);
586/// app.add_system(SystemStage::Update, apply_damage.after(physics_step));
587/// ```
588pub trait SystemOrderingExt<Marker>: IntoSystem<Marker> + Sized {
589    /// Require that `system` runs before this one, within the same stage.
590    fn after<F: 'static, Marker2>(self, system: F) -> Labeled<Self::System>
591    where
592        F: IntoSystem<Marker2>,
593    {
594        let _ = system;
595        Labeled {
596            inner: self.into_system(),
597            after: vec![std::any::TypeId::of::<F>()],
598            before: Vec::new(),
599        }
600    }
601
602    /// Require that `system` runs after this one, within the same stage.
603    fn before<F: 'static, Marker2>(self, system: F) -> Labeled<Self::System>
604    where
605        F: IntoSystem<Marker2>,
606    {
607        let _ = system;
608        Labeled {
609            inner: self.into_system(),
610            after: Vec::new(),
611            before: vec![std::any::TypeId::of::<F>()],
612        }
613    }
614}
615
616impl<T, Marker> SystemOrderingExt<Marker> for T where T: IntoSystem<Marker> {}
617
618/// Type-erased wrapper around a system function, created by [`IntoSystem`].
619///
620/// Holds `State`, the tuple of each parameter's [`SystemParam::State`] — this
621/// is where [`Local`] values actually live between calls to `run`.
622pub struct FunctionSystem<F, Marker, State = ()> {
623    pub func: F,
624    state: State,
625    _marker: std::marker::PhantomData<Marker>,
626}
627
628/// Converts a function (or closure) with valid system parameters into a
629/// [`System`] that can be registered with [`App::add_system`](crate::app::App::add_system).
630///
631/// Implemented via the [`impl_system!`] macro for function arities 0–8.
632pub trait IntoSystem<Marker> {
633    type System: System;
634
635    fn into_system(self) -> Self::System;
636}
637
638macro_rules! impl_system {
639    ($($param:ident),*) => {
640        impl<T, $($param),*> IntoSystem<($($param,)*)> for T
641        where
642            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
643            for<'a> &'a mut T: FnMut($($param),*),
644            $($param: SystemParam + 'static),*
645        {
646            type System = FunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
647
648            fn into_system(self) -> Self::System {
649                FunctionSystem {
650                    func: self,
651                    state: Default::default(),
652                    _marker: std::marker::PhantomData,
653                }
654            }
655        }
656
657        impl<T, $($param),*> System for FunctionSystem<T, ($($param,)*), ($($param::State,)*)>
658        where
659            T: for<'a> FnMut($($param::Item<'a>),*) + 'static,
660            $($param: SystemParam + 'static),*
661        {
662            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
663                #[allow(non_snake_case)]
664                let ($($param,)*) = &mut self.state;
665                (self.func)($($param::fetch($param, _world, _resources)),*);
666            }
667
668            fn requires(&self) -> Vec<RequiredResource> {
669                let mut _v = Vec::new();
670                $(_v.extend($param::requires());)*
671                _v
672            }
673
674            fn name(&self) -> &'static str {
675                std::any::type_name::<T>()
676            }
677
678            fn ordering_id(&self) -> std::any::TypeId {
679                std::any::TypeId::of::<T>()
680            }
681        }
682    };
683}
684
685impl_system!();
686impl_system!(A);
687impl_system!(A, B);
688impl_system!(A, B, C);
689impl_system!(A, B, C, D);
690impl_system!(A, B, C, D, E);
691impl_system!(A, B, C, D, E, F);
692impl_system!(A, B, C, D, E, F, G);
693impl_system!(A, B, C, D, E, F, G, H);
694impl_system!(A, B, C, D, E, F, G, H, I);
695impl_system!(A, B, C, D, E, F, G, H, I, J);
696impl_system!(A, B, C, D, E, F, G, H, I, J, K);
697impl_system!(A, B, C, D, E, F, G, H, I, J, K, L);
698
699/// Type-erased wrapper produced by [`OnceExt::once`]. Runs `func` every time
700/// it's invoked until `func` returns `Some(())`, at which point it's
701/// permanently retired — every subsequent invocation (and requirement check)
702/// is a no-op. The "have I already succeeded" bookkeeping lives entirely in
703/// `done`, hidden inside this wrapper; the wrapped function itself just
704/// returns `None` ("not ready, call me again") or `Some(())` ("done").
705pub struct OnceFunctionSystem<F, Marker, State = ()> {
706    func: F,
707    state: State,
708    done: bool,
709    _marker: std::marker::PhantomData<Marker>,
710}
711
712/// Adds [`.once()`](OnceExt::once) to a function/closure whose parameters
713/// are valid [`SystemParam`]s and whose return type is `Option<()>`,
714/// registering it as a system that runs on every tick of whichever stage
715/// it's added to until it returns `Some(())`, then never runs again.
716///
717/// This replaces manually tracking a "have I already done this" flag with
718/// a `Local<bool>`: return `None` from the function to mean "not ready,
719/// try again next tick" and `Some(())` to mean "done, retire me".
720///
721/// ```ignore
722/// fn setup(mut commands: Commands, pbr: Option<Res<PBR>>) -> Option<()> {
723///     let pbr = pbr?;
724///     if pbr.cubemap_material_inst == RawAssetHandle::default() {
725///         return None; // not ready yet — try again next tick
726///     }
727///     commands.spawn(/* ... */);
728///     Some(()) // done — never runs again
729/// }
730///
731/// app.add_system(SystemStage::PreUpdate, setup.once());
732/// ```
733pub trait OnceExt<Marker> {
734    type System: System;
735    fn once(self) -> Self::System;
736}
737
738macro_rules! impl_once_system {
739    ($($param:ident),*) => {
740        impl<T, $($param),*> OnceExt<($($param,)*)> for T
741        where
742            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
743            for<'a> &'a mut T: FnMut($($param),*) -> Option<()>,
744            $($param: SystemParam + 'static),*
745        {
746            type System = OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
747
748            fn once(self) -> Self::System {
749                OnceFunctionSystem {
750                    func: self,
751                    state: Default::default(),
752                    done: false,
753                    _marker: std::marker::PhantomData,
754                }
755            }
756        }
757
758        impl<T, $($param),*> IntoSystem<($($param,)*)> for OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
759        where
760            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
761            $($param: SystemParam + 'static),*
762        {
763            type System = Self;
764
765            fn into_system(self) -> Self::System {
766                self
767            }
768        }
769
770        impl<T, $($param),*> System for OnceFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
771        where
772            T: for<'a> FnMut($($param::Item<'a>),*) -> Option<()> + 'static,
773            $($param: SystemParam + 'static),*
774        {
775            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
776                if self.done {
777                    return;
778                }
779                #[allow(non_snake_case)]
780                let ($($param,)*) = &mut self.state;
781                let result = (self.func)($($param::fetch($param, _world, _resources)),*);
782                if result.is_some() {
783                    self.done = true;
784                }
785            }
786
787            fn requires(&self) -> Vec<RequiredResource> {
788                if self.done {
789                    return Vec::new();
790                }
791                let mut _v = Vec::new();
792                $(_v.extend($param::requires());)*
793                _v
794            }
795
796            fn name(&self) -> &'static str {
797                std::any::type_name::<T>()
798            }
799
800            fn ordering_id(&self) -> std::any::TypeId {
801                std::any::TypeId::of::<T>()
802            }
803        }
804    };
805}
806
807impl_once_system!();
808impl_once_system!(A);
809impl_once_system!(A, B);
810impl_once_system!(A, B, C);
811impl_once_system!(A, B, C, D);
812impl_once_system!(A, B, C, D, E);
813impl_once_system!(A, B, C, D, E, F);
814impl_once_system!(A, B, C, D, E, F, G);
815impl_once_system!(A, B, C, D, E, F, G, H);
816impl_once_system!(A, B, C, D, E, F, G, H, I);
817impl_once_system!(A, B, C, D, E, F, G, H, I, J);
818impl_once_system!(A, B, C, D, E, F, G, H, I, J, K);
819impl_once_system!(A, B, C, D, E, F, G, H, I, J, K, L);
820
821/// Type-erased wrapper produced by [`AsyncExt::detach`]. See that method's
822/// docs for the fire-and-forget semantics.
823pub struct DetachedFunctionSystem<F, Marker, State = ()> {
824    func: F,
825    state: State,
826    _marker: std::marker::PhantomData<Marker>,
827}
828
829/// Adds [`.detach()`](AsyncExt::detach) to a function/closure whose
830/// parameters are valid [`SystemParam`]s and which returns a
831/// `Future<Output = ()> + Send + 'static`, registering it as a system.
832///
833/// Each tick, the wrapped function is called synchronously like any other
834/// system — its `SystemParam`s (`Res`, `Query`, ...) are fetched and
835/// borrowed exactly as usual — but instead of doing work directly, it
836/// builds and returns a future (typically an `async move { .. }` block that
837/// has cloned or copied out whatever owned data it needs from those
838/// borrows). The scheduler then hands that future to
839/// [`BackgroundTasks::spawn_async`](crate::threading::BackgroundTasks::spawn_async)
840/// and moves on immediately — the future runs to completion on a worker
841/// thread, off the main loop, with no access to the `World`/`Resources`
842/// (which is exactly why it has to be `'static`: nothing borrowed from this
843/// tick is valid once the future outlives it).
844///
845/// A real `async fn` can't be used directly as the wrapped function here:
846/// its returned future borrows every one of its parameters by construction,
847/// so it's never `'static` on its own. Extract the owned pieces you need in
848/// the ordinary (synchronous) function body, then move only those into the
849/// `async move` block you return.
850///
851/// Fire-and-forget: nothing delivers the future's result back
852/// automatically, and a system that unconditionally detaches a new future
853/// every tick will spawn a new one every tick. If you need the result, or
854/// want to send only once, call [`BackgroundTasks::spawn_async`](crate::threading::BackgroundTasks::spawn_async) yourself
855/// inside an ordinary system (guarding with [`Local<bool>`](Local) or
856/// [`OnceExt::once`] as needed) and poll the returned
857/// [`TaskHandle`](crate::threading::TaskHandle) — same pattern already used
858/// for the async GPU backend init.
859///
860/// ```ignore
861/// fn load_level(tasks: Res<BackgroundTasks>) -> impl Future<Output = ()> + Send + 'static {
862///     let tasks = tasks.clone();
863///     async move {
864///         let bytes = std::fs::read("level.bin").unwrap();
865///         // ... process `bytes`, maybe tasks.spawn_blocking(...) more work ...
866///     }
867/// }
868///
869/// app.add_system(SystemStage::Update, load_level.detach());
870/// ```
871pub trait AsyncExt<Marker> {
872    type System: System;
873    fn detach(self) -> Self::System;
874}
875
876macro_rules! impl_async_system {
877    ($($param:ident),*) => {
878        impl<T, Fut, $($param),*> AsyncExt<($($param,)*)> for T
879        where
880            T: for<'a> FnMut($($param::Item<'a>),*) -> Fut + 'static,
881            for<'a> &'a mut T: FnMut($($param),*) -> Fut,
882            Fut: SpawnableFuture<()>,
883            $($param: SystemParam + 'static),*
884        {
885            type System = DetachedFunctionSystem<T, ($($param,)*), ($($param::State,)*)>;
886
887            fn detach(self) -> Self::System {
888                DetachedFunctionSystem {
889                    func: self,
890                    state: Default::default(),
891                    _marker: std::marker::PhantomData,
892                }
893            }
894        }
895
896        impl<T, Fut, $($param),*> IntoSystem<($($param,)*)> for DetachedFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
897        where
898            T: for<'a> FnMut($($param::Item<'a>),*) -> Fut + 'static,
899            Fut: SpawnableFuture<()>,
900            $($param: SystemParam + 'static),*
901        {
902            type System = Self;
903
904            fn into_system(self) -> Self::System {
905                self
906            }
907        }
908
909        impl<T, Fut, $($param),*> System for DetachedFunctionSystem<T, ($($param,)*), ($($param::State,)*)>
910        where
911            T: for<'a> FnMut($($param::Item<'a>),*) -> Fut + 'static,
912            Fut: SpawnableFuture<()>,
913            $($param: SystemParam + 'static),*
914        {
915            fn run(&mut self, _world: &hecs::World, _resources: &Resources) {
916                #[allow(non_snake_case)]
917                let ($($param,)*) = &mut self.state;
918                let future = (self.func)($($param::fetch($param, _world, _resources)),*);
919                let tasks = _resources.get_resource::<crate::threading::BackgroundTasks>(_world);
920                let _ = tasks.spawn_async(future);
921            }
922
923            fn requires(&self) -> Vec<RequiredResource> {
924                let mut _v = vec![RequiredResource {
925                    name: std::any::type_name::<crate::threading::BackgroundTasks>(),
926                    type_id: std::any::TypeId::of::<crate::threading::BackgroundTasks>(),
927                    present: |world, resources| resources.has_resource::<crate::threading::BackgroundTasks>(world),
928                    hint: Some(
929                        "`.detach()` drives its future through `BackgroundTasks` — register \
930                         `app.add_plugin(BackgroundTasksPlugin::new(worker_count))` before this system runs.",
931                    ),
932                }];
933                $(_v.extend($param::requires());)*
934                _v
935            }
936
937            fn name(&self) -> &'static str {
938                std::any::type_name::<T>()
939            }
940
941            fn ordering_id(&self) -> std::any::TypeId {
942                std::any::TypeId::of::<T>()
943            }
944        }
945    };
946}
947
948impl_async_system!();
949impl_async_system!(A);
950impl_async_system!(A, B);
951impl_async_system!(A, B, C);
952impl_async_system!(A, B, C, D);
953impl_async_system!(A, B, C, D, E);
954impl_async_system!(A, B, C, D, E, F);
955impl_async_system!(A, B, C, D, E, F, G);
956impl_async_system!(A, B, C, D, E, F, G, H);
957impl_async_system!(A, B, C, D, E, F, G, H, I);
958impl_async_system!(A, B, C, D, E, F, G, H, I, J);
959impl_async_system!(A, B, C, D, E, F, G, H, I, J, K);
960impl_async_system!(A, B, C, D, E, F, G, H, I, J, K, L);
961
962#[cfg(test)]
963mod tests {
964    use super::*;
965
966    struct Health(i32);
967    struct Enemy;
968    struct Dead;
969
970    fn make_query<Q: hecs::Query>(world: &hecs::World) -> Query<'_, Q> {
971        Query { world, borrow: world.query::<Q>(), scratch: None }
972    }
973
974    #[test]
975    fn iter_yields_every_matching_entity() {
976        let mut world = hecs::World::new();
977        world.spawn((Health(10),));
978        world.spawn((Health(20),));
979
980        let mut query = make_query::<&Health>(&world);
981        let mut totals: Vec<i32> = query.iter().map(|h| h.0).collect();
982        totals.sort();
983        assert_eq!(totals, vec![10, 20]);
984    }
985
986    #[test]
987    fn iter_composes_with_standard_iterator_adapters() {
988        // The whole point of returning a plain `impl Iterator` from `iter()` — `.filter(...)`
989        // (a value-level predicate) needs nothing beyond what `std::iter` already provides.
990        let mut world = hecs::World::new();
991        world.spawn((Health(5),));
992        world.spawn((Health(50),));
993
994        let mut query = make_query::<&Health>(&world);
995        let low_health_count = query.iter().filter(|h| h.0 < 10).count();
996        assert_eq!(low_health_count, 1);
997    }
998
999    #[test]
1000    fn get_returns_some_for_a_matching_entity_and_none_otherwise() {
1001        let mut world = hecs::World::new();
1002        let matching = world.spawn((Health(7),));
1003        let non_matching = world.spawn(()); // no Health
1004
1005        let mut query = make_query::<&Health>(&world);
1006        assert_eq!(query.get(matching).map(|h| h.0), Some(7));
1007        assert!(query.get(non_matching).is_none());
1008    }
1009
1010    #[test]
1011    fn get_can_be_called_more_than_once_on_the_same_query() {
1012        // Regression check for the `scratch` slot: a second `.get()` call must not panic or
1013        // reuse a stale `hecs::QueryOne` (which panics if `.get()` is called on it twice).
1014        let mut world = hecs::World::new();
1015        let a = world.spawn((Health(1),));
1016        let b = world.spawn((Health(2),));
1017
1018        let mut query = make_query::<&Health>(&world);
1019        assert_eq!(query.get(a).map(|h| h.0), Some(1));
1020        assert_eq!(query.get(b).map(|h| h.0), Some(2));
1021    }
1022
1023    #[test]
1024    fn with_and_without_chain_and_narrow_by_component_presence() {
1025        let mut world = hecs::World::new();
1026        let alive_enemy = world.spawn((Health(1), Enemy));
1027        world.spawn((Health(1), Enemy, Dead));
1028        world.spawn((Health(1),));
1029
1030        let query = make_query::<&Health>(&world);
1031        // Chained: still the engine's own `Query`, not a raw `hecs::QueryBorrow`/`With`/
1032        // `Without` — `.get`/`.iter` remain available after narrowing.
1033        let mut narrowed = query.with::<&Enemy>().without::<&Dead>();
1034
1035        assert_eq!(narrowed.iter().count(), 1);
1036        assert!(narrowed.get(alive_enemy).is_some());
1037    }
1038
1039    #[test]
1040    fn single_panics_on_zero_or_multiple_matches_get_single_does_not() {
1041        let mut world = hecs::World::new();
1042
1043        assert!(make_query::<&Health>(&world).get_single().is_none());
1044
1045        world.spawn((Health(1),));
1046        assert_eq!(make_query::<&Health>(&world).single().0, 1);
1047
1048        world.spawn((Health(2),));
1049        assert!(make_query::<&Health>(&world).get_single().is_none());
1050    }
1051}