Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the 16-field limitation on the systemparam derive #5965

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,13 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
} else {
fields.push(field.ident.as_ref().unwrap());
field_types.push(&field.ty);
field_indices.push(Index::from(i));
let mut punct = Punctuated::<_, Token![.]>::new();
if field_attributes.len() > 16 {
punct.push_value(Index::from(0));
punct.push_punct(Default::default());
}
punct.push_value(Index::from(i));
field_indices.push(punct);
}
}

Expand Down Expand Up @@ -374,13 +380,62 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
let struct_name = &ast.ident;
let fetch_struct_visibility = &ast.vis;

// This exists so the macro doesn't depend on the built-in invocation of the system param tuple macro, which is capped at 16.
let (independent_tuple_impl, fetchstate_type_param) = if field_types.len() > 16 {
let list = (0..field_types.len())
.map(|i| format!("T{i}"))
.map(|s| syn::Ident::new(&s, Span::call_site()))
.collect::<Punctuated<_, Token![,]>>();

let independent_tuple_impl = quote! {
#[doc(hidden)]
#fetch_struct_visibility struct MyTuple<T>(T);

const _: () = {
use #path::{
archetype::Archetype,
world::World,
system::{
SystemParam, SystemParamFetch, SystemParamState, SystemMeta, ReadOnlySystemParamFetch, AsTuple,
},
};

impl<T> AsTuple for MyTuple<T> {
type Tuple = T;
fn as_tuple_mut(&mut self) -> &mut T {
&mut self.0
}
}

impl<T> From<T> for MyTuple<T> {
fn from(t: T) -> Self {
Self(t)
}
}

#path::system::impl_system_param_tuple_base!(MyTuple, #list);
};
};
let fetchstate_type_param = quote! {
MyTuple<(#(<#field_types as #path::system::SystemParam>::Fetch,)*)>
};
(independent_tuple_impl, fetchstate_type_param)
} else {
let fetchstate_type_param = quote! {
(#(<#field_types as #path::system::SystemParam>::Fetch,)*)
};
(quote! {}, fetchstate_type_param)
};

TokenStream::from(quote! {
// We define the FetchState struct in an anonymous scope to avoid polluting the user namespace.
// The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via
// <EventReader<'static, 'static, T> as SystemParam>::Fetch
const _: () = {
#independent_tuple_impl

impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause {
type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>;
type Fetch = FetchState <#fetchstate_type_param, #punctuated_generic_idents>;
}

#[doc(hidden)]
Expand All @@ -406,7 +461,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
}
}

impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause {
impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <#fetchstate_type_param, #punctuated_generic_idents> #where_clause {
type Item = #struct_name #ty_generics;
unsafe fn get_param(
state: &'s mut Self,
Expand Down
50 changes: 36 additions & 14 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1400,19 +1400,20 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemNameState {
}
}

macro_rules! impl_system_param_tuple {
($($param: ident),*) => {
impl<$($param: SystemParam),*> SystemParam for ($($param,)*) {
type Fetch = ($($param::Fetch,)*);
#[macro_export]
macro_rules! impl_system_param_tuple_base {
($tuple:ident, $($param: ident),*) => {
impl<$($param: SystemParam),*> SystemParam for $tuple<($($param,)*)> {
type Fetch = $tuple<($($param::Fetch,)*)>;
}

// SAFETY: tuple consists only of ReadOnlySystemParamFetches
unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for ($($param,)*) {}
unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for $tuple<($($param,)*)> {}

#[allow(unused_variables)]
#[allow(non_snake_case)]
impl<'w, 's, $($param: SystemParamFetch<'w, 's>),*> SystemParamFetch<'w, 's> for ($($param,)*) {
type Item = ($($param::Item,)*);
impl<'w, 's, $($param: SystemParamFetch<'w, 's>),*> SystemParamFetch<'w, 's> for $tuple<($($param,)*)> {
type Item = $tuple<($($param::Item,)*)>;

#[inline]
#[allow(clippy::unused_unit)]
Expand All @@ -1422,36 +1423,57 @@ macro_rules! impl_system_param_tuple {
world: &'w World,
change_tick: u32,
) -> Self::Item {

let ($($param,)*) = state;
($($param::get_param($param, system_meta, world, change_tick),)*)
let ($($param,)*) = state.as_tuple_mut();
($($param::get_param($param, system_meta, world, change_tick),)*).into()
}
}

// SAFETY: implementors of each `SystemParamState` in the tuple have validated their impls
#[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy
#[allow(non_snake_case)]
unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) {
unsafe impl<$($param: SystemParamState),*> SystemParamState for $tuple<($($param,)*)> {
#[inline]
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
(($($param::init(_world, _system_meta),)*))
(($($param::init(_world, _system_meta),)*)).into()
}

#[inline]
fn new_archetype(&mut self, _archetype: &Archetype, _system_meta: &mut SystemMeta) {
let ($($param,)*) = self;
let ($($param,)*) = self.as_tuple_mut();
$($param.new_archetype(_archetype, _system_meta);)*
}

#[inline]
fn apply(&mut self, _world: &mut World) {
let ($($param,)*) = self;
let ($($param,)*) = self.as_tuple_mut();
$($param.apply(_world);)*
}
}
};
}

pub use impl_system_param_tuple_base;

#[doc(hidden)]
pub trait AsTuple {
type Tuple;
fn as_tuple_mut(&mut self) -> &mut Self::Tuple;
}

type Identity<T> = T;
macro_rules! impl_system_param_tuple {
($($param: ident),*) => {
impl_system_param_tuple_base!(Identity, $($param),*);

impl<$($param,)*> AsTuple for ($($param,)*) {
type Tuple = Self;
fn as_tuple_mut(&mut self) -> &mut Self {
self
}
}
};
}

all_tuples!(impl_system_param_tuple, 0, 16, P);

pub mod lifetimeless {
Expand Down