Skip to content

Commit 27c32ef

Browse files
committed
Require manual Component implementation
1 parent 677a94e commit 27c32ef

35 files changed

Lines changed: 1160 additions & 640 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Unreleased
2+
3+
### Changed
4+
5+
- `Component` is no longer automatically implemented for all `Send + Sync + 'static` types. Types
6+
must now implement it explicitly, either by hand (`impl Component for Position {}`) or with the
7+
new `#[derive(Component)]` macro from the `hecs-macros` crate (enabled by the `macros` feature).
8+
This rules out accidental use of third-party types as components and leaves the door open for
9+
future trait extensions.
10+
111
# 0.11.1
212

313
### Added

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,24 @@ your application however you like!
1313

1414
```rust
1515
let mut world = hecs::World::new();
16-
// Nearly any type can be used as a component with zero boilerplate
17-
let a = world.spawn((123, true, "abc"));
18-
let b = world.spawn((42, false));
16+
17+
// Component types can be defined with minimal boilerplate
18+
struct Name(&'static str);
19+
impl hecs::Component for Name {}
20+
struct Weight(u32);
21+
impl hecs::Component for Weight {}
22+
struct Price(u32);
23+
impl hecs::Component for Price {}
24+
25+
let a = world.spawn((Name("abc"), Weight(12), Price(123)));
26+
let b = world.spawn((Weight(38), Price(42)));
1927
// Systems can be simple for loops
20-
for (number, &flag) in world.query_mut::<(&mut i32, &bool)>() {
21-
if flag { *number *= 2; }
28+
for (price, weight) in world.query_mut::<(&mut Price, &Weight)>() {
29+
if weight.0 < 20 { price.0 *= 2; }
2230
}
2331
// Random access is simple and safe
24-
assert_eq!(*world.get::<&i32>(a).unwrap(), 246);
25-
assert_eq!(*world.get::<&i32>(b).unwrap(), 42);
32+
assert_eq!(world.get::<&Price>(a).unwrap().0, 246);
33+
assert_eq!(world.get::<&Price>(b).unwrap().0, 42);
2634
```
2735

2836
### Why ECS?

benches/bench.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ use hecs::*;
77

88
#[derive(Clone)]
99
struct Position(f32);
10+
impl Component for Position {}
1011
#[derive(Clone)]
1112
struct Velocity(f32);
13+
impl Component for Velocity {}
14+
15+
/// Stand-in component used to populate worlds with interesting archetype shapes
16+
struct Extra<const N: usize>;
17+
impl<const N: usize> Component for Extra<N> {}
1218

1319
fn spawn_tuple(c: &mut Criterion) {
1420
let mut world = World::new();
@@ -108,8 +114,8 @@ fn insert_remove(c: &mut Criterion) {
108114
b.iter(|| {
109115
let e = *entities.next().unwrap();
110116
world.remove_one::<Velocity>(e).unwrap();
111-
world.insert_one(e, true).unwrap();
112-
world.remove_one::<bool>(e).unwrap();
117+
world.insert_one(e, Extra::<0>).unwrap();
118+
world.remove_one::<Extra<0>>(e).unwrap();
113119
world.insert_one(e, Velocity(0.0)).unwrap();
114120
})
115121
});
@@ -124,8 +130,8 @@ fn exchange(c: &mut Criterion) {
124130
c.bench_function("exchange", |b| {
125131
b.iter(|| {
126132
let e = *entities.next().unwrap();
127-
world.exchange_one::<Velocity, _>(e, true).unwrap();
128-
world.exchange_one::<bool, _>(e, Velocity(0.0)).unwrap();
133+
world.exchange_one::<Velocity, _>(e, Extra::<0>).unwrap();
134+
world.exchange_one::<Extra<0>, _>(e, Velocity(0.0)).unwrap();
129135
})
130136
});
131137
}
@@ -212,8 +218,8 @@ fn for_each_batched_100k(c: &mut Criterion) {
212218

213219
fn spawn_100_by_50(world: &mut World) {
214220
fn spawn_two<const N: usize>(world: &mut World, i: i32) {
215-
world.spawn((Position(-(i as f32)), Velocity(i as f32), [(); N]));
216-
world.spawn((Position(-(i as f32)), [(); N]));
221+
world.spawn((Position(-(i as f32)), Velocity(i as f32), Extra::<N>));
222+
world.spawn((Position(-(i as f32)), Extra::<N>));
217223
}
218224

219225
for i in 0..2 {
@@ -264,7 +270,7 @@ fn iterate_uncached_1_of_100_by_50(c: &mut Criterion) {
264270
b.iter(|| {
265271
for (pos, vel) in world
266272
.query::<(&mut Position, &Velocity)>()
267-
.with::<&[(); 0]>()
273+
.with::<&Extra<0>>()
268274
.iter()
269275
{
270276
pos.0 += vel.0;
@@ -339,9 +345,9 @@ fn build_cloneable(c: &mut Criterion) {
339345
fn access_view(c: &mut Criterion) {
340346
let mut world = World::new();
341347
let _enta = world.spawn((Position(0.0), Velocity(0.0)));
342-
let _entb = world.spawn((true, 12));
348+
let _entb = world.spawn((Extra::<0>, Extra::<1>));
343349
let entc = world.spawn((Position(3.0),));
344-
let _entd = world.spawn((13, true, 4.0));
350+
let _entd = world.spawn((Extra::<1>, Extra::<2>, Extra::<3>));
345351
let mut query = PreparedQuery::<&Position>::new();
346352
let mut query = query.query(&world);
347353
let view = query.view();

examples/cloning.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ use std::any::TypeId;
1010

1111
use hecs::{Archetype, ColumnBatchBuilder, ColumnBatchType, Component, TypeIdMap, TypeInfo, World};
1212

13+
#[derive(Clone, Debug, PartialEq)]
14+
struct Count(i32);
15+
impl Component for Count {}
16+
17+
#[derive(Clone, Debug, PartialEq)]
18+
struct Name(String);
19+
impl Component for Name {}
20+
21+
/// A component that will not be registered with the [`WorldCloner`]
22+
#[derive(Clone, Debug, PartialEq)]
23+
struct Marker(u8);
24+
impl Component for Marker {}
25+
1326
struct ComponentCloneMetadata {
1427
type_info: TypeInfo,
1528
insert_into_batch_func: &'static dyn Fn(&Archetype, &mut ColumnBatchBuilder),
@@ -75,21 +88,15 @@ impl WorldCloner {
7588
}
7689

7790
pub fn main() {
78-
let int0 = 0;
79-
let int1 = 1;
80-
let str0 = "Ada".to_owned();
81-
let str1 = "Bob".to_owned();
82-
let str2 = "Cal".to_owned();
83-
8491
let mut world0 = World::new();
85-
let entity0 = world0.spawn((int0, str0));
86-
let entity1 = world0.spawn((int1, str1));
87-
let entity2 = world0.spawn((str2,));
88-
let entity3 = world0.spawn((0u8,)); // unregistered component
92+
let entity0 = world0.spawn((Count(0), Name("Ada".to_owned())));
93+
let entity1 = world0.spawn((Count(1), Name("Bob".to_owned())));
94+
let entity2 = world0.spawn((Name("Cal".to_owned()),));
95+
let entity3 = world0.spawn((Marker(0),)); // unregistered component
8996

9097
let mut cloner = WorldCloner::default();
91-
cloner.register::<i32>();
92-
cloner.register::<String>();
98+
cloner.register::<Count>();
99+
cloner.register::<Name>();
93100

94101
let world1 = cloner.clone_world(&world0);
95102

@@ -104,18 +111,18 @@ pub fn main() {
104111
world0
105112
.entity(entity3)
106113
.expect("w0 entity3 should exist")
107-
.has::<u8>(),
108-
"original world entity has u8 component"
114+
.has::<Marker>(),
115+
"original world entity has Marker component"
109116
);
110117
assert!(
111118
!world1
112119
.entity(entity3)
113120
.expect("w1 entity3 should exist")
114-
.has::<u8>(),
115-
"cloned world entity does not have u8 component because it was not registered"
121+
.has::<Marker>(),
122+
"cloned world entity does not have Marker component because it was not registered"
116123
);
117124

118-
type AllRegisteredComponentsQuery = (&'static i32, &'static String);
125+
type AllRegisteredComponentsQuery = (&'static Count, &'static Name);
119126
for entity in [entity0, entity1] {
120127
let w0_e = world0.entity(entity).expect("w0 entity should exist");
121128
let w1_e = world1.entity(entity).expect("w1 entity should exist");
@@ -128,7 +135,7 @@ pub fn main() {
128135
);
129136
}
130137

131-
type SomeRegisteredComponentsQuery = (&'static String,);
138+
type SomeRegisteredComponentsQuery = (&'static Name,);
132139
let w0_e = world0.entity(entity2).expect("w0 entity2 should exist");
133140
let w1_e = world1.entity(entity2).expect("w1 entity2 should exist");
134141
assert!(w0_e.satisfies::<SomeRegisteredComponentsQuery>());

examples/ffa_simulation.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,23 @@ struct Position {
1919
x: i32,
2020
y: i32,
2121
}
22+
impl Component for Position {}
2223

2324
#[derive(Debug)]
2425
struct Health(i32);
26+
impl Component for Health {}
2527

2628
#[derive(Debug)]
2729
struct Speed(i32);
30+
impl Component for Speed {}
2831

2932
#[derive(Debug)]
3033
struct Damage(i32);
34+
impl Component for Damage {}
3135

3236
#[derive(Debug)]
3337
struct KillCount(i32);
38+
impl Component for KillCount {}
3439

3540
fn manhattan_dist(x0: i32, x1: i32, y0: i32, y1: i32) -> i32 {
3641
let dx = (x0 - x1).abs();

examples/format.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,36 @@
33
44
type FormattingFunction = &'static dyn Fn(hecs::EntityRef<'_>) -> Option<String>;
55

6+
struct Number(i32);
7+
impl hecs::Component for Number {}
8+
impl std::fmt::Display for Number {
9+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
10+
self.0.fmt(f)
11+
}
12+
}
13+
14+
struct Flag(bool);
15+
impl hecs::Component for Flag {}
16+
impl std::fmt::Display for Flag {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
self.0.fmt(f)
19+
}
20+
}
21+
22+
struct Ratio(f64);
23+
impl hecs::Component for Ratio {}
24+
impl std::fmt::Display for Ratio {
25+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26+
self.0.fmt(f)
27+
}
28+
}
29+
630
fn format_entity(entity: hecs::EntityRef<'_>) -> String {
731
fn fmt<T: hecs::Component + std::fmt::Display>(entity: hecs::EntityRef<'_>) -> Option<String> {
832
Some(entity.get::<&T>()?.to_string())
933
}
1034

11-
const FUNCTIONS: &[FormattingFunction] = &[&fmt::<i32>, &fmt::<bool>, &fmt::<f64>];
35+
const FUNCTIONS: &[FormattingFunction] = &[&fmt::<Number>, &fmt::<Flag>, &fmt::<Ratio>];
1236

1337
let mut out = String::new();
1438
for f in FUNCTIONS {
@@ -31,6 +55,6 @@ fn format_entity(entity: hecs::EntityRef<'_>) -> String {
3155

3256
fn main() {
3357
let mut world = hecs::World::new();
34-
let e = world.spawn((42, true));
58+
let e = world.spawn((Number(42), Flag(true)));
3559
println!("{}", format_entity(world.entity(e).unwrap()));
3660
}

examples/serialize_to_disk.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@ struct SaveContextDeserialize {
3535
}
3636

3737
// Components of our world.
38-
// Only Serialize and Deserialize derives are necessary.
38+
// In addition to implementing `Component`, types need Serialize and Deserialize derives to be
39+
// included in the serialization process.
3940
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
4041
struct ComponentA {
4142
data: usize,
4243
}
44+
impl hecs::Component for ComponentA {}
4345

4446
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
4547
struct ComponentB {
4648
some_other_data: String,
4749
}
50+
impl hecs::Component for ComponentB {}
4851

4952
impl DeserializeContext for SaveContextDeserialize {
5053
fn deserialize_component_ids<'de, A>(&mut self, mut seq: A) -> Result<ColumnBatchType, A::Error>

examples/transform_hierarchy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct Parent {
1212
/// Converts child-relative coordinates to parent-relative coordinates
1313
from_child: Transform,
1414
}
15+
impl Component for Parent {}
1516

1617
fn main() {
1718
let mut world = World::new();
@@ -100,6 +101,7 @@ fn evaluate_relative_transforms(world: &mut World) {
100101
// In practice this would usually also include rotation, or even be a general homogeneous matrix
101102
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
102103
struct Transform(i32, i32);
104+
impl Component for Transform {}
103105

104106
impl std::ops::Mul for Transform {
105107
type Output = Transform;

macros/src/lib.rs

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod query;
77
pub(crate) mod common;
88

99
use proc_macro::TokenStream;
10+
use quote::quote;
1011
use syn::{parse_macro_input, DeriveInput};
1112

1213
/// Implement `Bundle` for a struct
@@ -17,15 +18,23 @@ use syn::{parse_macro_input, DeriveInput};
1718
/// # Example
1819
/// ```
1920
/// # use hecs::*;
21+
/// #[derive(Debug, PartialEq)]
22+
/// struct X(i32);
23+
/// impl Component for X {}
24+
///
25+
/// #[derive(Debug, PartialEq)]
26+
/// struct Y(char);
27+
/// impl Component for Y {}
28+
///
2029
/// #[derive(Bundle)]
2130
/// struct Foo {
22-
/// x: i32,
23-
/// y: char,
31+
/// x: X,
32+
/// y: Y,
2433
/// }
2534
///
2635
/// let mut world = World::new();
27-
/// let e = world.spawn(Foo { x: 42, y: 'a' });
28-
/// assert_eq!(*world.get::<&i32>(e).unwrap(), 42);
36+
/// let e = world.spawn(Foo { x: X(42), y: Y('a') });
37+
/// assert_eq!(*world.get::<&X>(e).unwrap(), X(42));
2938
/// ```
3039
#[proc_macro_derive(Bundle)]
3140
pub fn derive_bundle(input: TokenStream) -> TokenStream {
@@ -68,19 +77,27 @@ pub fn derive_dynamic_bundle_clone(input: TokenStream) -> TokenStream {
6877
/// # Example
6978
/// ```
7079
/// # use hecs::*;
80+
/// #[derive(Debug, PartialEq)]
81+
/// struct X(i32);
82+
/// impl Component for X {}
83+
///
84+
/// #[derive(Debug, PartialEq)]
85+
/// struct Y(bool);
86+
/// impl Component for Y {}
87+
///
7188
/// #[derive(Query, Debug, PartialEq)]
7289
/// struct Foo<'a> {
73-
/// x: &'a i32,
74-
/// y: &'a mut bool,
90+
/// x: &'a X,
91+
/// y: &'a mut Y,
7592
/// }
7693
///
7794
/// let mut world = World::new();
78-
/// let e = world.spawn((42, false));
95+
/// let e = world.spawn((X(42), Y(false)));
7996
/// assert_eq!(
8097
/// world.query_one_mut::<Foo>(e).unwrap(),
8198
/// Foo {
82-
/// x: &42,
83-
/// y: &mut false
99+
/// x: &X(42),
100+
/// y: &mut Y(false)
84101
/// }
85102
/// );
86103
/// ```
@@ -93,3 +110,24 @@ pub fn derive_query(input: TokenStream) -> TokenStream {
93110
}
94111
.into()
95112
}
113+
114+
/// Implement `Component` for some type.
115+
///
116+
/// Convenience short-hand for `impl Component for T {}`.
117+
///
118+
/// Generic type parameters automatically receive the `Send + Sync + 'static` bounds required by the
119+
/// trait's supertraits. When this is inappropriate, use a manual implementation instead.
120+
#[proc_macro_derive(Component)]
121+
pub fn derive_component(input: TokenStream) -> TokenStream {
122+
let input = parse_macro_input!(input as DeriveInput);
123+
let ident = input.ident;
124+
let mut generics = input.generics;
125+
for param in generics.type_params_mut() {
126+
param.bounds.push(syn::parse_quote!(::core::marker::Send));
127+
param.bounds.push(syn::parse_quote!(::core::marker::Sync));
128+
param.bounds.push(syn::parse_quote!('static));
129+
}
130+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
131+
132+
quote! { impl #impl_generics ::hecs::Component for #ident #ty_generics #where_clause {} }.into()
133+
}

0 commit comments

Comments
 (0)