Skip to content

Commit

Permalink
Implement and require #[derive(Component)] on all component structs (
Browse files Browse the repository at this point in the history
…#2254)

This implements the most minimal variant of #1843 - a derive for marker trait. This is a prerequisite to more complicated features like statically defined storage type or opt-out component reflection.

In order to make component struct's purpose explicit and avoid misuse, it must be annotated with `#[derive(Component)]` (manual impl is discouraged for compatibility). Right now this is just a marker trait, but in the future it might be expanded. Making this change early allows us to make further changes later without breaking backward compatibility for derive macro users.

This already prevents a lot of issues, like using bundles in `insert` calls. Primitive types are no longer valid components as well. This can be easily worked around by adding newtype wrappers and deriving `Component` for them.

One funny example of prevented bad code (from our own tests) is when an newtype struct or enum variant is used. Previously, it was possible to write `insert(Newtype)` instead of `insert(Newtype(value))`. That code compiled, because function pointers (in this case newtype struct constructor) implement `Send + Sync + 'static`, so we allowed them to be used as components. This is no longer the case and such invalid code will trigger a compile error.


Co-authored-by: = <=>
Co-authored-by: TheRawMeatball <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
3 people committed Oct 3, 2021
1 parent 5ba2b9a commit 07ed1d0
Show file tree
Hide file tree
Showing 91 changed files with 1,532 additions and 1,085 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ bevy_audio = ["bevy_internal/bevy_audio"]
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
bevy_gltf = ["bevy_internal/bevy_gltf"]
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
bevy_winit = ["bevy_internal/bevy_winit"]

trace_chrome = ["bevy_internal/trace_chrome"]
Expand Down
16 changes: 11 additions & 5 deletions benches/benches/bevy_ecs/commands.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use bevy::ecs::{
component::Component,
entity::Entity,
system::{Command, CommandQueue, Commands},
world::World,
Expand All @@ -18,8 +19,11 @@ criterion_group!(
);
criterion_main!(benches);

#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;

fn empty_commands(criterion: &mut Criterion) {
Expand Down Expand Up @@ -79,10 +83,10 @@ fn spawn_commands(criterion: &mut Criterion) {
group.finish();
}

#[derive(Default)]
#[derive(Default, Component)]
struct Matrix([[f32; 4]; 4]);

#[derive(Default)]
#[derive(Default, Component)]
struct Vec3([f32; 3]);

fn insert_commands(criterion: &mut Criterion) {
Expand All @@ -95,14 +99,16 @@ fn insert_commands(criterion: &mut Criterion) {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for i in 0..entity_count {
for _ in 0..entity_count {
entities.push(world.spawn().id());
}

bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
for entity in entities.iter() {
commands.entity(*entity).insert_bundle((Matrix::default(), Vec3::default()));
commands
.entity(*entity)
.insert_bundle((Matrix::default(), Vec3::default()));
}
drop(commands);
command_queue.apply(&mut world);
Expand All @@ -112,7 +118,7 @@ fn insert_commands(criterion: &mut Criterion) {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for i in 0..entity_count {
for _ in 0..entity_count {
entities.push(world.spawn().id());
}

Expand Down
8 changes: 7 additions & 1 deletion benches/benches/bevy_ecs/stages.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bevy::ecs::{
component::Component,
schedule::{Stage, SystemStage},
system::{IntoSystem, Query},
system::Query,
world::World,
};
use criterion::{criterion_group, criterion_main, Criterion};
Expand All @@ -12,10 +13,15 @@ fn run_stage(stage: &mut SystemStage, world: &mut World) {
stage.run(world);
}

#[derive(Component)]
struct A(f32);
#[derive(Component)]
struct B(f32);
#[derive(Component)]
struct C(f32);
#[derive(Component)]
struct D(f32);
#[derive(Component)]
struct E(f32);

const ENTITY_BUNCH: usize = 5000;
Expand Down
183 changes: 104 additions & 79 deletions benches/benches/bevy_ecs/world_get.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
use bevy::ecs::{
component::{ComponentDescriptor, StorageType},
entity::Entity,
world::World,
};
use bevy::ecs::{component::Component, entity::Entity, world::World};
use criterion::{black_box, criterion_group, criterion_main, Criterion};

criterion_group!(
Expand All @@ -15,16 +11,18 @@ criterion_group!(
);
criterion_main!(benches);

struct A(f32);
#[derive(Component, Default)]
#[component(storage = "Table")]
struct Table(f32);
#[derive(Component, Default)]
#[component(storage = "SparseSet")]
struct Sparse(f32);

const RANGE: std::ops::Range<u32> = 5..6;

fn setup(entity_count: u32, storage: StorageType) -> World {
fn setup<T: Component + Default>(entity_count: u32) -> World {
let mut world = World::default();
world
.register_component(ComponentDescriptor::new::<A>(storage))
.unwrap();
world.spawn_batch((0..entity_count).map(|_| (A(0.0),)));
world.spawn_batch((0..entity_count).map(|_| (T::default(),)));
world
}

Expand All @@ -35,7 +33,7 @@ fn world_entity(criterion: &mut Criterion) {

for entity_count in RANGE.map(|i| i * 10_000) {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
let world = setup(entity_count, StorageType::Table);
let world = setup::<Table>(entity_count);

bencher.iter(|| {
for i in 0..entity_count {
Expand All @@ -55,21 +53,26 @@ fn world_get(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(4));

for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let world = setup(entity_count, storage);

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(world.get::<A>(entity).is_some());
}
});
},
);
}
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
let world = setup::<Table>(entity_count);

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(world.get::<Table>(entity).is_some());
}
});
});
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
let world = setup::<Sparse>(entity_count);

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(world.get::<Sparse>(entity).is_some());
}
});
});
}

group.finish();
Expand All @@ -81,22 +84,28 @@ fn world_query_get(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(4));

for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(query.get(&world, entity).is_ok());
}
});
},
);
}
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
let mut world = setup::<Table>(entity_count);
let mut query = world.query::<&Table>();

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(query.get(&world, entity).is_ok());
}
});
});
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
let mut world = setup::<Sparse>(entity_count);
let mut query = world.query::<&Sparse>();

bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(query.get(&world, entity).is_ok());
}
});
});
}

group.finish();
Expand All @@ -108,24 +117,32 @@ fn world_query_iter(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(4));

for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();

bencher.iter(|| {
let mut count = 0;
for comp in query.iter(&world) {
black_box(comp);
count += 1;
}
assert_eq!(black_box(count), entity_count);
});
},
);
}
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
let mut world = setup::<Table>(entity_count);
let mut query = world.query::<&Table>();

bencher.iter(|| {
let mut count = 0;
for comp in query.iter(&world) {
black_box(comp);
count += 1;
}
assert_eq!(black_box(count), entity_count);
});
});
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
let mut world = setup::<Sparse>(entity_count);
let mut query = world.query::<&Sparse>();

bencher.iter(|| {
let mut count = 0;
for comp in query.iter(&world) {
black_box(comp);
count += 1;
}
assert_eq!(black_box(count), entity_count);
});
});
}

group.finish();
Expand All @@ -137,24 +154,32 @@ fn world_query_for_each(criterion: &mut Criterion) {
group.measurement_time(std::time::Duration::from_secs(4));

for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();

bencher.iter(|| {
let mut count = 0;
query.for_each(&world, |comp| {
black_box(comp);
count += 1;
});
assert_eq!(black_box(count), entity_count);
});
},
);
}
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
let mut world = setup::<Table>(entity_count);
let mut query = world.query::<&Table>();

bencher.iter(|| {
let mut count = 0;
query.for_each(&world, |comp| {
black_box(comp);
count += 1;
});
assert_eq!(black_box(count), entity_count);
});
});
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
let mut world = setup::<Sparse>(entity_count);
let mut query = world.query::<&Sparse>();

bencher.iter(|| {
let mut count = 0;
query.for_each(&world, |comp| {
black_box(comp);
count += 1;
});
assert_eq!(black_box(count), entity_count);
});
});
}

group.finish();
Expand Down
27 changes: 8 additions & 19 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use crate::{CoreStage, Events, Plugin, PluginGroup, PluginGroupBuilder, StartupStage};
use bevy_ecs::{
component::{Component, ComponentDescriptor},
prelude::{FromWorld, IntoExclusiveSystem},
schedule::{
IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, SystemSet, SystemStage,
IntoSystemDescriptor, RunOnce, Schedule, Stage, StageLabel, State, StateData, SystemSet,
SystemStage,
},
system::Resource,
world::World,
};
use bevy_utils::tracing::debug;
use std::{fmt::Debug, hash::Hash};
use std::fmt::Debug;

#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
Expand Down Expand Up @@ -493,7 +494,7 @@ impl App {
/// adding [State::get_driver] to additional stages you need it in.
pub fn add_state<T>(&mut self, initial: T) -> &mut Self
where
T: Component + Debug + Clone + Eq + Hash,
T: StateData,
{
self.add_state_to_stage(CoreStage::Update, initial)
}
Expand All @@ -505,7 +506,7 @@ impl App {
/// stages you need it in.
pub fn add_state_to_stage<T>(&mut self, stage: impl StageLabel, initial: T) -> &mut Self
where
T: Component + Debug + Clone + Eq + Hash,
T: StateData,
{
self.insert_resource(State::new(initial))
.add_system_set_to_stage(stage, State::<T>::get_driver())
Expand Down Expand Up @@ -582,7 +583,7 @@ impl App {
/// ```
pub fn add_event<T>(&mut self) -> &mut Self
where
T: Component,
T: Resource,
{
self.init_resource::<Events<T>>()
.add_system_to_stage(CoreStage::First, Events::<T>::update_system)
Expand All @@ -608,7 +609,7 @@ impl App {
/// ```
pub fn insert_resource<T>(&mut self, resource: T) -> &mut Self
where
T: Component,
T: Resource,
{
self.world.insert_resource(resource);
self
Expand Down Expand Up @@ -810,18 +811,6 @@ impl App {
self
}

/// Registers a new component using the given [ComponentDescriptor].
///
/// Components do not need to be manually registered. This just provides a way to
/// override default configuration. Attempting to register a component with a type
/// that has already been used by [World] will result in an error.
///
/// See [World::register_component]
pub fn register_component(&mut self, descriptor: ComponentDescriptor) -> &mut Self {
self.world.register_component(descriptor).unwrap();
self
}

/// Adds the type `T` to the type registry resource.
#[cfg(feature = "bevy_reflect")]
pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
Expand Down
Loading

0 comments on commit 07ed1d0

Please sign in to comment.