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;
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_macro2::Span;
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 ecs_path = bevy_ecs_path();
let named_fields = match get_named_struct_fields(&ast.data) {
Ok(fields) => &fields.named,
let named_fields = match get_struct_fields(&ast.data) {
Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(),
};
@ -59,8 +59,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let field = named_fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.map(|field| field.ident.as_ref())
.collect::<Vec<_>>();
let field_type = named_fields
.iter()
.map(|field| &field.ty)
@ -69,20 +70,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let mut field_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for ((field_type, field_kind), field) in
field_type.iter().zip(field_kind.iter()).zip(field.iter())
for (((i, field_type), field_kind), field) in field_type
.iter()
.enumerate()
.zip(field_kind.iter())
.zip(field.iter())
{
match field_kind {
BundleFieldKind::Component => {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
});
field_get_components.push(quote! {
self.#field.get_components(&mut *func);
});
field_from_components.push(quote! {
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func),
});
match field {
Some(field) => {
field_get_components.push(quote! {
self.#field.get_components(&mut *func);
});
field_from_components.push(quote! {
#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 => {
@ -115,7 +132,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
where
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self {
Self{
#(#field_from_components)*
}
}

View File

@ -833,8 +833,8 @@ impl Bundles {
T::component_ids(components, storages, &mut |id| component_ids.push(id));
let id = BundleId(bundle_infos.len());
let bundle_info =
// SAFETY: T::component_id ensures its:
// - info was created
// SAFETY: T::component_id ensures its:
// - info was created
// - appropriate storage for it has been initialized.
// - was created in the same order as the components in T
unsafe { BundleInfo::new(std::any::type_name::<T>(), components, component_ids, id) };

View File

@ -1724,4 +1724,22 @@ mod tests {
"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 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;
/// 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 {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => Ok(fields),
}) => Ok(&fields.named),
Data::Struct(DataStruct {
fields: Fields::Unnamed(fields),
..
}) => Ok(&fields.unnamed),
_ => Err(Error::new(
// This deliberately points to the call site rather than the structure
// body; marking the entire body as the source of the error makes it
// impossible to figure out which `derive` has a problem.
Span::call_site().into(),
"Only structs with named fields are supported",
"Only structs are supported",
)),
}
}