Skip to content

Make all builtin bundles !Component #403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 60 additions & 22 deletions crates/bevy_ecs/hecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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::<Vec<_>>();
let field_names = fields.iter().map(|f| f.1.clone()).collect::<Vec<_>>();
let path_str = if crate_name("bevy").is_ok() {
"bevy::ecs"
} else if crate_name("bevy_ecs").is_ok() {
Expand All @@ -55,7 +63,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {

let path: Path = syn::parse(path_str.parse::<TokenStream>().unwrap()).unwrap();

let n = tys.len();
let n = fields.len();
let code = quote! {
impl #path::DynamicBundle for #ident {
fn with_ids<T>(&self, f: impl FnOnce(&[std::any::TypeId]) -> T) -> T {
Expand All @@ -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::<u8>(), std::any::TypeId::of::<#tys>(), std::mem::size_of::<#tys>()) {
if f((&mut self.#field_names as *mut #tys).cast::<u8>(), 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);
}
)*
}
Expand Down Expand Up @@ -113,31 +121,61 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
mut f: impl FnMut(std::any::TypeId, usize) -> Option<std::ptr::NonNull<u8>>,
) -> Result<Self, #path::MissingComponent> {
#(
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<syn::Ident>) {
fn struct_fields(
fields: &syn::Fields,
) -> Result<(Vec<(&syn::Type, syn::Ident)>, Vec<syn::Ident>), 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<bool, TokenStream> {
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)
}
40 changes: 17 additions & 23 deletions crates/bevy_ecs/src/system/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ pub enum Command {
}

/// A [World] mutation
pub trait WorldWriter: Send + Sync {
pub trait WorldWriter: Send {
fn write(self: Box<Self>, world: &mut World);
}

pub(crate) struct Spawn<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
components: T,
}

impl<T> WorldWriter for Spawn<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
world.spawn(self.components);
Expand All @@ -33,15 +33,15 @@ where

pub(crate) struct SpawnAsEntity<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
entity: Entity,
components: T,
}

impl<T> WorldWriter for SpawnAsEntity<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
world.spawn_as_entity(self.entity, self.components);
Expand All @@ -58,7 +58,7 @@ where

impl<I> WorldWriter for SpawnBatch<I>
where
I: IntoIterator + Send + Sync,
I: IntoIterator + Send,
I::Item: Bundle,
{
fn write(self: Box<Self>, world: &mut World) {
Expand All @@ -78,15 +78,15 @@ impl WorldWriter for Despawn {

pub struct Insert<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
entity: Entity,
components: T,
}

impl<T> WorldWriter for Insert<T>
where
T: DynamicBundle + Send + Sync + 'static,
T: DynamicBundle + Send + 'static,
{
fn write(self: Box<Self>, world: &mut World) {
world.insert(self.entity, self.components).unwrap();
Expand Down Expand Up @@ -129,7 +129,7 @@ where
}
}

pub trait ResourcesWriter: Send + Sync {
pub trait ResourcesWriter: Send {
fn write(self: Box<Self>, resources: &mut Resources);
}

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -241,7 +238,7 @@ impl Commands {

pub fn spawn_batch<I>(&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 })
Expand All @@ -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);
Expand All @@ -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 })
}
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_pbr/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::cell::Cell<()>>,
}

impl Default for PbrComponents {
Expand Down Expand Up @@ -52,6 +57,7 @@ impl Default for PbrComponents {
translation: Default::default(),
rotation: Default::default(),
scale: Default::default(),
not_sync: Default::default(),
}
}
}
Expand All @@ -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<std::cell::Cell<()>>,
}
17 changes: 17 additions & 0 deletions crates/bevy_render/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::cell::Cell<()>>,
}

/// A component bundle for "3d camera" entities
Expand All @@ -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<std::cell::Cell<()>>,
}

impl Default for Camera3dComponents {
Expand All @@ -47,6 +57,7 @@ impl Default for Camera3dComponents {
translation: Default::default(),
rotation: Default::default(),
scale: Default::default(),
not_sync: Default::default(),
}
}
}
Expand All @@ -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<std::cell::Cell<()>>,
}

impl Default for Camera2dComponents {
Expand All @@ -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(),
}
}
}
Loading