bevy_ecs/system/
exclusive_function_system.rs

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