diff --git a/crates/bevy_ecs/hecs/macros/src/lib.rs b/crates/bevy_ecs/hecs/macros/src/lib.rs index 56660c1e899d0..f7be32f545a9f 100644 --- a/crates/bevy_ecs/hecs/macros/src/lib.rs +++ b/crates/bevy_ecs/hecs/macros/src/lib.rs @@ -19,15 +19,18 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::Span; use proc_macro_crate::crate_name; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, Path}; +use quote::{quote, quote_spanned}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Path}; /// Implement `Bundle` for a monomorphic struct /// /// Using derived `Bundle` impls improves spawn performance and can be convenient when combined with /// other derives like `serde::Deserialize`. +/// +/// Attributes: `#[bundle(skip)]` on fields, skips that field. Requires that the field type +/// implements `Default` #[allow(clippy::cognitive_complexity)] -#[proc_macro_derive(Bundle)] +#[proc_macro_derive(Bundle, attributes(bundle))] pub fn derive_bundle(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); if !input.generics.params.is_empty() { @@ -44,7 +47,12 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { } }; let ident = input.ident; - let (tys, fields) = struct_fields(&data.fields); + let (fields, skipped) = match struct_fields(&data.fields) { + Ok(fields) => fields, + Err(stream) => return stream, + }; + let tys = fields.iter().map(|f| f.0).collect::>(); + let field_names = fields.iter().map(|f| f.1.clone()).collect::>(); let path_str = if crate_name("bevy").is_ok() { "bevy::ecs" } else if crate_name("bevy_ecs").is_ok() { @@ -55,7 +63,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let path: Path = syn::parse(path_str.parse::().unwrap()).unwrap(); - let n = tys.len(); + let n = fields.len(); let code = quote! { impl #path::DynamicBundle for #ident { fn with_ids(&self, f: impl FnOnce(&[std::any::TypeId]) -> T) -> T { @@ -68,9 +76,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { unsafe fn put(mut self, mut f: impl FnMut(*mut u8, std::any::TypeId, usize) -> bool) { #( - if f((&mut self.#fields as *mut #tys).cast::(), std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) { + if f((&mut self.#field_names as *mut #tys).cast::(), std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) { #[allow(clippy::forget_copy)] - std::mem::forget(self.#fields); + std::mem::forget(self.#field_names); } )* } @@ -113,31 +121,61 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { mut f: impl FnMut(std::any::TypeId, usize) -> Option>, ) -> Result { #( - let #fields = f(std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) + let #field_names = f(std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) .ok_or_else(#path::MissingComponent::new::<#tys>)? .cast::<#tys>() .as_ptr(); )* - Ok(Self { #( #fields: #fields.read(), )* }) + Ok(Self { #( #field_names: #field_names.read(), )* #(#skipped: Default::default(),)* }) } } }; TokenStream::from(code) } -fn struct_fields(fields: &syn::Fields) -> (Vec<&syn::Type>, Vec) { +fn struct_fields( + fields: &syn::Fields, +) -> Result<(Vec<(&syn::Type, syn::Ident)>, Vec), TokenStream> { + let mut final_fields = Vec::new(); + let mut skipped = Vec::new(); match fields { - syn::Fields::Named(ref fields) => fields - .named - .iter() - .map(|f| (&f.ty, f.ident.clone().unwrap())) - .unzip(), - syn::Fields::Unnamed(ref fields) => fields - .unnamed - .iter() - .enumerate() - .map(|(i, f)| (&f.ty, syn::Ident::new(&i.to_string(), Span::call_site()))) - .unzip(), - syn::Fields::Unit => (Vec::new(), Vec::new()), + syn::Fields::Named(ref fields) => { + for field in &fields.named { + if should_include_in_bundle(field)? { + final_fields.push((&field.ty, field.ident.clone().unwrap())); + } else { + skipped.push(field.ident.clone().unwrap()); + } + } + } + syn::Fields::Unnamed(ref fields) => { + for (i, field) in fields.unnamed.iter().enumerate() { + if should_include_in_bundle(field)? { + final_fields.push(( + &field.ty, + syn::Ident::new(&i.to_string(), Span::call_site()), + )); + } else { + skipped.push(syn::Ident::new(&i.to_string(), Span::call_site())); + } + } + } + syn::Fields::Unit => {} + }; + Ok((final_fields, skipped)) +} + +fn should_include_in_bundle(f: &syn::Field) -> Result { + for attr in &f.attrs { + if attr.path.is_ident("bundle") { + let string = attr.tokens.to_string(); + if attr.tokens.to_string() == "(skip)" { + return Ok(false); + } else { + let error = format!("Invalid bundle attribute #[bundle{}]", string); + return Err(quote_spanned! {attr.span() => compile_error!(#error)}.into()); + } + } } + Ok(true) } diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 2082edf1427bb..a832544ff55ba 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -11,20 +11,20 @@ pub enum Command { } /// A [World] mutation -pub trait WorldWriter: Send + Sync { +pub trait WorldWriter: Send { fn write(self: Box, world: &mut World); } pub(crate) struct Spawn where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { components: T, } impl WorldWriter for Spawn where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { fn write(self: Box, world: &mut World) { world.spawn(self.components); @@ -33,7 +33,7 @@ where pub(crate) struct SpawnAsEntity where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { entity: Entity, components: T, @@ -41,7 +41,7 @@ where impl WorldWriter for SpawnAsEntity where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { fn write(self: Box, world: &mut World) { world.spawn_as_entity(self.entity, self.components); @@ -58,7 +58,7 @@ where impl WorldWriter for SpawnBatch where - I: IntoIterator + Send + Sync, + I: IntoIterator + Send, I::Item: Bundle, { fn write(self: Box, world: &mut World) { @@ -78,7 +78,7 @@ impl WorldWriter for Despawn { pub struct Insert where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { entity: Entity, components: T, @@ -86,7 +86,7 @@ where impl WorldWriter for Insert where - T: DynamicBundle + Send + Sync + 'static, + T: DynamicBundle + Send + 'static, { fn write(self: Box, world: &mut World) { world.insert(self.entity, self.components).unwrap(); @@ -129,7 +129,7 @@ where } } -pub trait ResourcesWriter: Send + Sync { +pub trait ResourcesWriter: Send { fn write(self: Box, resources: &mut Resources); } @@ -161,14 +161,14 @@ pub struct CommandsInternal { } impl CommandsInternal { - pub fn spawn(&mut self, components: impl DynamicBundle + Send + Sync + 'static) -> &mut Self { + pub fn spawn(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { self.spawn_as_entity(Entity::new(), components) } pub fn spawn_as_entity( &mut self, entity: Entity, - components: impl DynamicBundle + Send + Sync + 'static, + components: impl DynamicBundle + Send + 'static, ) -> &mut Self { self.current_entity = Some(entity); self.commands @@ -179,10 +179,7 @@ impl CommandsInternal { self } - pub fn with_bundle( - &mut self, - components: impl DynamicBundle + Send + Sync + 'static, - ) -> &mut Self { + pub fn with_bundle(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { let current_entity = self.current_entity.expect("Cannot add components because the 'current entity' is not set. You should spawn an entity first."); self.commands.push(Command::WriteWorld(Box::new(Insert { entity: current_entity, @@ -223,14 +220,14 @@ pub struct Commands { } impl Commands { - pub fn spawn(&mut self, components: impl DynamicBundle + Send + Sync + 'static) -> &mut Self { + pub fn spawn(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { self.spawn_as_entity(Entity::new(), components) } pub fn spawn_as_entity( &mut self, entity: Entity, - components: impl DynamicBundle + Send + Sync + 'static, + components: impl DynamicBundle + Send + 'static, ) -> &mut Self { { let mut commands = self.commands.lock(); @@ -241,7 +238,7 @@ impl Commands { pub fn spawn_batch(&mut self, components_iter: I) -> &mut Self where - I: IntoIterator + Send + Sync + 'static, + I: IntoIterator + Send + 'static, I::Item: Bundle, { self.write_world(SpawnBatch { components_iter }) @@ -260,10 +257,7 @@ impl Commands { self } - pub fn with_bundle( - &mut self, - components: impl DynamicBundle + Send + Sync + 'static, - ) -> &mut Self { + pub fn with_bundle(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { { let mut commands = self.commands.lock(); commands.with_bundle(components); @@ -274,7 +268,7 @@ impl Commands { pub fn insert( &mut self, entity: Entity, - components: impl DynamicBundle + Send + Sync + 'static, + components: impl DynamicBundle + Send + 'static, ) -> &mut Self { self.write_world(Insert { entity, components }) } diff --git a/crates/bevy_pbr/src/entity.rs b/crates/bevy_pbr/src/entity.rs index d8afaa1506034..8f75a941d5c42 100644 --- a/crates/bevy_pbr/src/entity.rs +++ b/crates/bevy_pbr/src/entity.rs @@ -21,6 +21,11 @@ pub struct PbrComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for PbrComponents { @@ -52,6 +57,7 @@ impl Default for PbrComponents { translation: Default::default(), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } } @@ -63,4 +69,9 @@ pub struct LightComponents { pub transform: Transform, pub translation: Translation, pub rotation: Rotation, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } diff --git a/crates/bevy_render/src/entity.rs b/crates/bevy_render/src/entity.rs index 417e6a3709b72..cfae49582191f 100644 --- a/crates/bevy_render/src/entity.rs +++ b/crates/bevy_render/src/entity.rs @@ -20,6 +20,11 @@ pub struct MeshComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } /// A component bundle for "3d camera" entities @@ -32,6 +37,11 @@ pub struct Camera3dComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for Camera3dComponents { @@ -47,6 +57,7 @@ impl Default for Camera3dComponents { translation: Default::default(), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } } @@ -61,6 +72,11 @@ pub struct Camera2dComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for Camera2dComponents { @@ -82,6 +98,7 @@ impl Default for Camera2dComponents { translation: Translation::new(0.0, 0.0, far - 0.1), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } } diff --git a/crates/bevy_sprite/src/entity.rs b/crates/bevy_sprite/src/entity.rs index 126fc68edf080..7703dd98d36ed 100644 --- a/crates/bevy_sprite/src/entity.rs +++ b/crates/bevy_sprite/src/entity.rs @@ -24,6 +24,11 @@ pub struct SpriteComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for SpriteComponents { @@ -59,6 +64,7 @@ impl Default for SpriteComponents { translation: Default::default(), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } } @@ -80,6 +86,11 @@ pub struct SpriteSheetComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for SpriteSheetComponents { @@ -115,6 +126,7 @@ impl Default for SpriteSheetComponents { translation: Default::default(), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } } diff --git a/crates/bevy_transform/src/hierarchy/child_builder.rs b/crates/bevy_transform/src/hierarchy/child_builder.rs index f9c4cc0ac6643..0d2cdd560f254 100644 --- a/crates/bevy_transform/src/hierarchy/child_builder.rs +++ b/crates/bevy_transform/src/hierarchy/child_builder.rs @@ -81,24 +81,21 @@ impl WorldWriter for PushChildren { } impl<'a> ChildBuilder<'a> { - pub fn spawn(&mut self, components: impl DynamicBundle + Send + Sync + 'static) -> &mut Self { + pub fn spawn(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { self.spawn_as_entity(Entity::new(), components) } pub fn spawn_as_entity( &mut self, entity: Entity, - components: impl DynamicBundle + Send + Sync + 'static, + components: impl DynamicBundle + Send + 'static, ) -> &mut Self { self.commands.spawn_as_entity(entity, components); self.push_children.children.push(entity); self } - pub fn with_bundle( - &mut self, - components: impl DynamicBundle + Send + Sync + 'static, - ) -> &mut Self { + pub fn with_bundle(&mut self, components: impl DynamicBundle + Send + 'static) -> &mut Self { self.commands.with_bundle(components); self } diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index c92177292391c..fa433fb34da05 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -28,6 +28,11 @@ pub struct NodeComponents { pub render_pipelines: RenderPipelines, pub transform: Transform, pub local_transform: LocalTransform, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for NodeComponents { @@ -58,6 +63,7 @@ impl Default for NodeComponents { draw: Default::default(), transform: Default::default(), local_transform: Default::default(), + not_sync: Default::default(), } } } @@ -74,6 +80,11 @@ pub struct ImageComponents { pub render_pipelines: RenderPipelines, pub transform: Transform, pub local_transform: LocalTransform, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for ImageComponents { @@ -106,6 +117,7 @@ impl Default for ImageComponents { draw: Default::default(), transform: Default::default(), local_transform: Default::default(), + not_sync: Default::default(), } } } @@ -120,6 +132,11 @@ pub struct TextComponents { pub focus_policy: FocusPolicy, pub transform: Transform, pub local_transform: LocalTransform, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for TextComponents { @@ -136,6 +153,7 @@ impl Default for TextComponents { style: Default::default(), transform: Default::default(), local_transform: Default::default(), + not_sync: Default::default(), } } } @@ -153,6 +171,11 @@ pub struct ButtonComponents { pub render_pipelines: RenderPipelines, pub transform: Transform, pub local_transform: LocalTransform, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for ButtonComponents { @@ -186,6 +209,7 @@ impl Default for ButtonComponents { draw: Default::default(), transform: Default::default(), local_transform: Default::default(), + not_sync: Default::default(), } } } @@ -199,6 +223,11 @@ pub struct UiCameraComponents { pub translation: Translation, pub rotation: Rotation, pub scale: Scale, + /// Hack: Prevents `Self: Component`, which prevents + /// `AppBuilder::spawn(Self {..}, )` and `Query<&Self>` from compiling + #[bundle(skip)] + #[doc(hidden)] + pub not_sync: std::marker::PhantomData>, } impl Default for UiCameraComponents { @@ -221,6 +250,7 @@ impl Default for UiCameraComponents { transform: Default::default(), rotation: Default::default(), scale: Default::default(), + not_sync: Default::default(), } } }