Allow #[derive(Bundle)] on tuple structs (take 3) (#10561)

- rework of old @Veykril's work in
[2499](https://github.com/bevyengine/bevy/pull/2499)
- Fixes [3537](https://github.com/bevyengine/bevy/issues/3537)
This commit is contained in:
Konstantin Kostiuk 2023-11-21 03:09:16 +02:00 committed by GitHub
parent 7ff61a8dc9
commit eeb0c2f2e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 19 deletions

View File

@ -5,7 +5,7 @@ mod fetch;
mod states; mod states;
use crate::fetch::derive_world_query_impl; use crate::fetch::derive_world_query_impl;
use bevy_macro_utils::{derive_label, ensure_no_collision, get_named_struct_fields, BevyManifest}; use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::Span;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -27,8 +27,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path(); let ecs_path = bevy_ecs_path();
let named_fields = match get_named_struct_fields(&ast.data) { let named_fields = match get_struct_fields(&ast.data) {
Ok(fields) => &fields.named, Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(), Err(e) => return e.into_compile_error().into(),
}; };
@ -59,8 +59,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let field = named_fields let field = named_fields
.iter() .iter()
.map(|field| field.ident.as_ref().unwrap()) .map(|field| field.ident.as_ref())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let field_type = named_fields let field_type = named_fields
.iter() .iter()
.map(|field| &field.ty) .map(|field| &field.ty)
@ -69,14 +70,19 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let mut field_component_ids = Vec::new(); let mut field_component_ids = Vec::new();
let mut field_get_components = Vec::new(); let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new(); let mut field_from_components = Vec::new();
for ((field_type, field_kind), field) in for (((i, field_type), field_kind), field) in field_type
field_type.iter().zip(field_kind.iter()).zip(field.iter()) .iter()
.enumerate()
.zip(field_kind.iter())
.zip(field.iter())
{ {
match field_kind { match field_kind {
BundleFieldKind::Component => { BundleFieldKind::Component => {
field_component_ids.push(quote! { field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
}); });
match field {
Some(field) => {
field_get_components.push(quote! { field_get_components.push(quote! {
self.#field.get_components(&mut *func); self.#field.get_components(&mut *func);
}); });
@ -84,6 +90,17 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func),
}); });
} }
None => {
let index = syn::Index::from(i);
field_get_components.push(quote! {
self.#index.get_components(&mut *func);
});
field_from_components.push(quote! {
#index: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func),
});
}
}
}
BundleFieldKind::Ignore => { BundleFieldKind::Ignore => {
field_from_components.push(quote! { field_from_components.push(quote! {

View File

@ -1724,4 +1724,22 @@ mod tests {
"new entity was spawned and received C component" "new entity was spawned and received C component"
); );
} }
#[derive(Component)]
struct ComponentA(u32);
#[derive(Component)]
struct ComponentB(u32);
#[derive(Bundle)]
struct Simple(ComponentA);
#[derive(Bundle)]
struct Tuple(Simple, ComponentB);
#[derive(Bundle)]
struct Record {
field0: Simple,
field1: ComponentB,
}
} }

View File

@ -1,20 +1,24 @@
use proc_macro::Span; use proc_macro::Span;
use syn::{Data, DataStruct, Error, Fields, FieldsNamed}; use syn::{punctuated::Punctuated, token::Comma, Data, DataStruct, Error, Field, Fields};
/// Get the fields of a data structure if that structure is a struct with named fields; /// Get the fields of a data structure if that structure is a struct with named fields;
/// otherwise, return a compile error that points to the site of the macro invocation. /// otherwise, return a compile error that points to the site of the macro invocation.
pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> { pub fn get_struct_fields(data: &syn::Data) -> syn::Result<&Punctuated<Field, Comma>> {
match data { match data {
Data::Struct(DataStruct { Data::Struct(DataStruct {
fields: Fields::Named(fields), fields: Fields::Named(fields),
.. ..
}) => Ok(fields), }) => Ok(&fields.named),
Data::Struct(DataStruct {
fields: Fields::Unnamed(fields),
..
}) => Ok(&fields.unnamed),
_ => Err(Error::new( _ => Err(Error::new(
// This deliberately points to the call site rather than the structure // This deliberately points to the call site rather than the structure
// body; marking the entire body as the source of the error makes it // body; marking the entire body as the source of the error makes it
// impossible to figure out which `derive` has a problem. // impossible to figure out which `derive` has a problem.
Span::call_site().into(), Span::call_site().into(),
"Only structs with named fields are supported", "Only structs are supported",
)), )),
} }
} }