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
19pub 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 marker: PhantomData<fn() -> Marker>,
34}
35
36impl<Marker, F> ExclusiveFunctionSystem<Marker, F>
37where
38 F: ExclusiveSystemParamFunction<Marker>,
39{
40 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#[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 false
100 }
101
102 #[inline]
103 fn is_exclusive(&self) -> bool {
104 true
105 }
106
107 #[inline]
108 fn has_deferred(&self) -> bool {
109 false
111 }
112
113 #[inline]
114 unsafe fn run_unsafe(
115 &mut self,
116 input: SystemIn<'_, Self>,
117 world: UnsafeWorldCell,
118 ) -> Self::Out {
119 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 }
144
145 #[inline]
146 fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {
147 }
151
152 #[inline]
153 unsafe fn validate_param_unsafe(
154 &mut self,
155 _world: UnsafeWorldCell,
156 ) -> Result<(), SystemParamValidationError> {
157 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#[diagnostic::on_unimplemented(
197 message = "`{Self}` is not an exclusive system",
198 label = "invalid system"
199)]
200pub trait ExclusiveSystemParamFunction<Marker>: Send + Sync + 'static {
201 type In: SystemInput;
203
204 type Out;
206
207 type Param: ExclusiveSystemParam;
209
210 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#[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 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 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}
301all_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}