mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 14:40:19 +00:00
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:
parent
1d22634cfb
commit
7a41efa227
3 changed files with 116 additions and 15 deletions
|
@ -12,8 +12,10 @@ use syn::{
|
|||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
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 {
|
||||
|
@ -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 {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
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(),
|
||||
};
|
||||
|
||||
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
|
||||
.iter()
|
||||
.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_get_components = Vec::new();
|
||||
let mut field_from_components = Vec::new();
|
||||
for (field_type, field) in field_type.iter().zip(field.iter()) {
|
||||
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),
|
||||
});
|
||||
for ((field_type, field_kind), field) in
|
||||
field_type.iter().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),
|
||||
});
|
||||
}
|
||||
|
||||
BundleFieldKind::Ignore => {
|
||||
field_from_components.push(quote! {
|
||||
#field: ::std::default::Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
|
|
@ -82,10 +82,12 @@ use std::{any::TypeId, collections::HashMap};
|
|||
/// used instead.
|
||||
/// The derived `Bundle` implementation contains the items of its fields, which all must
|
||||
/// 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)]
|
||||
/// struct XPosition(i32);
|
||||
|
@ -99,12 +101,20 @@ use std::{any::TypeId, collections::HashMap};
|
|||
/// y: YPosition,
|
||||
/// }
|
||||
///
|
||||
/// // You have to implement `Default` for ignored field types in bundle structs.
|
||||
/// #[derive(Default)]
|
||||
/// struct Other(f32);
|
||||
///
|
||||
/// #[derive(Bundle)]
|
||||
/// struct NamedPointBundle {
|
||||
/// struct NamedPointBundle<T: Send + Sync + 'static> {
|
||||
/// // Or other bundles
|
||||
/// a: PositionBundle,
|
||||
/// // In addition to more components
|
||||
/// z: PointName,
|
||||
///
|
||||
/// // when you need to use `PhantomData` you have to mark it as ignored
|
||||
/// #[bundle(ignore)]
|
||||
/// _phantom_data: PhantomData<T>
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
|
|
|
@ -238,6 +238,45 @@ mod tests {
|
|||
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]
|
||||
|
|
Loading…
Reference in a new issue