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