goud_engine/ecs/system/function_system/core.rs
1//! Core types for the function system: [`FunctionSystem`] and [`SystemParamFunction`].
2//!
3//! This module provides the [`FunctionSystem`] type that wraps a Rust function
4//! and its associated state to implement the [`System`] trait. This allows
5//! ergonomic system definition using regular functions.
6//!
7//! # Architecture
8//!
9//! The function system architecture consists of:
10//!
11//! - [`FunctionSystem`]: Wraps a function and its cached parameter state
12//! - [`SystemParamFunction`]: Trait for functions that can be systems
13//! - [`IntoSystem`] implementations: Convert functions to boxed systems
14//!
15//! # Design
16//!
17//! Function systems use compile-time type information to:
18//! 1. Determine what parameters the function needs
19//! 2. Cache parameter state for efficient repeated extraction
20//! 3. Track component/resource access for conflict detection
21//! 4. Extract parameters from the World at runtime
22
23use std::borrow::Cow;
24use std::marker::PhantomData;
25
26use crate::ecs::query::Access;
27use crate::ecs::system::{
28 BoxedSystem, IntoSystem, System, SystemMeta, SystemParam, SystemParamState,
29};
30use crate::ecs::World;
31
32// =============================================================================
33// FunctionSystem
34// =============================================================================
35
36/// A system that wraps a function and its cached parameter state.
37///
38/// `FunctionSystem` stores:
39/// - The function to call
40/// - Cached state for each parameter
41/// - Metadata (name, access patterns)
42///
43/// This type is created automatically when you call `.into_system()` on a
44/// function. You typically don't construct it directly.
45///
46/// # Type Parameters
47///
48/// - `Marker`: A type-level marker to distinguish different function arities
49/// - `F`: The function type
50///
51/// # Example
52///
53/// ```
54/// use goud_engine::ecs::{World, Component};
55/// use goud_engine::ecs::system::{IntoSystem, BoxedSystem};
56///
57/// fn my_system() {
58/// println!("Hello from system!");
59/// }
60///
61/// let boxed: BoxedSystem = my_system.into_system();
62/// assert_eq!(boxed.name(), "my_system");
63/// ```
64pub struct FunctionSystem<Marker, F>
65where
66 F: SystemParamFunction<Marker>,
67{
68 /// The wrapped function.
69 func: F,
70 /// Cached parameter state.
71 state: Option<F::State>,
72 /// System metadata.
73 meta: SystemMeta,
74 /// Marker for the function type.
75 _marker: PhantomData<fn() -> Marker>,
76}
77
78impl<Marker, F> FunctionSystem<Marker, F>
79where
80 F: SystemParamFunction<Marker>,
81{
82 /// Creates a new function system from a function.
83 ///
84 /// The state is initialized lazily on the first run.
85 ///
86 /// # Arguments
87 ///
88 /// * `func` - The function to wrap
89 #[inline]
90 pub fn new(func: F) -> Self {
91 Self {
92 func,
93 state: None,
94 meta: SystemMeta::new(std::any::type_name::<F>()),
95 _marker: PhantomData,
96 }
97 }
98
99 /// Sets a custom name for this system.
100 ///
101 /// By default, the name is derived from the function's type name.
102 ///
103 /// # Arguments
104 ///
105 /// * `name` - The new name for the system
106 #[inline]
107 pub fn with_name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
108 self.meta.set_name(name);
109 self
110 }
111}
112
113impl<Marker, F> System for FunctionSystem<Marker, F>
114where
115 Marker: 'static,
116 F: SystemParamFunction<Marker> + Send + 'static,
117 F::State: Send + Sync,
118{
119 fn name(&self) -> &'static str {
120 // Extract the short function name from the full path
121 let full_name = std::any::type_name::<F>();
122 // Find the last segment after ::
123 full_name.rsplit("::").next().unwrap_or(full_name)
124 }
125
126 fn component_access(&self) -> Access {
127 self.meta.component_access().clone()
128 }
129
130 fn initialize(&mut self, world: &mut World) {
131 // Initialize state if not already done
132 if self.state.is_none() {
133 let state = F::State::init(world);
134
135 // Update access patterns from state
136 let access = F::build_access(&state);
137 self.meta.set_component_access(access);
138
139 self.state = Some(state);
140 }
141 }
142
143 fn run(&mut self, world: &mut World) {
144 // Ensure state is initialized
145 if self.state.is_none() {
146 self.initialize(world);
147 }
148
149 // Get mutable reference to state
150 let state = self.state.as_mut().expect("State should be initialized");
151
152 // Run the function with extracted parameters
153 // SAFETY: We have exclusive access to world through &mut World.
154 // The SystemParamFunction implementation is responsible for ensuring
155 // that parameter access doesn't alias.
156 unsafe { self.func.run_unsafe(state, world) };
157
158 // Apply any pending state changes (like command buffers)
159 state.apply(world);
160 }
161
162 fn is_read_only(&self) -> bool {
163 self.meta.is_read_only()
164 }
165}
166
167impl<Marker, F> std::fmt::Debug for FunctionSystem<Marker, F>
168where
169 F: SystemParamFunction<Marker>,
170{
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("FunctionSystem")
173 .field("name", &std::any::type_name::<F>())
174 .field("initialized", &self.state.is_some())
175 .finish()
176 }
177}
178
179// =============================================================================
180// SystemParamFunction Trait
181// =============================================================================
182
183/// Trait for functions that can be used as systems.
184///
185/// This trait connects a function signature to its:
186/// - Parameter type (implementing [`SystemParam`])
187/// - Cached state type (implementing [`SystemParamState`])
188/// - Execution method
189///
190/// This trait is automatically implemented for functions with valid
191/// system parameter signatures. You don't need to implement it manually.
192///
193/// # Type Parameters
194///
195/// - `Marker`: A marker type used to disambiguate different function arities
196///
197/// # Safety
198///
199/// Implementations must ensure that parameter access patterns are accurately
200/// reported to enable safe parallel execution.
201pub trait SystemParamFunction<Marker>: Send + 'static {
202 /// The combined system parameter type for all function parameters.
203 type Param: SystemParam;
204
205 /// The combined state type for all parameters.
206 type State: SystemParamState;
207
208 /// Builds the access pattern for this function's parameters.
209 fn build_access(state: &Self::State) -> Access;
210
211 /// Runs the function with parameters extracted from the world.
212 ///
213 /// # Safety
214 ///
215 /// The caller must ensure exclusive access to the world. The implementation
216 /// uses unsafe code internally to extract multiple parameters, which is safe
217 /// because the access patterns are tracked and conflict detection ensures
218 /// no aliasing occurs at runtime.
219 ///
220 /// # Arguments
221 ///
222 /// * `state` - Mutable reference to cached parameter state
223 /// * `world` - Mutable reference to the world
224 unsafe fn run_unsafe(&mut self, state: &mut Self::State, world: &mut World);
225}
226
227// =============================================================================
228// IntoSystem impl for zero-parameter functions
229// =============================================================================
230
231/// Marker type for functions with no parameters.
232pub struct FnMarker;
233
234impl<F> SystemParamFunction<FnMarker> for F
235where
236 F: FnMut() + Send + 'static,
237{
238 type Param = ();
239 type State = ();
240
241 #[inline]
242 fn build_access(_state: &Self::State) -> Access {
243 Access::new()
244 }
245
246 #[inline]
247 unsafe fn run_unsafe(&mut self, _state: &mut Self::State, _world: &mut World) {
248 self();
249 }
250}
251
252impl<F> IntoSystem<(FnMarker,)> for F
253where
254 F: FnMut() + Send + 'static,
255{
256 type System = FunctionSystem<FnMarker, F>;
257
258 #[inline]
259 fn into_system(self) -> BoxedSystem {
260 BoxedSystem::new(FunctionSystem::new(self))
261 }
262}