mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Add EntityRefExcept
and EntityMutExcept
world queries, in preparation for generalized animation. (#15207)
This commit adds two new `WorldQuery` types: `EntityRefExcept` and `EntityMutExcept`. These types work just like `EntityRef` and `EntityMut`, but they prevent access to a statically-specified list of components. For example, `EntityMutExcept<(AnimationPlayer, Handle<AnimationGraph>)>` provides mutable access to all components except for `AnimationPlayer` and `Handle<AnimationGraph>`. These types are useful when you need to be able to process arbitrary queries while iterating over the results of another `EntityMut` query. The motivating use case is *generalized animation*, which is an upcoming feature that allows animation of any component property, not just rotation, translation, scaling, or morph weights. To implement this, we must change the current `AnyOf<(&mut Transform, &mut MorphWeights)>` to instead be `EntityMutExcept<(AnimationPlayer, Handle<AnimationGraph>)>`. It's possible to use `FilteredEntityMut` in conjunction with a dynamically-generated system instead, but `FilteredEntityMut` isn't optimized for the use case of a large number of allowed components coupled with a small set of disallowed components. No amount of optimization of `FilteredEntityMut` produced acceptable performance on the `many_foxes` benchmark. `Query<EntityMut, Without<AnimationPlayer>>` will not suffice either, as it's legal and idiomatic for an `AnimationTarget` and an `AnimationPlayer` to coexist on the same entity. An alternate proposal was to implement a somewhat-more-general `Except<Q, CL>` feature, where Q is a `WorldQuery` and CL is a `ComponentList`. I wasn't able to implement that proposal in a reasonable way, because of the fact that methods like `EntityMut::get_mut` and `EntityRef::get` are inherent methods instead of methods on `WorldQuery`, and therefore there was no way to delegate methods like `get` and `get_mut` to the inner query in a generic way. Refactoring those methods into a trait would probably be possible. However, I didn't see a use case for a hypothetical `Except` with arbitrary queries: `Query<Except<(&Transform, &Visibility), Visibility>>` would just be a complicated equivalent to `Query<&Transform>`, for instance. So, out of a desire for simplicity, I omitted a generic `Except` mechanism. I've tested the performance of generalized animation on `many_foxes` and found that, with this patch, `animate_targets` has a 7.4% slowdown over `main`. With `FilteredEntityMut` optimized to use `Arc<Access>`, the slowdown is 75.6%, due to contention on the reference count. Without `Arc<Access>`, the slowdown is even worse, over 2x. ## Testing New tests have been added that check that `EntityRefExcept` and `EntityMutExcept` allow and disallow access to components properly and that the query engine can correctly reject conflicting queries involving those types. A Tracy profile of `many_foxes` with 10,000 foxes showing generalized animation using `FilteredEntityMut` (red) vs. main (yellow) is as follows: ![Screenshot 2024-09-12 225914](https://github.com/user-attachments/assets/2993d74c-a513-4ba4-85bd-225672e7170a) A Tracy profile of `many_foxes` with 10,000 foxes showing generalized animation using this `EntityMutExcept` (yellow) vs. main (red) is as follows: ![Screenshot 2024-09-14 205831](https://github.com/user-attachments/assets/4241015e-0c5d-44ef-835b-43f78a24e604)
This commit is contained in:
parent
b45d83ebda
commit
3c41586154
10 changed files with 932 additions and 216 deletions
|
@ -34,6 +34,7 @@ serde = { version = "1", optional = true, default-features = false }
|
|||
thiserror = "1.0"
|
||||
nonmax = "0.5"
|
||||
arrayvec = { version = "0.7.4", optional = true }
|
||||
smallvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
|
|
@ -499,6 +499,16 @@ impl Archetype {
|
|||
self.components.len()
|
||||
}
|
||||
|
||||
/// Gets an iterator of all of the components in the archetype, along with
|
||||
/// their archetype component ID.
|
||||
pub(crate) fn components_with_archetype_component_id(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (ComponentId, ArchetypeComponentId)> + '_ {
|
||||
self.components
|
||||
.iter()
|
||||
.map(|(component_id, info)| (*component_id, info.archetype_component_id))
|
||||
}
|
||||
|
||||
/// Fetches an immutable reference to the archetype's [`Edges`], a cache of
|
||||
/// archetypal relationships.
|
||||
#[inline]
|
||||
|
|
|
@ -49,20 +49,22 @@ impl<'a, T: SparseSetIndex + Debug> 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 components.
|
||||
/// All accessed components, or forbidden components if
|
||||
/// `Self::component_read_and_writes_inverted` is set.
|
||||
component_read_and_writes: FixedBitSet,
|
||||
/// The exclusively-accessed components.
|
||||
/// All exclusively-accessed components, or components that may not be
|
||||
/// exclusively accessed if `Self::component_writes_inverted` is set.
|
||||
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 component can read all components *except* those
|
||||
/// present in `Self::component_read_and_writes`.
|
||||
component_read_and_writes_inverted: bool,
|
||||
/// Is `true` if this component can write to all components *except* those
|
||||
/// present in `Self::component_writes`.
|
||||
component_writes_inverted: 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_resources: bool,
|
||||
|
@ -82,8 +84,8 @@ impl<T: SparseSetIndex> Clone for Access<T> {
|
|||
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,
|
||||
component_read_and_writes_inverted: self.component_read_and_writes_inverted,
|
||||
component_writes_inverted: self.component_writes_inverted,
|
||||
reads_all_resources: self.reads_all_resources,
|
||||
writes_all_resources: self.writes_all_resources,
|
||||
archetypal: self.archetypal.clone(),
|
||||
|
@ -98,8 +100,8 @@ impl<T: SparseSetIndex> Clone for Access<T> {
|
|||
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.component_read_and_writes_inverted = source.component_read_and_writes_inverted;
|
||||
self.component_writes_inverted = source.component_writes_inverted;
|
||||
self.reads_all_resources = source.reads_all_resources;
|
||||
self.writes_all_resources = source.writes_all_resources;
|
||||
self.archetypal.clone_from(&source.archetypal);
|
||||
|
@ -125,8 +127,11 @@ impl<T: SparseSetIndex + Debug> Debug for Access<T> {
|
|||
"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(
|
||||
"component_read_and_writes_inverted",
|
||||
&self.component_read_and_writes_inverted,
|
||||
)
|
||||
.field("component_writes_inverted", &self.component_writes_inverted)
|
||||
.field("reads_all_resources", &self.reads_all_resources)
|
||||
.field("writes_all_resources", &self.writes_all_resources)
|
||||
.field("archetypal", &FormattedBitSet::<T>::new(&self.archetypal))
|
||||
|
@ -146,8 +151,8 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
Self {
|
||||
reads_all_resources: false,
|
||||
writes_all_resources: false,
|
||||
reads_all_components: false,
|
||||
writes_all_components: false,
|
||||
component_read_and_writes_inverted: false,
|
||||
component_writes_inverted: false,
|
||||
component_read_and_writes: FixedBitSet::new(),
|
||||
component_writes: FixedBitSet::new(),
|
||||
resource_read_and_writes: FixedBitSet::new(),
|
||||
|
@ -157,18 +162,33 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_component_sparse_set_index_read(&mut self, index: usize) {
|
||||
if !self.component_read_and_writes_inverted {
|
||||
self.component_read_and_writes.grow_and_insert(index);
|
||||
} else if index < self.component_read_and_writes.len() {
|
||||
self.component_read_and_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_component_sparse_set_index_write(&mut self, index: usize) {
|
||||
if !self.component_writes_inverted {
|
||||
self.component_writes.grow_and_insert(index);
|
||||
} else if index < self.component_writes.len() {
|
||||
self.component_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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());
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.add_component_sparse_set_index_read(sparse_set_index);
|
||||
}
|
||||
|
||||
/// 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());
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.add_component_sparse_set_index_read(sparse_set_index);
|
||||
self.add_component_sparse_set_index_write(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Adds access to the resource given by `index`.
|
||||
|
@ -185,6 +205,49 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
.grow_and_insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
fn remove_component_sparse_set_index_read(&mut self, index: usize) {
|
||||
if self.component_read_and_writes_inverted {
|
||||
self.component_read_and_writes.grow_and_insert(index);
|
||||
} else if index < self.component_read_and_writes.len() {
|
||||
self.component_read_and_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_component_sparse_set_index_write(&mut self, index: usize) {
|
||||
if self.component_writes_inverted {
|
||||
self.component_writes.grow_and_insert(index);
|
||||
} else if index < self.component_writes.len() {
|
||||
self.component_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes both read and write access to the component given by `index`.
|
||||
///
|
||||
/// Because this method corresponds to the set difference operator ∖, it can
|
||||
/// create complicated logical formulas that you should verify correctness
|
||||
/// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you
|
||||
/// can't replace a call to `remove_component_read` followed by a call to
|
||||
/// `extend` with a call to `extend` followed by a call to
|
||||
/// `remove_component_read`.
|
||||
pub fn remove_component_read(&mut self, index: T) {
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.remove_component_sparse_set_index_write(sparse_set_index);
|
||||
self.remove_component_sparse_set_index_read(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Removes write access to the component given by `index`.
|
||||
///
|
||||
/// Because this method corresponds to the set difference operator ∖, it can
|
||||
/// create complicated logical formulas that you should verify correctness
|
||||
/// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you
|
||||
/// can't replace a call to `remove_component_write` followed by a call to
|
||||
/// `extend` with a call to `extend` followed by a call to
|
||||
/// `remove_component_write`.
|
||||
pub fn remove_component_write(&mut self, index: T) {
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.remove_component_sparse_set_index_write(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Adds an archetypal (indirect) access to the component given by `index`.
|
||||
///
|
||||
/// This is for components whose values are not accessed (and thus will never cause conflicts),
|
||||
|
@ -199,25 +262,25 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
|
||||
/// 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
|
||||
self.component_read_and_writes_inverted
|
||||
^ self
|
||||
.component_read_and_writes
|
||||
.contains(index.sparse_set_index())
|
||||
}
|
||||
|
||||
/// 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()
|
||||
self.component_read_and_writes_inverted || !self.component_read_and_writes.is_clear()
|
||||
}
|
||||
|
||||
/// 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())
|
||||
self.component_writes_inverted ^ self.component_writes.contains(index.sparse_set_index())
|
||||
}
|
||||
|
||||
/// 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()
|
||||
self.component_writes_inverted || !self.component_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this can access the resource given by `index`.
|
||||
|
@ -258,14 +321,16 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Sets this as having access to all components (i.e. `EntityRef`).
|
||||
#[inline]
|
||||
pub fn read_all_components(&mut self) {
|
||||
self.reads_all_components = true;
|
||||
self.component_read_and_writes_inverted = true;
|
||||
self.component_read_and_writes.clear();
|
||||
}
|
||||
|
||||
/// 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;
|
||||
self.read_all_components();
|
||||
self.component_writes_inverted = true;
|
||||
self.component_writes.clear();
|
||||
}
|
||||
|
||||
/// Sets this as having access to all resources (i.e. `&World`).
|
||||
|
@ -298,13 +363,13 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// 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
|
||||
self.component_read_and_writes_inverted && self.component_read_and_writes.is_clear()
|
||||
}
|
||||
|
||||
/// 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
|
||||
self.component_writes_inverted && self.component_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this has access to all resources (i.e. `EntityRef`).
|
||||
|
@ -332,7 +397,7 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Removes all writes.
|
||||
pub fn clear_writes(&mut self) {
|
||||
self.writes_all_resources = false;
|
||||
self.writes_all_components = false;
|
||||
self.component_writes_inverted = false;
|
||||
self.component_writes.clear();
|
||||
self.resource_writes.clear();
|
||||
}
|
||||
|
@ -341,8 +406,8 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
pub fn clear(&mut self) {
|
||||
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_inverted = false;
|
||||
self.component_writes_inverted = false;
|
||||
self.component_read_and_writes.clear();
|
||||
self.component_writes.clear();
|
||||
self.resource_read_and_writes.clear();
|
||||
|
@ -351,13 +416,72 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
|
||||
/// Adds all access from `other`.
|
||||
pub fn extend(&mut self, other: &Access<T>) {
|
||||
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;
|
||||
let component_read_and_writes_inverted =
|
||||
self.component_read_and_writes_inverted || other.component_read_and_writes_inverted;
|
||||
let component_writes_inverted =
|
||||
self.component_writes_inverted || other.component_writes_inverted;
|
||||
|
||||
match (
|
||||
self.component_read_and_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
) {
|
||||
(true, true) => {
|
||||
self.component_read_and_writes
|
||||
.intersect_with(&other.component_read_and_writes);
|
||||
}
|
||||
(true, false) => {
|
||||
self.component_read_and_writes
|
||||
.difference_with(&other.component_read_and_writes);
|
||||
}
|
||||
(false, true) => {
|
||||
// We have to grow here because the new bits are going to get flipped to 1.
|
||||
self.component_read_and_writes.grow(
|
||||
self.component_read_and_writes
|
||||
.len()
|
||||
.max(other.component_read_and_writes.len()),
|
||||
);
|
||||
self.component_read_and_writes.toggle_range(..);
|
||||
self.component_read_and_writes
|
||||
.intersect_with(&other.component_read_and_writes);
|
||||
}
|
||||
(false, false) => {
|
||||
self.component_read_and_writes
|
||||
.union_with(&other.component_read_and_writes);
|
||||
}
|
||||
}
|
||||
|
||||
match (
|
||||
self.component_writes_inverted,
|
||||
other.component_writes_inverted,
|
||||
) {
|
||||
(true, true) => {
|
||||
self.component_writes
|
||||
.intersect_with(&other.component_writes);
|
||||
}
|
||||
(true, false) => {
|
||||
self.component_writes
|
||||
.difference_with(&other.component_writes);
|
||||
}
|
||||
(false, true) => {
|
||||
// We have to grow here because the new bits are going to get flipped to 1.
|
||||
self.component_writes.grow(
|
||||
self.component_writes
|
||||
.len()
|
||||
.max(other.component_writes.len()),
|
||||
);
|
||||
self.component_writes.toggle_range(..);
|
||||
self.component_writes
|
||||
.intersect_with(&other.component_writes);
|
||||
}
|
||||
(false, false) => {
|
||||
self.component_writes.union_with(&other.component_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.component_read_and_writes_inverted = component_read_and_writes_inverted;
|
||||
self.component_writes_inverted = component_writes_inverted;
|
||||
self.resource_read_and_writes
|
||||
.union_with(&other.resource_read_and_writes);
|
||||
self.resource_writes.union_with(&other.resource_writes);
|
||||
|
@ -369,27 +493,48 @@ 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_components_compatible(&self, other: &Access<T>) -> bool {
|
||||
if self.writes_all_components {
|
||||
return !other.has_any_component_read();
|
||||
// We have a conflict if we write and they read or write, or if they
|
||||
// write and we read or write.
|
||||
for (
|
||||
lhs_writes,
|
||||
rhs_reads_and_writes,
|
||||
lhs_writes_inverted,
|
||||
rhs_reads_and_writes_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&other.component_writes,
|
||||
&self.component_read_and_writes,
|
||||
other.component_writes_inverted,
|
||||
self.component_read_and_writes_inverted,
|
||||
),
|
||||
] {
|
||||
match (lhs_writes_inverted, rhs_reads_and_writes_inverted) {
|
||||
(true, true) => return false,
|
||||
(false, true) => {
|
||||
if !lhs_writes.is_subset(rhs_reads_and_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
if !rhs_reads_and_writes.is_subset(lhs_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(false, false) => {
|
||||
if !lhs_writes.is_disjoint(rhs_reads_and_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if the access and `other` can be active at the same time,
|
||||
|
@ -432,25 +577,48 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// 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;
|
||||
for (
|
||||
our_components,
|
||||
their_components,
|
||||
our_components_inverted,
|
||||
their_components_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_read_and_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_read_and_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_writes_inverted,
|
||||
),
|
||||
] {
|
||||
match (our_components_inverted, their_components_inverted) {
|
||||
(true, true) => {
|
||||
if !their_components.is_subset(our_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
return false;
|
||||
}
|
||||
(false, true) => {
|
||||
if !our_components.is_disjoint(their_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(false, false) => {
|
||||
if !our_components.is_subset(their_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if other.writes_all_components {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.reads_all_components {
|
||||
return other.reads_all_components;
|
||||
}
|
||||
|
||||
if other.reads_all_components {
|
||||
return self.component_writes.is_subset(&other.component_writes);
|
||||
}
|
||||
|
||||
self.component_read_and_writes
|
||||
.is_subset(&other.component_read_and_writes)
|
||||
&& self.component_writes.is_subset(&other.component_writes)
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access
|
||||
|
@ -483,30 +651,52 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
self.is_subset_components(other) && self.is_subset_resources(other)
|
||||
}
|
||||
|
||||
fn get_component_conflicts(&self, other: &Access<T>) -> AccessConflicts {
|
||||
let mut conflicts = FixedBitSet::new();
|
||||
|
||||
// We have a conflict if we write and they read or write, or if they
|
||||
// write and we read or write.
|
||||
for (
|
||||
lhs_writes,
|
||||
rhs_reads_and_writes,
|
||||
lhs_writes_inverted,
|
||||
rhs_reads_and_writes_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&other.component_writes,
|
||||
&self.component_read_and_writes,
|
||||
other.component_writes_inverted,
|
||||
self.component_read_and_writes_inverted,
|
||||
),
|
||||
] {
|
||||
// There's no way that I can see to do this without a temporary.
|
||||
// Neither CNF nor DNF allows us to avoid one.
|
||||
let temp_conflicts: FixedBitSet =
|
||||
match (lhs_writes_inverted, rhs_reads_and_writes_inverted) {
|
||||
(true, true) => return AccessConflicts::All,
|
||||
(false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(),
|
||||
(true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(),
|
||||
(false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(),
|
||||
};
|
||||
conflicts.union_with(&temp_conflicts);
|
||||
}
|
||||
|
||||
AccessConflicts::Individual(conflicts)
|
||||
}
|
||||
|
||||
/// Returns a vector of elements that the access and `other` cannot access at the same time.
|
||||
pub fn get_conflicts(&self, other: &Access<T>) -> AccessConflicts {
|
||||
let mut conflicts = FixedBitSet::new();
|
||||
if self.reads_all_components {
|
||||
if other.writes_all_components {
|
||||
return AccessConflicts::All;
|
||||
}
|
||||
conflicts.extend(other.component_writes.ones());
|
||||
}
|
||||
let mut conflicts = match self.get_component_conflicts(other) {
|
||||
AccessConflicts::All => return AccessConflicts::All,
|
||||
AccessConflicts::Individual(conflicts) => conflicts,
|
||||
};
|
||||
|
||||
if other.reads_all_components {
|
||||
if self.writes_all_components {
|
||||
return AccessConflicts::All;
|
||||
}
|
||||
conflicts.extend(self.component_writes.ones());
|
||||
}
|
||||
|
||||
if self.writes_all_components {
|
||||
conflicts.extend(other.component_read_and_writes.ones());
|
||||
}
|
||||
|
||||
if other.writes_all_components {
|
||||
conflicts.extend(self.component_read_and_writes.ones());
|
||||
}
|
||||
if self.reads_all_resources {
|
||||
if other.writes_all_resources {
|
||||
return AccessConflicts::All;
|
||||
|
@ -528,14 +718,6 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
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),
|
||||
|
@ -547,25 +729,6 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
AccessConflicts::Individual(conflicts)
|
||||
}
|
||||
|
||||
/// 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 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 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 components whose values are not accessed (and thus will never cause conflicts),
|
||||
|
@ -577,6 +740,40 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
pub fn archetypal(&self) -> impl Iterator<Item = T> + '_ {
|
||||
self.archetypal.ones().map(T::get_sparse_set_index)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component IDs that this `Access` either
|
||||
/// reads and writes or can't read or write.
|
||||
///
|
||||
/// The returned flag specifies whether the list consists of the components
|
||||
/// that the access *can* read or write (false) or whether the list consists
|
||||
/// of the components that the access *can't* read or write (true).
|
||||
///
|
||||
/// Because this method depends on internal implementation details of
|
||||
/// `Access`, it's not recommended. Prefer to manage your own lists of
|
||||
/// accessible components if your application needs to do that.
|
||||
#[doc(hidden)]
|
||||
#[deprecated]
|
||||
pub fn component_reads_and_writes(&self) -> (impl Iterator<Item = T> + '_, bool) {
|
||||
(
|
||||
self.component_read_and_writes
|
||||
.ones()
|
||||
.map(T::get_sparse_set_index),
|
||||
self.component_read_and_writes_inverted,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component IDs that this `Access` either
|
||||
/// writes or can't write.
|
||||
///
|
||||
/// The returned flag specifies whether the list consists of the components
|
||||
/// that the access *can* write (false) or whether the list consists of the
|
||||
/// components that the access *can't* write (true).
|
||||
pub(crate) fn component_writes(&self) -> (impl Iterator<Item = T> + '_, bool) {
|
||||
(
|
||||
self.component_writes.ones().map(T::get_sparse_set_index),
|
||||
self.component_writes_inverted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Access`] that has been filtered to include and exclude certain combinations of elements.
|
||||
|
|
|
@ -79,10 +79,14 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
|
|||
.map_or(false, |info| info.storage_type() == StorageType::Table)
|
||||
};
|
||||
|
||||
self.access
|
||||
.access()
|
||||
.component_reads_and_writes()
|
||||
.all(is_dense)
|
||||
#[allow(deprecated)]
|
||||
let (mut component_reads_and_writes, component_reads_and_writes_inverted) =
|
||||
self.access.access().component_reads_and_writes();
|
||||
if component_reads_and_writes_inverted {
|
||||
return false;
|
||||
}
|
||||
|
||||
component_reads_and_writes.all(is_dense)
|
||||
&& self.access.access().archetypal().all(is_dense)
|
||||
&& !self.access.access().has_read_all_components()
|
||||
&& self.access.with_filters().all(is_dense)
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, Archetypes},
|
||||
bundle::Bundle,
|
||||
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
|
||||
component::{Component, ComponentId, Components, StorageType, Tick},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
world::{
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
|
||||
FilteredEntityRef, Mut, Ref, World,
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
|
||||
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
|
||||
},
|
||||
};
|
||||
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
|
||||
use bevy_utils::all_tuples;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::UnsafeCell, marker::PhantomData};
|
||||
|
||||
/// Types that can be fetched from a [`World`] using a [`Query`].
|
||||
|
@ -626,27 +628,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
|
|||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
_: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -733,37 +723,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
|
|||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
_: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
state.access.component_writes().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
state.access.component_writes().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -815,6 +783,201 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
|
|||
type ReadOnly = FilteredEntityRef<'a>;
|
||||
}
|
||||
|
||||
/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B`
|
||||
/// and populates `Access` values so that queries that conflict with this access
|
||||
/// are rejected.
|
||||
unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type Fetch<'w> = UnsafeWorldCell<'w>;
|
||||
type Item<'w> = EntityRefExcept<'w, B>;
|
||||
type State = SmallVec<[ComponentId; 4]>;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_: &Self::State,
|
||||
_: Tick,
|
||||
_: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
world
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
_: &mut Self::Fetch<'w>,
|
||||
_: &Self::State,
|
||||
_: &'w Archetype,
|
||||
_: &'w Table,
|
||||
) {
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
|
||||
|
||||
unsafe fn fetch<'w>(
|
||||
world: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
_: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
let cell = world.get_entity(entity).unwrap();
|
||||
EntityRefExcept::new(cell)
|
||||
}
|
||||
|
||||
fn update_component_access(
|
||||
state: &Self::State,
|
||||
filtered_access: &mut FilteredAccess<ComponentId>,
|
||||
) {
|
||||
let mut my_access = Access::new();
|
||||
my_access.read_all_components();
|
||||
for id in state {
|
||||
my_access.remove_component_read(*id);
|
||||
}
|
||||
|
||||
let access = filtered_access.access_mut();
|
||||
assert!(
|
||||
access.is_compatible(&my_access),
|
||||
"`EntityRefExcept<{}>` conflicts with a previous access in this query.",
|
||||
std::any::type_name::<B>(),
|
||||
);
|
||||
access.extend(&my_access);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
Self::get_state(world.components()).unwrap()
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut ids = SmallVec::new();
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
ids.push(id);
|
||||
}
|
||||
});
|
||||
Some(ids)
|
||||
}
|
||||
|
||||
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: `Self` is the same as `Self::ReadOnly`.
|
||||
unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type ReadOnly = Self;
|
||||
}
|
||||
|
||||
/// SAFETY: `EntityRefExcept` enforces read-only access to its contained
|
||||
/// components.
|
||||
unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {}
|
||||
|
||||
/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B`
|
||||
/// and populates `Access` values so that queries that conflict with this access
|
||||
/// are rejected.
|
||||
unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type Fetch<'w> = UnsafeWorldCell<'w>;
|
||||
type Item<'w> = EntityMutExcept<'w, B>;
|
||||
type State = SmallVec<[ComponentId; 4]>;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_: &Self::State,
|
||||
_: Tick,
|
||||
_: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
world
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
_: &mut Self::Fetch<'w>,
|
||||
_: &Self::State,
|
||||
_: &'w Archetype,
|
||||
_: &'w Table,
|
||||
) {
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
|
||||
|
||||
unsafe fn fetch<'w>(
|
||||
world: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
_: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
let cell = world.get_entity(entity).unwrap();
|
||||
EntityMutExcept::new(cell)
|
||||
}
|
||||
|
||||
fn update_component_access(
|
||||
state: &Self::State,
|
||||
filtered_access: &mut FilteredAccess<ComponentId>,
|
||||
) {
|
||||
let mut my_access = Access::new();
|
||||
my_access.write_all_components();
|
||||
for id in state {
|
||||
my_access.remove_component_read(*id);
|
||||
}
|
||||
|
||||
let access = filtered_access.access_mut();
|
||||
assert!(
|
||||
access.is_compatible(&my_access),
|
||||
"`EntityMutExcept<{}>` conflicts with a previous access in this query.",
|
||||
std::any::type_name::<B>()
|
||||
);
|
||||
access.extend(&my_access);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
Self::get_state(world.components()).unwrap()
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut ids = SmallVec::new();
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
ids.push(id);
|
||||
}
|
||||
});
|
||||
Some(ids)
|
||||
}
|
||||
|
||||
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that
|
||||
/// `EntityMutExcept` provides.
|
||||
unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type ReadOnly = EntityRefExcept<'a, B>;
|
||||
}
|
||||
|
||||
/// SAFETY:
|
||||
/// `update_component_access` and `update_archetype_component_access` do nothing.
|
||||
/// This is sound because `fetch` does not access components.
|
||||
|
|
|
@ -508,22 +508,46 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
self.component_access
|
||||
.access
|
||||
.component_reads()
|
||||
.for_each(|id| {
|
||||
// As a fast path, we can iterate directly over the components involved
|
||||
// if the `access` isn't inverted.
|
||||
#[allow(deprecated)]
|
||||
let (component_reads_and_writes, component_reads_and_writes_inverted) =
|
||||
self.component_access.access.component_reads_and_writes();
|
||||
let (component_writes, component_writes_inverted) =
|
||||
self.component_access.access.component_writes();
|
||||
|
||||
if !component_reads_and_writes_inverted && !component_writes_inverted {
|
||||
component_reads_and_writes.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| {
|
||||
component_writes.for_each(|id| {
|
||||
if let Some(id) = archetype.get_archetype_component_id(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (component_id, archetype_component_id) in
|
||||
archetype.components_with_archetype_component_id()
|
||||
{
|
||||
if self
|
||||
.component_access
|
||||
.access
|
||||
.has_component_read(component_id)
|
||||
{
|
||||
access.add_component_read(archetype_component_id);
|
||||
}
|
||||
if self
|
||||
.component_access
|
||||
.access
|
||||
.has_component_write(component_id)
|
||||
{
|
||||
access.add_component_write(archetype_component_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this to transform a [`QueryState`] into a more generic [`QueryState`].
|
||||
|
|
|
@ -1493,13 +1493,10 @@ mod tests {
|
|||
// set up system and verify its access is empty
|
||||
system.initialize(&mut world);
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
|
||||
// add some entities with archetypes that should match and save their ids
|
||||
expected_ids.insert(
|
||||
|
@ -1523,13 +1520,10 @@ mod tests {
|
|||
|
||||
// update system and verify its accesses are correct
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
|
||||
// one more round
|
||||
expected_ids.insert(
|
||||
|
@ -1541,13 +1535,10 @@ mod tests {
|
|||
);
|
||||
world.spawn((A, B, D));
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1927,12 +1927,6 @@ impl<'w> FilteredEntityRef<'w> {
|
|||
self.entity.archetype()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component ids that are accessed by self.
|
||||
#[inline]
|
||||
pub fn accessed_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.access.component_reads_and_writes()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`Access`].
|
||||
#[inline]
|
||||
pub fn access(&self) -> &Access<ComponentId> {
|
||||
|
@ -2184,12 +2178,6 @@ impl<'w> FilteredEntityMut<'w> {
|
|||
self.entity.archetype()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component ids that are accessed by self.
|
||||
#[inline]
|
||||
pub fn accessed_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.access.component_reads_and_writes()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`Access`].
|
||||
#[inline]
|
||||
pub fn access(&self) -> &Access<ComponentId> {
|
||||
|
@ -2384,6 +2372,180 @@ pub enum TryFromFilteredError {
|
|||
MissingWriteAllAccess,
|
||||
}
|
||||
|
||||
/// Provides read-only access to a single entity and all its components, save
|
||||
/// for an explicitly-enumerated set.
|
||||
#[derive(Clone)]
|
||||
pub struct EntityRefExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
entity: UnsafeEntityCell<'w>,
|
||||
phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<'w, B> EntityRefExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity. Returns
|
||||
/// `None` if the component doesn't have a component of that type or if the
|
||||
/// type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get<C>(&self) -> Option<&'w C>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have read access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity,
|
||||
/// including change detection information. Returns `None` if the component
|
||||
/// doesn't have a component of that type or if the type is one of the
|
||||
/// excluded components.
|
||||
#[inline]
|
||||
pub fn get_ref<C>(&self) -> Option<Ref<'w, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have read access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get_ref() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
fn from(entity_mut: &'a EntityMutExcept<'_, B>) -> Self {
|
||||
// SAFETY: All accesses that `EntityRefExcept` provides are also
|
||||
// accesses that `EntityMutExcept` provides.
|
||||
unsafe { EntityRefExcept::new(entity_mut.entity) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides mutable access to all components of an entity, with the exception
|
||||
/// of an explicit set.
|
||||
///
|
||||
/// This is a rather niche type that should only be used if you need access to
|
||||
/// *all* components of an entity, while still allowing you to consult other
|
||||
/// queries that might match entities that this query also matches. If you don't
|
||||
/// need access to all components, prefer a standard query with a
|
||||
/// [`crate::query::Without`] filter.
|
||||
#[derive(Clone)]
|
||||
pub struct EntityMutExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
entity: UnsafeEntityCell<'w>,
|
||||
phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<'w, B> EntityMutExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new instance with a shorter lifetime.
|
||||
///
|
||||
/// This is useful if you have `&mut EntityMutExcept`, but you need
|
||||
/// `EntityMutExcept`.
|
||||
pub fn reborrow(&mut self) -> EntityMutExcept<'_, B> {
|
||||
// SAFETY: We have exclusive access to the entire entity and the
|
||||
// applicable components.
|
||||
unsafe { Self::new(self.entity) }
|
||||
}
|
||||
|
||||
/// Gets read-only access to all of the entity's components, except for the
|
||||
/// ones in `CL`.
|
||||
#[inline]
|
||||
pub fn as_readonly(&self) -> EntityRefExcept<'_, B> {
|
||||
EntityRefExcept::from(self)
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity. Returns
|
||||
/// `None` if the component doesn't have a component of that type or if the
|
||||
/// type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get<C>(&self) -> Option<&'_ C>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
self.as_readonly().get()
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity,
|
||||
/// including change detection information. Returns `None` if the component
|
||||
/// doesn't have a component of that type or if the type is one of the
|
||||
/// excluded components.
|
||||
#[inline]
|
||||
pub fn get_ref<C>(&self) -> Option<Ref<'_, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
self.as_readonly().get_ref()
|
||||
}
|
||||
|
||||
/// Gets mutable access to the component of type `C` for the current entity.
|
||||
/// Returns `None` if the component doesn't have a component of that type or
|
||||
/// if the type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get_mut<C>(&mut self) -> Option<Mut<'_, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have write access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get_mut() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bundle_contains_component<B>(components: &Components, query_id: ComponentId) -> bool
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
let mut found = false;
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
found = found || id == query_id;
|
||||
}
|
||||
});
|
||||
found
|
||||
}
|
||||
|
||||
/// Inserts a dynamic [`Bundle`] into the entity.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -2598,9 +2760,12 @@ mod tests {
|
|||
use bevy_ptr::OwningPtr;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use crate::system::RunSystemOnce as _;
|
||||
use crate::world::{FilteredEntityMut, FilteredEntityRef};
|
||||
use crate::{self as bevy_ecs, component::ComponentId, prelude::*, system::assert_is_system};
|
||||
|
||||
use super::{EntityMutExcept, EntityRefExcept};
|
||||
|
||||
#[test]
|
||||
fn sorted_remove() {
|
||||
let mut a = vec![1, 2, 3, 4, 5, 6, 7];
|
||||
|
@ -2993,6 +3158,164 @@ mod tests {
|
|||
world.spawn_empty().remove_by_id(test_component_id);
|
||||
}
|
||||
|
||||
/// Tests that components can be accessed through an `EntityRefExcept`.
|
||||
#[test]
|
||||
fn entity_ref_except() {
|
||||
let mut world = World::new();
|
||||
world.init_component::<TestComponent>();
|
||||
world.init_component::<TestComponent2>();
|
||||
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityRefExcept<TestComponent>>();
|
||||
|
||||
let mut found = false;
|
||||
for entity_ref in query.iter_mut(&mut world) {
|
||||
found = true;
|
||||
assert!(entity_ref.get::<TestComponent>().is_none());
|
||||
assert!(entity_ref.get_ref::<TestComponent>().is_none());
|
||||
assert!(matches!(
|
||||
entity_ref.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
|
||||
assert!(found);
|
||||
}
|
||||
|
||||
// Test that a single query can't both contain a mutable reference to a
|
||||
// component C and an `EntityRefExcept` that doesn't include C among its
|
||||
// exclusions.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_ref_except_conflicts_with_self() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<(&mut TestComponent, EntityRefExcept<TestComponent2>)>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityRefExcept` that doesn't include a component C among
|
||||
// its exclusions can't coexist with a mutable query for that component.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_ref_except_conflicts_with_other() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, _: Query<EntityRefExcept<TestComponent2>>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityRefExcept` with an exception for some component C can
|
||||
// coexist with a query for that component C.
|
||||
#[test]
|
||||
fn entity_ref_except_doesnt_conflict() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, query: Query<EntityRefExcept<TestComponent>>) {
|
||||
for entity_ref in query.iter() {
|
||||
assert!(matches!(
|
||||
entity_ref.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that components can be mutably accessed through an
|
||||
/// `EntityMutExcept`.
|
||||
#[test]
|
||||
fn entity_mut_except() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityMutExcept<TestComponent>>();
|
||||
|
||||
let mut found = false;
|
||||
for mut entity_mut in query.iter_mut(&mut world) {
|
||||
found = true;
|
||||
assert!(entity_mut.get::<TestComponent>().is_none());
|
||||
assert!(entity_mut.get_ref::<TestComponent>().is_none());
|
||||
assert!(entity_mut.get_mut::<TestComponent>().is_none());
|
||||
assert!(matches!(
|
||||
entity_mut.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
|
||||
assert!(found);
|
||||
}
|
||||
|
||||
// Test that a single query can't both contain a mutable reference to a
|
||||
// component C and an `EntityMutExcept` that doesn't include C among its
|
||||
// exclusions.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_mut_except_conflicts_with_self() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<(&mut TestComponent, EntityMutExcept<TestComponent2>)>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityMutExcept` that doesn't include a component C among
|
||||
// its exclusions can't coexist with a query for that component.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_mut_except_conflicts_with_other() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, mut query: Query<EntityMutExcept<TestComponent2>>) {
|
||||
for mut entity_mut in query.iter_mut() {
|
||||
assert!(entity_mut
|
||||
.get_mut::<TestComponent2>()
|
||||
.is_some_and(|component| component.0 == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an `EntityMutExcept` with an exception for some component C can
|
||||
// coexist with a query for that component C.
|
||||
#[test]
|
||||
fn entity_mut_except_doesnt_conflict() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, mut query: Query<EntityMutExcept<TestComponent>>) {
|
||||
for mut entity_mut in query.iter_mut() {
|
||||
assert!(entity_mut
|
||||
.get_mut::<TestComponent2>()
|
||||
.is_some_and(|component| component.0 == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ pub use crate::{
|
|||
pub use component_constants::*;
|
||||
pub use deferred_world::DeferredWorld;
|
||||
pub use entity_ref::{
|
||||
EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef,
|
||||
OccupiedEntry, VacantEntry,
|
||||
EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry,
|
||||
FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry,
|
||||
};
|
||||
pub use identifier::WorldId;
|
||||
pub use spawn_batch::*;
|
||||
|
|
|
@ -150,8 +150,11 @@ fn main() {
|
|||
let mut query = builder.build();
|
||||
|
||||
query.iter_mut(&mut world).for_each(|filtered_entity| {
|
||||
#[allow(deprecated)]
|
||||
let terms = filtered_entity
|
||||
.accessed_components()
|
||||
.access()
|
||||
.component_reads_and_writes()
|
||||
.0
|
||||
.map(|id| {
|
||||
let ptr = filtered_entity.get_by_id(id).unwrap();
|
||||
let info = component_info.get(&id).unwrap();
|
||||
|
|
Loading…
Reference in a new issue