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
145#[expect(clippy :: allow_attributes, reason =
"This is within a macro, and as such, the below lints may not always apply.")]
#[allow(non_snake_case, reason =
"Certain variable names are provided by the caller, not by us.")]
#[allow(unused_variables, reason =
"Zero-length tuples won't use any of the parameters.")]
#[allow(clippy :: unused_unit, reason = "Zero length tuple is unit.")]
#[doc(hidden)]
impl<P0: ExclusiveSystemParam, P1: ExclusiveSystemParam,
    P2: ExclusiveSystemParam, P3: ExclusiveSystemParam,
    P4: ExclusiveSystemParam, P5: ExclusiveSystemParam,
    P6: ExclusiveSystemParam, P7: ExclusiveSystemParam,
    P8: ExclusiveSystemParam, P9: ExclusiveSystemParam,
    P10: ExclusiveSystemParam, P11: ExclusiveSystemParam,
    P12: ExclusiveSystemParam, P13: ExclusiveSystemParam,
    P14: ExclusiveSystemParam, P15: ExclusiveSystemParam> ExclusiveSystemParam
    for (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15)
    {
    type State =
        (P0::State, P1::State, P2::State, P3::State, P4::State, P5::State,
        P6::State, P7::State, P8::State, P9::State, P10::State, P11::State,
        P12::State, P13::State, P14::State, P15::State);
    type Item<'s> =
        (P0::Item<'s>, P1::Item<'s>, P2::Item<'s>, P3::Item<'s>, P4::Item<'s>,
        P5::Item<'s>, P6::Item<'s>, P7::Item<'s>, P8::Item<'s>, P9::Item<'s>,
        P10::Item<'s>, P11::Item<'s>, P12::Item<'s>, P13::Item<'s>,
        P14::Item<'s>, P15::Item<'s>);
    #[inline]
    fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
        (P0::init(world, system_meta), P1::init(world, system_meta),
            P2::init(world, system_meta), P3::init(world, system_meta),
            P4::init(world, system_meta), P5::init(world, system_meta),
            P6::init(world, system_meta), P7::init(world, system_meta),
            P8::init(world, system_meta), P9::init(world, system_meta),
            P10::init(world, system_meta), P11::init(world, system_meta),
            P12::init(world, system_meta), P13::init(world, system_meta),
            P14::init(world, system_meta), P15::init(world, system_meta))
    }
    #[inline]
    fn get_param<'s>(state: &'s mut Self::State, system_meta: &SystemMeta)
        -> Result<Self::Item<'s>, SystemParamValidationError> {
        let (P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14,
                P15) = state;

        #[allow(clippy :: unused_unit, reason =
        "Zero-length tuples won't have any params to get.")]
        Ok((P0::get_param(P0, system_meta)?, P1::get_param(P1, system_meta)?,
                P2::get_param(P2, system_meta)?,
                P3::get_param(P3, system_meta)?,
                P4::get_param(P4, system_meta)?,
                P5::get_param(P5, system_meta)?,
                P6::get_param(P6, system_meta)?,
                P7::get_param(P7, system_meta)?,
                P8::get_param(P8, system_meta)?,
                P9::get_param(P9, system_meta)?,
                P10::get_param(P10, system_meta)?,
                P11::get_param(P11, system_meta)?,
                P12::get_param(P12, system_meta)?,
                P13::get_param(P13, system_meta)?,
                P14::get_param(P14, system_meta)?,
                P15::get_param(P15, system_meta)?))
    }
}all_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}