bevy_ecs/system/
exclusive_function_system.rs

1use crate::{
2    archetype::ArchetypeComponentId,
3    component::{ComponentId, Tick},
4    query::Access,
5    schedule::{InternedSystemSet, SystemSet},
6    system::{
7        check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
8        System, SystemIn, SystemInput, SystemMeta,
9    },
10    world::{unsafe_world_cell::UnsafeWorldCell, World},
11};
12
13use alloc::{borrow::Cow, vec, vec::Vec};
14use core::marker::PhantomData;
15use variadics_please::all_tuples;
16
17use super::SystemParamValidationError;
18
19/// A function system that runs with exclusive [`World`] access.
20///
21/// You get this by calling [`IntoSystem::into_system`]  on a function that only accepts
22/// [`ExclusiveSystemParam`]s.
23///
24/// [`ExclusiveFunctionSystem`] must be `.initialized` before they can be run.
25pub struct ExclusiveFunctionSystem<Marker, F>
26where
27    F: ExclusiveSystemParamFunction<Marker>,
28{
29    func: F,
30    param_state: Option<<F::Param as ExclusiveSystemParam>::State>,
31    system_meta: SystemMeta,
32    // NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
33    marker: PhantomData<fn() -> Marker>,
34}
35
36impl<Marker, F> ExclusiveFunctionSystem<Marker, F>
37where
38    F: ExclusiveSystemParamFunction<Marker>,
39{
40    /// Return this system with a new name.
41    ///
42    /// Useful to give closure systems more readable and unique names for debugging and tracing.
43    pub fn with_name(mut self, new_name: impl Into<Cow<'static, str>>) -> Self {
44        self.system_meta.set_name(new_name.into());
45        self
46    }
47}
48
49/// A marker type used to distinguish exclusive function systems from regular function systems.
50#[doc(hidden)]
51pub struct IsExclusiveFunctionSystem;
52
53impl<Marker, F> IntoSystem<F::In, F::Out, (IsExclusiveFunctionSystem, Marker)> for F
54where
55    Marker: 'static,
56    F: ExclusiveSystemParamFunction<Marker>,
57{
58    type System = ExclusiveFunctionSystem<Marker, F>;
59    fn into_system(func: Self) -> Self::System {
60        ExclusiveFunctionSystem {
61            func,
62            param_state: None,
63            system_meta: SystemMeta::new::<F>(),
64            marker: PhantomData,
65        }
66    }
67}
68
69const PARAM_MESSAGE: &str = "System's param_state was not found. Did you forget to initialize this system before running it?";
70
71impl<Marker, F> System for ExclusiveFunctionSystem<Marker, F>
72where
73    Marker: 'static,
74    F: ExclusiveSystemParamFunction<Marker>,
75{
76    type In = F::In;
77    type Out = F::Out;
78
79    #[inline]
80    fn name(&self) -> Cow<'static, str> {
81        self.system_meta.name.clone()
82    }
83
84    #[inline]
85    fn component_access(&self) -> &Access<ComponentId> {
86        self.system_meta.component_access_set.combined_access()
87    }
88
89    #[inline]
90    fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
91        &self.system_meta.archetype_component_access
92    }
93
94    #[inline]
95    fn is_send(&self) -> bool {
96        // exclusive systems should have access to non-send resources
97        // the executor runs exclusive systems on the main thread, so this
98        // field reflects that constraint
99        false
100    }
101
102    #[inline]
103    fn is_exclusive(&self) -> bool {
104        true
105    }
106
107    #[inline]
108    fn has_deferred(&self) -> bool {
109        // exclusive systems have no deferred system params
110        false
111    }
112
113    #[inline]
114    unsafe fn run_unsafe(
115        &mut self,
116        input: SystemIn<'_, Self>,
117        world: UnsafeWorldCell,
118    ) -> Self::Out {
119        // SAFETY: The safety is upheld by the caller.
120        let world = unsafe { world.world_mut() };
121        world.last_change_tick_scope(self.system_meta.last_run, |world| {
122            #[cfg(feature = "trace")]
123            let _span_guard = self.system_meta.system_span.enter();
124
125            let params = F::Param::get_param(
126                self.param_state.as_mut().expect(PARAM_MESSAGE),
127                &self.system_meta,
128            );
129            let out = self.func.run(world, input, params);
130
131            world.flush();
132            self.system_meta.last_run = world.increment_change_tick();
133
134            out
135        })
136    }
137
138    #[inline]
139    fn apply_deferred(&mut self, _world: &mut World) {
140        // "pure" exclusive systems do not have any buffers to apply.
141        // Systems made by piping a normal system with an exclusive system
142        // might have buffers to apply, but this is handled by `PipeSystem`.
143    }
144
145    #[inline]
146    fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {
147        // "pure" exclusive systems do not have any buffers to apply.
148        // Systems made by piping a normal system with an exclusive system
149        // might have buffers to apply, but this is handled by `PipeSystem`.
150    }
151
152    #[inline]
153    unsafe fn validate_param_unsafe(
154        &mut self,
155        _world: UnsafeWorldCell,
156    ) -> Result<(), SystemParamValidationError> {
157        // All exclusive system params are always available.
158        Ok(())
159    }
160
161    #[inline]
162    fn initialize(&mut self, world: &mut World) {
163        self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
164        self.param_state = Some(F::Param::init(world, &mut self.system_meta));
165    }
166
167    fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {}
168
169    #[inline]
170    fn check_change_tick(&mut self, change_tick: Tick) {
171        check_system_change_tick(
172            &mut self.system_meta.last_run,
173            change_tick,
174            self.system_meta.name.as_ref(),
175        );
176    }
177
178    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
179        let set = crate::schedule::SystemTypeSet::<Self>::new();
180        vec![set.intern()]
181    }
182
183    fn get_last_run(&self) -> Tick {
184        self.system_meta.last_run
185    }
186
187    fn set_last_run(&mut self, last_run: Tick) {
188        self.system_meta.last_run = last_run;
189    }
190}
191
192/// A trait implemented for all exclusive system functions that can be used as [`System`]s.
193///
194/// This trait can be useful for making your own systems which accept other systems,
195/// sometimes called higher order systems.
196#[diagnostic::on_unimplemented(
197    message = "`{Self}` is not an exclusive system",
198    label = "invalid system"
199)]
200pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
201    /// The input type to this system. See [`System::In`].
202    type In: SystemInput;
203
204    /// The return type of this system. See [`System::Out`].
205    type Out;
206
207    /// The [`ExclusiveSystemParam`]'s defined by this system's `fn` parameters.
208    type Param: ExclusiveSystemParam;
209
210    /// Executes this system once. See [`System::run`].
211    fn run(
212        &mut self,
213        world: &mut World,
214        input: <Self::In as SystemInput>::Inner<'_>,
215        param_value: ExclusiveSystemParamItem<Self::Param>,
216    ) -> Self::Out;
217}
218
219/// A marker type used to distinguish exclusive function systems with and without input.
220#[doc(hidden)]
221pub struct HasExclusiveSystemInput;
222
223macro_rules! impl_exclusive_system_function {
224    ($($param: ident),*) => {
225        #[expect(
226            clippy::allow_attributes,
227            reason = "This is within a macro, and as such, the below lints may not always apply."
228        )]
229        #[allow(
230            non_snake_case,
231            reason = "Certain variable names are provided by the caller, not by us."
232        )]
233        impl<Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn($($param,)*) -> Out> for Func
234        where
235            Func: Send + Sync + 'static,
236            for <'a> &'a mut Func:
237                FnMut(&mut World, $($param),*) -> Out +
238                FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
239            Out: 'static,
240        {
241            type In = ();
242            type Out = Out;
243            type Param = ($($param,)*);
244            #[inline]
245            fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
246                // Yes, this is strange, but `rustc` fails to compile this impl
247                // without using this function. It fails to recognize that `func`
248                // is a function, potentially because of the multiple impls of `FnMut`
249                fn call_inner<Out, $($param,)*>(
250                    mut f: impl FnMut(&mut World, $($param,)*) -> Out,
251                    world: &mut World,
252                    $($param: $param,)*
253                ) -> Out {
254                    f(world, $($param,)*)
255                }
256                let ($($param,)*) = param_value;
257                call_inner(self, world, $($param),*)
258            }
259        }
260
261        #[expect(
262            clippy::allow_attributes,
263            reason = "This is within a macro, and as such, the below lints may not always apply."
264        )]
265        #[allow(
266            non_snake_case,
267            reason = "Certain variable names are provided by the caller, not by us."
268        )]
269        impl<In, Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func
270        where
271            Func: Send + Sync + 'static,
272            for <'a> &'a mut Func:
273                FnMut(In, &mut World, $($param),*) -> Out +
274                FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
275            In: SystemInput + 'static,
276            Out: 'static,
277        {
278            type In = In;
279            type Out = Out;
280            type Param = ($($param,)*);
281            #[inline]
282            fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
283                // Yes, this is strange, but `rustc` fails to compile this impl
284                // without using this function. It fails to recognize that `func`
285                // is a function, potentially because of the multiple impls of `FnMut`
286                fn call_inner<In: SystemInput, Out, $($param,)*>(
287                    _: PhantomData<In>,
288                    mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out,
289                    input: In::Inner<'_>,
290                    world: &mut World,
291                    $($param: $param,)*
292                ) -> Out {
293                    f(In::wrap(input), world, $($param,)*)
294                }
295                let ($($param,)*) = param_value;
296                call_inner(PhantomData::<In>, self, input, world, $($param),*)
297            }
298        }
299    };
300}
301// Note that we rely on the highest impl to be <= the highest order of the tuple impls
302// of `SystemParam` created.
303all_tuples!(impl_exclusive_system_function, 0, 16, F);
304
305#[cfg(test)]
306mod tests {
307    use crate::system::input::SystemInput;
308
309    use super::*;
310
311    #[test]
312    fn into_system_type_id_consistency() {
313        fn test<T, In: SystemInput, Out, Marker>(function: T)
314        where
315            T: IntoSystem<In, Out, Marker> + Copy,
316        {
317            fn reference_system(_world: &mut World) {}
318
319            use core::any::TypeId;
320
321            let system = IntoSystem::into_system(function);
322
323            assert_eq!(
324                system.type_id(),
325                function.system_type_id(),
326                "System::type_id should be consistent with IntoSystem::system_type_id"
327            );
328
329            assert_eq!(
330                system.type_id(),
331                TypeId::of::<T::System>(),
332                "System::type_id should be consistent with TypeId::of::<T::System>()"
333            );
334
335            assert_ne!(
336                system.type_id(),
337                IntoSystem::into_system(reference_system).type_id(),
338                "Different systems should have different TypeIds"
339            );
340        }
341
342        fn exclusive_function_system(_world: &mut World) {}
343
344        test(exclusive_function_system);
345    }
346}