Skip to content

Required components accept const values (#16720)#18309

Merged
alice-i-cecile merged 5 commits intobevyengine:mainfrom
Wuketuke:issue-16720
Mar 21, 2025
Merged

Required components accept const values (#16720)#18309
alice-i-cecile merged 5 commits intobevyengine:mainfrom
Wuketuke:issue-16720

Conversation

@Wuketuke
Copy link
Contributor

@Wuketuke Wuketuke commented Mar 14, 2025

Objective

Const values should be more ergonomic to insert, since this is too verbose

#[derive(Component)]
#[require(
    LockedAxes(||LockedAxes::ROTATION_LOCKED),
)]
pub struct CharacterController;

instead, users can now abbreviate that nonsense like this

#[derive(Component)]
#[require(
    LockedAxes = ROTATION_LOCKED),
)]
pub struct CharacterController;

it also works for enum labels.
I chose to omit the type, since were trying to reduce typing here. The alternative would have been this:

#[require(
    LockedAxes = LockedAxes::ROTATION_LOCKED),
)]

This of course has its disadvantages, since the const has to be associated, but the old closure method is still possible, so I dont think its a problem.

Testing

I added one new test in the docs, which also explain the new change. I also saw that the docs for the required components on line 165 was missing an assertion, so I added it back in

}
} else if input.peek(Token![=]) {
let _t: syn::Token![=] = input.parse()?;
let label: Ident = input.parse()?;
Copy link
Contributor

Choose a reason for hiding this comment

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

We could parse out an expression instead and support more ways to set the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, but you would somehow need to extract the type from the expression, so it will be messy

Copy link
Contributor

@Carter0 Carter0 left a comment

Choose a reason for hiding this comment

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

Not really a macro expert but this seems reasonable. I think it would be really awesome if we had a required components example showcasing this work, but I can do that in another PR too

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use labels Mar 20, 2025
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.

A bit implicit but I think it's worth it. Using constants here should be dead easy.

@alice-i-cecile alice-i-cecile added this to the 0.16 milestone Mar 20, 2025
@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Mar 20, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Mar 21, 2025
Merged via the queue into bevyengine:main with commit 55fd105 Mar 21, 2025
33 checks passed
mockersf pushed a commit that referenced this pull request Mar 23, 2025
# Objective

Const values should be more ergonomic to insert, since this is too
verbose
``` rust
#[derive(Component)]
#[require(
    LockedAxes(||LockedAxes::ROTATION_LOCKED),
)]
pub struct CharacterController;
```
instead, users can now abbreviate that nonsense like this
``` rust
#[derive(Component)]
#[require(
    LockedAxes = ROTATION_LOCKED),
)]
pub struct CharacterController;
```
it also works for enum labels.
I chose to omit the type, since were trying to reduce typing here. The
alternative would have been this:
```rust
#[require(
    LockedAxes = LockedAxes::ROTATION_LOCKED),
)]
```
This of course has its disadvantages, since the const has to be
associated, but the old closure method is still possible, so I dont
think its a problem.
- Fixes #16720

## Testing

I added one new test in the docs, which also explain the new change. I
also saw that the docs for the required components on line 165 was
missing an assertion, so I added it back in

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
@cart
Copy link
Member

cart commented Mar 25, 2025

I would have preferred the less implicit:

#[derive(Component)]
#[require(
    LockedAxes = LockedAxes::ROTATION_LOCKED),
)]
pub struct CharacterController;

That would also support this pattern:

const CHARACTER_CONTROLLER_LOCKED_AXES: LockedAxes = LockedAxes::from_bits(...);

#[derive(Component)]
#[require(
    LockedAxes = CHARACTER_CONTROLLER_LOCKED_AXES,
)]
pub struct CharacterController;

The current implementation is unintuitive and will be confusingly broken when people try to do something like the above example.

@cart
Copy link
Member

cart commented Mar 25, 2025

Actually I think we should go even further and do this:

require(
   Transform, // becomes Transform::default
   LockedAxes::ROTATION_LOCKED, // becomes || LockedAxes::ROTATION_LOCKED
   Health(10), // becomes || Health(10)
   Defense = || Defense(8), // becomes  || Defense(8)
   Speed = my_speed, // becomes my_speed
   Position { x: 10, y: 20 }, // becomes || Position { x: 10, y: 20 }
)

This would be a breaking change, but I think its what pretty much everyone actually wants. I'll take a stab at this

@cart cart mentioned this pull request Mar 26, 2025
github-merge-queue bot pushed a commit that referenced this pull request Mar 26, 2025
# Objective

Requires are currently more verbose than they need to be. People would
like to define inline component values. Additionally, the current
`#[require(Foo(custom_constructor))]` and `#[require(Foo(|| Foo(10))]`
syntax doesn't really make sense within the context of the Rust type
system. #18309 was an attempt to improve ergonomics for some cases, but
it came at the cost of even more weirdness / unintuitive behavior. Our
approach as a whole needs a rethink.

## Solution

Rework the `#[require()]` syntax to make more sense. This is a breaking
change, but I think it will make the system easier to learn, while also
improving ergonomics substantially:

```rust
#[derive(Component)]
#[require(
    A, // this will use A::default()
    B(1), // inline tuple-struct value
    C { value: 1 }, // inline named-struct value
    D::Variant, // inline enum variant
    E::SOME_CONST, // inline associated const
    F::new(1), // inline constructor
    G = returns_g(), // an expression that returns G
    H = SomethingElse::new(), // expression returns SomethingElse, where SomethingElse: Into<H> 
)]
struct Foo;
```

## Migration Guide

Custom-constructor requires should use the new expression-style syntax:

```rust
// before
#[derive(Component)]
#[require(A(returns_a))]
struct Foo;

// after
#[derive(Component)]
#[require(A = returns_a())]
struct Foo;
```

Inline-closure-constructor requires should use the inline value syntax
where possible:

```rust
// before
#[derive(Component)]
#[require(A(|| A(10))]
struct Foo;

// after
#[derive(Component)]
#[require(A(10)]
struct Foo;
```

In cases where that is not possible, use the expression-style syntax:

```rust
// before
#[derive(Component)]
#[require(A(|| A(10))]
struct Foo;

// after
#[derive(Component)]
#[require(A = A(10)]
struct Foo;
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
mockersf added a commit that referenced this pull request Mar 26, 2025
# Objective

Requires are currently more verbose than they need to be. People would
like to define inline component values. Additionally, the current
`#[require(Foo(custom_constructor))]` and `#[require(Foo(|| Foo(10))]`
syntax doesn't really make sense within the context of the Rust type
system. #18309 was an attempt to improve ergonomics for some cases, but
it came at the cost of even more weirdness / unintuitive behavior. Our
approach as a whole needs a rethink.

## Solution

Rework the `#[require()]` syntax to make more sense. This is a breaking
change, but I think it will make the system easier to learn, while also
improving ergonomics substantially:

```rust
#[derive(Component)]
#[require(
    A, // this will use A::default()
    B(1), // inline tuple-struct value
    C { value: 1 }, // inline named-struct value
    D::Variant, // inline enum variant
    E::SOME_CONST, // inline associated const
    F::new(1), // inline constructor
    G = returns_g(), // an expression that returns G
    H = SomethingElse::new(), // expression returns SomethingElse, where SomethingElse: Into<H> 
)]
struct Foo;
```

## Migration Guide

Custom-constructor requires should use the new expression-style syntax:

```rust
// before
#[derive(Component)]
#[require(A(returns_a))]
struct Foo;

// after
#[derive(Component)]
#[require(A = returns_a())]
struct Foo;
```

Inline-closure-constructor requires should use the inline value syntax
where possible:

```rust
// before
#[derive(Component)]
#[require(A(|| A(10))]
struct Foo;

// after
#[derive(Component)]
#[require(A(10)]
struct Foo;
```

In cases where that is not possible, use the expression-style syntax:

```rust
// before
#[derive(Component)]
#[require(A(|| A(10))]
struct Foo;

// after
#[derive(Component)]
#[require(A = A(10)]
struct Foo;
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Required components should accept constant and enum values of the appropriate type

5 participants