Skip to main content

bevy_ecs/system/
exclusive_function_system.rs

1use crate::{
2    change_detection::{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, 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    fn initialize(&mut self, world: &mut World) -> FilteredAccessSet {
171        self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
172        self.param_state = Some(F::Param::init(world, &mut self.system_meta));
173        FilteredAccessSet::new()
174    }
175
176    #[inline]
177    fn check_change_tick(&mut self, check: CheckChangeTicks) {
178        check_system_change_tick(
179            &mut self.system_meta.last_run,
180            check,
181            self.system_meta.name.clone(),
182        );
183    }
184
185    fn default_system_sets(&self) -> Vec<InternedSystemSet> {
186        let set = crate::schedule::SystemTypeSet::<F>::new();
187        vec![set.intern()]
188    }
189
190    fn get_last_run(&self) -> Tick {
191        self.system_meta.last_run
192    }
193
194    fn set_last_run(&mut self, last_run: Tick) {
195        self.system_meta.last_run = last_run;
196    }
197}
198
199/// A trait implemented for all exclusive system functions that can be used as [`System`]s.
200///
201/// This trait can be useful for making your own systems which accept other systems,
202/// sometimes called higher order systems.
203#[diagnostic::on_unimplemented(
204    message = "`{Self}` is not an exclusive system",
205    label = "invalid system"
206)]
207pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
208    /// The input type to this system. See [`System::In`].
209    type In: SystemInput;
210
211    /// The return type of this system. See [`System::Out`].
212    type Out;
213
214    /// The [`ExclusiveSystemParam`]'s defined by this system's `fn` parameters.
215    type Param: ExclusiveSystemParam;
216
217    /// Executes this system once. See [`System::run`].
218    fn run(
219        &mut self,
220        world: &mut World,
221        input: <Self::In as SystemInput>::Inner<'_>,
222        param_value: ExclusiveSystemParamItem<Self::Param>,
223    ) -> Self::Out;
224}
225
226/// A marker type used to distinguish exclusive function systems with and without input.
227#[doc(hidden)]
228pub struct HasExclusiveSystemInput;
229
230macro_rules! impl_exclusive_system_function {
231    ($($param: ident),*) => {
232        #[expect(
233            clippy::allow_attributes,
234            reason = "This is within a macro, and as such, the below lints may not always apply."
235        )]
236        #[allow(
237            non_snake_case,
238            reason = "Certain variable names are provided by the caller, not by us."
239        )]
240        impl<Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<fn($($param,)*) -> Out> for Func
241        where
242            Func: Send + Sync + 'static,
243            for <'a> &'a mut Func:
244                FnMut(&mut World, $($param),*) -> Out +
245                FnMut(&mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
246            Out: 'static,
247        {
248            type In = ();
249            type Out = Out;
250            type Param = ($($param,)*);
251            #[inline]
252            fn run(&mut self, world: &mut World, _in: (), param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
253                // Yes, this is strange, but `rustc` fails to compile this impl
254                // without using this function. It fails to recognize that `func`
255                // is a function, potentially because of the multiple impls of `FnMut`
256                fn call_inner<Out, $($param,)*>(
257                    mut f: impl FnMut(&mut World, $($param,)*) -> Out,
258                    world: &mut World,
259                    $($param: $param,)*
260                ) -> Out {
261                    f(world, $($param,)*)
262                }
263                let ($($param,)*) = param_value;
264                call_inner(self, world, $($param),*)
265            }
266        }
267
268        #[expect(
269            clippy::allow_attributes,
270            reason = "This is within a macro, and as such, the below lints may not always apply."
271        )]
272        #[allow(
273            non_snake_case,
274            reason = "Certain variable names are provided by the caller, not by us."
275        )]
276        impl<In, Out, Func, $($param: ExclusiveSystemParam),*> ExclusiveSystemParamFunction<(HasExclusiveSystemInput, fn(In, $($param,)*) -> Out)> for Func
277        where
278            Func: Send + Sync + 'static,
279            for <'a> &'a mut Func:
280                FnMut(In, &mut World, $($param),*) -> Out +
281                FnMut(In::Param<'_>, &mut World, $(ExclusiveSystemParamItem<$param>),*) -> Out,
282            In: SystemInput + 'static,
283            Out: 'static,
284        {
285            type In = In;
286            type Out = Out;
287            type Param = ($($param,)*);
288            #[inline]
289            fn run(&mut self, world: &mut World, input: In::Inner<'_>, param_value: ExclusiveSystemParamItem< ($($param,)*)>) -> Out {
290                // Yes, this is strange, but `rustc` fails to compile this impl
291                // without using this function. It fails to recognize that `func`
292                // is a function, potentially because of the multiple impls of `FnMut`
293                fn call_inner<In: SystemInput, Out, $($param,)*>(
294                    _: PhantomData<In>,
295                    mut f: impl FnMut(In::Param<'_>, &mut World, $($param,)*) -> Out,
296                    input: In::Inner<'_>,
297                    world: &mut World,
298                    $($param: $param,)*
299                ) -> Out {
300                    f(In::wrap(input), world, $($param,)*)
301                }
302                let ($($param,)*) = param_value;
303                call_inner(PhantomData::<In>, self, input, world, $($param),*)
304            }
305        }
306    };
307}
308// Note that we rely on the highest impl to be <= the highest order of the tuple impls
309// of `SystemParam` created.
310all_tuples!(impl_exclusive_system_function, 0, 16, F);
311
312#[cfg(test)]
313mod tests {
314    use crate::system::input::SystemInput;
315
316    use super::*;
317
318    #[test]
319    fn into_system_type_id_consistency() {
320        fn test<T, In: SystemInput, Out, Marker>(function: T)
321        where
322            T: IntoSystem<In, Out, Marker> + Copy,
323        {
324            fn reference_system(_world: &mut World) {}
325
326            use core::any::TypeId;
327
328            let system = IntoSystem::into_system(function);
329
330            assert_eq!(
331                system.system_type(),
332                function.system_type_id(),
333                "System::system_type should be consistent with IntoSystem::system_type_id"
334            );
335
336            assert_eq!(
337                system.system_type(),
338                TypeId::of::<T::System>(),
339                "System::system_type should be consistent with TypeId::of::<T::System>()"
340            );
341
342            assert_ne!(
343                system.system_type(),
344                IntoSystem::into_system(reference_system).system_type(),
345                "Different systems should have different TypeIds"
346            );
347        }
348
349        fn exclusive_function_system(_world: &mut World) {}
350
351        test(exclusive_function_system);
352    }
353}