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