Skip to main content

galeon_engine/
system_param.rs

1// SPDX-License-Identifier: AGPL-3.0-only OR Commercial
2
3use std::any::TypeId;
4use std::ops::{Deref, DerefMut};
5
6use crate::component::Component;
7use crate::entity::Entity;
8use crate::query::{Mut, QueryIter, QueryIterMut};
9use crate::world::UnsafeWorldCell;
10
11// =============================================================================
12// Access — describes what a system parameter touches
13// =============================================================================
14
15/// Describes what a system parameter accesses — used for conflict detection.
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub enum Access {
18    /// Shared read of a resource.
19    ResRead(TypeId),
20    /// Exclusive write of a resource.
21    ResWrite(TypeId),
22    /// Shared read of a component type.
23    CompRead(TypeId),
24    /// Exclusive write of a component type.
25    CompWrite(TypeId),
26}
27
28impl Access {
29    /// Returns `true` if `self` and `other` cannot safely coexist in the same
30    /// system execution.
31    ///
32    /// Conflict rules:
33    /// - Read  + Write (same TypeId, same namespace) → conflict
34    /// - Write + Write (same TypeId, same namespace) → conflict
35    /// - Read  + Read  → no conflict
36    /// - Cross-namespace (Res vs Comp) → no conflict
37    pub fn conflicts_with(&self, other: &Access) -> bool {
38        match (self, other) {
39            (Access::ResRead(a), Access::ResWrite(b))
40            | (Access::ResWrite(a), Access::ResRead(b))
41            | (Access::ResWrite(a), Access::ResWrite(b)) => a == b,
42
43            (Access::CompRead(a), Access::CompWrite(b))
44            | (Access::CompWrite(a), Access::CompRead(b))
45            | (Access::CompWrite(a), Access::CompWrite(b)) => a == b,
46
47            _ => false,
48        }
49    }
50}
51
52/// Returns `true` if any access in `a` conflicts with any in `b`.
53pub fn has_conflicts(a: &[Access], b: &[Access]) -> bool {
54    a.iter().any(|x| b.iter().any(|y| x.conflicts_with(y)))
55}
56
57// =============================================================================
58// SystemParam trait
59// =============================================================================
60
61/// A type that can be extracted from a `World` as a system parameter.
62///
63/// # Safety
64///
65/// Implementations must correctly report all data accessed via `access()`.
66/// `fetch()` may only touch the data declared in `access()`. The caller
67/// guarantees that no other parameter has aliasing mutable access to the same
68/// data — enforced at system registration time by conflict detection.
69///
70/// Each resource/component lives in its own heap allocation (`Box` inside
71/// `HashMap`), so accesses to different `TypeId`s do not alias even through
72/// the same `*mut World`.
73pub unsafe trait SystemParam {
74    /// The concrete type produced for a given world lifetime.
75    type Item<'w>;
76
77    /// Declare what world data this parameter accesses.
78    fn access() -> Vec<Access>;
79
80    /// Extract the parameter from the world.
81    ///
82    /// # Safety
83    ///
84    /// Caller must guarantee no aliasing mutable access to the data declared
85    /// in `access()`. The cell provides field-level access via `addr_of!`
86    /// to avoid creating intermediate `&World` or `&mut World` references,
87    /// preventing Stacked Borrows aliasing UB when multiple params are
88    /// fetched in sequence.
89    unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Self::Item<'w>;
90}
91
92// =============================================================================
93// Res<T> — shared resource access
94// =============================================================================
95
96/// Shared read access to a world resource.
97pub struct Res<'w, T: Send + 'static> {
98    value: &'w T,
99}
100
101impl<T: Send + 'static> Deref for Res<'_, T> {
102    type Target = T;
103    fn deref(&self) -> &T {
104        self.value
105    }
106}
107
108// SAFETY: access() correctly reports ResRead. fetch() only reads the resource.
109unsafe impl<T: Send + 'static> SystemParam for Res<'_, T> {
110    type Item<'w> = Res<'w, T>;
111
112    fn access() -> Vec<Access> {
113        vec![Access::ResRead(TypeId::of::<T>())]
114    }
115
116    unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Res<'w, T> {
117        Res {
118            value: unsafe { world.get_resource::<T>() },
119        }
120    }
121}
122
123// =============================================================================
124// ResMut<T> — exclusive resource access
125// =============================================================================
126
127/// Exclusive write access to a world resource.
128pub struct ResMut<'w, T: Send + 'static> {
129    value: &'w mut T,
130}
131
132impl<T: Send + 'static> Deref for ResMut<'_, T> {
133    type Target = T;
134    fn deref(&self) -> &T {
135        self.value
136    }
137}
138
139impl<T: Send + 'static> DerefMut for ResMut<'_, T> {
140    fn deref_mut(&mut self) -> &mut T {
141        self.value
142    }
143}
144
145// SAFETY: access() correctly reports ResWrite. fetch() only mutates this resource.
146unsafe impl<T: Send + 'static> SystemParam for ResMut<'_, T> {
147    type Item<'w> = ResMut<'w, T>;
148
149    fn access() -> Vec<Access> {
150        vec![Access::ResWrite(TypeId::of::<T>())]
151    }
152
153    unsafe fn fetch<'w>(world: UnsafeWorldCell) -> ResMut<'w, T> {
154        ResMut {
155            value: unsafe { world.get_resource_mut::<T>() },
156        }
157    }
158}
159
160// =============================================================================
161// Query<T> — shared component query
162// =============================================================================
163
164/// Shared read access to all entities with component `T`.
165pub struct Query<'w, T: Component> {
166    results: Vec<(Entity, &'w T)>,
167}
168
169impl<'w, T: Component> Query<'w, T> {
170    /// Iterate over matching `(Entity, &T)` pairs.
171    pub fn iter(&self) -> impl Iterator<Item = (Entity, &T)> {
172        self.results.iter().map(|&(e, v)| (e, v))
173    }
174
175    /// Returns `true` if no entities matched.
176    pub fn is_empty(&self) -> bool {
177        self.results.is_empty()
178    }
179
180    /// Returns the number of matching entities.
181    pub fn len(&self) -> usize {
182        self.results.len()
183    }
184}
185
186// SAFETY: access() correctly reports CompRead. fetch() collects an immutable
187// query — the archetype iterator borrows world.archetypes immutably via
188// UnsafeWorldCell::archetypes() (no intermediate &World).
189unsafe impl<T: Component> SystemParam for Query<'_, T> {
190    type Item<'w> = Query<'w, T>;
191
192    fn access() -> Vec<Access> {
193        vec![Access::CompRead(TypeId::of::<T>())]
194    }
195
196    unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Query<'w, T> {
197        Query {
198            results: unsafe { QueryIter::<'w, &T>::new(world.archetypes()).collect() },
199        }
200    }
201}
202
203// =============================================================================
204// QueryMut<T> — exclusive component query
205// =============================================================================
206
207/// Exclusive write access to all entities with component `T`.
208pub struct QueryMut<'w, T: Component> {
209    results: Vec<(Entity, Mut<'w, T>)>,
210}
211
212impl<'w, T: Component> QueryMut<'w, T> {
213    /// Iterate mutably over matching `(Entity, &mut Mut<T>)` pairs.
214    ///
215    /// Reading through `Mut<T>` (via `Deref`) does not stamp the change tick.
216    /// Only writing through `DerefMut` stamps it, so `query_changed` sees
217    /// only entities that were actually mutated.
218    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Entity, &mut Mut<'w, T>)> + '_ {
219        self.results.iter_mut().map(|(e, v)| (*e, v))
220    }
221
222    /// Returns `true` if no entities matched.
223    pub fn is_empty(&self) -> bool {
224        self.results.is_empty()
225    }
226
227    /// Returns the number of matching entities.
228    pub fn len(&self) -> usize {
229        self.results.len()
230    }
231}
232
233// SAFETY: access() correctly reports CompWrite. fetch() collects a mutable
234// query — the archetype iterator yields `&'w mut T` references into distinct
235// column heap allocations per archetype. Uses archetypes_mut_ptr() to get a
236// raw pointer, and QueryIterMut::new_from_ptr() to avoid creating
237// `&mut ArchetypeStore` — preventing overlap with concurrent `&ArchetypeStore`
238// from Query params.
239unsafe impl<T: Component> SystemParam for QueryMut<'_, T> {
240    type Item<'w> = QueryMut<'w, T>;
241
242    fn access() -> Vec<Access> {
243        vec![Access::CompWrite(TypeId::of::<T>())]
244    }
245
246    unsafe fn fetch<'w>(world: UnsafeWorldCell) -> QueryMut<'w, T> {
247        QueryMut {
248            results: unsafe {
249                let tick = world.change_tick();
250                QueryIterMut::<'w, &mut T>::new_from_ptr(world.archetypes_mut_ptr(), tick).collect()
251            },
252        }
253    }
254}
255
256// =============================================================================
257// Unit tuple — no parameters
258// =============================================================================
259
260// SAFETY: No access, no fetch.
261unsafe impl SystemParam for () {
262    type Item<'w> = ();
263
264    fn access() -> Vec<Access> {
265        Vec::new()
266    }
267
268    unsafe fn fetch<'w>(_world: UnsafeWorldCell) -> Self::Item<'w> {}
269}
270
271// =============================================================================
272// Tuple expansion — 1..8 arity
273// =============================================================================
274
275macro_rules! impl_system_param_tuple {
276    ($($P:ident),+) => {
277        // SAFETY: access() is the union of all inner accesses.
278        unsafe impl<$($P: SystemParam),+> SystemParam for ($($P,)+) {
279            type Item<'w> = ($($P::Item<'w>,)+);
280
281            fn access() -> Vec<Access> {
282                let mut acc = Vec::new();
283                $(acc.extend($P::access());)+
284                acc
285            }
286
287            unsafe fn fetch<'w>(world: UnsafeWorldCell) -> Self::Item<'w> {
288                ($(unsafe { $P::fetch(world) },)+)
289            }
290        }
291    };
292}
293
294impl_system_param_tuple!(P0);
295impl_system_param_tuple!(P0, P1);
296impl_system_param_tuple!(P0, P1, P2);
297impl_system_param_tuple!(P0, P1, P2, P3);
298impl_system_param_tuple!(P0, P1, P2, P3, P4);
299impl_system_param_tuple!(P0, P1, P2, P3, P4, P5);
300impl_system_param_tuple!(P0, P1, P2, P3, P4, P5, P6);
301impl_system_param_tuple!(P0, P1, P2, P3, P4, P5, P6, P7);
302
303// =============================================================================
304// Tests
305// =============================================================================
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::component::Component;
311    use crate::world::World;
312
313    fn type_id<T: 'static>() -> TypeId {
314        TypeId::of::<T>()
315    }
316
317    #[derive(Debug, PartialEq)]
318    struct Pos {
319        x: f32,
320    }
321    impl Component for Pos {}
322
323    // -- Access conflict tests --
324
325    #[test]
326    fn read_read_same_type_no_conflict() {
327        let a = Access::ResRead(type_id::<u32>());
328        let b = Access::ResRead(type_id::<u32>());
329        assert!(!a.conflicts_with(&b));
330    }
331
332    #[test]
333    fn read_write_same_type_conflicts() {
334        let a = Access::ResRead(type_id::<u32>());
335        let b = Access::ResWrite(type_id::<u32>());
336        assert!(a.conflicts_with(&b));
337        assert!(b.conflicts_with(&a));
338    }
339
340    #[test]
341    fn write_write_same_type_conflicts() {
342        let a = Access::ResWrite(type_id::<u32>());
343        let b = Access::ResWrite(type_id::<u32>());
344        assert!(a.conflicts_with(&b));
345    }
346
347    #[test]
348    fn read_write_different_type_no_conflict() {
349        let a = Access::ResRead(type_id::<u32>());
350        let b = Access::ResWrite(type_id::<f32>());
351        assert!(!a.conflicts_with(&b));
352    }
353
354    #[test]
355    fn comp_read_write_conflicts() {
356        let a = Access::CompRead(type_id::<u64>());
357        let b = Access::CompWrite(type_id::<u64>());
358        assert!(a.conflicts_with(&b));
359        assert!(b.conflicts_with(&a));
360    }
361
362    #[test]
363    fn res_and_comp_same_type_no_conflict() {
364        let a = Access::ResWrite(type_id::<u32>());
365        let b = Access::CompWrite(type_id::<u32>());
366        assert!(!a.conflicts_with(&b));
367    }
368
369    #[test]
370    fn has_conflicts_finds_conflict_in_sets() {
371        let set_a = vec![
372            Access::ResRead(type_id::<u32>()),
373            Access::CompRead(type_id::<f32>()),
374        ];
375        let set_b = vec![
376            Access::ResWrite(type_id::<u32>()),
377            Access::CompRead(type_id::<f32>()),
378        ];
379        assert!(has_conflicts(&set_a, &set_b));
380    }
381
382    #[test]
383    fn has_conflicts_empty_sets_no_conflict() {
384        assert!(!has_conflicts(&[], &[]));
385        assert!(!has_conflicts(&[Access::ResRead(type_id::<u32>())], &[]));
386        assert!(!has_conflicts(&[], &[Access::ResWrite(type_id::<u32>())]));
387    }
388
389    // -- Res / ResMut fetch tests --
390
391    #[test]
392    fn res_fetches_resource() {
393        let mut world = World::new();
394        world.insert_resource(42_i32);
395        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
396        unsafe {
397            let res: Res<'_, i32> = <Res<'_, i32> as SystemParam>::fetch(cell);
398            assert_eq!(*res, 42);
399        }
400    }
401
402    #[test]
403    fn res_access_is_read() {
404        let access = <Res<'_, i32> as SystemParam>::access();
405        assert_eq!(access, vec![Access::ResRead(TypeId::of::<i32>())]);
406    }
407
408    #[test]
409    fn res_mut_fetches_and_mutates() {
410        let mut world = World::new();
411        world.insert_resource(10_u32);
412        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
413        unsafe {
414            let mut res: ResMut<'_, u32> = <ResMut<'_, u32> as SystemParam>::fetch(cell);
415            *res = 20;
416        }
417        assert_eq!(*world.resource::<u32>(), 20);
418    }
419
420    #[test]
421    fn res_mut_access_is_write() {
422        let access = <ResMut<'_, u32> as SystemParam>::access();
423        assert_eq!(access, vec![Access::ResWrite(TypeId::of::<u32>())]);
424    }
425
426    #[test]
427    fn res_and_res_mut_different_types_no_conflict() {
428        let a = <Res<'_, i32> as SystemParam>::access();
429        let b = <ResMut<'_, u32> as SystemParam>::access();
430        assert!(!has_conflicts(&a, &b));
431    }
432
433    #[test]
434    fn res_and_res_mut_same_type_conflicts() {
435        let a = <Res<'_, i32> as SystemParam>::access();
436        let b = <ResMut<'_, i32> as SystemParam>::access();
437        assert!(has_conflicts(&a, &b));
438    }
439
440    // -- Query / QueryMut fetch tests --
441
442    #[test]
443    fn query_fetches_matching_entities() {
444        let mut world = World::new();
445        world.spawn((Pos { x: 1.0 },));
446        world.spawn((Pos { x: 2.0 },));
447        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
448        unsafe {
449            let q: Query<'_, Pos> = <Query<'_, Pos> as SystemParam>::fetch(cell);
450            assert_eq!(q.len(), 2);
451        }
452    }
453
454    #[test]
455    fn query_access_is_comp_read() {
456        let access = <Query<'_, Pos> as SystemParam>::access();
457        assert_eq!(access, vec![Access::CompRead(TypeId::of::<Pos>())]);
458    }
459
460    #[test]
461    fn query_mut_allows_mutation() {
462        let mut world = World::new();
463        let e = world.spawn((Pos { x: 5.0 },));
464        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
465        unsafe {
466            let mut q: QueryMut<'_, Pos> = <QueryMut<'_, Pos> as SystemParam>::fetch(cell);
467            for (_, pos) in q.iter_mut() {
468                pos.x += 10.0;
469            }
470        }
471        assert_eq!(world.get::<Pos>(e).unwrap().x, 15.0);
472    }
473
474    #[test]
475    fn query_mut_access_is_comp_write() {
476        let access = <QueryMut<'_, Pos> as SystemParam>::access();
477        assert_eq!(access, vec![Access::CompWrite(TypeId::of::<Pos>())]);
478    }
479
480    #[test]
481    fn query_empty_world() {
482        let mut world = World::new();
483        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
484        unsafe {
485            let q: Query<'_, Pos> = <Query<'_, Pos> as SystemParam>::fetch(cell);
486            assert!(q.is_empty());
487        }
488    }
489
490    // -- Tuple expansion tests --
491
492    #[test]
493    fn unit_tuple_has_no_access() {
494        let access = <() as SystemParam>::access();
495        assert!(access.is_empty());
496    }
497
498    #[test]
499    fn pair_tuple_aggregates_access() {
500        let access = <(Res<'_, i32>, ResMut<'_, u32>) as SystemParam>::access();
501        assert_eq!(access.len(), 2);
502        assert!(access.contains(&Access::ResRead(TypeId::of::<i32>())));
503        assert!(access.contains(&Access::ResWrite(TypeId::of::<u32>())));
504    }
505
506    #[test]
507    fn triple_tuple_aggregates_access() {
508        let access = <(Res<'_, i32>, ResMut<'_, u32>, Query<'_, Pos>) as SystemParam>::access();
509        assert_eq!(access.len(), 3);
510    }
511
512    // -- Missing resource panic test (#58) --
513
514    #[test]
515    #[should_panic(expected = "resource not found")]
516    fn res_fetch_panics_on_missing_resource() {
517        let mut world = World::new();
518        // Do NOT insert any i32 resource.
519        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
520        unsafe {
521            let _: Res<'_, i32> = <Res<'_, i32> as SystemParam>::fetch(cell);
522        }
523    }
524
525    // -- QueryMut on empty world (#58) --
526
527    #[test]
528    fn query_mut_empty_world() {
529        let mut world = World::new();
530        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
531        unsafe {
532            let q: QueryMut<'_, Pos> = <QueryMut<'_, Pos> as SystemParam>::fetch(cell);
533            assert!(q.is_empty());
534        }
535    }
536
537    // -- 4+ arity smoke test (#58) --
538
539    #[derive(Debug, PartialEq)]
540    struct Vel {
541        y: f32,
542    }
543    impl Component for Vel {}
544
545    struct TimeRes(f32);
546    struct GravRes(f32);
547
548    #[test]
549    fn four_arity_tuple_access_and_fetch() {
550        let mut world = World::new();
551        world.insert_resource(TimeRes(1.0));
552        world.insert_resource(GravRes(9.8));
553        world.spawn((Pos { x: 0.0 },));
554        world.spawn((Vel { y: 0.0 },));
555
556        // Verify access aggregation for 4-param tuple.
557        let access = <(
558            Res<'_, TimeRes>,
559            Res<'_, GravRes>,
560            Query<'_, Pos>,
561            Query<'_, Vel>,
562        ) as SystemParam>::access();
563        assert_eq!(access.len(), 4);
564
565        // Verify fetch works.
566        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
567        unsafe {
568            let (time, grav, positions, velocities) = <(
569                Res<'_, TimeRes>,
570                Res<'_, GravRes>,
571                Query<'_, Pos>,
572                Query<'_, Vel>,
573            ) as SystemParam>::fetch(cell);
574            assert!((time.0 - 1.0).abs() < f32::EPSILON);
575            assert!((grav.0 - 9.8).abs() < f32::EPSILON);
576            assert_eq!(positions.len(), 1);
577            assert_eq!(velocities.len(), 1);
578        }
579    }
580
581    // -- Query + QueryMut combo on different types (#58) --
582
583    #[test]
584    fn query_read_and_query_mut_different_types() {
585        let mut world = World::new();
586        world.spawn((Pos { x: 1.0 }, Vel { y: 2.0 }));
587        world.spawn((Pos { x: 3.0 }, Vel { y: 4.0 }));
588
589        // No conflict: CompRead(Pos) + CompWrite(Vel).
590        let a = <Query<'_, Pos> as SystemParam>::access();
591        let b = <QueryMut<'_, Vel> as SystemParam>::access();
592        assert!(!has_conflicts(&a, &b));
593
594        // Fetch both simultaneously.
595        let cell = unsafe { UnsafeWorldCell::new(&mut world as *mut World) };
596        unsafe {
597            let positions: Query<'_, Pos> = <Query<'_, Pos> as SystemParam>::fetch(cell);
598            let mut velocities: QueryMut<'_, Vel> = <QueryMut<'_, Vel> as SystemParam>::fetch(cell);
599
600            assert_eq!(positions.len(), 2);
601            assert_eq!(velocities.len(), 2);
602
603            // Mutate velocities while positions are live — the soundness scenario.
604            for (_, v) in velocities.iter_mut() {
605                v.y += 10.0;
606            }
607        }
608
609        // Verify mutations applied.
610        let ys: Vec<f32> = world.query::<&Vel>().map(|(_, v)| v.y).collect();
611        assert!(ys.iter().all(|&y| y > 10.0));
612    }
613}