Skip to content

Commit 2768af5

Browse files
authored
Allow not emitting BundleFromComponents with Bundle derive macro (#19249)
# Objective Fixes #19136 ## Solution - Add a new container attribute which when set does not emit `BundleFromComponents` ## Testing - Did you test these changes? Yes, a new test was added. - Are there any parts that need more testing? Since `BundleFromComponents` is unsafe I made extra sure that I did not misunderstand its purpose. As far as I can tell, _not_ implementing it is ok. - How can other people (reviewers) test your changes? Is there anything specific they need to know? Nope - If relevant, what platforms did you test these changes on, and are there any important ones you can't test? I don't think the platform is relevant --- One thing I am not sure about is how to document this? I'll gladly add it --------- Signed-off-by: Marcel Müller <[email protected]>
1 parent b6e4d17 commit 2768af5

File tree

2 files changed

+128
-14
lines changed

2 files changed

+128
-14
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,48 @@ enum BundleFieldKind {
2929

3030
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
3131
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
32+
const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components";
33+
34+
#[derive(Debug)]
35+
struct BundleAttributes {
36+
impl_from_components: bool,
37+
}
38+
39+
impl Default for BundleAttributes {
40+
fn default() -> Self {
41+
Self {
42+
impl_from_components: true,
43+
}
44+
}
45+
}
3246

3347
/// Implement the `Bundle` trait.
3448
#[proc_macro_derive(Bundle, attributes(bundle))]
3549
pub fn derive_bundle(input: TokenStream) -> TokenStream {
3650
let ast = parse_macro_input!(input as DeriveInput);
3751
let ecs_path = bevy_ecs_path();
3852

53+
let mut errors = vec![];
54+
55+
let mut attributes = BundleAttributes::default();
56+
57+
for attr in &ast.attrs {
58+
if attr.path().is_ident(BUNDLE_ATTRIBUTE_NAME) {
59+
let parsing = attr.parse_nested_meta(|meta| {
60+
if meta.path.is_ident(BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS) {
61+
attributes.impl_from_components = false;
62+
return Ok(());
63+
}
64+
65+
Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`")))
66+
});
67+
68+
if let Err(error) = parsing {
69+
errors.push(error.into_compile_error());
70+
}
71+
}
72+
}
73+
3974
let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
4075
Ok(fields) => fields,
4176
Err(e) => return e.into_compile_error().into(),
@@ -130,7 +165,28 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
130165
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
131166
let struct_name = &ast.ident;
132167

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+
133187
TokenStream::from(quote! {
188+
#(#attribute_errors)*
189+
134190
// SAFETY:
135191
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
136192
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
@@ -159,20 +215,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
159215
}
160216
}
161217

162-
// SAFETY:
163-
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
164-
#[allow(deprecated)]
165-
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
166-
#[allow(unused_variables, non_snake_case)]
167-
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
168-
where
169-
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
170-
{
171-
Self{
172-
#(#field_from_components)*
173-
}
174-
}
175-
}
218+
#from_components
176219

177220
#[allow(deprecated)]
178221
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {

crates/bevy_ecs/src/bundle.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,57 @@
22
//!
33
//! This module contains the [`Bundle`] trait and some other helper types.
44
5+
/// Derive the [`Bundle`] trait
6+
///
7+
/// You can apply this derive macro to structs that are
8+
/// composed of [`Component`]s or
9+
/// other [`Bundle`]s.
10+
///
11+
/// ## Attributes
12+
///
13+
/// Sometimes parts of the Bundle should not be inserted.
14+
/// Those can be marked with `#[bundle(ignore)]`, and they will be skipped.
15+
/// In that case, the field needs to implement [`Default`] unless you also ignore
16+
/// the [`BundleFromComponents`] implementation.
17+
///
18+
/// ```rust
19+
/// # use bevy_ecs::prelude::{Component, Bundle};
20+
/// # #[derive(Component)]
21+
/// # struct Hitpoint;
22+
/// #
23+
/// #[derive(Bundle)]
24+
/// struct HitpointMarker {
25+
/// hitpoints: Hitpoint,
26+
///
27+
/// #[bundle(ignore)]
28+
/// creator: Option<String>
29+
/// }
30+
/// ```
31+
///
32+
/// Some fields may be bundles that do not implement
33+
/// [`BundleFromComponents`]. This happens for bundles that cannot be extracted.
34+
/// For example with [`SpawnRelatedBundle`](bevy_ecs::spawn::SpawnRelatedBundle), see below for an
35+
/// example usage.
36+
/// In those cases you can either ignore it as above,
37+
/// or you can opt out the whole Struct by marking it as ignored with
38+
/// `#[bundle(ignore_from_components)]`.
39+
///
40+
/// ```rust
41+
/// # use bevy_ecs::prelude::{Component, Bundle, ChildOf, Spawn};
42+
/// # #[derive(Component)]
43+
/// # struct Hitpoint;
44+
/// # #[derive(Component)]
45+
/// # struct Marker;
46+
/// #
47+
/// use bevy_ecs::spawn::SpawnRelatedBundle;
48+
///
49+
/// #[derive(Bundle)]
50+
/// #[bundle(ignore_from_components)]
51+
/// struct HitpointMarker {
52+
/// hitpoints: Hitpoint,
53+
/// related_spawner: SpawnRelatedBundle<ChildOf, Spawn<Marker>>,
54+
/// }
55+
/// ```
556
pub use bevy_ecs_macros::Bundle;
657

758
use crate::{
@@ -2122,6 +2173,26 @@ mod tests {
21222173
}
21232174
}
21242175

2176+
#[derive(Bundle)]
2177+
#[bundle(ignore_from_components)]
2178+
struct BundleNoExtract {
2179+
b: B,
2180+
no_from_comp: crate::spawn::SpawnRelatedBundle<ChildOf, Spawn<C>>,
2181+
}
2182+
2183+
#[test]
2184+
fn can_spawn_bundle_without_extract() {
2185+
let mut world = World::new();
2186+
let id = world
2187+
.spawn(BundleNoExtract {
2188+
b: B,
2189+
no_from_comp: Children::spawn(Spawn(C)),
2190+
})
2191+
.id();
2192+
2193+
assert!(world.entity(id).get::<Children>().is_some());
2194+
}
2195+
21252196
#[test]
21262197
fn component_hook_order_spawn_despawn() {
21272198
let mut world = World::new();

0 commit comments

Comments
 (0)