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> SystemParam for Option<Res<'a, T>> {
183    type State = AtomicResource<T>;
184    type Param<'p> = Option<Res<'p, T>>;
185
186    fn get_state(world: &World) -> Self::State {
187        world.resources.get_cell::<T>()
188    }
189
190    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
191        state.borrow().map(|x| Res(x))
192    }
193}
194
195impl<'a, T: HasSchema + FromWorld> SystemParam for ResInit<'a, T> {
196    type State = AtomicResource<T>;
197    type Param<'p> = ResInit<'p, T>;
198
199    fn get_state(world: &World) -> Self::State {
200        let cell = world.resources.get_cell::<T>();
201        cell.init(world);
202        cell
203    }
204
205    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
206        ResInit(state.borrow().unwrap())
207    }
208}
209
210impl<'a, T: HasSchema> SystemParam for ResMut<'a, T> {
211    type State = AtomicResource<T>;
212    type Param<'p> = ResMut<'p, T>;
213
214    fn get_state(world: &World) -> Self::State {
215        world.resources.get_cell::<T>()
216    }
217
218    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
219        ResMut(state.borrow_mut().unwrap_or_else(|| {
220            panic!(
221                "Resource of type `{}` not in world. \
222                You may need to insert or initialize the resource or use \
223                `ResMutInit` instead of `ResMut` to automatically initialize the \
224                resource with the default value.",
225                std::any::type_name::<T>()
226            )
227        }))
228    }
229}
230
231impl<'a, T: HasSchema> SystemParam for Option<ResMut<'a, T>> {
232    type State = AtomicResource<T>;
233    type Param<'p> = Option<ResMut<'p, T>>;
234
235    fn get_state(world: &World) -> Self::State {
236        world.resources.get_cell::<T>()
237    }
238
239    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
240        state.borrow_mut().map(|state| ResMut(state))
241    }
242}
243
244impl<'a, T: HasSchema + FromWorld> SystemParam for ResMutInit<'a, T> {
245    type State = AtomicResource<T>;
246    type Param<'p> = ResMutInit<'p, T>;
247
248    fn get_state(world: &World) -> Self::State {
249        let cell = world.resources.get_cell::<T>();
250        cell.init(world);
251        cell
252    }
253
254    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
255        ResMutInit(state.borrow_mut().unwrap())
256    }
257}
258
259/// [`SystemParam`] for getting read access to a [`ComponentStore`].
260pub type Comp<'a, T> = Ref<'a, ComponentStore<T>>;
261/// [`SystemParam`] for getting mutable access to a [`ComponentStore`].
262pub type CompMut<'a, T> = RefMut<'a, ComponentStore<T>>;
263
264impl<'a, T: HasSchema> SystemParam for Comp<'a, T> {
265    type State = Arc<AtomicCell<ComponentStore<T>>>;
266    type Param<'p> = Comp<'p, T>;
267
268    fn get_state(world: &World) -> Self::State {
269        world.components.get_cell::<T>()
270    }
271
272    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
273        state.borrow()
274    }
275}
276
277impl<'a, T: HasSchema> SystemParam for CompMut<'a, T> {
278    type State = Arc<AtomicCell<ComponentStore<T>>>;
279    type Param<'p> = CompMut<'p, T>;
280
281    fn get_state(world: &World) -> Self::State {
282        world.components.get_cell::<T>()
283    }
284
285    fn borrow<'s>(_world: &'s World, state: &'s mut Self::State) -> Self::Param<'s> {
286        state.borrow_mut()
287    }
288}
289
290macro_rules! impl_system {
291    ($($args:ident,)*) => {
292        #[allow(unused_parens)]
293        impl<
294            F,
295            Out,
296            $(
297                $args: SystemParam,
298            )*
299        > IntoSystem<(F, $($args,)*), (), Out> for F
300        where for<'a> F: 'static + Send + Sync +
301            FnMut(
302                $(
303                    <$args as SystemParam>::Param<'a>,
304                )*
305            ) -> Out +
306            FnMut(
307                $(
308                    $args,
309                )*
310            ) -> Out
311        {
312            type Sys = StaticSystem<(), Out>;
313            fn system(mut self) -> Self::Sys {
314                StaticSystem {
315                    name: std::any::type_name::<F>(),
316                    run: Box::new(move |_world, _input| {
317                        $(
318                            #[allow(non_snake_case)]
319                            let mut $args = $args::get_state(_world);
320                        )*
321
322                        self(
323                            $(
324                                $args::borrow(_world, &mut $args),
325                            )*
326                        )
327                    })
328                }
329            }
330        }
331    };
332}
333
334macro_rules! impl_system_with_input {
335    ($($args:ident,)*) => {
336        #[allow(unused_parens)]
337        impl<
338            'input,
339            F,
340            InT: 'input,
341            Out,
342            $(
343                $args: SystemParam,
344            )*
345        > IntoSystem<(F, InT, $($args,)*), InT, Out> for F
346        where for<'a> F: 'static + Send + Sync +
347            FnMut(
348                In<InT>,
349                $(
350                    <$args as SystemParam>::Param<'a>,
351                )*
352            ) -> Out +
353            FnMut(
354                In<InT>,
355                $(
356                    $args,
357                )*
358            ) -> Out
359        {
360            type Sys = StaticSystem<InT, Out>;
361            fn system(mut self) -> Self::Sys {
362                StaticSystem {
363                    name: std::any::type_name::<F>(),
364                    run: Box::new(move |_world, input| {
365                        $(
366                            #[allow(non_snake_case)]
367                            let mut $args = $args::get_state(_world);
368                        )*
369
370                        self(
371                            In(input),
372                            $(
373                                $args::borrow(_world, &mut $args),
374                            )*
375                        )
376                    })
377                }
378            }
379        }
380    };
381}
382
383macro_rules! impl_systems {
384    // base case
385    () => {
386        impl_system!();
387        impl_system_with_input!();
388    };
389    // recursive call
390    ($head:ident, $($idents:ident,)*) => {
391        impl_system!($head, $($idents,)*);
392        impl_system_with_input!($head, $($idents,)*);
393        impl_systems!($($idents,)*);
394    }
395}
396
397impl_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,);
398
399#[cfg(test)]
400mod tests {
401    use crate::prelude::*;
402
403    #[test]
404    fn convert_system() {
405        fn tmp(
406            _var1: Ref<ComponentStore<u32>>,
407            _var2: Ref<ComponentStore<u64>>,
408            _var3: Res<i32>,
409            _var4: ResMut<i64>,
410        ) -> u32 {
411            0
412        }
413        // Technically reusing the same type is incorrect and causes a runtime panic.
414        // However, there doesn't seem to be a clean way to handle type inequality in generics.
415        #[allow(clippy::too_many_arguments)]
416        fn tmp2(
417            _var7: Comp<i64>,
418            _var8: CompMut<i64>,
419            _var1: Res<u32>,
420            _var2: ResMut<u64>,
421            _var3: Res<u32>,
422            _var4: ResMut<u64>,
423            _var5: Res<u32>,
424            _var6: ResMut<u64>,
425            _var9: Comp<i64>,
426            _var10: CompMut<i64>,
427            _var11: Comp<i64>,
428            _var12: CompMut<u64>,
429        ) {
430        }
431        fn tmp3(_in: In<usize>, _comp1: Comp<i64>) {}
432        let _ = tmp.system();
433        let _ = tmp2.system();
434        let _ = tmp3.system();
435    }
436
437    #[test]
438    fn system_is_send() {
439        let x = 6;
440        send(
441            (move |_var1: Res<u32>| {
442                let _y = x;
443            })
444            .system(),
445        );
446        send((|| ()).system());
447        send(sys.system());
448    }
449
450    fn sys(_var1: Res<u32>) {}
451    fn send<T: Send>(_t: T) {}
452
453    #[test]
454    fn optional_resource() {
455        fn access_resource(
456            a: Option<Res<u8>>,
457            b: Option<Res<u16>>,
458            c: Option<ResMut<u32>>,
459            d: Option<ResMut<u64>>,
460        ) {
461            assert!(a.as_deref().is_none());
462            assert!(b.as_deref() == Some(&1));
463            assert!(c.as_deref().is_none());
464            assert!(d.as_deref() == Some(&2));
465        }
466
467        let mut world = World::new();
468        world.insert_resource(1u16);
469        world.insert_resource(2u64);
470        world.run_system(access_resource, ());
471    }
472
473    #[test]
474    fn in_and_out() {
475        fn mul_by_res(n: In<usize>, r: Res<usize>) -> usize {
476            *n * *r
477        }
478
479        fn sys_with_ref_in(mut n: In<&mut usize>) {
480            **n *= 3;
481        }
482
483        let mut world = World::new();
484        world.insert_resource(2usize);
485
486        let result = world.run_system(mul_by_res, 3);
487        assert_eq!(result, 6);
488
489        let mut n = 3;
490        world.run_system(sys_with_ref_in, &mut n)
491    }
492
493    #[test]
494    fn system_replace_resource() {
495        #[derive(Default, HasSchema, Clone, PartialEq, Eq, Debug)]
496        pub struct A;
497        #[derive(Default, HasSchema, Clone, Debug)]
498        pub struct B {
499            x: u32,
500        }
501        let world = World::default();
502        let my_system = (|_a: ResInit<A>, mut b: ResMutInit<B>| {
503            let b2 = B { x: 45 };
504            *b = b2;
505        })
506        .system();
507
508        assert!(world.resources.get_cell::<B>().borrow().is_none());
509        world.run_system(my_system, ());
510
511        let res = world.resource::<B>();
512        assert_eq!(res.x, 45);
513
514        let res = world.resource::<A>();
515        assert_eq!(*res, A);
516    }
517}