Skip to content

Commit 35166d9

Browse files
authored
Refactor bundle derive (#19749)
# Objective - Splitted off from #19491 - Make adding generated code to the `Bundle` derive macro easier - Fix a bug when multiple fields are `#[bundle(ignore)]` ## Solution - Instead of accumulating the code for each method in a different `Vec`, accumulate only the names of non-ignored fields and their types, then use `quote` to generate the code for each of them in the method body. - To fix the bug, change the code populating the `BundleFieldKind` to push only one of them per-field (previously each `#[bundle(ignore)]` resulted in pushing twice, once for the correct `BundleFieldKind::Ignore` and then again unconditionally for `BundleFieldKind::Component`) ## Testing - Added a regression test for the bug that was fixed
1 parent 8e1d005 commit 35166d9

File tree

2 files changed

+61
-72
lines changed

2 files changed

+61
-72
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 52 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::{
1616
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
1717
use proc_macro::TokenStream;
1818
use proc_macro2::{Ident, Span};
19-
use quote::{format_ident, quote};
19+
use quote::{format_ident, quote, ToTokens};
2020
use syn::{
2121
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
2222
ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam,
@@ -79,14 +79,16 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
7979
let mut field_kind = Vec::with_capacity(named_fields.len());
8080

8181
for field in named_fields {
82+
let mut kind = BundleFieldKind::Component;
83+
8284
for attr in field
8385
.attrs
8486
.iter()
8587
.filter(|a| a.path().is_ident(BUNDLE_ATTRIBUTE_NAME))
8688
{
8789
if let Err(error) = attr.parse_nested_meta(|meta| {
8890
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
89-
field_kind.push(BundleFieldKind::Ignore);
91+
kind = BundleFieldKind::Ignore;
9092
Ok(())
9193
} else {
9294
Err(meta.error(format!(
@@ -98,7 +100,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
98100
}
99101
}
100102

101-
field_kind.push(BundleFieldKind::Component);
103+
field_kind.push(kind);
102104
}
103105

104106
let field = named_fields
@@ -111,82 +113,33 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
111113
.map(|field| &field.ty)
112114
.collect::<Vec<_>>();
113115

114-
let mut field_component_ids = Vec::new();
115-
let mut field_get_component_ids = Vec::new();
116-
let mut field_get_components = Vec::new();
117-
let mut field_from_components = Vec::new();
118-
let mut field_required_components = Vec::new();
116+
let mut active_field_types = Vec::new();
117+
let mut active_field_tokens = Vec::new();
118+
let mut inactive_field_tokens = Vec::new();
119119
for (((i, field_type), field_kind), field) in field_type
120120
.iter()
121121
.enumerate()
122122
.zip(field_kind.iter())
123123
.zip(field.iter())
124124
{
125+
let field_tokens = match field {
126+
Some(field) => field.to_token_stream(),
127+
None => Index::from(i).to_token_stream(),
128+
};
125129
match field_kind {
126130
BundleFieldKind::Component => {
127-
field_component_ids.push(quote! {
128-
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids);
129-
});
130-
field_required_components.push(quote! {
131-
<#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);
132-
});
133-
field_get_component_ids.push(quote! {
134-
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
135-
});
136-
match field {
137-
Some(field) => {
138-
field_get_components.push(quote! {
139-
self.#field.get_components(&mut *func);
140-
});
141-
field_from_components.push(quote! {
142-
#field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
143-
});
144-
}
145-
None => {
146-
let index = Index::from(i);
147-
field_get_components.push(quote! {
148-
self.#index.get_components(&mut *func);
149-
});
150-
field_from_components.push(quote! {
151-
#index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
152-
});
153-
}
154-
}
131+
active_field_types.push(field_type);
132+
active_field_tokens.push(field_tokens);
155133
}
156134

157-
BundleFieldKind::Ignore => {
158-
field_from_components.push(quote! {
159-
#field: ::core::default::Default::default(),
160-
});
161-
}
135+
BundleFieldKind::Ignore => inactive_field_tokens.push(field_tokens),
162136
}
163137
}
164138
let generics = ast.generics;
165139
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
166140
let struct_name = &ast.ident;
167141

168-
let from_components = attributes.impl_from_components.then(|| quote! {
169-
// SAFETY:
170-
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
171-
#[allow(deprecated)]
172-
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
173-
#[allow(unused_variables, non_snake_case)]
174-
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
175-
where
176-
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
177-
{
178-
Self{
179-
#(#field_from_components)*
180-
}
181-
}
182-
}
183-
});
184-
185-
let attribute_errors = &errors;
186-
187-
TokenStream::from(quote! {
188-
#(#attribute_errors)*
189-
142+
let bundle_impl = quote! {
190143
// SAFETY:
191144
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
192145
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
@@ -196,27 +149,27 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
196149
fn component_ids(
197150
components: &mut #ecs_path::component::ComponentsRegistrator,
198151
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
199-
){
200-
#(#field_component_ids)*
152+
) {
153+
#(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components, ids);)*
201154
}
202155

203156
fn get_component_ids(
204157
components: &#ecs_path::component::Components,
205158
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
206-
){
207-
#(#field_get_component_ids)*
159+
) {
160+
#(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)*
208161
}
209162

210163
fn register_required_components(
211164
components: &mut #ecs_path::component::ComponentsRegistrator,
212165
required_components: &mut #ecs_path::component::RequiredComponents
213-
){
214-
#(#field_required_components)*
166+
) {
167+
#(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);)*
215168
}
216169
}
170+
};
217171

218-
#from_components
219-
172+
let dynamic_bundle_impl = quote! {
220173
#[allow(deprecated)]
221174
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
222175
type Effect = ();
@@ -226,9 +179,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
226179
self,
227180
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
228181
) {
229-
#(#field_get_components)*
182+
#(<#active_field_types as #ecs_path::bundle::DynamicBundle>::get_components(self.#active_field_tokens, &mut *func);)*
230183
}
231184
}
185+
};
186+
187+
let from_components_impl = attributes.impl_from_components.then(|| quote! {
188+
// SAFETY:
189+
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
190+
#[allow(deprecated)]
191+
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
192+
#[allow(unused_variables, non_snake_case)]
193+
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
194+
where
195+
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
196+
{
197+
Self {
198+
#(#active_field_tokens: <#active_field_types as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),)*
199+
#(#inactive_field_tokens: ::core::default::Default::default(),)*
200+
}
201+
}
202+
}
203+
});
204+
205+
let attribute_errors = &errors;
206+
207+
TokenStream::from(quote! {
208+
#(#attribute_errors)*
209+
#bundle_impl
210+
#from_components_impl
211+
#dynamic_bundle_impl
232212
})
233213
}
234214

crates/bevy_ecs/src/bundle.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,4 +2397,13 @@ mod tests {
23972397

23982398
assert_eq!(world.resource::<Count>().0, 3);
23992399
}
2400+
2401+
#[derive(Bundle)]
2402+
#[expect(unused, reason = "tests the output of the derive macro is valid")]
2403+
struct Ignore {
2404+
#[bundle(ignore)]
2405+
foo: i32,
2406+
#[bundle(ignore)]
2407+
bar: i32,
2408+
}
24002409
}

0 commit comments

Comments
 (0)