bones_ecs/
system.rs

1//! Implements the system API for the ECS.
2
3use std::sync::Arc;
4
5use crate::prelude::*;
6
7/// Trait implemented by systems.
8pub trait System<In, Out> {
9    /// Run the system.
10    fn run(&mut self, world: &World, input: In) -> Out;
11    /// Get a best-effort name for the system, used in diagnostics.
12    fn name(&self) -> &str;
13}
14
15/// Struct containing a static system.
16pub struct StaticSystem<In, Out> {
17    /// This is run every time the system is executed
18    pub run: Box<dyn FnMut(&World, In) -> Out + Send + Sync>,
19    /// A best-effort name for the system, for diagnostic purposes.
20    pub name: &'static str,
21}
22
23impl<In, Out> System<In, Out> for StaticSystem<In, Out> {
24    fn run(&mut self, world: &World, input: In) -> Out {
25        (self.run)(world, input)
26    }
27    fn name(&self) -> &str {
28        self.name
29    }
30}
31
32/// Converts a function into a [`System`].
33///
34/// [`IntoSystem`] is automatically implemented for all functions and closures that:
35///
36/// - Have 26 or less arguments,
37/// - Where every argument implments [`SystemParam`], and
38///
39/// The most common [`SystemParam`] types that you will use as arguments to a system will be:
40/// - [`Res`] and [`ResMut`] parameters to access resources
41/// - [`Comp`] and [`CompMut`] parameters to access components
42/// - [`&World`][World] to access the world directly
43/// - [`In`] for systems which have an input value. This must be the first argument of the function.
44pub trait IntoSystem<Args, In, Out> {
45    /// The type of the system that is output
46    type Sys: System<In, Out>;
47
48    /// Convert into a [`System`].
49    fn system(self) -> Self::Sys;
50}
51
52impl<T, In, Out> IntoSystem<T, In, Out> for T
53where
54    T: System<In, Out>,
55{
56    type Sys = T;
57    fn system(self) -> Self::Sys {
58        self
59    }
60}
61
62/// Trait used to implement parameters for [`System`] functions.
63///
64/// Functions that only take arguments implementing [`SystemParam`] automatically implment
65/// [`IntoSystem`].
66///
67/// Implementing [`SystemParam`] manually can be useful for creating new kinds of parameters you may
68/// use in your system funciton arguments. Examples might inlclude event readers and writers or
69/// other custom ways to access the data inside a [`World`].
70pub trait SystemParam: Sized {
71    /// The intermediate state for the parameter, that may be extracted from the world.
72    type State;
73    /// The type of the parameter, ranging over the lifetime of the intermediate state.
74    ///
75    /// > **ℹ️ Important:** This type must be the same type as `Self`, other than the fact that it
76    /// > may range over the lifetime `'s` instead of a generic lifetime from your `impl`.
77    /// >
78    /// > If the type is not the same, then system functions will not be able to take it as an
79    /// > argument.
80    type Param<'s>;
81    /// This is called to produce the intermediate state of the system parameter.
82    ///
83    /// This state will be created immediately before the system is run, and will kept alive until
84    /// the system is done running.
85    fn get_state(world: &World) -> Self::State;
86    /// This is used create an instance of the system parame, possibly borrowed from the
87    /// intermediate parameter state.
88    #[allow(clippy::needless_lifetimes)] // Explicit lifetimes help clarity in this case
89    fn borrow<'s>(world: &'s World, state: &'s mut Self::State) -> Self::Param<'s>;
90}
91
92impl SystemParam for &'_ World {
93    type State = ();
94    type Param<'s> = &'s World;
95    fn get_state(_world: &World) -> Self::State {}
96    fn borrow<'s>(world: &'s World, _state: &'s mut Self::State) -> Self::Param<'s> {
97        world
98    }
99}
100
101/// The system input parameter.
102#[derive(Deref, DerefMut)]
103pub struct In<T>(pub T);
104
105/// [`SystemParam`] for getting read access to a resource.
106///
107/// Use [`ResInit`] if you want to automatically initialize the resource.
108pub struct Res<'a, T: HasSchema>(Ref<'a, T>);
109impl<'a, T: HasSchema> std::ops::Deref for Res<'a, T> {
110    type Target = T;
111    fn deref(&self) -> &Self::Target {
112        &self.0
113    }
114}
115
116/// [`SystemParam`] for getting read access to a resource and initialzing it if it doesn't already
117/// exist.
118///
119/// Use [`Res`] if you don't want to automatically initialize the resource.
120pub struct ResInit<'a, T: HasSchema + FromWorld>(Ref<'a, T>);
121impl<'a, T: HasSchema + FromWorld> std::ops::Deref for ResInit<'a, T> {
122    type Target = T;
123    fn deref(&self) -> &Self::Target {
124        &self.0
125    }
126}
127
128/// [`SystemParam`] for getting mutable access to a resource.
129///
130/// Use [`ResMutInit`] if you want to automatically initialize the resource.
131pub struct ResMut<'a, T: HasSchema>(RefMut<'a, T>);
132impl<'a, T: HasSchema> std::ops::Deref for ResMut<'a, T> {
133    type Target = T;
134    fn deref(&self) -> &Self::Target {
135        &self.0
136    }
137}
138impl<'a, T: HasSchema> std::ops::DerefMut for ResMut<'a, T> {
139    fn deref_mut(&mut self) -> &mut Self::Target {
140        &mut self.0
141    }
142}
143
144/// [`SystemParam`] for getting mutable access to a resource and initializing it if it doesn't
145/// already exist.
146///
147/// Use [`ResMut`] if you don't want to automatically initialize the resource.
148pub struct ResMutInit<'a, T: HasSchema + FromWorld>(RefMut<'a, T>);
149impl<'a, T: HasSchema + FromWorld> std::ops::Deref for ResMutInit<'a, T> {
150    type Target = T;
151    fn deref(&self) -> &Self::Target {
152        &self.0
153    }
154}
155impl<'a, T: HasSchema + FromWorld> std::ops::DerefMut for ResMutInit<'a, T> {
156    fn deref_mut(&mut self) -> &mut Self::Target {
157        &mut self.0
158    }
159}
160
161impl<'a, T: HasSchema> SystemParam for Res<'a, T> {
162    type State = AtomicResource<T>;
163    type Param<'p> = Res<'p, T>;
164
165    fn get_state(world: &World) -> Self::State {
166        world.resources.get_cell::<T>()
167    }
168
169    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
170        Res(state.borrow().unwrap_or_else(|| {
171            panic!(
172                "Resource of type `{}` not in world. \
173                You may need to insert or initialize the resource or use \
174                `ResInit` instead of `Res` to automatically initialize the \
175                resource with the default value.",
176                std::any::type_name::<T>()
177            )
178        }))
179    }
180}
181
182impl<'a, T: HasSchema + FromWorld> SystemParam for ResInit<'a, T> {
183    type State = AtomicResource<T>;
184    type Param<'p> = ResInit<'p, T>;
185
186    fn get_state(world: &World) -> Self::State {
187        let cell = world.resources.get_cell::<T>();
188        cell.init(world);
189        cell
190    }
191
192    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
193        ResInit(state.borrow().unwrap())
194    }
195}
196
197impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> {
198    type State = AtomicResource<T>;
199    type Param<'p> = ResMut<'p, T>;
200
201    fn get_state(world: &World) -> Self::State {
202        world.resources.get_cell::<T>()
203    }
204
205    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
206        ResMut(state.borrow_mut().unwrap_or_else(|| {
207            panic!(
208                "Resource of type `{}` not in world. \
209                You may need to insert or initialize the resource or use \
210                `ResMutInit` instead of `ResMut` to automatically initialize the \
211                resource with the default value.",
212                std::any::type_name::<T>()
213            )
214        }))
215    }
216}
217
218impl<'a, T: HasSchema + FromWorld> SystemParam for ResMutInit<'a, T> {
219    type State = AtomicResource<T>;
220    type Param<'p> = ResMutInit<'p, T>;
221
222    fn get_state(world: &World) -> Self::State {
223        let cell = world.resources.get_cell::<T>();
224        cell.init(world);
225        cell
226    }
227
228    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
229        ResMutInit(state.borrow_mut().unwrap())
230    }
231}
232
233/// [`SystemParam`] for getting read access to a [`ComponentStore`].
234pub type Comp<'a, T> = Ref<'a, ComponentStore<T>>;
235/// [`SystemParam`] for getting mutable access to a [`ComponentStore`].
236pub type CompMut<'a, T> = RefMut<'a, ComponentStore<T>>;
237
238impl<'a, T: HasSchema> SystemParam for Comp<'a, T> {
239    type State = Arc<AtomicCell<ComponentStore<T>>>;
240    type Param<'p> = Comp<'p, T>;
241
242    fn get_state(world: &World) -> Self::State {
243        world.components.get_cell::<T>()
244    }
245
246    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
247        state.borrow()
248    }
249}
250
251impl<'a, T: HasSchema> SystemParam for CompMut<'a, T> {
252    type State = Arc<AtomicCell<ComponentStore<T>>>;
253    type Param<'p> = CompMut<'p, T>;
254
255    fn get_state(world: &World) -> Self::State {
256        world.components.get_cell::<T>()
257    }
258
259    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
260        state.borrow_mut()
261    }
262}
263
264/// Implementing this trait on a [`SystemParam`] will allow it to be wrapped in
265/// an [`Option`] when used in a system function.
266///
267/// In other words, [`SystemParam`] is automatically implemented for `Option<T>`
268/// where `T` implements [`SystemParam`] *and* [`OptionalSystemParam`].
269///
270/// You cannot implement [`OptionalSystemParam`] on a type that doesn't
271/// implement [`SystemParam`]. Using `Option<T>` for a [`SystemParam`] is
272/// considered as a failsafe for a parameter that can still be gotten short and
273/// fallible. If your type returns an optional value but does not have an
274/// unwrapped version of the parameter value then your option should be stored
275/// inside your type and implemented accordingly in [`SystemParam`].
276pub trait OptionalSystemParam: SystemParam {
277    /// This is called to produce the intermediate state of the system parameter.
278    ///
279    /// This state will be created immediately before the system is run, and will kept alive until
280    /// the system is done running.
281    fn try_get_state(world: &World) -> Option<Self::State>;
282    /// This is used create an instance of the system parame, possibly borrowed from the
283    /// intermediate parameter state.
284    #[allow(clippy::needless_lifetimes)] // Explicit lifetimes help clarity in this case
285    fn try_borrow<'s>(world: &'s World, state: &'s mut Self::State) -> Option<Self::Param<'s>>;
286}
287impl<T: OptionalSystemParam> SystemParam for Option<T> {
288    type State = Option<T::State>;
289    type Param<'s> = Option<T::Param<'s>>;
290
291    fn get_state(world: &World) -> Self::State {
292        T::try_get_state(world)
293    }
294    fn borrow<'s>(world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
295        state.as_mut().and_then(|state| T::try_borrow(world, state))
296    }
297}
298
299impl<'a, T: HasSchema> OptionalSystemParam for Res<'a, T> {
300    fn try_get_state(world: &World) -> Option<Self::State> {
301        Some(world.resources.get_cell::<T>())
302    }
303    fn try_borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Option<Self::Param<'s>> {
304        state.borrow().map(|x| Res(x))
305    }
306}
307
308impl<'a, T: HasSchema> OptionalSystemParam for ResMut<'a, T> {
309    fn try_get_state(world: &World) -> Option<Self::State> {
310        Some(world.resources.get_cell::<T>())
311    }
312    fn try_borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Option<Self::Param<'s>> {
313        state.borrow_mut().map(|x| ResMut(x))
314    }
315}
316
317macro_rules! impl_system {
318    ($($args:ident,)*) => {
319        #[allow(unused_parens)]
320        impl<
321            F,
322            Out,
323            $(
324                $args: SystemParam,
325            )*
326        > IntoSystem<(F, $($args,)*), (), Out> for F
327        where for<'a> F: 'static + Send + Sync +
328            FnMut(
329                $(
330                    <$args as SystemParam>::Param<'a>,
331                )*
332            ) -> Out +
333            FnMut(
334                $(
335                    $args,
336                )*
337            ) -> Out
338        {
339            type Sys = StaticSystem<(), Out>;
340            fn system(mut self) -> Self::Sys {
341                StaticSystem {
342                    name: std::any::type_name::<F>(),
343                    run: Box::new(move |_world, _input| {
344                        $(
345                            #[allow(non_snake_case)]
346                            let mut $args = $args::get_state(_world);
347                        )*
348
349                        self(
350                            $(
351                                $args::borrow(_world, &mut $args),
352                            )*
353                        )
354                    })
355                }
356            }
357        }
358    };
359}
360
361macro_rules! impl_system_with_input {
362    ($($args:ident,)*) => {
363        #[allow(unused_parens)]
364        impl<
365            'input,
366            F,
367            InT: 'input,
368            Out,
369            $(
370                $args: SystemParam,
371            )*
372        > IntoSystem<(F, InT, $($args,)*), InT, Out> for F
373        where for<'a> F: 'static + Send + Sync +
374            FnMut(
375                In<InT>,
376                $(
377                    <$args as SystemParam>::Param<'a>,
378                )*
379            ) -> Out +
380            FnMut(
381                In<InT>,
382                $(
383                    $args,
384                )*
385            ) -> Out
386        {
387            type Sys = StaticSystem<InT, Out>;
388            fn system(mut self) -> Self::Sys {
389                StaticSystem {
390                    name: std::any::type_name::<F>(),
391                    run: Box::new(move |_world, input| {
392                        $(
393                            #[allow(non_snake_case)]
394                            let mut $args = $args::get_state(_world);
395                        )*
396
397                        self(
398                            In(input),
399                            $(
400                                $args::borrow(_world, &mut $args),
401                            )*
402                        )
403                    })
404                }
405            }
406        }
407    };
408}
409
410macro_rules! impl_systems {
411    // base case
412    () => {
413        impl_system!();
414        impl_system_with_input!();
415    };
416    // recursive call
417    ($head:ident, $($idents:ident,)*) => {
418        impl_system!($head, $($idents,)*);
419        impl_system_with_input!($head, $($idents,)*);
420        impl_systems!($($idents,)*);
421    }
422}
423
424impl_systems!(A, B, C, D, E, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,);
425
426#[cfg(test)]
427mod tests {
428    use crate::prelude::*;
429
430    #[test]
431    fn convert_system() {
432        fn tmp(
433            _var1: Ref<ComponentStore<u32>>,
434            _var2: Ref<ComponentStore<u64>>,
435            _var3: Res<i32>,
436            _var4: ResMut<i64>,
437        ) -> u32 {
438            0
439        }
440        // Technically reusing the same type is incorrect and causes a runtime panic.
441        // However, there doesn't seem to be a clean way to handle type inequality in generics.
442        #[allow(clippy::too_many_arguments)]
443        fn tmp2(
444            _var7: Comp<i64>,
445            _var8: CompMut<i64>,
446            _var1: Res<u32>,
447            _var2: ResMut<u64>,
448            _var3: Res<u32>,
449            _var4: ResMut<u64>,
450            _var5: Res<u32>,
451            _var6: ResMut<u64>,
452            _var9: Comp<i64>,
453            _var10: CompMut<i64>,
454            _var11: Comp<i64>,
455            _var12: CompMut<u64>,
456        ) {
457        }
458        fn tmp3(_in: In<usize>, _comp1: Comp<i64>) {}
459        let _ = tmp.system();
460        let _ = tmp2.system();
461        let _ = tmp3.system();
462    }
463
464    #[test]
465    fn system_is_send() {
466        let x = 6;
467        send(
468            (move |_var1: Res<u32>| {
469                let _y = x;
470            })
471            .system(),
472        );
473        send((|| ()).system());
474        send(sys.system());
475    }
476
477    fn sys(_var1: Res<u32>) {}
478    fn send<T: Send>(_t: T) {}
479
480    #[test]
481    fn optional_resource() {
482        fn access_resource(
483            a: Option<Res<u8>>,
484            b: Option<Res<u16>>,
485            c: Option<ResMut<u32>>,
486            d: Option<ResMut<u64>>,
487        ) {
488            assert!(a.as_deref().is_none());
489            assert!(b.as_deref() == Some(&1));
490            assert!(c.as_deref().is_none());
491            assert!(d.as_deref() == Some(&2));
492        }
493
494        let world = World::new();
495        world.insert_resource(1u16);
496        world.insert_resource(2u64);
497        world.run_system(access_resource, ());
498    }
499
500    #[test]
501    fn in_and_out() {
502        fn mul_by_res(n: In<usize>, r: Res<usize>) -> usize {
503            *n * *r
504        }
505
506        fn sys_with_ref_in(mut n: In<&mut usize>) {
507            **n *= 3;
508        }
509
510        let world = World::new();
511        world.insert_resource(2usize);
512
513        let result = world.run_system(mul_by_res, 3);
514        assert_eq!(result, 6);
515
516        let mut n = 3;
517        world.run_system(sys_with_ref_in, &mut n)
518    }
519
520    #[test]
521    fn system_replace_resource() {
522        #[derive(Default, HasSchema, Clone, PartialEq, Eq, Debug)]
523        pub struct A;
524        #[derive(Default, HasSchema, Clone, Debug)]
525        pub struct B {
526            x: u32,
527        }
528        let world = World::default();
529        let my_system = (|_a: ResInit<A>, mut b: ResMutInit<B>| {
530            let b2 = B { x: 45 };
531            *b = b2;
532        })
533        .system();
534
535        assert!(world.resources.get_cell::<B>().borrow().is_none());
536        world.run_system(my_system, ());
537
538        let res = world.resource::<B>();
539        assert_eq!(res.x, 45);
540
541        let res = world.resource::<A>();
542        assert_eq!(*res, A);
543    }
544}