diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 7ee660f9987d7..ecb3a57c9c477 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -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::>(); + 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)* } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 79f3098146432..361689e5793ad 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -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::(), components, component_ids, id) }; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f971697beb539..ae093733c3b78 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1725,4 +1725,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, + } } diff --git a/crates/bevy_macro_utils/src/shape.rs b/crates/bevy_macro_utils/src/shape.rs index 36ce443bf4396..30eee9a7acbe2 100644 --- a/crates/bevy_macro_utils/src/shape.rs +++ b/crates/bevy_macro_utils/src/shape.rs @@ -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> { 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", )), } }