Separate component and resource access (#14561)

# Objective

- Fixes https://github.com/bevyengine/bevy/issues/13139
- Fixes https://github.com/bevyengine/bevy/issues/7255
- Separates component from resource access so that we can correctly
handles edge cases like the issue above
- Inspired from https://github.com/bevyengine/bevy/pull/14472

## Solution

- Update access to have `component` fields and `resource` fields

## Testing

- Added some unit tests
This commit is contained in:
Periwink 2024-08-05 21:19:39 -04:00 committed by GitHub
parent 0d0f77a7ab
commit 3a664b052d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 590 additions and 242 deletions

View File

@ -1420,8 +1420,8 @@ mod tests {
let mut expected = FilteredAccess::<ComponentId>::default();
let a_id = world.components.get_id(TypeId::of::<A>()).unwrap();
let b_id = world.components.get_id(TypeId::of::<B>()).unwrap();
expected.add_write(a_id);
expected.add_read(b_id);
expected.add_component_write(a_id);
expected.add_component_read(b_id);
assert!(
query.component_access.eq(&expected),
"ComponentId access from query fetch and query filter should be combined"

View File

@ -49,17 +49,27 @@ impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> {
/// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions.
#[derive(Eq, PartialEq)]
pub struct Access<T: SparseSetIndex> {
/// All accessed elements.
reads_and_writes: FixedBitSet,
/// The exclusively-accessed elements.
writes: FixedBitSet,
/// Is `true` if this has access to all elements in the collection.
/// All accessed components.
component_read_and_writes: FixedBitSet,
/// The exclusively-accessed components.
component_writes: FixedBitSet,
/// All accessed resources.
resource_read_and_writes: FixedBitSet,
/// The exclusively-accessed resources.
resource_writes: FixedBitSet,
/// Is `true` if this has access to all components.
/// (Note that this does not include `Resources`)
reads_all_components: bool,
/// Is `true` if this has mutable access to all components.
/// (Note that this does not include `Resources`)
writes_all_components: bool,
/// Is `true` if this has access to all resources.
/// This field is a performance optimization for `&World` (also harder to mess up for soundness).
reads_all: bool,
/// Is `true` if this has mutable access to all elements in the collection.
reads_all_resources: bool,
/// Is `true` if this has mutable access to all resources.
/// If this is true, then `reads_all` must also be true.
writes_all: bool,
// Elements that are not accessed, but whose presence in an archetype affect query results.
writes_all_resources: bool,
// Components that are not accessed, but whose presence in an archetype affect query results.
archetypal: FixedBitSet,
marker: PhantomData<T>,
}
@ -68,20 +78,30 @@ pub struct Access<T: SparseSetIndex> {
impl<T: SparseSetIndex> Clone for Access<T> {
fn clone(&self) -> Self {
Self {
reads_and_writes: self.reads_and_writes.clone(),
writes: self.writes.clone(),
reads_all: self.reads_all,
writes_all: self.writes_all,
component_read_and_writes: self.component_read_and_writes.clone(),
component_writes: self.component_writes.clone(),
resource_read_and_writes: self.resource_read_and_writes.clone(),
resource_writes: self.resource_writes.clone(),
reads_all_components: self.reads_all_components,
writes_all_components: self.writes_all_components,
reads_all_resources: self.reads_all_resources,
writes_all_resources: self.writes_all_resources,
archetypal: self.archetypal.clone(),
marker: PhantomData,
}
}
fn clone_from(&mut self, source: &Self) {
self.reads_and_writes.clone_from(&source.reads_and_writes);
self.writes.clone_from(&source.writes);
self.reads_all = source.reads_all;
self.writes_all = source.writes_all;
self.component_read_and_writes
.clone_from(&source.component_read_and_writes);
self.component_writes.clone_from(&source.component_writes);
self.resource_read_and_writes
.clone_from(&source.resource_read_and_writes);
self.resource_writes.clone_from(&source.resource_writes);
self.reads_all_components = source.reads_all_components;
self.writes_all_components = source.writes_all_components;
self.reads_all_resources = source.reads_all_resources;
self.writes_all_resources = source.writes_all_resources;
self.archetypal.clone_from(&source.archetypal);
}
}
@ -90,12 +110,25 @@ impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for Access<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Access")
.field(
"read_and_writes",
&FormattedBitSet::<T>::new(&self.reads_and_writes),
"component_read_and_writes",
&FormattedBitSet::<T>::new(&self.component_read_and_writes),
)
.field("writes", &FormattedBitSet::<T>::new(&self.writes))
.field("reads_all", &self.reads_all)
.field("writes_all", &self.writes_all)
.field(
"component_writes",
&FormattedBitSet::<T>::new(&self.component_writes),
)
.field(
"resource_read_and_writes",
&FormattedBitSet::<T>::new(&self.resource_read_and_writes),
)
.field(
"resource_writes",
&FormattedBitSet::<T>::new(&self.resource_writes),
)
.field("reads_all_components", &self.reads_all_components)
.field("writes_all_components", &self.writes_all_components)
.field("reads_all_resources", &self.reads_all_resources)
.field("writes_all_resources", &self.writes_all_resources)
.field("archetypal", &FormattedBitSet::<T>::new(&self.archetypal))
.finish()
}
@ -111,31 +144,50 @@ impl<T: SparseSetIndex> Access<T> {
/// Creates an empty [`Access`] collection.
pub const fn new() -> Self {
Self {
reads_all: false,
writes_all: false,
reads_and_writes: FixedBitSet::new(),
writes: FixedBitSet::new(),
reads_all_resources: false,
writes_all_resources: false,
reads_all_components: false,
writes_all_components: false,
component_read_and_writes: FixedBitSet::new(),
component_writes: FixedBitSet::new(),
resource_read_and_writes: FixedBitSet::new(),
resource_writes: FixedBitSet::new(),
archetypal: FixedBitSet::new(),
marker: PhantomData,
}
}
/// Adds access to the element given by `index`.
pub fn add_read(&mut self, index: T) {
self.reads_and_writes
/// Adds access to the component given by `index`.
pub fn add_component_read(&mut self, index: T) {
self.component_read_and_writes
.grow_and_insert(index.sparse_set_index());
}
/// Adds exclusive access to the element given by `index`.
pub fn add_write(&mut self, index: T) {
self.reads_and_writes
/// Adds exclusive access to the component given by `index`.
pub fn add_component_write(&mut self, index: T) {
self.component_read_and_writes
.grow_and_insert(index.sparse_set_index());
self.component_writes
.grow_and_insert(index.sparse_set_index());
self.writes.grow_and_insert(index.sparse_set_index());
}
/// Adds an archetypal (indirect) access to the element given by `index`.
/// Adds access to the resource given by `index`.
pub fn add_resource_read(&mut self, index: T) {
self.resource_read_and_writes
.grow_and_insert(index.sparse_set_index());
}
/// Adds exclusive access to the resource given by `index`.
pub fn add_resource_write(&mut self, index: T) {
self.resource_read_and_writes
.grow_and_insert(index.sparse_set_index());
self.resource_writes
.grow_and_insert(index.sparse_set_index());
}
/// Adds an archetypal (indirect) access to the component given by `index`.
///
/// This is for elements whose values are not accessed (and thus will never cause conflicts),
/// This is for components whose values are not accessed (and thus will never cause conflicts),
/// but whose presence in an archetype may affect query results.
///
/// Currently, this is only used for [`Has<T>`].
@ -145,29 +197,55 @@ impl<T: SparseSetIndex> Access<T> {
self.archetypal.grow_and_insert(index.sparse_set_index());
}
/// Returns `true` if this can access the element given by `index`.
pub fn has_read(&self, index: T) -> bool {
self.reads_all || self.reads_and_writes.contains(index.sparse_set_index())
/// Returns `true` if this can access the component given by `index`.
pub fn has_component_read(&self, index: T) -> bool {
self.reads_all_components
|| self
.component_read_and_writes
.contains(index.sparse_set_index())
}
/// Returns `true` if this can access anything.
pub fn has_any_read(&self) -> bool {
self.reads_all || !self.reads_and_writes.is_clear()
/// Returns `true` if this can access any component.
pub fn has_any_component_read(&self) -> bool {
self.reads_all_components || !self.component_read_and_writes.is_clear()
}
/// Returns `true` if this can exclusively access the element given by `index`.
pub fn has_write(&self, index: T) -> bool {
self.writes_all || self.writes.contains(index.sparse_set_index())
/// Returns `true` if this can exclusively access the component given by `index`.
pub fn has_component_write(&self, index: T) -> bool {
self.writes_all_components || self.component_writes.contains(index.sparse_set_index())
}
/// Returns `true` if this accesses anything mutably.
pub fn has_any_write(&self) -> bool {
self.writes_all || !self.writes.is_clear()
/// Returns `true` if this accesses any component mutably.
pub fn has_any_component_write(&self) -> bool {
self.writes_all_components || !self.component_writes.is_clear()
}
/// Returns true if this has an archetypal (indirect) access to the element given by `index`.
/// Returns `true` if this can access the resource given by `index`.
pub fn has_resource_read(&self, index: T) -> bool {
self.reads_all_resources
|| self
.resource_read_and_writes
.contains(index.sparse_set_index())
}
/// Returns `true` if this can access any resource.
pub fn has_any_resource_read(&self) -> bool {
self.reads_all_resources || !self.resource_read_and_writes.is_clear()
}
/// Returns `true` if this can exclusively access the resource given by `index`.
pub fn has_resource_write(&self, index: T) -> bool {
self.writes_all_resources || self.resource_writes.contains(index.sparse_set_index())
}
/// Returns `true` if this accesses any resource mutably.
pub fn has_any_resource_write(&self) -> bool {
self.writes_all_resources || !self.resource_writes.is_clear()
}
/// Returns true if this has an archetypal (indirect) access to the component given by `index`.
///
/// This is an element whose value is not accessed (and thus will never cause conflicts),
/// This is a component whose value is not accessed (and thus will never cause conflicts),
/// but whose presence in an archetype may affect query results.
///
/// Currently, this is only used for [`Has<T>`].
@ -177,47 +255,170 @@ impl<T: SparseSetIndex> Access<T> {
self.archetypal.contains(index.sparse_set_index())
}
/// Sets this as having access to all indexed elements (i.e. `&World`).
pub fn read_all(&mut self) {
self.reads_all = true;
/// Sets this as having access to all components (i.e. `EntityRef`).
#[inline]
pub fn read_all_components(&mut self) {
self.reads_all_components = true;
}
/// Sets this as having mutable access to all indexed elements (i.e. `EntityMut`).
/// Sets this as having mutable access to all components (i.e. `EntityMut`).
#[inline]
pub fn write_all_components(&mut self) {
self.reads_all_components = true;
self.writes_all_components = true;
}
/// Sets this as having access to all resources (i.e. `&World`).
#[inline]
pub fn read_all_resources(&mut self) {
self.reads_all_resources = true;
}
/// Sets this as having mutable access to all resources (i.e. `&mut World`).
#[inline]
pub fn write_all_resources(&mut self) {
self.reads_all_resources = true;
self.writes_all_resources = true;
}
/// Sets this as having access to all indexed elements (i.e. `&World`).
#[inline]
pub fn read_all(&mut self) {
self.read_all_components();
self.read_all_resources();
}
/// Sets this as having mutable access to all indexed elements (i.e. `&mut World`).
#[inline]
pub fn write_all(&mut self) {
self.reads_all = true;
self.writes_all = true;
self.write_all_components();
self.write_all_resources();
}
/// Returns `true` if this has access to all components (i.e. `EntityRef`).
#[inline]
pub fn has_read_all_components(&self) -> bool {
self.reads_all_components
}
/// Returns `true` if this has write access to all components (i.e. `EntityMut`).
#[inline]
pub fn has_write_all_components(&self) -> bool {
self.writes_all_components
}
/// Returns `true` if this has access to all resources (i.e. `EntityRef`).
#[inline]
pub fn has_read_all_resources(&self) -> bool {
self.reads_all_resources
}
/// Returns `true` if this has write access to all resources (i.e. `EntityMut`).
#[inline]
pub fn has_write_all_resources(&self) -> bool {
self.writes_all_resources
}
/// Returns `true` if this has access to all indexed elements (i.e. `&World`).
pub fn has_read_all(&self) -> bool {
self.reads_all
self.has_read_all_components() && self.has_read_all_resources()
}
/// Returns `true` if this has write access to all indexed elements (i.e. `EntityMut`).
/// Returns `true` if this has write access to all indexed elements (i.e. `&mut World`).
pub fn has_write_all(&self) -> bool {
self.writes_all
self.has_write_all_components() && self.has_write_all_resources()
}
/// Removes all writes.
pub fn clear_writes(&mut self) {
self.writes_all = false;
self.writes.clear();
self.writes_all_resources = false;
self.writes_all_components = false;
self.component_writes.clear();
self.resource_writes.clear();
}
/// Removes all accesses.
pub fn clear(&mut self) {
self.reads_all = false;
self.writes_all = false;
self.reads_and_writes.clear();
self.writes.clear();
self.reads_all_resources = false;
self.writes_all_resources = false;
self.reads_all_components = false;
self.writes_all_components = false;
self.component_read_and_writes.clear();
self.component_writes.clear();
self.resource_read_and_writes.clear();
self.resource_writes.clear();
}
/// Adds all access from `other`.
pub fn extend(&mut self, other: &Access<T>) {
self.reads_all = self.reads_all || other.reads_all;
self.writes_all = self.writes_all || other.writes_all;
self.reads_and_writes.union_with(&other.reads_and_writes);
self.writes.union_with(&other.writes);
self.reads_all_resources = self.reads_all_resources || other.reads_all_resources;
self.writes_all_resources = self.writes_all_resources || other.writes_all_resources;
self.reads_all_components = self.reads_all_components || other.reads_all_components;
self.writes_all_components = self.writes_all_components || other.writes_all_components;
self.component_read_and_writes
.union_with(&other.component_read_and_writes);
self.component_writes.union_with(&other.component_writes);
self.resource_read_and_writes
.union_with(&other.resource_read_and_writes);
self.resource_writes.union_with(&other.resource_writes);
}
/// Returns `true` if the access and `other` can be active at the same time,
/// only looking at their component access.
///
/// [`Access`] instances are incompatible if one can write
/// an element that the other can read or write.
pub fn is_components_compatible(&self, other: &Access<T>) -> bool {
if self.writes_all_components {
return !other.has_any_component_read();
}
if other.writes_all_components {
return !self.has_any_component_read();
}
if self.reads_all_components {
return !other.has_any_component_write();
}
if other.reads_all_components {
return !self.has_any_component_write();
}
self.component_writes
.is_disjoint(&other.component_read_and_writes)
&& other
.component_writes
.is_disjoint(&self.component_read_and_writes)
}
/// Returns `true` if the access and `other` can be active at the same time,
/// only looking at their resource access.
///
/// [`Access`] instances are incompatible if one can write
/// an element that the other can read or write.
pub fn is_resources_compatible(&self, other: &Access<T>) -> bool {
if self.writes_all_resources {
return !other.has_any_resource_read();
}
if other.writes_all_resources {
return !self.has_any_resource_read();
}
if self.reads_all_resources {
return !other.has_any_resource_write();
}
if other.reads_all_resources {
return !self.has_any_resource_write();
}
self.resource_writes
.is_disjoint(&other.resource_read_and_writes)
&& other
.resource_writes
.is_disjoint(&self.resource_read_and_writes)
}
/// Returns `true` if the access and `other` can be active at the same time.
@ -225,98 +426,145 @@ impl<T: SparseSetIndex> Access<T> {
/// [`Access`] instances are incompatible if one can write
/// an element that the other can read or write.
pub fn is_compatible(&self, other: &Access<T>) -> bool {
if self.writes_all {
return !other.has_any_read();
self.is_components_compatible(other) && self.is_resources_compatible(other)
}
/// Returns `true` if the set's component access is a subset of another, i.e. `other`'s component access
/// contains at least all the values in `self`.
pub fn is_subset_components(&self, other: &Access<T>) -> bool {
if self.writes_all_components {
return other.writes_all_components;
}
if other.writes_all {
return !self.has_any_read();
if other.writes_all_components {
return true;
}
if self.reads_all {
return !other.has_any_write();
if self.reads_all_components {
return other.reads_all_components;
}
if other.reads_all {
return !self.has_any_write();
if other.reads_all_components {
return self.component_writes.is_subset(&other.component_writes);
}
self.writes.is_disjoint(&other.reads_and_writes)
&& other.writes.is_disjoint(&self.reads_and_writes)
self.component_read_and_writes
.is_subset(&other.component_read_and_writes)
&& self.component_writes.is_subset(&other.component_writes)
}
/// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access
/// contains at least all the values in `self`.
pub fn is_subset_resources(&self, other: &Access<T>) -> bool {
if self.writes_all_resources {
return other.writes_all_resources;
}
if other.writes_all_resources {
return true;
}
if self.reads_all_resources {
return other.reads_all_resources;
}
if other.reads_all_resources {
return self.resource_writes.is_subset(&other.resource_writes);
}
self.resource_read_and_writes
.is_subset(&other.resource_read_and_writes)
&& self.resource_writes.is_subset(&other.resource_writes)
}
/// Returns `true` if the set is a subset of another, i.e. `other` contains
/// at least all the values in `self`.
pub fn is_subset(&self, other: &Access<T>) -> bool {
if self.writes_all {
return other.writes_all;
}
if other.writes_all {
return true;
}
if self.reads_all {
return other.reads_all;
}
if other.reads_all {
return self.writes.is_subset(&other.writes);
}
self.reads_and_writes.is_subset(&other.reads_and_writes)
&& self.writes.is_subset(&other.writes)
self.is_subset_components(other) && self.is_subset_resources(other)
}
/// 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();
if self.reads_all {
if self.reads_all_components {
// QUESTION: How to handle `other.writes_all`?
conflicts.extend(other.writes.ones());
conflicts.extend(other.component_writes.ones());
}
if other.reads_all {
if other.reads_all_components {
// QUESTION: How to handle `self.writes_all`.
conflicts.extend(self.writes.ones());
conflicts.extend(self.component_writes.ones());
}
if self.writes_all {
conflicts.extend(other.reads_and_writes.ones());
if self.writes_all_components {
conflicts.extend(other.component_read_and_writes.ones());
}
if other.writes_all {
conflicts.extend(self.reads_and_writes.ones());
if other.writes_all_components {
conflicts.extend(self.component_read_and_writes.ones());
}
if self.reads_all_resources {
// QUESTION: How to handle `other.writes_all`?
conflicts.extend(other.resource_writes.ones());
}
conflicts.extend(self.writes.intersection(&other.reads_and_writes));
conflicts.extend(self.reads_and_writes.intersection(&other.writes));
if other.reads_all_resources {
// QUESTION: How to handle `self.writes_all`.
conflicts.extend(self.resource_writes.ones());
}
if self.writes_all_resources {
conflicts.extend(other.resource_read_and_writes.ones());
}
if other.writes_all_resources {
conflicts.extend(self.resource_read_and_writes.ones());
}
conflicts.extend(
self.component_writes
.intersection(&other.component_read_and_writes),
);
conflicts.extend(
self.component_read_and_writes
.intersection(&other.component_writes),
);
conflicts.extend(
self.resource_writes
.intersection(&other.resource_read_and_writes),
);
conflicts.extend(
self.resource_read_and_writes
.intersection(&other.resource_writes),
);
conflicts
.ones()
.map(SparseSetIndex::get_sparse_set_index)
.collect()
}
/// Returns the indices of the elements this has access to.
pub fn reads_and_writes(&self) -> impl Iterator<Item = T> + '_ {
self.reads_and_writes.ones().map(T::get_sparse_set_index)
}
/// Returns the indices of the elements this has non-exclusive access to.
pub fn reads(&self) -> impl Iterator<Item = T> + '_ {
self.reads_and_writes
.difference(&self.writes)
/// Returns the indices of the components this has access to.
pub fn component_reads_and_writes(&self) -> impl Iterator<Item = T> + '_ {
self.component_read_and_writes
.ones()
.map(T::get_sparse_set_index)
}
/// Returns the indices of the elements this has exclusive access to.
pub fn writes(&self) -> impl Iterator<Item = T> + '_ {
self.writes.ones().map(T::get_sparse_set_index)
/// Returns the indices of the components this has non-exclusive access to.
pub fn component_reads(&self) -> impl Iterator<Item = T> + '_ {
self.component_read_and_writes
.difference(&self.component_writes)
.map(T::get_sparse_set_index)
}
/// Returns the indices of the elements that this has an archetypal access to.
/// Returns the indices of the components this has exclusive access to.
pub fn component_writes(&self) -> impl Iterator<Item = T> + '_ {
self.component_writes.ones().map(T::get_sparse_set_index)
}
/// Returns the indices of the components that this has an archetypal access to.
///
/// These are elements whose values are not accessed (and thus will never cause conflicts),
/// These are components whose values are not accessed (and thus will never cause conflicts),
/// but whose presence in an archetype may affect query results.
///
/// Currently, this is only used for [`Has<T>`].
@ -420,20 +668,30 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
&mut self.access
}
/// Adds access to the element given by `index`.
pub fn add_read(&mut self, index: T) {
self.access.add_read(index.clone());
/// Adds access to the component given by `index`.
pub fn add_component_read(&mut self, index: T) {
self.access.add_component_read(index.clone());
self.add_required(index.clone());
self.and_with(index);
}
/// Adds exclusive access to the element given by `index`.
pub fn add_write(&mut self, index: T) {
self.access.add_write(index.clone());
/// Adds exclusive access to the component given by `index`.
pub fn add_component_write(&mut self, index: T) {
self.access.add_component_write(index.clone());
self.add_required(index.clone());
self.and_with(index);
}
/// Adds access to the resource given by `index`.
pub fn add_resource_read(&mut self, index: T) {
self.access.add_resource_read(index.clone());
}
/// Adds exclusive access to the resource given by `index`.
pub fn add_resource_write(&mut self, index: T) {
self.access.add_resource_write(index.clone());
}
fn add_required(&mut self, index: T) {
self.required.grow_and_insert(index.sparse_set_index());
}
@ -544,6 +802,16 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
self.access.write_all();
}
/// Sets the underlying unfiltered access as having access to all components.
pub fn read_all_components(&mut self) {
self.access.read_all_components();
}
/// Sets the underlying unfiltered access as having mutable access to all components.
pub fn write_all_components(&mut self) {
self.access.write_all_components();
}
/// Returns `true` if the set is a subset of another, i.e. `other` contains
/// at least all the values in `self`.
pub fn is_subset(&self, other: &FilteredAccess<T>) -> bool {
@ -711,17 +979,17 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
self.filtered_accesses.push(filtered_access);
}
/// Adds a read access without filters to the set.
pub(crate) fn add_unfiltered_read(&mut self, index: T) {
/// Adds a read access to a resource to the set.
pub(crate) fn add_unfiltered_resource_read(&mut self, index: T) {
let mut filter = FilteredAccess::default();
filter.add_read(index);
filter.add_resource_read(index);
self.add(filter);
}
/// Adds a write access without filters to the set.
pub(crate) fn add_unfiltered_write(&mut self, index: T) {
/// Adds a write access to a resource to the set.
pub(crate) fn add_unfiltered_resource_write(&mut self, index: T) {
let mut filter = FilteredAccess::default();
filter.add_write(index);
filter.add_resource_write(index);
self.add(filter);
}
@ -769,9 +1037,9 @@ mod tests {
fn create_sample_access() -> Access<usize> {
let mut access = Access::<usize>::default();
access.add_read(1);
access.add_read(2);
access.add_write(3);
access.add_component_read(1);
access.add_component_read(2);
access.add_component_write(3);
access.add_archetypal(5);
access.read_all();
@ -781,8 +1049,8 @@ mod tests {
fn create_sample_filtered_access() -> FilteredAccess<usize> {
let mut filtered_access = FilteredAccess::<usize>::default();
filtered_access.add_write(1);
filtered_access.add_read(2);
filtered_access.add_component_write(1);
filtered_access.add_component_read(2);
filtered_access.add_required(3);
filtered_access.and_with(4);
@ -801,8 +1069,8 @@ mod tests {
fn create_sample_filtered_access_set() -> FilteredAccessSet<usize> {
let mut filtered_access_set = FilteredAccessSet::<usize>::default();
filtered_access_set.add_unfiltered_read(2);
filtered_access_set.add_unfiltered_write(4);
filtered_access_set.add_unfiltered_resource_read(2);
filtered_access_set.add_unfiltered_resource_write(4);
filtered_access_set.read_all();
filtered_access_set
@ -821,8 +1089,8 @@ mod tests {
let original: Access<usize> = create_sample_access();
let mut cloned = Access::<usize>::default();
cloned.add_write(7);
cloned.add_read(4);
cloned.add_component_write(7);
cloned.add_component_read(4);
cloned.add_archetypal(8);
cloned.write_all();
@ -844,8 +1112,8 @@ mod tests {
let original: FilteredAccess<usize> = create_sample_filtered_access();
let mut cloned = FilteredAccess::<usize>::default();
cloned.add_write(7);
cloned.add_read(4);
cloned.add_component_write(7);
cloned.add_component_read(4);
cloned.append_or(&FilteredAccess::default());
cloned.clone_from(&original);
@ -887,8 +1155,8 @@ mod tests {
let original: FilteredAccessSet<usize> = create_sample_filtered_access_set();
let mut cloned = FilteredAccessSet::<usize>::default();
cloned.add_unfiltered_read(7);
cloned.add_unfiltered_write(9);
cloned.add_unfiltered_resource_read(7);
cloned.add_unfiltered_resource_write(9);
cloned.write_all();
cloned.clone_from(&original);
@ -900,7 +1168,7 @@ mod tests {
fn read_all_access_conflicts() {
// read_all / single write
let mut access_a = Access::<usize>::default();
access_a.add_write(0);
access_a.add_component_write(0);
let mut access_b = Access::<usize>::default();
access_b.read_all();
@ -920,24 +1188,24 @@ mod tests {
#[test]
fn access_get_conflicts() {
let mut access_a = Access::<usize>::default();
access_a.add_read(0);
access_a.add_read(1);
access_a.add_component_read(0);
access_a.add_component_read(1);
let mut access_b = Access::<usize>::default();
access_b.add_read(0);
access_b.add_write(1);
access_b.add_component_read(0);
access_b.add_component_write(1);
assert_eq!(access_a.get_conflicts(&access_b), vec![1]);
let mut access_c = Access::<usize>::default();
access_c.add_write(0);
access_c.add_write(1);
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]);
let mut access_d = Access::<usize>::default();
access_d.add_read(0);
access_d.add_component_read(0);
assert_eq!(access_d.get_conflicts(&access_a), vec![]);
assert_eq!(access_d.get_conflicts(&access_b), vec![]);
@ -947,10 +1215,10 @@ mod tests {
#[test]
fn filtered_combined_access() {
let mut access_a = FilteredAccessSet::<usize>::default();
access_a.add_unfiltered_read(1);
access_a.add_unfiltered_resource_read(1);
let mut filter_b = FilteredAccess::<usize>::default();
filter_b.add_write(1);
filter_b.add_resource_write(1);
let conflicts = access_a.get_conflicts_single(&filter_b);
assert_eq!(
@ -963,22 +1231,22 @@ mod tests {
#[test]
fn filtered_access_extend() {
let mut access_a = FilteredAccess::<usize>::default();
access_a.add_read(0);
access_a.add_read(1);
access_a.add_component_read(0);
access_a.add_component_read(1);
access_a.and_with(2);
let mut access_b = FilteredAccess::<usize>::default();
access_b.add_read(0);
access_b.add_write(3);
access_b.add_component_read(0);
access_b.add_component_write(3);
access_b.and_without(4);
access_a.extend(&access_b);
let mut expected = FilteredAccess::<usize>::default();
expected.add_read(0);
expected.add_read(1);
expected.add_component_read(0);
expected.add_component_read(1);
expected.and_with(2);
expected.add_write(3);
expected.add_component_write(3);
expected.and_without(4);
assert!(access_a.eq(&expected));
@ -988,8 +1256,8 @@ mod tests {
fn filtered_access_extend_or() {
let mut access_a = FilteredAccess::<usize>::default();
// Exclusive access to `(&mut A, &mut B)`.
access_a.add_write(0);
access_a.add_write(1);
access_a.add_component_write(0);
access_a.add_component_write(1);
// Filter by `With<C>`.
let mut access_b = FilteredAccess::<usize>::default();
@ -1010,8 +1278,8 @@ mod tests {
// The intention here is to test that exclusive access implied by `add_write`
// forms correct normalized access structs when extended with `Or` filters.
let mut expected = FilteredAccess::<usize>::default();
expected.add_write(0);
expected.add_write(1);
expected.add_component_write(0);
expected.add_component_write(1);
// The resulted access is expected to represent `Or<((With<A>, With<B>, With<C>), (With<A>, With<B>, With<D>, Without<E>))>`.
expected.filter_sets = vec![
AccessFilters {

View File

@ -142,14 +142,14 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Adds `&T` to the [`FilteredAccess`] of self.
pub fn ref_id(&mut self, id: ComponentId) -> &mut Self {
self.with_id(id);
self.access.add_read(id);
self.access.add_component_read(id);
self
}
/// Adds `&mut T` to the [`FilteredAccess`] of self.
pub fn mut_id(&mut self, id: ComponentId) -> &mut Self {
self.with_id(id);
self.access.add_write(id);
self.access.add_component_write(id);
self
}

View File

@ -480,10 +480,10 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
assert!(
!access.access().has_any_write(),
!access.access().has_any_component_write(),
"EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
);
access.read_all();
access.read_all_components();
}
fn init_state(_world: &mut World) {}
@ -556,10 +556,10 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
assert!(
!access.access().has_any_read(),
!access.access().has_any_component_read(),
"EntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
access.write_all();
access.write_all_components();
}
fn init_state(_world: &mut World) {}
@ -600,7 +600,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
_this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.read_all();
access.read_all_components();
(world, access)
}
@ -612,9 +612,9 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
_table: &Table,
) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
state.access.component_reads().for_each(|id| {
if archetype.contains(id) {
access.add_read(id);
access.add_component_read(id);
}
});
fetch.1 = access;
@ -623,9 +623,9 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
state.access.component_reads().for_each(|id| {
if table.has_column(id) {
access.add_read(id);
access.add_component_read(id);
}
});
fetch.1 = access;
@ -703,7 +703,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
_this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.write_all();
access.write_all_components();
(world, access)
}
@ -715,14 +715,14 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
_table: &Table,
) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
state.access.component_reads().for_each(|id| {
if archetype.contains(id) {
access.add_read(id);
access.add_component_read(id);
}
});
state.access.writes().for_each(|id| {
state.access.component_writes().for_each(|id| {
if archetype.contains(id) {
access.add_write(id);
access.add_component_write(id);
}
});
fetch.1 = access;
@ -731,14 +731,14 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
let mut access = Access::default();
state.access.reads().for_each(|id| {
state.access.component_reads().for_each(|id| {
if table.has_column(id) {
access.add_read(id);
access.add_component_read(id);
}
});
state.access.writes().for_each(|id| {
state.access.component_writes().for_each(|id| {
if table.has_column(id) {
access.add_write(id);
access.add_component_write(id);
}
});
fetch.1 = access;
@ -988,11 +988,11 @@ unsafe impl<T: Component> WorldQuery for &T {
access: &mut FilteredAccess<ComponentId>,
) {
assert!(
!access.access().has_write(component_id),
!access.access().has_component_write(component_id),
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
std::any::type_name::<T>(),
std::any::type_name::<T>(),
);
access.add_read(component_id);
access.add_component_read(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
@ -1183,11 +1183,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
access: &mut FilteredAccess<ComponentId>,
) {
assert!(
!access.access().has_write(component_id),
!access.access().has_component_write(component_id),
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
std::any::type_name::<T>(),
std::any::type_name::<T>(),
);
access.add_read(component_id);
access.add_component_read(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
@ -1378,11 +1378,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
access: &mut FilteredAccess<ComponentId>,
) {
assert!(
!access.access().has_read(component_id),
!access.access().has_component_read(component_id),
"&mut {} conflicts with a previous access in this query. Mutable component access must be unique.",
std::any::type_name::<T>(),
std::any::type_name::<T>(),
);
access.add_write(component_id);
access.add_component_write(component_id);
}
fn init_state(world: &mut World) -> ComponentId {
@ -1476,11 +1476,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> {
// Update component access here instead of in `<&mut T as WorldQuery>` to avoid erroneously referencing
// `&mut T` in error message.
assert!(
!access.access().has_read(component_id),
!access.access().has_component_read(component_id),
"Mut<{}> conflicts with a previous access in this query. Mutable component access mut be unique.",
std::any::type_name::<T>(),
std::any::type_name::<T>(),
);
access.add_write(component_id);
access.add_component_write(component_id);
}
// Forwarded to `&mut T`

View File

@ -688,10 +688,10 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
#[inline]
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
if access.access().has_write(id) {
if access.access().has_component_write(id) {
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",std::any::type_name::<T>());
}
access.add_read(id);
access.add_component_read(id);
}
fn init_state(world: &mut World) -> ComponentId {
@ -899,10 +899,10 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
#[inline]
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
if access.access().has_write(id) {
if access.access().has_component_write(id) {
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",std::any::type_name::<T>());
}
access.add_read(id);
access.add_component_read(id);
}
fn init_state(world: &mut World) -> ComponentId {

View File

@ -495,16 +495,22 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
archetype: &Archetype,
access: &mut Access<ArchetypeComponentId>,
) {
self.component_access.access.reads().for_each(|id| {
if let Some(id) = archetype.get_archetype_component_id(id) {
access.add_read(id);
}
});
self.component_access.access.writes().for_each(|id| {
if let Some(id) = archetype.get_archetype_component_id(id) {
access.add_write(id);
}
});
self.component_access
.access
.component_reads()
.for_each(|id| {
if let Some(id) = archetype.get_archetype_component_id(id) {
access.add_component_read(id);
}
});
self.component_access
.access
.component_writes()
.for_each(|id| {
if let Some(id) = archetype.get_archetype_component_id(id) {
access.add_component_write(id);
}
});
}
/// Use this to transform a [`QueryState`] into a more generic [`QueryState`].

View File

@ -737,6 +737,9 @@ mod tests {
#[derive(Event)]
struct E;
#[derive(Resource, Component)]
struct RC;
fn empty_system() {}
fn res_system(_res: Res<R>) {}
fn resmut_system(_res: ResMut<R>) {}
@ -746,6 +749,8 @@ mod tests {
fn write_component_system(_query: Query<&mut A>) {}
fn with_filtered_component_system(_query: Query<&mut A, With<B>>) {}
fn without_filtered_component_system(_query: Query<&mut A, Without<B>>) {}
fn entity_ref_system(_query: Query<EntityRef>) {}
fn entity_mut_system(_query: Query<EntityMut>) {}
fn event_reader_system(_reader: EventReader<E>) {}
fn event_writer_system(_writer: EventWriter<E>) {}
fn event_resource_system(_events: ResMut<Events<E>>) {}
@ -788,6 +793,8 @@ mod tests {
nonsend_system,
read_component_system,
read_component_system,
entity_ref_system,
entity_ref_system,
event_reader_system,
event_reader_system,
read_world_system,
@ -893,6 +900,73 @@ mod tests {
assert_eq!(schedule.graph().conflicting_systems().len(), 3);
}
/// Test that when a struct is both a Resource and a Component, they do not
/// conflict with each other.
#[test]
fn shared_resource_mut_component() {
let mut world = World::new();
world.insert_resource(RC);
let mut schedule = Schedule::default();
schedule.add_systems((|_: ResMut<RC>| {}, |_: Query<&mut RC>| {}));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn resource_mut_and_entity_ref() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((resmut_system, entity_ref_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn resource_and_entity_mut() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((res_system, nonsend_system, entity_mut_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 0);
}
#[test]
fn write_component_and_entity_ref() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((write_component_system, entity_ref_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn read_component_and_entity_mut() {
let mut world = World::new();
world.insert_resource(R);
let mut schedule = Schedule::default();
schedule.add_systems((read_component_system, entity_mut_system));
let _ = schedule.initialize(&mut world);
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
}
#[test]
fn exclusive() {
let mut world = World::new();

View File

@ -1495,7 +1495,7 @@ mod tests {
assert_eq!(
system
.archetype_component_access()
.reads()
.component_reads()
.collect::<HashSet<_>>(),
expected_ids
);
@ -1525,7 +1525,7 @@ mod tests {
assert_eq!(
system
.archetype_component_access()
.reads()
.component_reads()
.collect::<HashSet<_>>(),
expected_ids
);
@ -1543,7 +1543,7 @@ mod tests {
assert_eq!(
system
.archetype_component_access()
.reads()
.component_reads()
.collect::<HashSet<_>>(),
expected_ids
);

View File

@ -504,21 +504,21 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
assert!(
!combined_access.has_write(component_id),
!combined_access.has_resource_write(component_id),
"error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(),
system_meta.name,
);
system_meta
.component_access_set
.add_unfiltered_read(component_id);
.add_unfiltered_resource_read(component_id);
let archetype_component_id = world
.get_resource_archetype_component_id(component_id)
.unwrap();
system_meta
.archetype_component_access
.add_read(archetype_component_id);
.add_resource_read(archetype_component_id);
component_id
}
@ -600,25 +600,25 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
world.initialize_resource_internal(component_id);
let combined_access = system_meta.component_access_set.combined_access();
if combined_access.has_write(component_id) {
if combined_access.has_resource_write(component_id) {
panic!(
"error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(), system_meta.name);
} else if combined_access.has_read(component_id) {
} else if combined_access.has_resource_read(component_id) {
panic!(
"error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(), system_meta.name);
}
system_meta
.component_access_set
.add_unfiltered_write(component_id);
.add_unfiltered_resource_write(component_id);
let archetype_component_id = world
.get_resource_archetype_component_id(component_id)
.unwrap();
system_meta
.archetype_component_access
.add_write(archetype_component_id);
.add_resource_write(archetype_component_id);
component_id
}
@ -1153,21 +1153,21 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
assert!(
!combined_access.has_write(component_id),
!combined_access.has_resource_write(component_id),
"error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(),
system_meta.name,
);
system_meta
.component_access_set
.add_unfiltered_read(component_id);
.add_unfiltered_resource_read(component_id);
let archetype_component_id = world
.get_non_send_archetype_component_id(component_id)
.unwrap();
system_meta
.archetype_component_access
.add_read(archetype_component_id);
.add_resource_read(archetype_component_id);
component_id
}
@ -1246,25 +1246,25 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
world.initialize_non_send_internal(component_id);
let combined_access = system_meta.component_access_set.combined_access();
if combined_access.has_write(component_id) {
if combined_access.has_component_write(component_id) {
panic!(
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(), system_meta.name);
} else if combined_access.has_read(component_id) {
} else if combined_access.has_component_read(component_id) {
panic!(
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
std::any::type_name::<T>(), system_meta.name);
}
system_meta
.component_access_set
.add_unfiltered_write(component_id);
.add_unfiltered_resource_write(component_id);
let archetype_component_id = world
.get_non_send_archetype_component_id(component_id)
.unwrap();
system_meta
.archetype_component_access
.add_write(archetype_component_id);
.add_resource_write(archetype_component_id);
component_id
}

View File

@ -1860,7 +1860,7 @@ impl<'w> FilteredEntityRef<'w> {
/// Returns an iterator over the component ids that are accessed by self.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.access.reads_and_writes()
self.access.component_reads_and_writes()
}
/// Returns a reference to the underlying [`Access`].
@ -1912,7 +1912,7 @@ impl<'w> FilteredEntityRef<'w> {
pub fn get<T: Component>(&self) -> Option<&'w T> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_read(id)
.has_component_read(id)
// SAFETY: We have read access
.then(|| unsafe { self.entity.get() })
.flatten()
@ -1926,7 +1926,7 @@ impl<'w> FilteredEntityRef<'w> {
pub fn get_ref<T: Component>(&self) -> Option<Ref<'w, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_read(id)
.has_component_read(id)
// SAFETY: We have read access
.then(|| unsafe { self.entity.get_ref() })
.flatten()
@ -1938,7 +1938,7 @@ impl<'w> FilteredEntityRef<'w> {
pub fn get_change_ticks<T: Component>(&self) -> Option<ComponentTicks> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_read(id)
.has_component_read(id)
// SAFETY: We have read access
.then(|| unsafe { self.entity.get_change_ticks::<T>() })
.flatten()
@ -1953,7 +1953,7 @@ impl<'w> FilteredEntityRef<'w> {
#[inline]
pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option<ComponentTicks> {
self.access
.has_read(component_id)
.has_component_read(component_id)
// SAFETY: We have read access
.then(|| unsafe { self.entity.get_change_ticks_by_id(component_id) })
.flatten()
@ -1970,7 +1970,7 @@ impl<'w> FilteredEntityRef<'w> {
#[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
self.access
.has_read(component_id)
.has_component_read(component_id)
// SAFETY: We have read access
.then(|| unsafe { self.entity.get_by_id(component_id) })
.flatten()
@ -2117,7 +2117,7 @@ impl<'w> FilteredEntityMut<'w> {
/// Returns an iterator over the component ids that are accessed by self.
#[inline]
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.access.reads_and_writes()
self.access.component_reads_and_writes()
}
/// Returns a reference to the underlying [`Access`].
@ -2185,7 +2185,7 @@ impl<'w> FilteredEntityMut<'w> {
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_write(id)
.has_component_write(id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut() })
.flatten()
@ -2198,7 +2198,7 @@ impl<'w> FilteredEntityMut<'w> {
pub fn into_mut<T: Component>(self) -> Option<Mut<'w, T>> {
let id = self.entity.world().components().get_id(TypeId::of::<T>())?;
self.access
.has_write(id)
.has_component_write(id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut() })
.flatten()
@ -2246,7 +2246,7 @@ impl<'w> FilteredEntityMut<'w> {
#[inline]
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
self.access
.has_write(component_id)
.has_component_write(component_id)
// SAFETY: We have write access
.then(|| unsafe { self.entity.get_mut_by_id(component_id) })
.flatten()

View File

@ -168,7 +168,7 @@ fn main() {
};
// If we have write access, increment each value once
if filtered_entity.access().has_write(id) {
if filtered_entity.access().has_component_write(id) {
data.iter_mut().for_each(|data| {
*data += 1;
});