Skip to main content

goud_engine/ecs/system/function_system/
impls.rs

1//! Macro-generated [`SystemParamFunction`] and [`IntoSystem`] implementations
2//! for functions with 1 to 8 parameters.
3//!
4//! These implementations allow regular Rust functions with up to 8 system
5//! parameters to be converted into boxed systems via [`IntoSystem`].
6//!
7//! # Safety
8//!
9//! Multi-parameter implementations use raw pointer aliasing to work around
10//! Rust's borrow checker when extracting multiple parameters from the same
11//! `World`. This is safe because:
12//! 1. We hold exclusive `&mut World` access.
13//! 2. Access tracking ensures no two parameters alias the same component
14//!    storage.
15//! 3. Each `SystemParam` implementation is responsible for accessing
16//!    disjoint data.
17
18use std::marker::PhantomData;
19
20use crate::ecs::query::Access;
21use crate::ecs::system::{BoxedSystem, IntoSystem, SystemParam};
22use crate::ecs::World;
23
24use super::core::{FunctionSystem, SystemParamFunction};
25
26// =============================================================================
27// Marker types for function arities 1–8
28// =============================================================================
29
30/// Marker type for functions with 1 parameter.
31pub struct FnMarker1<P>(PhantomData<P>);
32/// Marker type for functions with 2 parameters.
33pub struct FnMarker2<P1, P2>(PhantomData<(P1, P2)>);
34/// Marker type for functions with 3 parameters.
35pub struct FnMarker3<P1, P2, P3>(PhantomData<(P1, P2, P3)>);
36/// Marker type for functions with 4 parameters.
37pub struct FnMarker4<P1, P2, P3, P4>(PhantomData<(P1, P2, P3, P4)>);
38/// Marker type for functions with 5 parameters.
39pub struct FnMarker5<P1, P2, P3, P4, P5>(PhantomData<(P1, P2, P3, P4, P5)>);
40/// Marker type for functions with 6 parameters.
41pub struct FnMarker6<P1, P2, P3, P4, P5, P6>(PhantomData<(P1, P2, P3, P4, P5, P6)>);
42/// Marker type for functions with 7 parameters.
43pub struct FnMarker7<P1, P2, P3, P4, P5, P6, P7>(PhantomData<(P1, P2, P3, P4, P5, P6, P7)>);
44/// Marker type for functions with 8 parameters.
45pub struct FnMarker8<P1, P2, P3, P4, P5, P6, P7, P8>(PhantomData<(P1, P2, P3, P4, P5, P6, P7, P8)>);
46
47// =============================================================================
48// 1-parameter macro
49// =============================================================================
50
51macro_rules! impl_system_param_function_1 {
52    ($marker:ident, $param:ident) => {
53        #[allow(non_snake_case)]
54        impl<F, $param: SystemParam + 'static> SystemParamFunction<$marker<$param>> for F
55        where
56            F: FnMut($param) + Send + 'static,
57            for<'w, 's> F: FnMut($param::Item<'w, 's>),
58            $param::State: Send + Sync + Clone + 'static,
59        {
60            type Param = $param;
61            type State = $param::State;
62
63            #[inline]
64            fn build_access(state: &Self::State) -> Access {
65                let mut access = Access::new();
66                $param::update_access(state, &mut access);
67                access
68            }
69
70            #[inline]
71            unsafe fn run_unsafe(&mut self, state: &mut Self::State, world: &mut World) {
72                // SAFETY: We have exclusive access to world. The access patterns
73                // registered in build_access ensure no aliasing with other systems.
74                let world_ptr = world as *mut World;
75                let param = $param::get_param_mut(state, &mut *world_ptr);
76                self(param);
77            }
78        }
79
80        impl<F, $param: SystemParam + 'static> IntoSystem<($marker<$param>,)> for F
81        where
82            F: FnMut($param) + Send + 'static,
83            for<'w, 's> F: FnMut($param::Item<'w, 's>),
84            $param::State: Send + Sync + Clone + 'static,
85        {
86            type System = FunctionSystem<$marker<$param>, F>;
87
88            #[inline]
89            fn into_system(self) -> BoxedSystem {
90                BoxedSystem::new(FunctionSystem::new(self))
91            }
92        }
93    };
94}
95
96impl_system_param_function_1!(FnMarker1, P1);
97
98// =============================================================================
99// 2–8 parameter macro
100// =============================================================================
101
102// Macro to implement for functions with 2+ parameters.
103// These require unsafe pointer manipulation to work around Rust's borrow rules.
104// This is safe because:
105// 1. We have exclusive access to the world (&mut World)
106// 2. The access tracking ensures systems don't run in parallel if they conflict
107// 3. Each parameter accesses disjoint data (tracked by ComponentId/ResourceId)
108macro_rules! impl_system_param_function_multi {
109    ($marker:ident $(, $param:ident)* ; $($state_name:ident)*) => {
110        #[allow(non_snake_case)]
111        #[allow(unused_parens)]
112        impl<F, $($param: SystemParam + 'static),*> SystemParamFunction<$marker<$($param),*>> for F
113        where
114            F: FnMut($($param),*) + Send + 'static,
115            for<'w, 's> F: FnMut($($param::Item<'w, 's>),*),
116            $($param::State: Send + Sync + Clone + 'static,)*
117        {
118            type Param = ($($param,)*);
119            type State = ($($param::State,)*);
120
121            #[inline]
122            fn build_access(state: &Self::State) -> Access {
123                let mut access = Access::new();
124                let ($($state_name,)*) = state;
125                $($param::update_access($state_name, &mut access);)*
126                access
127            }
128
129            #[inline]
130            unsafe fn run_unsafe(&mut self, state: &mut Self::State, world: &mut World) {
131                // SAFETY: We have exclusive access to world through &mut World.
132                // The parameter access patterns are tracked and conflict detection
133                // ensures this system doesn't run in parallel with conflicting systems.
134                // Each parameter type is responsible for accessing disjoint data.
135                let world_ptr = world as *mut World;
136                let ($($state_name,)*) = state;
137                $(let $param = $param::get_param_mut($state_name, &mut *world_ptr);)*
138                self($($param),*);
139            }
140        }
141
142        impl<F, $($param: SystemParam + 'static),*> IntoSystem<($marker<$($param),*>,)> for F
143        where
144            F: FnMut($($param),*) + Send + 'static,
145            for<'w, 's> F: FnMut($($param::Item<'w, 's>),*),
146            $($param::State: Send + Sync + Clone + 'static,)*
147        {
148            type System = FunctionSystem<$marker<$($param),*>, F>;
149
150            #[inline]
151            fn into_system(self) -> BoxedSystem {
152                BoxedSystem::new(FunctionSystem::new(self))
153            }
154        }
155    };
156}
157
158// Implement for 2–8 parameters with unique state variable names
159impl_system_param_function_multi!(FnMarker2, P1, P2; s1 s2);
160impl_system_param_function_multi!(FnMarker3, P1, P2, P3; s1 s2 s3);
161impl_system_param_function_multi!(FnMarker4, P1, P2, P3, P4; s1 s2 s3 s4);
162impl_system_param_function_multi!(FnMarker5, P1, P2, P3, P4, P5; s1 s2 s3 s4 s5);
163impl_system_param_function_multi!(FnMarker6, P1, P2, P3, P4, P5, P6; s1 s2 s3 s4 s5 s6);
164impl_system_param_function_multi!(FnMarker7, P1, P2, P3, P4, P5, P6, P7; s1 s2 s3 s4 s5 s6 s7);
165impl_system_param_function_multi!(FnMarker8, P1, P2, P3, P4, P5, P6, P7, P8; s1 s2 s3 s4 s5 s6 s7 s8);