bevyengine / bevyengine/bevy

Making Required Components force existence as a follow-up of #18514

Open
#18,566 8 comments 1 reaction 0 assignees View on GitHub
A-ECS C-Feature X-Needs-SME
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## What problem does this solve or what need does it fill?

The #18514 proposes a way to enforce the invariant of existence of required components.
While the following opinion can be controversial, I believe that this invariant should be held by default - as even the wording of **Required Components** means that the specified component is *required* for correct behaviour of the component it is specified for.

As for justification of having the API to enforce such a variant - see #18514, it is described there wonderfully that this both improves DX and can improve performance by removing unnecessary checks.

I propose that the behaviour of
```rust
#[removal(stops(B)]
struct A;
```
(states that when `A` is present, `B` can not be removed) should be *the* default behaviour of required components, and that
```rust
#[removal(when(B)]
struct A;
```
(states that when `B` is removed, `A` is also removed) and the current behaviour of allowing to remove the required component afterwards should be options on the required component.

## What solution would you like?

- `#[require(B)]` and `world.register_required_components` now also apply the logic of `#[removal(stops(B)]` by default.
- Add a parameter to `world.register_required_components` for specifying the behaviour of removal tracking.
- If you want the current behaviour of allowing removals of required component on `#[require(B)]`, you should write
```rust
#[require(B, allow_removal)]
struct A;
```
- If you want the behaviour of `#[removal(when(B)]` on `#[require(B)]`, you should write
```rust
#[require(B, removed_alongside)]
struct A;
```

## What alternative(s) have you considered?

Do not change the behaviour, so in most cases the developer would write either (most of the time)
```rust
#[required(B)]
#[removal(stops(B)]
struct A;
```
or like now (second place of commonness) to allow removal
```rust
#[required(B)]
struct A;
```
or (in I think very rare cases)
```rust
#[required(B)]
#[removal(when(B)]
struct A;
```

I personally think this is a worse DX than changing the behaviour of required components.

## Additional context

Unity has the notion of required components in its EC architecture, and it prevents the removal of the required component both in Editor and in Playmode both from Editor UI and from scripts with the error `Can't remove *B* because *A* depends on it`.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.