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
20pub 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 marker: PhantomData<fn() -> (Marker, Out)>,
37}
38
39impl<Marker, Out, F> ExclusiveFunctionSystem<Marker, Out, F>
40where
41 F: ExclusiveSystemParamFunction<Marker>,
42{
43 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#[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 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 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 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 }
161
162 #[inline]
163 fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {
164 }
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#[diagnostic::on_unimplemented(
204 message = "`{Self}` is not an exclusive system",
205 label = "invalid system"
206)]
207pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
208 type In: SystemInput;
210
211 type Out;
213
214 type Param: ExclusiveSystemParam;
216
217 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#[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 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 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}
308all_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}