Skip to content
This repository has been archived by the owner on Apr 18, 2022. It is now read-only.

PrefabData derive support for enums #1625

Merged
merged 16 commits into from May 26, 2019

Conversation

alec-deason
Copy link
Contributor

@alec-deason alec-deason commented May 21, 2019

Description

PrefabData derive support for enums.

This is my first ever macro code in rust and I'm still learning about how the prefab system works so I would super, super appreciate it if folks could double check that I haven't missed cases in the code or the test.

This requires an update to the book which I'm still working on (though this already includes an updated example). I'll add that to the PR shortly but I was hoping to get some eyes on the code while I work on the docs.

Additions

You can now derive PrefabData for enums which is a thing I would like to do.

Removals

n/a

Modifications

n/a

PR Checklist

By placing an x in the boxes I certify that I have:

  • Ran cargo test --all locally if this modified any rs files.
  • Ran cargo +stable fmt --all locally if this modified any rs files.
  • Updated the content of the book if this PR would make the book outdated.
  • Added a changelog entry if this will impact users, or modified more than 5 lines of Rust that wasn't a doc comment.
  • Added unit tests for new APIs if any were added in this PR.
  • Acknowledged that by making this pull request I release this code under an MIT/Apache 2.0 dual licensing scheme.

Copy link
Contributor

@AnneKitsune AnneKitsune left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooh looks very interesting!

@alec-deason
Copy link
Contributor Author

Ok. I think that's a reasonable update to the book for this stuff. I'm done changing things unless someone has additional comments.

Copy link
Member

@torkleyy torkleyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely, thank you @alec-deason!

player: Option<Named>,
weapon: Option<Weapon>,
```rust,edition2018,no_run,noplaypen
# extern crate amethyst;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can get rid of all those extern crate statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a good way to check syntax on these snippets? mdbook test doesn't seem to run them.

# extern crate serde;
# extern crate specs_derive;
#
# use amethyst::{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense to show these imports (and maybe even the two structs below)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was following the style of the existing writeup but I think you're right, showing more is useful. I'll open it up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I included the structs but now that I look at this again I see that the imports are already visible in a block just above. I think that's cleaner that showing them twice so I'm going to leave it that way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right, sorry. I've missed that.

Three {},
Four,
Five(Stuff<usize>, #[prefab(Component)] External),
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some actual tests (functions tagged with #[test])? I think EnumPrefab covers the cases pretty well, we just need to check that it correctly instantiates the prefab.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good idea.

@alec-deason
Copy link
Contributor Author

alec-deason commented May 24, 2019

As I start to use this in my project I realize that it falls down for a more complex, but common, case. If two variants both use the same resource then that leads to a double borrow so you have to go back to the superset struct with nullable fields model.

That's a bad experience obviously and makes this much less useful in practice. I think I see how to support the shared resource case but I'm going to need fiddle with it to make sure it works. I think I'd like to leave the scope of this PR as is and do the remaining work as a second PR unless folks think this isn't worth having without support for the shared resource case.

@torkleyy
Copy link
Member

torkleyy commented May 24, 2019

If two variants both use the same resource then that leads to a double borrow so you have to go back to the superset struct with nullable fields model.

Ah, right, that was the reason it wasn't implemented in the first place! I'm not sure how useful enum support is on its own, with the current behaviour.

If we want to merge enum support with the current limitation, it would be great to check duplicated resource access in the derive macro; I'm not sure how easy this is, and I believe we can only do this for collisions caused by one struct (not being able to check nested prefabs).

cc @azriel91 Any opinion on this?

@torkleyy torkleyy requested a review from azriel91 May 24, 2019 18:16
@alec-deason
Copy link
Contributor Author

Assuming SystemData works the way I think it does then I can check for duplicates and emit a useful error even with arbitrarily deep nesting. So there's that at least.

If every SystemData in the tree is a tuple then I think I should be able to automatically construct everything that needs to get passed down while correctly handling duplicates. Since tuple SystemDatas are (I think) overwhelmingly the most common case then I believe that would be enough to be useful.

Both those things assume that my mental model of some stuff I don't really understand well is correct so I'll have to write code to know if I can actually do it. But I really want this feature so I'll poke at it today.

@alec-deason
Copy link
Contributor Author

No, wait. The derive macro only cares about the first level of the tree relative to itself. I think that's true... Makes it simpler.

@alec-deason
Copy link
Contributor Author

No, this is much harder. I'm not thinking clearly about what it means to be inside a macro. Sigh. Seems like it must be possible though...

Copy link
Member

@azriel91 azriel91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't finished reviewing, but thank you for working through the whole flow (code, tests, docs) 🙇!

@@ -165,7 +165,7 @@ cargo run --example prefab_multi

### Multiple Entities, Different Components

The next level is to instantiate multiple entities, each with their own set of [`Component`]s. The current implementation of [`Prefab`] requires the `data` field to be the same type for *every* [`PrefabEntity`] in the list. This means we would be unable to declare something like the following snippet, because it uses a `Player` prefab data in one entity, and `Weapon` in another:
The next level is to instantiate multiple entities, each with their own set of [`Component`]s. The current implementation of [`Prefab`] requires the `data` field to be the same type for *every* [`PrefabEntity`] in the list. This means that to have different types of entity in the same prefab they must be variants of an enum. For instance, a prefab like this:

```rust,ignore
// Note: Invalid / erroneous example
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no longer invalid 😄

Sword,
}

/// All fields implement `PrefabData`, and are wrapped in `Option<_>`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is somewhat inconsistent with the enum

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's wrong.

@@ -29,101 +24,91 @@ If you intend to include a [`Component`] that has not yet got a corresponding [`
Error,
};
use serde::{Deserialize, Serialize};
use specs_derive::Component;
```

3. Define the aggregate prefab data type.

In these examples, `Named`, `Position`, and `Weapon` all derive [`PrefabData`].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line and the code snippet don't match up any more; I think the enum prefab data should come under here now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, actually I think I disagree. It shows an example of grouping multiple PrefabDatas into an aggregate first and then moves to the more complex example of using an enum to support heterogeneous types. That seems like the right flow to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes, read the whole thing (instead of just the snippet) and it makes sense

Copy link
Member

@azriel91 azriel91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for this, also the attention to detail (like _suppressing_warnings)

some comments on cleaning, but overall amazing work 💯

amethyst_derive/src/prefab_data.rs Show resolved Hide resolved
amethyst_derive/src/prefab_data.rs Show resolved Hide resolved
.enumerate()
.map(|(field_number, field)| match &field.ident {
Some(name) => {
if !have_component_attribute(&field.attrs[..]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this negation appears in a number of places, so this tiny helper function would help my brain a bit better 😛

#[inline]
fn is_aggregate_prefab(attrs: &[Attribute]) -> bool {
    !have_component_attribute(attrs)
}

Maybe rename have_component_attribute to is_component_prefab

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legit.

amethyst_derive/src/prefab_data.rs Show resolved Hide resolved
@@ -132,48 +117,48 @@ If you intend to include a [`Component`] that has not yet got a corresponding [`
Finally, the [`#[serde(deny_unknown_fields)]`] ensures that deserialization produces an error if it encounters an unknown field. This will help expose mistakes in the prefab file, such as when there is a typo.

4. Now the type can be used in a prefab.

* *Superset* prefab data:
* *struct* prefab data:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe backticks instead of italics now

@alec-deason
Copy link
Contributor Author

Ok I give up. I've churned through about six designs for how to solve the double borrow problem and all of them fall apart when I get into the details. I'm not sure you can do it without fundamentally changing shred. Or at least I really don't see the solution. I'll respond to the feedback on this PR (thank you everyone for your comments) and be done.

@azriel91
Copy link
Member

Sorry I missed replying to the earlier comment about the double borrow. I think the way @alec-deason implemented it is the cleanest way it can be done (without attempting to change an insurmountable amount of things -- shred, specs, prefab system).

@codecov
Copy link

codecov bot commented May 26, 2019

Codecov Report

Merging #1625 into master will increase coverage by 1.03%.
The diff coverage is 95.19%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1625      +/-   ##
==========================================
+ Coverage   64.45%   65.48%   +1.03%     
==========================================
  Files          94      102       +8     
  Lines        6122     6696     +574     
==========================================
+ Hits         3946     4385     +439     
- Misses       2176     2311     +135
Impacted Files Coverage Δ
amethyst_input/src/button.rs 66.66% <ø> (ø) ⬆️
amethyst_input/src/bindings.rs 97.68% <100%> (+0.22%) ⬆️
amethyst_input/src/axis.rs 100% <100%> (ø) ⬆️
amethyst_input/src/system.rs 62.5% <100%> (+1.63%) ⬆️
...methyst_core/src/transform/components/transform.rs 98.42% <100%> (+0.22%) ⬆️
amethyst_input/src/input_handler.rs 87.14% <90.19%> (+5.55%) ⬆️
amethyst_network/src/connection.rs 92.85% <0%> (ø)
amethyst_audio/src/components/audio_emitter.rs 61.11% <0%> (ø)
amethyst_network/src/server/host.rs 100% <0%> (ø)
amethyst_network/src/test.rs 60.31% <0%> (ø)
... and 7 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4800ac5...d3ab328. Read the comment docs.

@alec-deason
Copy link
Contributor Author

Ok, I believe I've responded to everyone's comments (thanks again for the reviews). The biggest change is a small new section in the book about the limitations of enum PrefabDatas and how to get around them. Basically it brings back the discussion of superset prefabs from the original version.

Copy link
Member

@torkleyy torkleyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Looks good to me!

Copy link
Member

@azriel91 azriel91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet thank you! This is an exemplary PR

@azriel91
Copy link
Member

bors r=jaynus, torkleyy, azriel91

bors bot added a commit that referenced this pull request May 26, 2019
1625: PrefabData derive support for enums r=jaynus,torkleyy,azriel91 a=alec-deason

## Description

PrefabData derive support for enums.

This is my first ever macro code in rust and I'm still learning about how the prefab system works so I would super, super appreciate it if folks could double check that I haven't missed cases in the code or the test.

This requires an update to the book which I'm still working on (though this already includes an updated example). I'll add that to the PR shortly but I was hoping to get some eyes on the code while I work on the docs.

## Additions

You can now derive PrefabData for enums which is a thing I would like to do.

## Removals

n/a

## Modifications

n/a

## PR Checklist

By placing an x in the boxes I certify that I have:

- [x] Ran `cargo test --all` locally if this modified any rs files.
- [x] Ran `cargo +stable fmt --all` locally if this modified any rs files.
- [x] Updated the content of the book if this PR would make the book outdated.
- [x] Added a changelog entry if this will impact users, or modified more than 5 lines of Rust that wasn't a doc comment.
- [x] Added unit tests for new APIs if any were added in this PR.
- [x] Acknowledged that by making this pull request I release this code under an MIT/Apache 2.0 dual licensing scheme.


Co-authored-by: Alec Deason <alec@tinycountry.com>
@bors
Copy link
Contributor

bors bot commented May 26, 2019

Build succeeded

@bors bors bot merged commit d3ab328 into amethyst:master May 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants