Skip to content

Commit

Permalink
Merge #192
Browse files Browse the repository at this point in the history
192: Custom derive for Component trait r=torkleyy

This PR introduces an optional `specs_derive` crate containing a custom derive macro for defining new components in a less verbose way. The ergonomics loosely resemble the [`derivative`](https://github.com/mcarton/rust-derivative) crate.

### Before

```rust
#[derive(Debug)]
struct Pos(f32, f32, f32);

impl Component for Pos {
    type Storage = VecStorage<Pos>;
}
```

### After

```rust
#[derive(Component, Debug)]
struct Pos(f32, f32, f32);
```

The macro will store components in `VecStorage`s by default. To specify a different storage type, you may use the `#[component]` attribute.

```rust
#[derive(Component, Debug)]
#[component(HashMapStorage)]
struct Pos(f32, f32, f32);
```

I also included a revised `basic.rs` example called `basic_derive.rs` to demonstrate its usage, but this can probably be omitted from the PR if people ultimately prefer implementing `Component` the explicit way.

**EDIT**: Use `DenseVecStorage` by default.
  • Loading branch information
bors[bot] committed Jul 22, 2017
2 parents d31e0f4 + 086f698 commit 546b04f
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 52 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Cargo.lock
# Generated by mdbook
/book/book/

# Generated by rustfmt
*.bk

# IDEs / Editor
*.iml
.idea
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## 0.9.3 (Unreleased)
* Add `specs-derive` crate, custom `#[derive]` for components ([#192])
* Add lazy updates: insert and remove components, execute closures on world ([#214], [#221])

[#192]: https://github.com/slide-rs/specs/pull/192
[#214]: https://github.com/slide-rs/specs/pull/214
[#221]: https://github.com/slide-rs/specs/pull/221

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,6 @@ required-features = ["common"]
[[example]]
name = "serialize"
required-features = ["serialize"]

[workspace]
members = ["specs-derive"]
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@ Unlike most other ECS libraries out there, it provides
```rust
// A component contains data
// which is associated with an entity.

#[derive(Debug)]
struct Vel(f32);
#[derive(Debug)]
struct Pos(f32);

impl Component for Vel {
type Storage = VecStorage<Self>;
}

#[derive(Debug)]
struct Pos(f32);

impl Component for Pos {
type Storage = VecStorage<Self>;
}
Expand Down
8 changes: 3 additions & 5 deletions benches/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
extern crate cgmath;
extern crate rand;
extern crate specs;

extern crate test;

use cgmath::Vector2;
use rand::thread_rng;
use specs::{Component, DenseVecStorage, DispatcherBuilder, Entities, Entity, Fetch,
HashMapStorage, Join, NullStorage, ReadStorage, RunningTime, System, VecStorage,
World, WriteStorage};

use specs::{Component, DenseVecStorage, DispatcherBuilder, Entities, Entity,
Fetch, HashMapStorage, Join, NullStorage, ReadStorage, RunningTime,
System, VecStorage, World, WriteStorage};
use test::Bencher;

type Vec2 = Vector2<f32>;
Expand Down
1 change: 0 additions & 1 deletion benches/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ extern crate cgmath;
extern crate rand;
extern crate rayon;
extern crate specs;

extern crate test;

use specs::{Component, HashMapStorage, Join, ParJoin, VecStorage, World};
Expand Down
41 changes: 30 additions & 11 deletions book/src/02_hello_world.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,31 @@ impl Component for Velocity {
}
```

These will be our components, stored in a `VecStorage`
(see [the storages chapter][sc] for more details), so we can associate some data
with an entity. Before doing that, we need to create a world, because this is
where the storage for all the components is located.
These will be our two component types. Optionally, the `specs-derive` crate
provides a convenient custom `#[derive]` you can use to define component types
more succinctly:

```rust,ignore
#[derive(Component, Debug)]
#[component(VecStorage)]
struct Position {
x: f32,
y: f32
}
#[derive(Component, Debug)]
#[component(VecStorage)]
struct Velocity {
x: f32,
y: f32,
}
```

If the `#[component(...)]` attribute is omitted, the given component will be
stored in a `DenseVecStorage` by default. But for this example, we are
explicitly asking for these components to be kept in a `VecStorage` instead (see
the later [storages chapter][sc] for more details). But before we move on, we
need to create a world in which to store all of our components.

[sc]: ./05_storages.html

Expand Down Expand Up @@ -114,9 +135,8 @@ Note that all components that a system accesses must be registered with
`world.register::<Component>()` before that system is run, or you will get a
panic.

> There are many other types you can use as system
data. Please see the [System Data Chapter][cs] for more
information.
> There are many other types you can use as system data. Please see the
> [System Data Chapter][cs] for more information.
[cs]: ./06_system_data.html

Expand Down Expand Up @@ -186,10 +206,9 @@ fn main() {

---

This was a pretty basic example so far. A key feature we
haven't seen is the `Dispatcher`, which allows to
configure run systems in parallel (and it offers some other
nice features, too).
This was a pretty basic example so far. A key feature we haven't seen is the
`Dispatcher`, which allows to configure run systems in parallel (and it offers
some other nice features, too).

Let's see how that works in [Chapter 3: Dispatcher][c3].

Expand Down
3 changes: 1 addition & 2 deletions book/src/04_resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ Note that all resources that a system accesses must be registered with
`world.add_resource(resource)` before that system is run, or you will get a
panic.

For more information on `SystemData`, see [the
system data chapter][cs].
For more information on `SystemData`, see [the system data chapter][cs].

[cs]: ./06_system_data.html

Expand Down
12 changes: 6 additions & 6 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
extern crate specs;

use specs::{Component, DispatcherBuilder, Join, ReadStorage, System, VecStorage, World,
WriteStorage};
use specs::{Component, DispatcherBuilder, Join, ReadStorage, System, VecStorage,
World, WriteStorage};

// A component contains data
// which is associated with an entity.
// A component contains data which is associated with an entity.

#[derive(Debug)]
struct Vel(f32);
#[derive(Debug)]
struct Pos(f32);

impl Component for Vel {
type Storage = VecStorage<Self>;
}

#[derive(Debug)]
struct Pos(f32);

impl Component for Pos {
type Storage = VecStorage<Self>;
}
Expand Down
16 changes: 16 additions & 0 deletions specs-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "specs-derive"
version = "0.1.0"
authors = ["Eyal Kalderon <[email protected]>"]
description = "Custom derive macro for Specs components"
documentation = "https://docs.rs/specs-derive"
repository = "https://github.com/slide-rs/specs/specs-derive"
keywords = ["gamedev", "parallel", "specs", "ecs", "derive"]
license = "MIT/Apache-2.0"

[dependencies]
syn = "0.11"
quote = "0.3"

[lib]
proc-macro = true
65 changes: 65 additions & 0 deletions specs-derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Implements the `#[derive(Component)]` macro and `#[component]` attribute for
//! [Specs][sp].
//!
//! [sp]: https://slide-rs.github.io/specs-website/

extern crate proc_macro;
#[macro_use]
extern crate quote;
extern crate syn;

use proc_macro::TokenStream;
use syn::{Ident, MacroInput, MetaItem, NestedMetaItem};
use quote::Tokens;

/// Custom derive macro for the `Component` trait.
///
/// ## Example
///
/// ```
/// #[derive(Component, Debug)]
/// struct Pos(f32, f32, f32);
/// ```
///
/// The macro will store components in `DenseVecStorage`s by default. To specify
/// a different storage type, you may use the `#[component]` attribute.
///
/// ```
/// #[derive(Component, Debug)]
/// #[component(HashMapStorage)]
/// struct Pos(f32, f32, f32);
/// ```
#[proc_macro_derive(Component, attributes(component))]
pub fn component(input: TokenStream) -> TokenStream {
let s = input.to_string();
let ast = syn::parse_derive_input(&s).unwrap();
let gen = impl_component(&ast);
gen.parse().unwrap()
}

fn impl_component(ast: &MacroInput) -> Tokens {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

let storage = ast.attrs.first()
.and_then(|attr| match attr.value {
MetaItem::List(ref ident, ref items) if ident == "component" => items.first(),
_ => None,
})
.and_then(|attr| match *attr {
NestedMetaItem::MetaItem(ref item) => Some(item),
_ => None,
})
.and_then(|attr| match *attr {
MetaItem::Word(ref ident) => Some(ident),
_ => None,
})
.cloned()
.unwrap_or(Ident::new("::specs::DenseVecStorage"));

quote! {
impl #impl_generics ::specs::Component for #name #ty_generics #where_clause {
type Storage = #storage<#name>;
}
}
}
23 changes: 11 additions & 12 deletions src/join.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std;
use std::cell::UnsafeCell;

use hibitset::{BitSetAnd, BitIter, BitSetLike, BitProducer};
use hibitset::{BitIter, BitProducer, BitSetAnd, BitSetLike};
use rayon::iter::ParallelIterator;
use rayon::iter::internal::{Folder, UnindexedConsumer, UnindexedProducer, bridge_unindexed};
use tuple_utils::Split;
Expand Down Expand Up @@ -135,7 +135,7 @@ impl<J> ParallelIterator for JoinParIter<J>
where J: Join + Send,
J::Mask: Send + Sync,
J::Type: Send,
J::Value: Send,
J::Value: Send
{
type Item = J::Type;

Expand All @@ -154,7 +154,7 @@ struct JoinProducer<'a, J>
where J: Join + Send,
J::Mask: Send + Sync + 'a,
J::Type: Send,
J::Value: Send + 'a,
J::Value: Send + 'a
{
keys: BitProducer<'a, J::Mask>,
values: &'a UnsafeCell<J::Value>,
Expand Down Expand Up @@ -202,15 +202,14 @@ impl<'a, J> UnindexedProducer for JoinProducer<'a, J>
where F: Folder<Self::Item>
{
let JoinProducer { values, keys, .. } = self;
let iter = keys.0
.map(|idx| unsafe {
// This unsafe block should be safe if the `J::get`
// can be safely called from different threads with distinct indices.

// The indices here are guaranteed to be distinct because of the fact
// that the bit set is split.
J::get(&mut *values.get(), idx)
});
let iter = keys.0.map(|idx| unsafe {
// This unsafe block should be safe if the `J::get`
// can be safely called from different threads with distinct indices.

// The indices here are guaranteed to be distinct because of the fact
// that the bit set is split.
J::get(&mut *values.get(), idx)
});

folder.consume_iter(iter)
}
Expand Down
23 changes: 17 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
//! }
//! ```
//!
//! Or alternatively, if you import the `specs-derive` crate, you can use a
//! custom `#[derive]` macro:
//!
//! ```rust,ignore
//! #[derive(Component)]
//! #[component(VecStorage)]
//! struct MyComp;
//! ```
//!
//! You can choose different storages according to your needs.
//!
//! These storages can be [`join`]ed together, for example joining a `Velocity`
Expand Down Expand Up @@ -77,18 +86,20 @@
//! ```rust
//! extern crate specs;
//!
//! use specs::{DispatcherBuilder, Component, Join, ReadStorage, System, VecStorage, WriteStorage,
//! World};
//! use specs::{Component, DispatcherBuilder, Join, ReadStorage, System, VecStorage,
//! WriteStorage, World};
//!
//! // A component contains data which is
//! // associated with an entity.
//!
//! // A component contains data
//! // which is associated with an entity.
//! struct Vel(f32);
//! struct Pos(f32);
//!
//! impl Component for Vel {
//! type Storage = VecStorage<Self>;
//! }
//!
//! struct Pos(f32);
//!
//! impl Component for Pos {
//! type Storage = VecStorage<Self>;
//! }
Expand Down Expand Up @@ -172,9 +183,9 @@ extern crate crossbeam;
extern crate fnv;
extern crate hibitset;
extern crate mopa;
extern crate rayon;
extern crate shred;
extern crate tuple_utils;
extern crate rayon;

#[cfg(feature = "common")]
extern crate futures;
Expand Down
6 changes: 3 additions & 3 deletions src/storage/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ mod test {

for i in 0..1_000 {
*s.get_mut(Entity::new(i, Generation::new(1)))
.unwrap()
.as_mut() -= 718;
.unwrap()
.as_mut() -= 718;
}

for i in 0..1_000 {
Expand Down Expand Up @@ -548,7 +548,7 @@ mod test {
}
}

#[cfg(feature="serialize")]
#[cfg(feature = "serialize")]
mod serialize_test {
extern crate serde_json;

Expand Down
Loading

0 comments on commit 546b04f

Please sign in to comment.