-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Fix soudness issue with Conflicts involving read_all and write_all
#14579
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
Changes from all commits
0b9da99
74d0339
517640e
e07acbf
a721ecb
944cc18
378eb03
fe4a49b
4a092d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| use crate::storage::SparseSetIndex; | ||
| use bevy_utils::HashSet; | ||
| use core::fmt; | ||
| use fixedbitset::FixedBitSet; | ||
| use std::fmt::Debug; | ||
| use std::marker::PhantomData; | ||
|
|
||
| /// A wrapper struct to make Debug representations of [`FixedBitSet`] easier | ||
|
|
@@ -484,15 +484,19 @@ impl<T: SparseSetIndex> Access<T> { | |
| } | ||
|
|
||
| /// Returns a vector of elements that the access and `other` cannot access at the same time. | ||
| pub fn get_conflicts(&self, other: &Access<T>) -> Vec<T> { | ||
| let mut conflicts = FixedBitSet::default(); | ||
| pub fn get_conflicts(&self, other: &Access<T>) -> AccessConflicts { | ||
| let mut conflicts = FixedBitSet::new(); | ||
| if self.reads_all_components { | ||
| // QUESTION: How to handle `other.writes_all`? | ||
| if other.writes_all_components { | ||
| return AccessConflicts::All; | ||
| } | ||
| conflicts.extend(other.component_writes.ones()); | ||
| } | ||
|
|
||
| if other.reads_all_components { | ||
| // QUESTION: How to handle `self.writes_all`. | ||
| if self.writes_all_components { | ||
| return AccessConflicts::All; | ||
| } | ||
| conflicts.extend(self.component_writes.ones()); | ||
| } | ||
|
|
||
|
|
@@ -504,15 +508,18 @@ impl<T: SparseSetIndex> Access<T> { | |
| conflicts.extend(self.component_read_and_writes.ones()); | ||
| } | ||
| if self.reads_all_resources { | ||
| // QUESTION: How to handle `other.writes_all`? | ||
| if other.reads_all_resources { | ||
| return AccessConflicts::All; | ||
| } | ||
| conflicts.extend(other.resource_writes.ones()); | ||
| } | ||
|
|
||
| if other.reads_all_resources { | ||
| // QUESTION: How to handle `self.writes_all`. | ||
| if self.reads_all_resources { | ||
| return AccessConflicts::All; | ||
| } | ||
| conflicts.extend(self.resource_writes.ones()); | ||
|
Comment on lines
-507
to
521
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two new inner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh you'r right, that's a bug.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we remove the |
||
| } | ||
|
|
||
| if self.writes_all_resources { | ||
| conflicts.extend(other.resource_read_and_writes.ones()); | ||
| } | ||
|
|
@@ -537,10 +544,7 @@ impl<T: SparseSetIndex> Access<T> { | |
| self.resource_read_and_writes | ||
| .intersection(&other.resource_writes), | ||
| ); | ||
| conflicts | ||
| .ones() | ||
| .map(SparseSetIndex::get_sparse_set_index) | ||
| .collect() | ||
| AccessConflicts::Individual(conflicts) | ||
| } | ||
|
|
||
| /// Returns the indices of the components this has access to. | ||
|
|
@@ -635,6 +639,53 @@ impl<T: SparseSetIndex> From<FilteredAccess<T>> for FilteredAccessSet<T> { | |
| } | ||
| } | ||
|
|
||
| /// Records how two accesses conflict with each other | ||
| #[derive(Debug, PartialEq)] | ||
| pub enum AccessConflicts { | ||
| /// Conflict is for all indices | ||
| All, | ||
| /// There is a conflict for a subset of indices | ||
| Individual(FixedBitSet), | ||
| } | ||
|
|
||
| impl AccessConflicts { | ||
| fn add(&mut self, other: &Self) { | ||
| match (self, other) { | ||
| (s, AccessConflicts::All) => { | ||
| *s = AccessConflicts::All; | ||
| } | ||
| (AccessConflicts::Individual(this), AccessConflicts::Individual(other)) => { | ||
| this.extend(other.ones()); | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn is_empty(&self) -> bool { | ||
| match self { | ||
| Self::All => false, | ||
| Self::Individual(set) => set.is_empty(), | ||
| } | ||
| } | ||
|
|
||
| /// An [`AccessConflicts`] which represents the absence of any conflict | ||
| pub(crate) fn empty() -> Self { | ||
| Self::Individual(FixedBitSet::new()) | ||
| } | ||
| } | ||
|
|
||
| impl From<FixedBitSet> for AccessConflicts { | ||
| fn from(value: FixedBitSet) -> Self { | ||
| Self::Individual(value) | ||
| } | ||
| } | ||
|
|
||
| impl<T: SparseSetIndex> From<Vec<T>> for AccessConflicts { | ||
| fn from(value: Vec<T>) -> Self { | ||
| Self::Individual(value.iter().map(T::sparse_set_index).collect()) | ||
| } | ||
| } | ||
|
|
||
| impl<T: SparseSetIndex> FilteredAccess<T> { | ||
| /// Returns a `FilteredAccess` which has no access and matches everything. | ||
| /// This is the equivalent of a `TRUE` logic atom. | ||
|
|
@@ -752,12 +803,12 @@ impl<T: SparseSetIndex> FilteredAccess<T> { | |
| } | ||
|
|
||
| /// Returns a vector of elements that this and `other` cannot access at the same time. | ||
| pub fn get_conflicts(&self, other: &FilteredAccess<T>) -> Vec<T> { | ||
| pub fn get_conflicts(&self, other: &FilteredAccess<T>) -> AccessConflicts { | ||
| if !self.is_compatible(other) { | ||
| // filters are disjoint, so we can just look at the unfiltered intersection | ||
| return self.access.get_conflicts(&other.access); | ||
| } | ||
| Vec::new() | ||
| AccessConflicts::empty() | ||
| } | ||
|
|
||
| /// Adds all access and filters from `other`. | ||
|
|
@@ -948,29 +999,29 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> { | |
| } | ||
|
|
||
| /// Returns a vector of elements that this set and `other` cannot access at the same time. | ||
| pub fn get_conflicts(&self, other: &FilteredAccessSet<T>) -> Vec<T> { | ||
| pub fn get_conflicts(&self, other: &FilteredAccessSet<T>) -> AccessConflicts { | ||
| // if the unfiltered access is incompatible, must check each pair | ||
| let mut conflicts = HashSet::new(); | ||
| let mut conflicts = AccessConflicts::empty(); | ||
| if !self.combined_access.is_compatible(other.combined_access()) { | ||
| for filtered in &self.filtered_accesses { | ||
| for other_filtered in &other.filtered_accesses { | ||
| conflicts.extend(filtered.get_conflicts(other_filtered).into_iter()); | ||
| conflicts.add(&filtered.get_conflicts(other_filtered)); | ||
| } | ||
| } | ||
| } | ||
| conflicts.into_iter().collect() | ||
| conflicts | ||
| } | ||
|
|
||
| /// Returns a vector of elements that this set and `other` cannot access at the same time. | ||
| pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess<T>) -> Vec<T> { | ||
| pub fn get_conflicts_single(&self, filtered_access: &FilteredAccess<T>) -> AccessConflicts { | ||
| // if the unfiltered access is incompatible, must check each pair | ||
| let mut conflicts = HashSet::new(); | ||
| let mut conflicts = AccessConflicts::empty(); | ||
| if !self.combined_access.is_compatible(filtered_access.access()) { | ||
| for filtered in &self.filtered_accesses { | ||
| conflicts.extend(filtered.get_conflicts(filtered_access).into_iter()); | ||
| conflicts.add(&filtered.get_conflicts(filtered_access)); | ||
| } | ||
| } | ||
| conflicts.into_iter().collect() | ||
| conflicts | ||
| } | ||
|
|
||
| /// Adds the filtered access to the set. | ||
|
|
@@ -1030,7 +1081,7 @@ impl<T: SparseSetIndex> Default for FilteredAccessSet<T> { | |
| #[cfg(test)] | ||
| mod tests { | ||
| use crate::query::access::AccessFilters; | ||
| use crate::query::{Access, FilteredAccess, FilteredAccessSet}; | ||
| use crate::query::{Access, AccessConflicts, FilteredAccess, FilteredAccessSet}; | ||
| use fixedbitset::FixedBitSet; | ||
| use std::marker::PhantomData; | ||
|
|
||
|
|
@@ -1195,21 +1246,27 @@ mod tests { | |
| access_b.add_component_read(0); | ||
| access_b.add_component_write(1); | ||
|
|
||
| assert_eq!(access_a.get_conflicts(&access_b), vec![1]); | ||
| assert_eq!(access_a.get_conflicts(&access_b), vec![1_usize].into()); | ||
|
|
||
| let mut access_c = Access::<usize>::default(); | ||
| access_c.add_component_write(0); | ||
| access_c.add_component_write(1); | ||
|
|
||
| assert_eq!(access_a.get_conflicts(&access_c), vec![0, 1]); | ||
| assert_eq!(access_b.get_conflicts(&access_c), vec![0, 1]); | ||
| assert_eq!( | ||
| access_a.get_conflicts(&access_c), | ||
| vec![0_usize, 1_usize].into() | ||
| ); | ||
| assert_eq!( | ||
| access_b.get_conflicts(&access_c), | ||
| vec![0_usize, 1_usize].into() | ||
| ); | ||
|
|
||
| let mut access_d = Access::<usize>::default(); | ||
| access_d.add_component_read(0); | ||
|
|
||
| assert_eq!(access_d.get_conflicts(&access_a), vec![]); | ||
| assert_eq!(access_d.get_conflicts(&access_b), vec![]); | ||
| assert_eq!(access_d.get_conflicts(&access_c), vec![0]); | ||
| assert_eq!(access_d.get_conflicts(&access_a), AccessConflicts::empty()); | ||
| assert_eq!(access_d.get_conflicts(&access_b), AccessConflicts::empty()); | ||
| assert_eq!(access_d.get_conflicts(&access_c), vec![0_usize].into()); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1223,7 +1280,7 @@ mod tests { | |
| let conflicts = access_a.get_conflicts_single(&filter_b); | ||
| assert_eq!( | ||
| &conflicts, | ||
| &[1_usize], | ||
| &AccessConflicts::from(vec![1_usize]), | ||
| "access_a: {access_a:?}, filter_b: {filter_b:?}" | ||
| ); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,8 @@ use crate::{ | |
| world::World, | ||
| }; | ||
|
|
||
| use crate::query::AccessConflicts; | ||
| use crate::storage::SparseSetIndex; | ||
| pub use stepping::Stepping; | ||
|
|
||
| /// Resource that stores [`Schedule`]s mapped to [`ScheduleLabel`]s excluding the current running [`Schedule`]. | ||
|
|
@@ -1363,14 +1365,22 @@ impl ScheduleGraph { | |
| let access_a = system_a.component_access(); | ||
| let access_b = system_b.component_access(); | ||
| if !access_a.is_compatible(access_b) { | ||
| let conflicts: Vec<_> = access_a | ||
| .get_conflicts(access_b) | ||
| .into_iter() | ||
| .filter(|id| !ignored_ambiguities.contains(id)) | ||
| .collect(); | ||
|
|
||
| if !conflicts.is_empty() { | ||
| conflicting_systems.push((a, b, conflicts)); | ||
| match access_a.get_conflicts(access_b) { | ||
| AccessConflicts::Individual(conflicts) => { | ||
| let conflicts: Vec<_> = conflicts | ||
| .ones() | ||
| .map(ComponentId::get_sparse_set_index) | ||
| .filter(|id| !ignored_ambiguities.contains(id)) | ||
| .collect(); | ||
| if !conflicts.is_empty() { | ||
| conflicting_systems.push((a, b, conflicts)); | ||
| } | ||
| } | ||
| AccessConflicts::All => { | ||
| // there is no specific component conflicting, but the systems are overall incompatible | ||
| // for example 2 systems with `Query<EntityMut>` | ||
| conflicting_systems.push((a, b, Vec::new())); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is not ideal.. maybe we'd like to introduce a similar enum in the schedule graph conflicts? not sure if that's what we want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this can wait until follow-up.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiousity, what happens with the ambiguity reporting in this case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It says that the two systems are ambiguous and conflict on |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.