diff --git a/.gitignore b/.gitignore index d7ae0ff17..bfd162d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ Cargo.lock # Generated by mdbook /book/book/ +# Generated by rustfmt +*.bk + # IDEs / Editor *.iml .idea diff --git a/CHANGELOG.md b/CHANGELOG.md index b5bc6e221..2d52d82f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 9ceb133a4..4f95372f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,3 +63,6 @@ required-features = ["common"] [[example]] name = "serialize" required-features = ["serialize"] + +[workspace] +members = ["specs-derive"] diff --git a/README.md b/README.md index e0a34ceb7..d06ee19bc 100644 --- a/README.md +++ b/README.md @@ -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; } +#[derive(Debug)] +struct Pos(f32); + impl Component for Pos { type Storage = VecStorage; } diff --git a/benches/parallel.rs b/benches/parallel.rs index cbacd9f23..6d957f704 100644 --- a/benches/parallel.rs +++ b/benches/parallel.rs @@ -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; diff --git a/benches/world.rs b/benches/world.rs index 4c58a0513..190c83c3b 100644 --- a/benches/world.rs +++ b/benches/world.rs @@ -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}; diff --git a/book/src/02_hello_world.md b/book/src/02_hello_world.md index 7e134cb09..3aeb38ca9 100644 --- a/book/src/02_hello_world.md +++ b/book/src/02_hello_world.md @@ -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 @@ -114,9 +135,8 @@ Note that all components that a system accesses must be registered with `world.register::()` 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 @@ -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]. diff --git a/book/src/04_resources.md b/book/src/04_resources.md index c287cf2b2..940264e99 100644 --- a/book/src/04_resources.md +++ b/book/src/04_resources.md @@ -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 diff --git a/examples/basic.rs b/examples/basic.rs index e5eb6a060..0d9784ae6 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -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; } +#[derive(Debug)] +struct Pos(f32); + impl Component for Pos { type Storage = VecStorage; } diff --git a/specs-derive/Cargo.toml b/specs-derive/Cargo.toml new file mode 100644 index 000000000..31c0b04a0 --- /dev/null +++ b/specs-derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "specs-derive" +version = "0.1.0" +authors = ["Eyal Kalderon "] +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 diff --git a/specs-derive/src/lib.rs b/specs-derive/src/lib.rs new file mode 100644 index 000000000..7bd91bfaf --- /dev/null +++ b/specs-derive/src/lib.rs @@ -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>; + } + } +} diff --git a/src/join.rs b/src/join.rs index 24a922b58..12502feed 100644 --- a/src/join.rs +++ b/src/join.rs @@ -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; @@ -135,7 +135,7 @@ impl ParallelIterator for JoinParIter where J: Join + Send, J::Mask: Send + Sync, J::Type: Send, - J::Value: Send, + J::Value: Send { type Item = J::Type; @@ -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, @@ -202,15 +202,14 @@ impl<'a, J> UnindexedProducer for JoinProducer<'a, J> where F: Folder { 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) } diff --git a/src/lib.rs b/src/lib.rs index fb3139bac..fc00cc573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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` @@ -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; //! } //! +//! struct Pos(f32); +//! //! impl Component for Pos { //! type Storage = VecStorage; //! } @@ -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; diff --git a/src/storage/tests.rs b/src/storage/tests.rs index e89b35bf3..d8f2e6eea 100644 --- a/src/storage/tests.rs +++ b/src/storage/tests.rs @@ -233,8 +233,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 { @@ -497,7 +497,7 @@ mod test { } } -#[cfg(feature="serialize")] +#[cfg(feature = "serialize")] mod serialize_test { extern crate serde_json; diff --git a/tests/tests.rs b/tests/tests.rs index 8dbb6f4b6..e9c3d9226 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,17 +1,20 @@ extern crate specs; extern crate rayon; -use specs::{Component, DispatcherBuilder, Entities, Entity, Fetch, FetchMut, HashMapStorage, - InsertResult, Join, ParJoin, ReadStorage, System, VecStorage, World, WriteStorage}; +use specs::{Component, DispatcherBuilder, Entities, Entity, Fetch, FetchMut, + HashMapStorage, InsertResult, Join, ParJoin, ReadStorage, System, + VecStorage, World, WriteStorage}; #[derive(Clone, Debug)] struct CompInt(i8); + impl Component for CompInt { type Storage = VecStorage; } #[derive(Clone, Debug)] struct CompBool(bool); + impl Component for CompBool { type Storage = HashMapStorage; } @@ -377,8 +380,7 @@ fn par_join_two_components() { .for_each(|(int, boolean)| if !first.load(Ordering::SeqCst) && int.0 == 1 && !boolean.0 { first.store(true, Ordering::SeqCst); - } else if !second.load(Ordering::SeqCst) && int.0 == 2 && - boolean.0 { + } else if !second.load(Ordering::SeqCst) && int.0 == 2 && boolean.0 { second.store(true, Ordering::SeqCst); } else { *error.lock().unwrap() = Some((int.0, boolean.0));