-
Notifications
You must be signed in to change notification settings - Fork 219
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
Custom derive for Component trait #192
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see you hear, @ebkalderon!
Looks great, but I would use another storage for the default. Options would be BTreeStorage
and DenseVecStorage
. The latter one was a bit faster when I benchmarked it IIRC.
specs_derive/src/lib.rs
Outdated
use quote::Tokens; | ||
|
||
#[proc_macro_derive(Component, attributes(component))] | ||
pub fn component(input: TokenStream) -> TokenStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to add a small doc comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, will do.
specs_derive/src/lib.rs
Outdated
_ => None, | ||
}) | ||
.cloned() | ||
.unwrap_or(Ident::new("::specs::VecStorage")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if VecStorage
should be the default. Maybe DenseVecStorage
would be better here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's indeed the case, I'll switch it to use DenseVecStorage
.
specs_derive/src/lib.rs
Outdated
|
||
fn impl_component(ast: &MacroInput) -> Tokens { | ||
let name = &ast.ident; | ||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't even know that syn has this method. Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's very nice! I learned about it from the Procedural Macros chapter of the Rust book.
@@ -1,7 +1,7 @@ | |||
use std; | |||
use std::cell::UnsafeCell; | |||
|
|||
use hibitset::{BitSetAnd, BitIter, BitSetLike, BitProducer}; | |||
use hibitset::{BitIter, BitProducer, BitSetAnd, BitSetLike}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, forgot to reorder this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, I really like the derive for components, so maybe we could update the README with it?
@torkleyy Sure, I would be happy to update the README! But if we're using |
Yes, I think we should do that. And we could just show both ways of implementing |
@torkleyy Another question: would you like me to re-export these macros under the top-level EDIT: If so, under that same logic, couldn't the |
@ebkalderon Is it possible to reexport custom-derive macros? |
@torkleyy I'm pretty sure you can. I can quickly check on my end to verify, once I'm done revising the PR. EDIT: Yes we can! 🎉 Would you like me to do that for both |
@torkleyy I successfully re-exported |
Finally done! I have updated the README, book, examples, and and as many doc comments as I could find. Let me know if there are any nits to take care of. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks for addressing all of my comments. Only one nit left.
README.md
Outdated
@@ -33,20 +33,19 @@ Unlike most other ECS libraries out there, it provides | |||
## Example | |||
|
|||
```rust | |||
#[macro_use] | |||
extern crate specs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not have any extern crate
s or imports in the README, it's irrelevant for the API usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, I'll fix that.
specs_derive/src/lib.rs
Outdated
/// #[derive(Component, Debug)] | ||
/// #[component(HashMapStorage)] | ||
/// struct Pos(f32, f32, f32); | ||
/// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great, thanks 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, let's let @kvark glance over it before merging.
examples/full.rs
Outdated
|
||
// -- Components -- | ||
// A component exists for 0..n | ||
// entities. | ||
|
||
#[derive(Clone, Debug)] | ||
// `VecStorage` is meant to be used for components that are in almost every | ||
// entity tend to be highly contested. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, forgot the word "and" here.
I'm sorry but I don't find this useful enough. It's saving just a few lines of user code, at the cost of more lines in our code ( |
@kvark Regardless, people will need to understand the storage types in order to use the attribute properly; it's just presented more concisely from the user's side. But still, you raise some good points. In the end, it's your call! 😀 |
I'm using specs in a codebase with nearly 50 different component types (and climbing). I currently implement |
@malleusinferni thanks for your input! What's wrong with the macro you are using now? |
My first instinct was exactly what @kvark said. It saves a few lines per component, and comes at a cost of some indirection which can confuse new users. That being said, I'm not strong in my position. I think we could probably strike a balance, if @malleusinferni finds it useful. There are bound to be other users who want to use the more terse form. Since this must be published as a secondary crate anyhow, maybe we can show the old form in the examples. We can include a quick passage in the book/readme so that people can find this crate. |
Please don't forget: while it's great to be beginner-friendly (which we are; we got a book, nice examples and a Gitter channel to ask for help), the people dealing with these components and the Specs API will do that for many, many hours (like @malleusinferni). The Using the right storage for the right situation isn't any different: now, beginners would just copy the example code, then they'd copy |
Just FYI, for all participants, Also, if you haven't noticed, the crate itself is pretty darn small (just one source file, with only two functions inside). Nearly all of the changes included in this PR are updating docs, examples, doc comments, the relevant book chapters, and the README. Both the book and the basic example clearly explain what the desugared API boils down to, and I've done my best to document all that I could. Personally, I think that the reduced user-side boilerplate and the lessening of possible copy-paste errors is worth the effort, but those are just my two cents. |
Im impartial about this, but I have one concern: Does this support wrapper storages like |
@Aceeri Maybe we could use |
Doesn't crates.io demand that all dependencies are in crates.io? I would like to have derive as an option as it basically doesn't remove any relevant information and just reduces boilerplate. It's shame that it cannot have syntax like this though: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also with custom derives, compiletest would be useful :P
specs_derive/src/lib.rs
Outdated
let name = &ast.ident; | ||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); | ||
|
||
let storage = ast.attrs.first() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this fail if there is more that one attribute and the component
attribute isn't first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, you're right! I forgot to change that while I was refactoring.
So, let me recap: Edit: I'm fine with a separate crate being published that provides the derives. I don't find it particularly useful, given that a user can just throw a 4 line macro to do the same thing in an even shorter form, but I'm fine with it if it's not propagating to specs and just builds on top of it. |
Proposing compromise:
|
Okay, I think we found a consensus here (I proposed, @kvark upvoted and @csherratt suggested pretty much the same earlier). @ebkalderon Would it be okay for you to move on like this? |
Yes! That sounds reasonable enough. EDIT: Unfortunately, I don't have time to work on it tonight, but expect the PR to be revised sometime tomorrow when I'm off work. |
Okay, I've finally had some spare time to rebase and update this PR to address the concerns raised. Please feel free to review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to see progress here!
Could we use specs-derive
instead of specs_derive
like I did it with shred
and shred-derive
?
benches/parallel.rs
Outdated
use specs::{Component, DenseVecStorage, DispatcherBuilder, Entities, Entity, Fetch, | ||
HashMapStorage, Join, NullStorage, ReadStorage, RunningTime, System, VecStorage, | ||
World, WriteStorage}; | ||
use specs::{DenseVecStorage, DispatcherBuilder, Entities, Entity, Fetch, HashMapStorage, Join, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need that export.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thank you.
benches/world.rs
Outdated
@@ -3,11 +3,12 @@ | |||
extern crate cgmath; | |||
extern crate rand; | |||
extern crate rayon; | |||
#[macro_use] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I missed that.
benches/world.rs
Outdated
extern crate specs; | ||
|
||
extern crate test; | ||
|
||
use specs::{Component, HashMapStorage, Join, ParJoin, VecStorage, World}; | ||
use specs::{HashMapStorage, Join, ParJoin, VecStorage, World}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, forgot to add it back in after rebasing.
book/src/02_hello_world.md
Outdated
@@ -20,7 +21,7 @@ extern crate specs; | |||
Let's start by creating some data: | |||
|
|||
```rust,ignore | |||
use specs::{Component, VecStorage}; | |||
use specs::VecStorage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, I'll fix that.
book/src/02_hello_world.md
Outdated
(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, `specs` includes a convenient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. I think we agreed we wouldn't want #[derive]
shown in the book - except in one dedicated chapter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need a dedicated chapter for this macro, though? It is mentioned here once as an optional thing, and then never used again in the book. Still, I can create a new chapter if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we don't need to. If somebody wants it, it's not hard to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
book/src/02_hello_world.md
Outdated
@@ -137,6 +157,7 @@ hello_world.run_now(&world.res); | |||
Here the complete example of this chapter: | |||
|
|||
```rust,ignore | |||
<<<<<<< HEAD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was left when rebasing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, you're right. I'll go remove that.
book/src/03_dispatcher.md
Outdated
@@ -102,6 +102,7 @@ dispatcher.dispatch(&mut world.res); | |||
Here the code for this chapter: | |||
|
|||
```rust,ignore | |||
<<<<<<< HEAD |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I messed up on that one.
src/lib.rs
Outdated
@@ -167,17 +177,22 @@ | |||
//! | |||
//! | |||
|
|||
#[allow(unused_imports)] | |||
#[macro_use] | |||
extern crate specs_derive; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to include it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was hoping to allow specs_derive
to re-export the procedural macro to the user, meaning they don't need to add extern crate specs_derive;
to their project. If you prefer that users add it explicitly as a separate crate to their projects, I can remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure. cc @kvark
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just discussed with @kvark, we decided not to reexport it here so it doesn't get included if it's unnecessary for the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I'll take it out.
src/lib.rs
Outdated
pub use storage::{BTreeStorage, CheckStorage, DenseVecStorage, DistinctStorage, FlaggedStorage, | ||
HashMapStorage, InsertResult, NullStorage, ReadStorage, Storage, | ||
UnprotectedStorage, VecStorage, WriteStorage}; | ||
pub use world::{Component, CreateIter, CreateIterAtomic, EntitiesRes, Entity, EntityBuilder, | ||
Generation, LazyUpdate, World}; | ||
|
||
#[cfg(not(target_os = "emscripten"))] | ||
pub use shred::{AsyncDispatcher}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why move the re-export?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I must have made a mistake while copy-pasting sections around during the rebase.
book/src/02_hello_world.md
Outdated
@@ -12,6 +12,7 @@ specs = "0.9" | |||
And add this to your crate root (`main.rs` or `lib.rs`): | |||
|
|||
```rust,ignore | |||
#[macro_use] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for #[macro_use]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great PR, thank you so much!
Hold on, please add an entry to the changelog. |
src/lib.rs
Outdated
@@ -191,6 +202,7 @@ pub use shred::{Dispatcher, DispatcherBuilder, Fetch, FetchId, FetchIdMut, | |||
#[cfg(not(target_os = "emscripten"))] | |||
pub use shred::{AsyncDispatcher}; | |||
|
|||
pub use specs_derive::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line causes the build to fail.
@torkleyy Done! It should build correctly now, and I've updated the changelog. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thank you so much!
bors r+ |
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.
Build succeeded |
193: Maintenance/192/ongoing code maintenance r=azriel91 a=azriel91 Closes amethyst#192. Co-authored-by: Azriel Hoh <[email protected]>
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 thederivative
crate.Before
After
The macro will store components in
VecStorage
s by default. To specify a different storage type, you may use the#[component]
attribute.I also included a revised
basic.rs
example calledbasic_derive.rs
to demonstrate its usage, but this can probably be omitted from the PR if people ultimately prefer implementingComponent
the explicit way.EDIT: Use
DenseVecStorage
by default.