From 025996b18c741e81db708c62b89cf5505d2a27dc Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Wed, 21 Dec 2022 01:54:10 +0000 Subject: [PATCH] Lift the 16-field limit from the `SystemParam` derive (#6867) # Objective * The `SystemParam` derive internally uses tuples, which means it is constrained by the 16-field limit on `all_tuples`. * The error message if you exceed this limit is abysmal. * Supercedes #5965 -- this does the same thing, but is simpler. ## Solution If any tuples have more than 16 fields, they are folded into tuples of tuples until they are under the 16-field limit. --- crates/bevy_ecs/macros/src/lib.rs | 26 +++++++++++++++---- crates/bevy_ecs/src/system/system_param.rs | 29 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 6256c03b0d..0c1b0020e8 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -10,7 +10,7 @@ use proc_macro2::Span; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, - parse_macro_input, + parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, @@ -359,8 +359,8 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { ) }) .collect::>(); + let mut field_locals = Vec::new(); let mut fields = Vec::new(); - let mut field_indices = Vec::new(); let mut field_types = Vec::new(); let mut ignored_fields = Vec::new(); let mut ignored_field_types = Vec::new(); @@ -369,6 +369,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { ignored_fields.push(field.ident.as_ref().unwrap()); ignored_field_types.push(&field.ty); } else { + field_locals.push(format_ident!("f{i}")); let i = Index::from(i); fields.push( field @@ -378,7 +379,6 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { .unwrap_or_else(|| quote! { #i }), ); field_types.push(&field.ty); - field_indices.push(i); } } @@ -424,6 +424,19 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { _ => unreachable!(), })); + let mut tuple_types: Vec<_> = field_types.iter().map(|x| quote! { #x }).collect(); + let mut tuple_patterns: Vec<_> = field_locals.iter().map(|x| quote! { #x }).collect(); + + // If the number of fields exceeds the 16-parameter limit, + // fold the fields into tuples of tuples until we are below the limit. + const LIMIT: usize = 16; + while tuple_types.len() > LIMIT { + let end = Vec::from_iter(tuple_types.drain(..LIMIT)); + tuple_types.push(parse_quote!( (#(#end,)*) )); + + let end = Vec::from_iter(tuple_patterns.drain(..LIMIT)); + tuple_patterns.push(parse_quote!( (#(#end,)*) )); + } // Create a where clause for the `ReadOnlySystemParam` impl. // Ensure that each field implements `ReadOnlySystemParam`. let mut read_only_generics = generics.clone(); @@ -448,7 +461,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { #[doc(hidden)] type State<'w, 's, #punctuated_generic_idents> = FetchState< - (#(<#field_types as #path::system::SystemParam>::State,)*), + (#(<#tuple_types as #path::system::SystemParam>::State,)*), #punctuated_generic_idents >; @@ -484,8 +497,11 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { world: &'w #path::world::World, change_tick: u32, ) -> Self::Item<'w, 's> { + let (#(#tuple_patterns,)*) = < + <(#(#tuple_types,)*) as #path::system::SystemParam>::State as #path::system::SystemParamState + >::get_param(&mut state.state, system_meta, world, change_tick); #struct_name { - #(#fields: <<#field_types as #path::system::SystemParam>::State as #path::system::SystemParamState>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* + #(#fields: #field_locals,)* #(#ignored_fields: <#ignored_field_types>::default(),)* } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index e2309d09a2..7099029fcc 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1661,6 +1661,35 @@ mod tests { _local: Local<'s, T>, } + #[derive(Resource)] + pub struct R; + + #[derive(SystemParam)] + pub struct LongParam<'w> { + _r0: Res<'w, R<0>>, + _r1: Res<'w, R<1>>, + _r2: Res<'w, R<2>>, + _r3: Res<'w, R<3>>, + _r4: Res<'w, R<4>>, + _r5: Res<'w, R<5>>, + _r6: Res<'w, R<6>>, + _r7: Res<'w, R<7>>, + _r8: Res<'w, R<8>>, + _r9: Res<'w, R<9>>, + _r10: Res<'w, R<10>>, + _r11: Res<'w, R<11>>, + _r12: Res<'w, R<12>>, + _r13: Res<'w, R<13>>, + _r14: Res<'w, R<14>>, + _r15: Res<'w, R<15>>, + _r16: Res<'w, R<16>>, + } + + #[allow(dead_code)] + fn long_system(_param: LongParam) { + crate::system::assert_is_system(long_system); + } + #[derive(SystemParam)] pub struct UnitParam;