implemented #[bundle(ignore)] (#6123)

# Objective

Fixes #5559

Replaces #5628

## Solution

Because the generated method from_components() creates an instance of Self my implementation requires any field type that is marked to be ignored to implement Default.

---

## Changelog

Added the possibility to ignore fields in a bundle with `#[bundle(ignore)]`. Typically used when `PhantomData` needs to be added to a `Bundle`.
This commit is contained in:
Marc-Stefan Cassola 2022-10-24 14:33:45 +00:00
parent 1d22634cfb
commit 7a41efa227
3 changed files with 116 additions and 15 deletions

View File

@ -12,8 +12,10 @@ use syn::{
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
parse_macro_input, parse_macro_input,
punctuated::Punctuated, punctuated::Punctuated,
spanned::Spanned,
token::Comma, token::Comma,
DeriveInput, Field, GenericParam, Ident, Index, LitInt, Result, Token, TypeParam, DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result,
Token, TypeParam,
}; };
struct AllTuples { struct AllTuples {
@ -80,7 +82,15 @@ pub fn all_tuples(input: TokenStream) -> TokenStream {
}) })
} }
#[proc_macro_derive(Bundle)] enum BundleFieldKind {
Component,
Ignore,
}
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
#[proc_macro_derive(Bundle, attributes(bundle))]
pub fn derive_bundle(input: TokenStream) -> TokenStream { 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();
@ -90,6 +100,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
Err(e) => return e.into_compile_error().into(), Err(e) => return e.into_compile_error().into(),
}; };
let mut field_kind = Vec::with_capacity(named_fields.len());
'field_loop: for field in named_fields.iter() {
for attr in &field.attrs {
if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) {
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() {
if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
field_kind.push(BundleFieldKind::Ignore);
continue 'field_loop;
}
return syn::Error::new(
path.span(),
format!(
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
),
)
.into_compile_error()
.into();
}
return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into();
}
}
}
field_kind.push(BundleFieldKind::Component);
}
let field = named_fields let field = named_fields
.iter() .iter()
.map(|field| field.ident.as_ref().unwrap()) .map(|field| field.ident.as_ref().unwrap())
@ -102,16 +142,28 @@ 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) in field_type.iter().zip(field.iter()) { for ((field_type, field_kind), field) in
field_component_ids.push(quote! { field_type.iter().zip(field_kind.iter()).zip(field.iter())
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids); {
}); match field_kind {
field_get_components.push(quote! { BundleFieldKind::Component => {
self.#field.get_components(&mut *func); field_component_ids.push(quote! {
}); <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
field_from_components.push(quote! { });
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(ctx, &mut *func), 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),
});
}
BundleFieldKind::Ignore => {
field_from_components.push(quote! {
#field: ::std::default::Default::default(),
});
}
}
} }
let generics = ast.generics; let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

View File

@ -82,10 +82,12 @@ use std::{any::TypeId, collections::HashMap};
/// used instead. /// used instead.
/// The derived `Bundle` implementation contains the items of its fields, which all must /// The derived `Bundle` implementation contains the items of its fields, which all must
/// implement `Bundle`. /// implement `Bundle`.
/// As explained above, this includes any [`Component`] type, and other derived bundles: /// As explained above, this includes any [`Component`] type, and other derived bundles.
/// ///
/// If you want to add `PhantomData` to your `Bundle` you have to mark it with `#[bundle(ignore)]`.
/// ``` /// ```
/// # use bevy_ecs::{component::Component, bundle::Bundle}; /// # use std::marker::PhantomData;
/// use bevy_ecs::{component::Component, bundle::Bundle};
/// ///
/// #[derive(Component)] /// #[derive(Component)]
/// struct XPosition(i32); /// struct XPosition(i32);
@ -99,12 +101,20 @@ use std::{any::TypeId, collections::HashMap};
/// y: YPosition, /// y: YPosition,
/// } /// }
/// ///
/// // You have to implement `Default` for ignored field types in bundle structs.
/// #[derive(Default)]
/// struct Other(f32);
///
/// #[derive(Bundle)] /// #[derive(Bundle)]
/// struct NamedPointBundle { /// struct NamedPointBundle<T: Send + Sync + 'static> {
/// // Or other bundles /// // Or other bundles
/// a: PositionBundle, /// a: PositionBundle,
/// // In addition to more components /// // In addition to more components
/// z: PointName, /// z: PointName,
///
/// // when you need to use `PhantomData` you have to mark it as ignored
/// #[bundle(ignore)]
/// _phantom_data: PhantomData<T>
/// } /// }
/// ///
/// #[derive(Component)] /// #[derive(Component)]

View File

@ -238,6 +238,45 @@ mod tests {
b: B(2), b: B(2),
} }
); );
#[derive(Default, Component, PartialEq, Debug)]
struct Ignored;
#[derive(Bundle, PartialEq, Debug)]
struct BundleWithIgnored {
c: C,
#[bundle(ignore)]
ignored: Ignored,
}
let mut ids = Vec::new();
<BundleWithIgnored as Bundle>::component_ids(
&mut world.components,
&mut world.storages,
&mut |id| {
ids.push(id);
},
);
assert_eq!(ids, &[world.init_component::<C>(),]);
let e4 = world
.spawn(BundleWithIgnored {
c: C,
ignored: Ignored,
})
.id();
assert_eq!(world.get::<C>(e4).unwrap(), &C);
assert_eq!(world.get::<Ignored>(e4), None);
assert_eq!(
world.entity_mut(e4).remove::<BundleWithIgnored>().unwrap(),
BundleWithIgnored {
c: C,
ignored: Ignored,
}
);
} }
#[test] #[test]