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

[Merged by Bors] - Fix FilteredAccessSet get_conflicts inconsistency #5105

Closed
wants to merge 3 commits into from

Conversation

nicopap
Copy link
Contributor

@nicopap nicopap commented Jun 26, 2022

Objective

The FilteredAccessSet::get_conflicts methods didn't work properly with
Res and ResMut parameters. Because those added their access by using
the combined_access_mut method and directly modifying the global
access state of the FilteredAccessSet. This caused an inconsistency,
because get_conflicts assumes that ALL added access have a corresponding
FilteredAccess added to the filtered_accesses field.

In practice, that means that SystemParam that adds their access through
the Access returned by combined_access_mut and the ones that add
their access using the add method lived in two different universes. As
a result, they could never be mutually exclusive.

Solution

This commit fixes it by removing the combined_access_mut method. This
ensures that the combined_access field of FilteredAccessSet is always
updated consistently with the addition of a filter. When checking for
filtered access, it is now possible to account for Res and ResMut
invalid access. This is currently not needed, but might be in the
future.

We add the add_unfiltered_{read,write} methods to replace previous
usages of combined_access_mut.

We also add improved Debug implementations on FixedBitSet so that their
meaning is much clearer in debug output.


Changelog

  • Fix Res and Query parameter never being mutually exclusive.

Migration Guide

Note: this mostly changes ECS internals, but since the API is public, it is technically breaking:

  • Removed FilteredAccessSet::combined_access_mut
    • Replace immutable usage of those by combined_access
    • For mutable usages, use the new add_unfiltered_{read,write} methods instead of combined_access_mut followed by add_{read,write}

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-ECS Entities, components, systems, and events labels Jun 26, 2022
@nicopap nicopap force-pushed the improve-filtered-access-set branch from 0bdf8e4 to 04758b8 Compare June 26, 2022 16:05
@alice-i-cecile alice-i-cecile requested a review from BoxyUwU June 26, 2022 16:11
@nicopap nicopap force-pushed the improve-filtered-access-set branch from 04758b8 to 3b71c03 Compare June 26, 2022 16:22
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.

This is signifcantly clearer, and the logic changes make sense to me.

I particularly like the improved Debug methods.

@alice-i-cecile alice-i-cecile added the C-Code-Quality A section of code that is hard to understand or change label Jun 26, 2022
@nicopap nicopap force-pushed the improve-filtered-access-set branch from 3b71c03 to ecb159a Compare June 26, 2022 16:35
@alice-i-cecile alice-i-cecile requested a review from cart June 26, 2022 16:40
@nicopap
Copy link
Contributor Author

nicopap commented Jun 26, 2022

I've hesitated between forcing to add filters and special-handling the case where there are no filters and a conflict in get_conflicts, but I'm not sure if it acts the same way. The force-add filters will be slower. (but since the check is done only once at initialization, I don't think it's much of an issue)

@alice-i-cecile
Copy link
Member

I've hesitated between forcing to add filters and special-handling the case where there are no filters and a conflict in get_conflicts, but I'm not sure if it acts the same way. The force-add filters will be slower. (but since the check is done only once at initialization, I don't think it's much of an issue)

I would optimize for clarity and correctness over initialization speed here, due to the critical and complex nature of this code. I agree with your choice.

@alice-i-cecile
Copy link
Member

bors try

bors bot added a commit that referenced this pull request Jun 26, 2022
@bors
Copy link
Contributor

bors bot commented Jun 27, 2022

try

Timed out.

@alice-i-cecile
Copy link
Member

@nicopap is this building for you locally? It's timing out on CI.

@nicopap
Copy link
Contributor Author

nicopap commented Jun 27, 2022

No idea. Builds on my machine

@alice-i-cecile alice-i-cecile requested a review from maniwani June 29, 2022 03:58
@nicopap nicopap force-pushed the improve-filtered-access-set branch from ecb159a to c2072e0 Compare June 30, 2022 12:36
@nicopap
Copy link
Contributor Author

nicopap commented Jul 1, 2022

Ok, now the tests are timing out on my own machine.

@nicopap
Copy link
Contributor Author

nicopap commented Jul 1, 2022

Oh my god, I think this is a deadlock in the world test in crates/bevy_ecs/src/schedule/executor_parallel.rs. It's very racy. Someone send help 🙏

Edit: Turns out I'm an idiot! The change to Access::is_compatible was erroneous and caused some nasty bugs in the scheduler.

@nicopap nicopap force-pushed the improve-filtered-access-set branch 2 times, most recently from c29ca7e to 61ce88e Compare July 1, 2022 10:47
@BoxyUwU
Copy link
Member

BoxyUwU commented Jul 21, 2022

Why can't we change Res(Mut) to call .add instead of using combined_access_mut

@nicopap
Copy link
Contributor Author

nicopap commented Jul 22, 2022

Why can't we change Res(Mut) to call .add instead of using combined_access_mut

add_unfiltered is exactly the same as add, but with a default empty FilteredAccess, it's just less work on the caller side, a more convenient API.

Also I removed combined_access_mut since you can't use it without breaking the assumptions about combined_access being a combination of the filtered accesses.

@nicopap nicopap force-pushed the improve-filtered-access-set branch from 61ce88e to 84ecf53 Compare September 1, 2022 08:53
@nicopap
Copy link
Contributor Author

nicopap commented Nov 3, 2022

bors try

bors bot added a commit that referenced this pull request Nov 3, 2022
Copy link
Member

@JoJoJet JoJoJet left a comment

Choose a reason for hiding this comment

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

I agree with the motivation for this PR, encapsulating the access invariants is very valuable. The implementation looks good and the doc changes are excellent.

/// ```text
/// read_and_writes: [ ComponentId(5), ComponentId(7) ]
/// ```
struct FormattedBitSet<'a, T: SparseSetIndex> {
Copy link
Member

Choose a reason for hiding this comment

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

This debug formatting is so much nicer.

@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 Nov 14, 2022
@alice-i-cecile alice-i-cecile added this to the 0.9.1 milestone Nov 14, 2022
Copy link
Member

@cart cart left a comment

Choose a reason for hiding this comment

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

I'm on board for these changes!

crates/bevy_ecs/src/storage/sparse_set.rs Outdated Show resolved Hide resolved
The `FilteredAccessSet::get_conflicts` methods didn't work properly with
`Res` and `ResMut` parameters. Because those added their access by using
the `combined_access_mut` method and directly modifying the global
access state of the FilteredAccessSet. This caused an inconsistency,
because get_conflicts assumes that ALL added access have a corresponding
`FilteredAccess` added to the `filtered_accesses` field.

In practice, that means that SystemParam that adds their access through
the `Access` returned by `combined_access_mut` and the ones that add
their access using the `add` method lived in two different universes. As
a result, they could never be mutually exclusive.

This commit fixes it by removing the `combined_access_mut` method. This
ensures that the `combined_access` field of FilteredAccessSet is always
updated consistently with the addition of a filter. When checking for
filtered access, it is now possible to account for `Res` and `ResMut`
invalid access. This is currently not needed, but might be in the
future.

We add the `add_unfiltered_{read,write}` methods to replace previous
usages of `combined_access_mut`.

We also add improved Debug implementations on FixedBitSet so that their
meaning is much clearer in debug output.
@nicopap nicopap force-pushed the improve-filtered-access-set branch from 84ecf53 to 8df367a Compare November 16, 2022 08:03
@alice-i-cecile
Copy link
Member

bors r+

bors bot pushed a commit that referenced this pull request Nov 16, 2022
# Objective

* Enable `Res` and `Query` parameter mutual exclusion
* Required for #5080

The `FilteredAccessSet::get_conflicts` methods didn't work properly with
`Res` and `ResMut` parameters. Because those added their access by using
the `combined_access_mut` method and directly modifying the global
access state of the FilteredAccessSet. This caused an inconsistency,
because get_conflicts assumes that ALL added access have a corresponding
`FilteredAccess` added to the `filtered_accesses` field.

In practice, that means that SystemParam that adds their access through
the `Access` returned by `combined_access_mut` and the ones that add
their access using the `add` method lived in two different universes. As
a result, they could never be mutually exclusive.

## Solution

This commit fixes it by removing the `combined_access_mut` method. This
ensures that the `combined_access` field of FilteredAccessSet is always
updated consistently with the addition of a filter. When checking for
filtered access, it is now possible to account for `Res` and `ResMut`
invalid access. This is currently not needed, but might be in the
future.

We add the `add_unfiltered_{read,write}` methods to replace previous
usages of `combined_access_mut`.

We also add improved Debug implementations on FixedBitSet so that their
meaning is much clearer in debug output.


---

## Changelog

* Fix `Res` and `Query` parameter never being mutually exclusive.

## Migration Guide

Note: this mostly changes ECS internals, but since the API is public, it is technically breaking:
* Removed `FilteredAccessSet::combined_access_mut`
  * Replace _immutable_ usage of those by `combined_access`
  * For _mutable_ usages, use the new `add_unfiltered_{read,write}` methods instead of `combined_access_mut` followed by `add_{read,write}`
@bors bors bot changed the title Fix FilteredAccessSet get_conflicts inconsistency [Merged by Bors] - Fix FilteredAccessSet get_conflicts inconsistency Nov 16, 2022
@bors bors bot closed this Nov 16, 2022
cart pushed a commit that referenced this pull request Nov 30, 2022
# Objective

* Enable `Res` and `Query` parameter mutual exclusion
* Required for #5080

The `FilteredAccessSet::get_conflicts` methods didn't work properly with
`Res` and `ResMut` parameters. Because those added their access by using
the `combined_access_mut` method and directly modifying the global
access state of the FilteredAccessSet. This caused an inconsistency,
because get_conflicts assumes that ALL added access have a corresponding
`FilteredAccess` added to the `filtered_accesses` field.

In practice, that means that SystemParam that adds their access through
the `Access` returned by `combined_access_mut` and the ones that add
their access using the `add` method lived in two different universes. As
a result, they could never be mutually exclusive.

## Solution

This commit fixes it by removing the `combined_access_mut` method. This
ensures that the `combined_access` field of FilteredAccessSet is always
updated consistently with the addition of a filter. When checking for
filtered access, it is now possible to account for `Res` and `ResMut`
invalid access. This is currently not needed, but might be in the
future.

We add the `add_unfiltered_{read,write}` methods to replace previous
usages of `combined_access_mut`.

We also add improved Debug implementations on FixedBitSet so that their
meaning is much clearer in debug output.


---

## Changelog

* Fix `Res` and `Query` parameter never being mutually exclusive.

## Migration Guide

Note: this mostly changes ECS internals, but since the API is public, it is technically breaking:
* Removed `FilteredAccessSet::combined_access_mut`
  * Replace _immutable_ usage of those by `combined_access`
  * For _mutable_ usages, use the new `add_unfiltered_{read,write}` methods instead of `combined_access_mut` followed by `add_{read,write}`
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective

* Enable `Res` and `Query` parameter mutual exclusion
* Required for bevyengine#5080

The `FilteredAccessSet::get_conflicts` methods didn't work properly with
`Res` and `ResMut` parameters. Because those added their access by using
the `combined_access_mut` method and directly modifying the global
access state of the FilteredAccessSet. This caused an inconsistency,
because get_conflicts assumes that ALL added access have a corresponding
`FilteredAccess` added to the `filtered_accesses` field.

In practice, that means that SystemParam that adds their access through
the `Access` returned by `combined_access_mut` and the ones that add
their access using the `add` method lived in two different universes. As
a result, they could never be mutually exclusive.

## Solution

This commit fixes it by removing the `combined_access_mut` method. This
ensures that the `combined_access` field of FilteredAccessSet is always
updated consistently with the addition of a filter. When checking for
filtered access, it is now possible to account for `Res` and `ResMut`
invalid access. This is currently not needed, but might be in the
future.

We add the `add_unfiltered_{read,write}` methods to replace previous
usages of `combined_access_mut`.

We also add improved Debug implementations on FixedBitSet so that their
meaning is much clearer in debug output.


---

## Changelog

* Fix `Res` and `Query` parameter never being mutually exclusive.

## Migration Guide

Note: this mostly changes ECS internals, but since the API is public, it is technically breaking:
* Removed `FilteredAccessSet::combined_access_mut`
  * Replace _immutable_ usage of those by `combined_access`
  * For _mutable_ usages, use the new `add_unfiltered_{read,write}` methods instead of `combined_access_mut` followed by `add_{read,write}`
@nicopap nicopap deleted the improve-filtered-access-set branch September 5, 2023 15:44
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-Bug An unexpected or incorrect behavior C-Code-Quality A section of code that is hard to understand or change 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.

5 participants