Skip to content

Commit

Permalink
implemented #[bundle(ignore)] (#6123)
Browse files Browse the repository at this point in the history
# 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`.
  • Loading branch information
maccesch committed Oct 24, 2022
1 parent 0f3f628 commit c5b8f87
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 15 deletions.
76 changes: 64 additions & 12 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand All @@ -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())
Expand All @@ -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();
Expand Down
16 changes: 13 additions & 3 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)]
Expand Down
39 changes: 39 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit c5b8f87

Please sign in to comment.