Files
Trashtalk217 c89541a1af Remove resources from Access (#22910)
# Objective

There's a lot of code duplication in `access.rs`. The same logic is
duplicated between components and resources. This also takes up
unnecessary memory in `Access`, as it relies on bitsets spanning the
entire `ComponentId` range.

## Solution

Since resources are now a special kind of component, this can be
removed.

## Limitations

Since `!Send` data queries used `Access` resources, `!Send` data queries
now conflict with broad queries.
```rust
// 0.18
fn system(q1_: Query<EntityMut>, q2_: NonSend<R>) {} // valid, does not conflict

// 0.19
fn system(q1_: Query<EntityMut>, q2_: NonSend<R>) {} // invalid, does conflict
```
Given how rarely non-send data is used, I recommend using
```
// 0.19
fn system(q1_: Query<EntityMut, Without<R>>, q2_: NonSend<R>) {} // works again
```

If this is also unacceptable, this PR is blocked on the `!Send` data
removal from the ECS (or some hacky workaround).

## Extra Attention

@chescock brought `AssetChanged` to my attention. It has a weird access
pattern. See the following example:
```rust
fn system(c: Query<&mut AssetChanges<Mesh>>, r: Query<(), AssetChanged<Mesh>>) {}
```
System `c` registers access with `add_write` for `AssetChanges<Mesh>`,
while `r` registers access with `add_read` for both `Mesh` and
`AssetChanges<Mesh>`. This system is invalid, and I've added a test to
reflect that. However, since this stuff is tricky, I would like some
extra eyes on it. Currently, it looks *fine*.
2026-03-02 23:48:04 +00:00

2.4 KiB

B0001

To keep Rust rules on references (either one mutable reference or any number of immutable references) on a component, it is not possible to have two queries on the same component when one requests mutable access to it in the same system.

Erroneous code example:

use bevy::prelude::*;

#[derive(Component)]
struct Player;

#[derive(Component)]
struct Enemy;

fn move_enemies_to_player(
    mut enemies: Query<&mut Transform, With<Enemy>>,
    player: Query<&Transform, With<Player>>,
) {
    // ...
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, move_enemies_to_player)
        .run();
}

This will panic, as it's not possible to have both a mutable and an immutable query on Transform at the same time.

You have two solutions:

Solution #1: use disjoint queries using Without

As a Player entity won't be an Enemy at the same time, those two queries will actually never target the same entity. This can be encoded in the query filter with Without:

use bevy::prelude::*;

#[derive(Component)]
struct Player;

#[derive(Component)]
struct Enemy;

fn move_enemies_to_player(
    mut enemies: Query<&mut Transform, With<Enemy>>,
    player: Query<&Transform, (With<Player>, Without<Enemy>)>,
) {
    // ...
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, move_enemies_to_player)
        .run();
}

Solution #2: use a ParamSet

A ParamSet will let you have conflicting queries as a parameter, but you will still be responsible for not using them at the same time in your system.

use bevy::prelude::*;

#[derive(Component)]
struct Player;

#[derive(Component)]
struct Enemy;

fn move_enemies_to_player(
    mut transforms: ParamSet<(
        Query<&mut Transform, With<Enemy>>,
        Query<&Transform, With<Player>>,
    )>,
) {
    // ...
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Update, move_enemies_to_player)
        .run();
}

Note

If you run this error while using resources, check out B0001 for additional information.