Skip to main content

bevy_ecs/system/
exclusive_system_param.rs

1use crate::{
2    prelude::{FromWorld, QueryState},
3    query::{QueryData, QueryFilter},
4    system::{Local, SystemMeta, SystemParam, SystemParamValidationError, SystemState},
5    world::World,
6};
7use bevy_platform::cell::SyncCell;
8use core::marker::PhantomData;
9use variadics_please::all_tuples;
10
11/// A parameter that can be used in an exclusive system (a system with an `&mut World` parameter).
12/// Any parameters implementing this trait must come after the `&mut World` parameter.
13#[diagnostic::on_unimplemented(
14    message = "`{Self}` can not be used as a parameter for an exclusive system",
15    label = "invalid system parameter"
16)]
17pub trait ExclusiveSystemParam: Sized {
18    /// Used to store data which persists across invocations of a system.
19    type State: Send + Sync + 'static;
20    /// The item type returned when constructing this system param.
21    /// See [`SystemParam::Item`].
22    type Item<'s>: ExclusiveSystemParam<State = Self::State>;
23
24    /// Creates a new instance of this param's [`State`](Self::State).
25    fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State;
26
27    /// Creates a parameter to be passed into an [`ExclusiveSystemParamFunction`].
28    ///
29    /// [`ExclusiveSystemParamFunction`]: super::ExclusiveSystemParamFunction
30    fn get_param<'s>(
31        state: &'s mut Self::State,
32        system_meta: &SystemMeta,
33    ) -> Result<Self::Item<'s>, SystemParamValidationError>;
34}
35
36/// Shorthand way of accessing the associated type [`ExclusiveSystemParam::Item`]
37/// for a given [`ExclusiveSystemParam`].
38pub type ExclusiveSystemParamItem<'s, P> = <P as ExclusiveSystemParam>::Item<'s>;
39
40impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> ExclusiveSystemParam
41    for &'a mut QueryState<D, F>
42{
43    type State = QueryState<D, F>;
44    type Item<'s> = &'s mut QueryState<D, F>;
45
46    fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
47        QueryState::new(world)
48    }
49
50    fn get_param<'s>(
51        state: &'s mut Self::State,
52        _system_meta: &SystemMeta,
53    ) -> Result<Self::Item<'s>, SystemParamValidationError> {
54        Ok(state)
55    }
56}
57
58impl<'a, P: SystemParam + 'static> ExclusiveSystemParam for &'a mut SystemState<P> {
59    type State = SystemState<P>;
60    type Item<'s> = &'s mut SystemState<P>;
61
62    fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
63        SystemState::new(world)
64    }
65
66    fn get_param<'s>(
67        state: &'s mut Self::State,
68        _system_meta: &SystemMeta,
69    ) -> Result<Self::Item<'s>, SystemParamValidationError> {
70        Ok(state)
71    }
72}
73
74impl<'_s, T: FromWorld + Send + 'static> ExclusiveSystemParam for Local<'_s, T> {
75    type State = SyncCell<T>;
76    type Item<'s> = Local<'s, T>;
77
78    fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
79        SyncCell::new(T::from_world(world))
80    }
81
82    fn get_param<'s>(
83        state: &'s mut Self::State,
84        _system_meta: &SystemMeta,
85    ) -> Result<Self::Item<'s>, SystemParamValidationError> {
86        Ok(Local(state.get()))
87    }
88}
89
90impl<S: ?Sized> ExclusiveSystemParam for PhantomData<S> {
91    type State = ();
92    type Item<'s> = PhantomData<S>;
93
94    fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
95
96    fn get_param<'s>(
97        _state: &'s mut Self::State,
98        _system_meta: &SystemMeta,
99    ) -> Result<Self::Item<'s>, SystemParamValidationError> {
100        Ok(PhantomData)
101    }
102}
103
104macro_rules! impl_exclusive_system_param_tuple {
105    ($(#[$meta:meta])* $($param: ident),*) => {
106        #[expect(
107            clippy::allow_attributes,
108            reason = "This is within a macro, and as such, the below lints may not always apply."
109        )]
110        #[allow(
111            non_snake_case,
112            reason = "Certain variable names are provided by the caller, not by us."
113        )]
114        #[allow(
115            unused_variables,
116            reason = "Zero-length tuples won't use any of the parameters."
117        )]
118        #[allow(clippy::unused_unit, reason = "Zero length tuple is unit.")]
119        $(#[$meta])*
120        impl<$($param: ExclusiveSystemParam),*> ExclusiveSystemParam for ($($param,)*) {
121            type State = ($($param::State,)*);
122            type Item<'s> = ($($param::Item<'s>,)*);
123
124            #[inline]
125            fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
126                ($($param::init(world, system_meta),)*)
127            }
128
129            #[inline]
130            fn get_param<'s>(
131                state: &'s mut Self::State,
132                system_meta: &SystemMeta,
133            ) -> Result<Self::Item<'s>, SystemParamValidationError> {
134                let ($($param,)*) = state;
135                #[allow(
136                    clippy::unused_unit,
137                    reason = "Zero-length tuples won't have any params to get."
138                )]
139                Ok(($($param::get_param($param, system_meta)?,)*))
140            }
141        }
142    };
143}
144
145all_tuples!(
146    #[doc(fake_variadic)]
147    impl_exclusive_system_param_tuple,
148    0,
149    16,
150    P
151);
152
153#[cfg(test)]
154mod tests {
155    use crate::{schedule::Schedule, system::Local, world::World};
156    use alloc::vec::Vec;
157    use bevy_ecs_macros::Resource;
158    use core::marker::PhantomData;
159
160    #[test]
161    fn test_exclusive_system_params() {
162        #[derive(Resource, Default)]
163        struct Res {
164            test_value: u32,
165        }
166
167        fn my_system(world: &mut World, mut local: Local<u32>, _phantom: PhantomData<Vec<u32>>) {
168            assert_eq!(world.resource::<Res>().test_value, *local);
169            *local += 1;
170            world.resource_mut::<Res>().test_value += 1;
171        }
172
173        let mut schedule = Schedule::default();
174        schedule.add_systems(my_system);
175
176        let mut world = World::default();
177        world.init_resource::<Res>();
178
179        schedule.run(&mut world);
180        schedule.run(&mut world);
181
182        assert_eq!(2, world.get_resource::<Res>().unwrap().test_value);
183    }
184}