Skip to main content

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}