Skip to content
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

Reflexive world queries #68

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Mar 6, 2023

RENDERED.

Currently, the #[derive(WorldQuery)] macro produces types with a sub-par API. For each user-defined type, the macro generates an additional "Item" type associated with it, and the API is spread between these two types in a way that increases complexity and makes custom world query types more difficult to use and create. I propose we amend this by requiring derived world queries to be reflexive, which removes the need for "Item" types.

@CGMossa
Copy link

CGMossa commented Mar 6, 2023

I'd welcome an improvement to this honestly.
An example from my stuff: I have an enum and marker types related to this enum;
Since we don't have enum-variants-something-feature, I wanted to roll my own setup;
I could succinctly write it as

/// Query for figuring out if a given animal is [`Piglet`], [`Juvenile`] or [`Adult`].
/// See [`AgeGroup`].
#[derive(Debug, WorldQuery)]
pub struct ReadAgeGroup {
    piglet: Option<&'static Piglet>,
    juvenile: Option<&'static Juvenile>,
    adult: Option<&'static Adult>,
    //FIXME: this should probably have `With<WildBoar>` on it.
}

impl ReadAgeGroupItem<'_> {
    /// Returns the [`AgeGroup`] of the given entity.
    pub fn get(&self) -> AgeGroup {
        match (self.piglet, self.juvenile, self.adult) {
            // piglet
            (Some(_), None, None) => AgeGroup::Piglet,
            // juvenile
            (None, Some(_), None) => AgeGroup::Juvenile,
            // adult
            (None, None, Some(_)) => AgeGroup::Adult,
            _ => panic!("`WildBoar`-entity has the wrong age-groups component."),
        }
    }
}

Notice the *Item I need to write. Now the documentation for this is in three places: Module with marker types; the enum;
and now this.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Well-motivated and a good change. I'm in favor!

This is particularly nasty when trying to write impl blocks for the QueryItem type. It's a great pattern, but hard to discover and hard to document.

## Drawbacks

This will add slightly more friction in some cases, since
types such as `&mut T` are forbidden from being used in the derive macro.
Copy link
Member

@james7132 james7132 Mar 8, 2023

Choose a reason for hiding this comment

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

This is my biggest gripe with this approach. This sacrifices the simpler mental model for normal users and happy path and forces any mutative query to use Mut<T> over &mut T, and that mapping from &T to &mut T is now lost.

We can address this by implementing &mut T: World Query, and just forcibly mark everything matched as mutated, but that would make the distinction between &mut T and Mut<T> extremely nuanced.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'm nervous about that, especially because of how subtly it breaks existing code.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is my biggest gripe with this approach. This sacrifices the simpler mental model for normal users and happy path and forces any mutative query to use Mut<T> over &mut T, and that mapping from &T to &mut T is now lost.

Not sure if we're on the same page, but I want to clarify that you can still write Query<&mut T> and have it return Mut<T> -- the reflexive constraint is only for named WorldQuery structs made using the derive macro. Very little user code will be affected in practice. I think this constraint will surprise users at first, but I also think it will make sense when they think about it, or read the docs and have it explained to them (we'll have to think of a good way of explaining this that would make it click more easily). Also, this behavior is consistent with how the SystemParam derive macro works, which might make it easier to understand for some users.

Copy link
Member Author

Choose a reason for hiding this comment

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

Another point I want to add: using &mut T in a derived WorldQuery already fails with a confusing type error, until you read the docs and see that you need the special attribute #[world_query(mutable)]. Since we're already relying on the user to read the docs for this macro to be usable, I don't think there's much of a regression caused by requiring world queries to be reflexive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants