Skip to content

Commit e177ae4

Browse files
joseph-gioItsDoot
authored andcommitted
Make the SystemParam derive macro more flexible (bevyengine#6694)
# Objective Currently, the `SystemParam` derive forces you to declare the lifetime parameters `<'w, 's>`, even if you don't use them. If you don't follow this structure, the error message is quite nasty. ### Example (before): ```rust #[derive(SystemParam)] pub struct EventWriter<'w, 's, E: Event> { events: ResMut<'w, Events<E>>, // The derive forces us to declare the `'s` lifetime even though we don't use it, // so we have to add this `PhantomData` to please rustc. #[system_param(ignore)] _marker: PhantomData<&'s ()>, } ``` ## Solution * Allow the user to omit either lifetime. * Emit a descriptive error if any lifetimes used are invalid. ### Example (after): ```rust #[derive(SystemParam)] pub struct EventWriter<'w, E: Event> { events: ResMut<'w, Events<E>>, } ``` --- ## Changelog * The `SystemParam` derive is now more flexible, allowing you to omit unused lifetime parameters.
1 parent 7b3f911 commit e177ae4

File tree

3 files changed

+52
-21
lines changed

3 files changed

+52
-21
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,25 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
373373
}
374374

375375
let generics = ast.generics;
376-
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
376+
377+
// Emit an error if there's any unrecognized lifetime names.
378+
for lt in generics.lifetimes() {
379+
let ident = &lt.lifetime.ident;
380+
let w = format_ident!("w");
381+
let s = format_ident!("s");
382+
if ident != &w && ident != &s {
383+
return syn::Error::new_spanned(
384+
lt,
385+
r#"invalid lifetime name: expected `'w` or `'s`
386+
'w -- refers to data stored in the World.
387+
's -- refers to data stored in the SystemParam's state.'"#,
388+
)
389+
.into_compile_error()
390+
.into();
391+
}
392+
}
393+
394+
let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
377395

378396
let lifetimeless_generics: Vec<_> = generics
379397
.params
@@ -404,10 +422,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
404422
// The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via
405423
// <EventReader<'static, 'static, T> as SystemParam>::Fetch
406424
const _: () = {
407-
impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause {
408-
type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>;
425+
impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause {
426+
type Fetch = State<'w, 's, #punctuated_generic_idents>;
409427
}
410428

429+
#[doc(hidden)]
430+
type State<'w, 's, #punctuated_generic_idents> = FetchState<
431+
(#(<#field_types as #path::system::SystemParam>::Fetch,)*),
432+
#punctuated_generic_idents
433+
>;
434+
411435
#[doc(hidden)]
412436
#fetch_struct_visibility struct FetchState <TSystemParamState, #punctuated_generic_idents> {
413437
state: TSystemParamState,
@@ -431,7 +455,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
431455
}
432456
}
433457

434-
impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause {
458+
impl<'w, 's, #punctuated_generics> #path::system::SystemParamFetch<'w, 's> for State<'w, 's, #punctuated_generic_idents> #where_clause {
435459
type Item = #struct_name #ty_generics;
436460
unsafe fn get_param(
437461
state: &'s mut Self,

crates/bevy_ecs/src/event.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -295,13 +295,11 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
295295
/// ```
296296
/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work.
297297
#[derive(SystemParam)]
298-
pub struct EventWriter<'w, 's, E: Event> {
298+
pub struct EventWriter<'w, E: Event> {
299299
events: ResMut<'w, Events<E>>,
300-
#[system_param(ignore)]
301-
marker: PhantomData<&'s usize>,
302300
}
303301

304-
impl<'w, 's, E: Event> EventWriter<'w, 's, E> {
302+
impl<'w, E: Event> EventWriter<'w, E> {
305303
/// Sends an `event`. [`EventReader`]s can then read the event.
306304
/// See [`Events`] for details.
307305
pub fn send(&mut self, event: E) {

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,15 @@ use std::{
3333
/// See the *Generic `SystemParam`s* section for details and workarounds of the probable
3434
/// cause if this derive causes an error to be emitted.
3535
///
36-
///
37-
/// The struct for which `SystemParam` is derived must (currently) have exactly
38-
/// two lifetime parameters.
39-
/// The first is the lifetime of the world, and the second the lifetime
40-
/// of the parameter's state.
36+
/// Derived `SystemParam` structs may have two lifetimes: `'w` for data stored in the [`World`],
37+
/// and `'s` for data stored in the parameter's state.
4138
///
4239
/// ## Attributes
4340
///
4441
/// `#[system_param(ignore)]`:
4542
/// Can be added to any field in the struct. Fields decorated with this attribute
4643
/// will be created with the default value upon realisation.
47-
/// This is most useful for `PhantomData` fields, to ensure that the required lifetimes are
48-
/// used, as shown in the example.
44+
/// This is most useful for `PhantomData` fields, such as markers for generic types.
4945
///
5046
/// # Example
5147
///
@@ -57,17 +53,17 @@ use std::{
5753
/// use bevy_ecs::system::SystemParam;
5854
///
5955
/// #[derive(SystemParam)]
60-
/// struct MyParam<'w, 's> {
56+
/// struct MyParam<'w, Marker: 'static> {
6157
/// foo: Res<'w, SomeResource>,
6258
/// #[system_param(ignore)]
63-
/// marker: PhantomData<&'s ()>,
59+
/// marker: PhantomData<Marker>,
6460
/// }
6561
///
66-
/// fn my_system(param: MyParam) {
62+
/// fn my_system<T: 'static>(param: MyParam<T>) {
6763
/// // Access the resource through `param.foo`
6864
/// }
6965
///
70-
/// # bevy_ecs::system::assert_is_system(my_system);
66+
/// # bevy_ecs::system::assert_is_system(my_system::<()>);
7167
/// ```
7268
///
7369
/// # Generic `SystemParam`s
@@ -1567,7 +1563,7 @@ pub mod lifetimeless {
15671563
/// struct GenericParam<'w,'s, T: SystemParam> {
15681564
/// field: T,
15691565
/// #[system_param(ignore)]
1570-
/// // Use the lifetimes, as the `SystemParam` derive requires them
1566+
/// // Use the lifetimes in this type, or they will be unbound.
15711567
/// phantom: core::marker::PhantomData<&'w &'s ()>
15721568
/// }
15731569
/// # fn check_always_is_system<T: SystemParam + 'static>(){
@@ -1651,7 +1647,7 @@ unsafe impl<S: SystemParamState, P: SystemParam + 'static> SystemParamState
16511647

16521648
#[cfg(test)]
16531649
mod tests {
1654-
use super::SystemParam;
1650+
use super::*;
16551651
use crate::{
16561652
self as bevy_ecs, // Necessary for the `SystemParam` Derive when used inside `bevy_ecs`.
16571653
query::{ReadOnlyWorldQuery, WorldQuery},
@@ -1668,4 +1664,17 @@ mod tests {
16681664
> {
16691665
_query: Query<'w, 's, Q, F>,
16701666
}
1667+
1668+
#[derive(SystemParam)]
1669+
pub struct SpecialRes<'w, T: Resource> {
1670+
_res: Res<'w, T>,
1671+
}
1672+
1673+
#[derive(SystemParam)]
1674+
pub struct SpecialLocal<'s, T: FromWorld + Send + 'static> {
1675+
_local: Local<'s, T>,
1676+
}
1677+
1678+
#[derive(SystemParam)]
1679+
pub struct UnitParam {}
16711680
}

0 commit comments

Comments
 (0)