mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
Remove resources from Access (#22910)
# Objective
There's a lot of code duplication in `access.rs`. The same logic is
duplicated between components and resources. This also takes up
unnecessary memory in `Access`, as it relies on bitsets spanning the
entire `ComponentId` range.
## Solution
Since resources are now a special kind of component, this can be
removed.
## Limitations
Since `!Send` data queries used `Access` resources, `!Send` data queries
now conflict with broad queries.
```rust
// 0.18
fn system(q1_: Query<EntityMut>, q2_: NonSend<R>) {} // valid, does not conflict
// 0.19
fn system(q1_: Query<EntityMut>, q2_: NonSend<R>) {} // invalid, does conflict
```
Given how rarely non-send data is used, I recommend using
```
// 0.19
fn system(q1_: Query<EntityMut, Without<R>>, q2_: NonSend<R>) {} // works again
```
If this is also unacceptable, this PR is blocked on the `!Send` data
removal from the ECS (or some hacky workaround).
## Extra Attention
@chescock brought `AssetChanged` to my attention. It has a weird access
pattern. See the following example:
```rust
fn system(c: Query<&mut AssetChanges<Mesh>>, r: Query<(), AssetChanged<Mesh>>) {}
```
System `c` registers access with `add_write` for `AssetChanges<Mesh>`,
while `r` registers access with `add_read` for both `Mesh` and
`AssetChanges<Mesh>`. This system is invalid, and I've added a test to
reflect that. However, since this stuff is tricky, I would like some
extra eyes on it. Currently, it looks *fine*.
This commit is contained in:
@@ -246,28 +246,16 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
|
||||
component_access_set: &mut FilteredAccessSet,
|
||||
_world: UnsafeWorldCell,
|
||||
) {
|
||||
let combined_access = component_access_set.combined_access();
|
||||
assert!(
|
||||
!combined_access.has_resource_write(state.resource_id),
|
||||
"error[B0002]: AssetChanged<{}> in system {:?} conflicts with a previous system param. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<A>(),
|
||||
system_name,
|
||||
);
|
||||
|
||||
let mut filter = FilteredAccess::default();
|
||||
filter.add_component_read(state.resource_id);
|
||||
filter.add_resource_read(state.resource_id);
|
||||
filter.add_read(state.resource_id);
|
||||
filter.and_with(IS_RESOURCE);
|
||||
|
||||
assert!(component_access_set
|
||||
.get_conflicts_single(&filter)
|
||||
.is_empty(),
|
||||
"error[B0002]: AssetChanged<{}> in system {:?} conflicts with a previous system param. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<A>(),
|
||||
system_name,
|
||||
);
|
||||
|
||||
component_access_set.add(filter);
|
||||
let conflicts = component_access_set.get_conflicts_single(&filter);
|
||||
if conflicts.is_empty() {
|
||||
component_access_set.add(filter);
|
||||
return;
|
||||
}
|
||||
panic!("error[B0002]: AssetChanged<{}> in system {:?} conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::<A>(), system_name);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> AssetChangedState<A> {
|
||||
@@ -326,6 +314,7 @@ mod tests {
|
||||
use crate::tests::create_app;
|
||||
use crate::{AssetEventSystems, Handle};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use bevy_ecs::system::assert_is_system;
|
||||
use core::num::NonZero;
|
||||
use std::println;
|
||||
|
||||
@@ -336,7 +325,7 @@ mod tests {
|
||||
component::Component,
|
||||
message::MessageWriter,
|
||||
resource::Resource,
|
||||
system::{assert_is_system, Commands, IntoSystem, Local, Query, Res, ResMut},
|
||||
system::{Commands, IntoSystem, Local, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_reflect::TypePath;
|
||||
|
||||
|
||||
@@ -257,9 +257,7 @@ mod tests {
|
||||
|
||||
// A component access with an unrelated component
|
||||
let mut component_access = FilteredAccess::default();
|
||||
component_access
|
||||
.access_mut()
|
||||
.add_component_read(ComponentId::new(2));
|
||||
component_access.access_mut().add_read(ComponentId::new(2));
|
||||
|
||||
let mut applied_access = component_access.clone();
|
||||
filters.modify_access(&mut applied_access);
|
||||
|
||||
@@ -1542,8 +1542,8 @@ mod tests {
|
||||
let mut expected = FilteredAccess::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_component_write(a_id);
|
||||
expected.add_component_read(b_id);
|
||||
expected.add_write(a_id);
|
||||
expected.add_read(b_id);
|
||||
assert!(
|
||||
query.component_access.eq(&expected),
|
||||
"ComponentId access from query fetch and query filter should be combined"
|
||||
|
||||
+346
-475
File diff suppressed because it is too large
Load Diff
@@ -72,14 +72,8 @@ fn has_conflicts_large<'a, Q: QueryData>(
|
||||
}
|
||||
EcsAccessType::Component(EcsAccessLevel::ReadAll)
|
||||
| EcsAccessType::Component(EcsAccessLevel::WriteAll) => true,
|
||||
EcsAccessType::Resource(ResourceAccessLevel::Read(resource_id))
|
||||
| EcsAccessType::Resource(ResourceAccessLevel::Write(resource_id)) => {
|
||||
filter.check_insert(&resource_id.index())
|
||||
}
|
||||
EcsAccessType::Access(access) => {
|
||||
if access.has_read_all_resources() || access.has_write_all_resources() {
|
||||
true
|
||||
} else if let Ok(component_iter) = access.try_iter_component_access() {
|
||||
if let Ok(component_iter) = access.try_iter_access() {
|
||||
let mut needs_check = false;
|
||||
for kind in component_iter {
|
||||
let index = match kind {
|
||||
@@ -91,11 +85,6 @@ fn has_conflicts_large<'a, Q: QueryData>(
|
||||
needs_check = true;
|
||||
}
|
||||
}
|
||||
for resource_id in access.resource_reads_and_writes() {
|
||||
if filter.check_insert(&resource_id.index()) {
|
||||
needs_check = true;
|
||||
}
|
||||
}
|
||||
needs_check
|
||||
} else {
|
||||
true
|
||||
@@ -123,8 +112,6 @@ fn has_conflicts_large<'a, Q: QueryData>(
|
||||
pub enum EcsAccessType<'a> {
|
||||
/// Accesses [`Component`](crate::prelude::Component) data
|
||||
Component(EcsAccessLevel),
|
||||
/// Accesses [`Resource`](crate::prelude::Resource) data
|
||||
Resource(ResourceAccessLevel),
|
||||
/// borrowed access from [`WorldQuery::State`](crate::query::WorldQuery)
|
||||
Access(&'a Access),
|
||||
/// Does not access any data that can conflict.
|
||||
@@ -146,32 +133,19 @@ impl<'a> EcsAccessType<'a> {
|
||||
|
||||
(Empty, _)
|
||||
| (_, Empty)
|
||||
| (Component(_), Resource(_))
|
||||
| (Resource(_), Component(_))
|
||||
// read only access doesn't conflict
|
||||
| (Component(Read(_)), Component(Read(_)))
|
||||
| (Component(ReadAll), Component(Read(_)))
|
||||
| (Component(Read(_)), Component(ReadAll))
|
||||
| (Component(ReadAll), Component(ReadAll))
|
||||
| (Resource(ResourceAccessLevel::Read(_)), Resource(ResourceAccessLevel::Read(_))) => {
|
||||
=> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
(Component(Read(id)), Component(Write(id_other)))
|
||||
| (Component(Write(id)), Component(Read(id_other)))
|
||||
| (Component(Write(id)), Component(Write(id_other)))
|
||||
| (
|
||||
Resource(ResourceAccessLevel::Read(id)),
|
||||
Resource(ResourceAccessLevel::Write(id_other)),
|
||||
)
|
||||
| (
|
||||
Resource(ResourceAccessLevel::Write(id)),
|
||||
Resource(ResourceAccessLevel::Read(id_other)),
|
||||
)
|
||||
| (
|
||||
Resource(ResourceAccessLevel::Write(id)),
|
||||
Resource(ResourceAccessLevel::Write(id_other)),
|
||||
) => if id == id_other {
|
||||
=> if id == id_other {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -179,41 +153,28 @@ impl<'a> EcsAccessType<'a> {
|
||||
|
||||
// Borrowed Access
|
||||
(Component(Read(component_id)), Access(access))
|
||||
| (Access(access), Component(Read(component_id))) => if access.has_component_write(component_id) {
|
||||
| (Access(access), Component(Read(component_id))) => if access.has_write(component_id) {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(Component(Write(component_id)), Access(access))
|
||||
| (Access(access), Component(Write(component_id))) => if access.has_component_read(component_id) {
|
||||
| (Access(access), Component(Write(component_id))) => if access.has_read(component_id) {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(Component(ReadAll), Access(access))
|
||||
| (Access(access), Component(ReadAll)) => if access.has_any_component_write() {
|
||||
| (Access(access), Component(ReadAll)) => if access.has_any_write() {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(Component(WriteAll), Access(access))
|
||||
| (Access(access), Component(WriteAll))=> if access.has_any_component_read() {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
|
||||
(Resource(ResourceAccessLevel::Read(component_id)), Access(access))
|
||||
| (Access(access), Resource(ResourceAccessLevel::Read(component_id))) => if access.has_resource_write(component_id) {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
(Resource(ResourceAccessLevel::Write(component_id)), Access(access))
|
||||
| (Access(access), Resource(ResourceAccessLevel::Write(component_id))) => if access.has_resource_read(component_id) {
|
||||
| (Access(access), Component(WriteAll))=> if access.has_any_read() {
|
||||
Err(AccessConflictError(*self, other))
|
||||
} else {
|
||||
Ok(())
|
||||
@@ -242,15 +203,6 @@ pub enum EcsAccessLevel {
|
||||
WriteAll,
|
||||
}
|
||||
|
||||
/// Access level needed by [`QueryData`] fetch to the resource.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Hash)]
|
||||
pub enum ResourceAccessLevel {
|
||||
/// Reads the resource with [`ComponentId`]
|
||||
Read(ComponentId),
|
||||
/// Writes the resource with [`ComponentId`]
|
||||
Write(ComponentId),
|
||||
}
|
||||
|
||||
/// Error returned from [`EcsAccessType::is_compatible`]
|
||||
pub struct AccessConflictError<'a>(EcsAccessType<'a>, EcsAccessType<'a>);
|
||||
|
||||
@@ -319,16 +271,6 @@ impl Display for AccessConflictError<'_> {
|
||||
f,
|
||||
"Access has a read that conflicts with component write all"
|
||||
),
|
||||
(Access(_), Resource(ResourceAccessLevel::Read(id)))
|
||||
| (Resource(ResourceAccessLevel::Read(id)), Access(_)) => write!(
|
||||
f,
|
||||
"Access has a write that conflicts with resource {id:?} read."
|
||||
),
|
||||
(Access(_), Resource(ResourceAccessLevel::Write(id)))
|
||||
| (Resource(ResourceAccessLevel::Write(id)), Access(_)) => write!(
|
||||
f,
|
||||
"Access has a read that conflicts with resource {id:?} write."
|
||||
),
|
||||
(Access(_), Access(_)) => write!(f, "Access conflicts with other Access"),
|
||||
|
||||
_ => {
|
||||
@@ -355,7 +297,7 @@ impl Display for QueryAccessError {
|
||||
QueryAccessError::ComponentNotRegistered => {
|
||||
write!(
|
||||
f,
|
||||
"At least one component in Q was not registered in world.
|
||||
"At least one component in Q was not registered in world.
|
||||
Consider calling `World::register_component`"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -170,14 +170,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_component_read(id);
|
||||
self.access.add_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_component_write(id);
|
||||
self.access.add_write(id);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -955,10 +955,10 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
|
||||
|
||||
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_any_component_write(),
|
||||
!access.access().has_any_write(),
|
||||
"EntityRef conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
|
||||
);
|
||||
access.read_all_components();
|
||||
access.read_all();
|
||||
}
|
||||
|
||||
fn init_state(_world: &mut World) {}
|
||||
@@ -1071,10 +1071,10 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
|
||||
|
||||
fn update_component_access(_state: &Self::State, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_any_component_read(),
|
||||
!access.access().has_any_read(),
|
||||
"EntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
|
||||
);
|
||||
access.write_all_components();
|
||||
access.write_all();
|
||||
}
|
||||
|
||||
fn init_state(_world: &mut World) {}
|
||||
@@ -1449,16 +1449,16 @@ where
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
let mut access = Access::new();
|
||||
access.read_all_components();
|
||||
access.read_all();
|
||||
for id in B::component_ids(&mut world.components_registrator()) {
|
||||
access.remove_component_read(id);
|
||||
access.remove_read(id);
|
||||
}
|
||||
access
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut access = Access::new();
|
||||
access.read_all_components();
|
||||
access.read_all();
|
||||
// If the component isn't registered, we don't have a `ComponentId`
|
||||
// to use to exclude its access.
|
||||
// Rather than fail, just try to take additional access.
|
||||
@@ -1466,7 +1466,7 @@ where
|
||||
// Since the component isn't registered, there are no entities with that
|
||||
// component, and the extra access will usually have no effect.
|
||||
for id in B::get_component_ids(components).flatten() {
|
||||
access.remove_component_read(id);
|
||||
access.remove_read(id);
|
||||
}
|
||||
Some(access)
|
||||
}
|
||||
@@ -1572,16 +1572,16 @@ where
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
let mut access = Access::new();
|
||||
access.write_all_components();
|
||||
access.write_all();
|
||||
for id in B::component_ids(&mut world.components_registrator()) {
|
||||
access.remove_component_read(id);
|
||||
access.remove_read(id);
|
||||
}
|
||||
access
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut access = Access::new();
|
||||
access.write_all_components();
|
||||
access.write_all();
|
||||
// If the component isn't registered, we don't have a `ComponentId`
|
||||
// to use to exclude its access.
|
||||
// Rather than fail, just try to take additional access.
|
||||
@@ -1589,7 +1589,7 @@ where
|
||||
// Since the component isn't registered, there are no entities with that
|
||||
// component, and the extra access will usually have no effect.
|
||||
for id in B::get_component_ids(components).flatten() {
|
||||
access.remove_component_read(id);
|
||||
access.remove_read(id);
|
||||
}
|
||||
Some(access)
|
||||
}
|
||||
@@ -1841,11 +1841,11 @@ unsafe impl<T: Component> WorldQuery for &T {
|
||||
|
||||
fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_component_write(component_id),
|
||||
!access.access().has_write(component_id),
|
||||
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
|
||||
DebugName::type_name::<T>(),
|
||||
);
|
||||
access.add_component_read(component_id);
|
||||
access.add_read(component_id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
@@ -2065,11 +2065,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
||||
|
||||
fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_component_write(component_id),
|
||||
!access.access().has_write(component_id),
|
||||
"&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
|
||||
DebugName::type_name::<T>(),
|
||||
);
|
||||
access.add_component_read(component_id);
|
||||
access.add_read(component_id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
@@ -2332,11 +2332,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
||||
|
||||
fn update_component_access(&component_id: &ComponentId, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_component_read(component_id),
|
||||
!access.access().has_read(component_id),
|
||||
"&mut {} conflicts with a previous access in this query. Mutable component access must be unique.",
|
||||
DebugName::type_name::<T>(),
|
||||
);
|
||||
access.add_component_write(component_id);
|
||||
access.add_write(component_id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
@@ -2539,11 +2539,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_component_read(component_id),
|
||||
!access.access().has_read(component_id),
|
||||
"Mut<{}> conflicts with a previous access in this query. Mutable component access mut be unique.",
|
||||
DebugName::type_name::<T>(),
|
||||
);
|
||||
access.add_component_write(component_id);
|
||||
access.add_write(component_id);
|
||||
}
|
||||
|
||||
// Forwarded to `&mut T`
|
||||
|
||||
@@ -826,10 +826,10 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) {
|
||||
if access.access().has_component_write(id) {
|
||||
if access.access().has_write(id) {
|
||||
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::<T>());
|
||||
}
|
||||
access.add_component_read(id);
|
||||
access.add_read(id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
@@ -1053,10 +1053,10 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
|
||||
|
||||
#[inline]
|
||||
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess) {
|
||||
if access.access().has_component_write(id) {
|
||||
if access.access().has_write(id) {
|
||||
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::<T>());
|
||||
}
|
||||
access.add_component_read(id);
|
||||
access.add_read(id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
|
||||
@@ -113,18 +113,15 @@ impl<T> DebugCheckedUnwrap for Option<T> {
|
||||
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
|
||||
mod tests {
|
||||
use crate::{
|
||||
archetype::Archetype,
|
||||
change_detection::Tick,
|
||||
component::{Component, ComponentId, Components},
|
||||
prelude::{AnyOf, Changed, Entity, Or, QueryState, Resource, With, Without},
|
||||
component::Component,
|
||||
prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without},
|
||||
query::{
|
||||
ArchetypeFilter, ArchetypeQueryData, FilteredAccess, Has, IterQueryData,
|
||||
QueryCombinationIter, QueryData, QueryFilter, ReadOnlyQueryData, WorldQuery,
|
||||
ArchetypeFilter, ArchetypeQueryData, Has, QueryCombinationIter, QueryData, QueryFilter,
|
||||
ReadOnlyQueryData,
|
||||
},
|
||||
schedule::{IntoScheduleConfigs, Schedule},
|
||||
storage::{Table, TableRow},
|
||||
system::{assert_is_system, IntoSystem, Query, System, SystemState},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||
system::{IntoSystem, Query, System, SystemState},
|
||||
world::World,
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::{any::type_name, fmt::Debug, hash::Hash};
|
||||
@@ -817,114 +814,4 @@ mod tests {
|
||||
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
||||
assert_eq!(values, vec![&B(2)]);
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct R;
|
||||
|
||||
/// `QueryData` that performs read access on R to test that resource access is tracked
|
||||
struct ReadsRData;
|
||||
|
||||
// SAFETY:
|
||||
// `update_component_access` adds resource read access for `R`.
|
||||
unsafe impl WorldQuery for ReadsRData {
|
||||
type Fetch<'w> = ();
|
||||
type State = ComponentId;
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {}
|
||||
|
||||
unsafe fn init_fetch<'w, 's>(
|
||||
_world: UnsafeWorldCell<'w>,
|
||||
_state: &'s Self::State,
|
||||
_last_run: Tick,
|
||||
_this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true;
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w, 's>(
|
||||
_fetch: &mut Self::Fetch<'w>,
|
||||
_state: &'s Self::State,
|
||||
_archetype: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w, 's>(
|
||||
_fetch: &mut Self::Fetch<'w>,
|
||||
_state: &'s Self::State,
|
||||
_table: &'w Table,
|
||||
) {
|
||||
}
|
||||
|
||||
fn update_component_access(&component_id: &Self::State, access: &mut FilteredAccess) {
|
||||
assert!(
|
||||
!access.access().has_resource_write(component_id),
|
||||
"ReadsRData conflicts with a previous access in this query. Shared access cannot coincide with exclusive access."
|
||||
);
|
||||
access.add_resource_read(component_id);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
world.components_registrator().register_component::<R>()
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
components.component_id::<R>()
|
||||
}
|
||||
|
||||
fn matches_component_set(
|
||||
_state: &Self::State,
|
||||
_set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: `Self` is the same as `Self::ReadOnly`
|
||||
unsafe impl QueryData for ReadsRData {
|
||||
const IS_READ_ONLY: bool = true;
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
type ReadOnly = Self;
|
||||
type Item<'w, 's> = ();
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort, 's>(
|
||||
_item: Self::Item<'wlong, 's>,
|
||||
) -> Self::Item<'wshort, 's> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn fetch<'w, 's>(
|
||||
_state: &'s Self::State,
|
||||
_fetch: &mut Self::Fetch<'w>,
|
||||
_entity: Entity,
|
||||
_table_row: TableRow,
|
||||
) -> Option<Self::Item<'w, 's>> {
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn iter_access(
|
||||
state: &Self::State,
|
||||
) -> impl Iterator<Item = super::access_iter::EcsAccessType<'_>> {
|
||||
core::iter::once(super::access_iter::EcsAccessType::Resource(
|
||||
super::access_iter::ResourceAccessLevel::Read(*state),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: access is read only
|
||||
unsafe impl ReadOnlyQueryData for ReadsRData {}
|
||||
|
||||
/// SAFETY: access is read only
|
||||
unsafe impl IterQueryData for ReadsRData {}
|
||||
|
||||
impl ArchetypeQueryData for ReadsRData {}
|
||||
|
||||
#[test]
|
||||
fn read_res_read_res_no_conflict() {
|
||||
fn system(_q1: Query<ReadsRData, With<A>>, _q2: Query<ReadsRData, Without<A>>) {}
|
||||
assert_is_system(system);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,8 +1012,8 @@ mod tests {
|
||||
|
||||
let _ = schedule.initialize(&mut world);
|
||||
|
||||
// this should fail, since resources are components
|
||||
assert_eq!(schedule.graph().conflicting_systems().len(), 1);
|
||||
// this should fail, since resources are components and non_sends also do access with components
|
||||
assert_eq!(schedule.graph().conflicting_systems().len(), 2);
|
||||
|
||||
schedule = Schedule::default();
|
||||
schedule.add_systems((
|
||||
|
||||
@@ -252,7 +252,7 @@ where
|
||||
// We might need to read the default error handler after the component
|
||||
// systems have run to report failures.
|
||||
let error_resource = world.register_resource::<crate::error::DefaultErrorHandler>();
|
||||
a_access.add_unfiltered_resource_read(error_resource);
|
||||
a_access.add_resource_read(error_resource);
|
||||
a_access
|
||||
}
|
||||
|
||||
|
||||
@@ -749,30 +749,24 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
||||
&component_id: &Self::State,
|
||||
system_meta: &mut SystemMeta,
|
||||
component_access_set: &mut FilteredAccessSet,
|
||||
_world: &mut World,
|
||||
world: &mut World,
|
||||
) {
|
||||
let combined_access = component_access_set.combined_access();
|
||||
assert!(
|
||||
!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://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(),
|
||||
system_meta.name,
|
||||
);
|
||||
|
||||
let mut filter = FilteredAccess::default();
|
||||
filter.add_component_read(component_id);
|
||||
filter.add_resource_read(component_id);
|
||||
filter.add_read(component_id);
|
||||
filter.and_with(IS_RESOURCE);
|
||||
|
||||
assert!(component_access_set
|
||||
.get_conflicts_single(&filter)
|
||||
.is_empty(),
|
||||
"error[B0002]: Res<{}> in system {} conflicts with a previous query. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(),
|
||||
system_meta.name
|
||||
);
|
||||
let conflicts = component_access_set.get_conflicts_single(&filter);
|
||||
if conflicts.is_empty() {
|
||||
component_access_set.add(filter);
|
||||
return;
|
||||
}
|
||||
|
||||
component_access_set.add(filter);
|
||||
let mut accesses = conflicts.format_conflict_list(world.as_unsafe_world_cell());
|
||||
// Access list may be empty (if access to all components requested)
|
||||
if !accesses.is_empty() {
|
||||
accesses.push(' ');
|
||||
}
|
||||
panic!("error[B0002]: Res<{}> in system {} conflicts with a previous system parameter. Consider removing the duplicate access using `Without<IsResource>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::<T>(), system_meta.name);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -837,33 +831,24 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
||||
&component_id: &Self::State,
|
||||
system_meta: &mut SystemMeta,
|
||||
component_access_set: &mut FilteredAccessSet,
|
||||
_world: &mut World,
|
||||
world: &mut World,
|
||||
) {
|
||||
let combined_access = component_access_set.combined_access();
|
||||
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://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(), system_meta.name);
|
||||
} 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://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(), system_meta.name);
|
||||
}
|
||||
|
||||
let mut filter = FilteredAccess::default();
|
||||
filter.add_component_write(component_id);
|
||||
filter.add_resource_write(component_id);
|
||||
filter.add_write(component_id);
|
||||
filter.and_with(IS_RESOURCE);
|
||||
|
||||
assert!(component_access_set
|
||||
.get_conflicts_single(&filter)
|
||||
.is_empty(),
|
||||
"error[B0002]: ResMut<{}> in system {} conflicts with a previous query. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(),
|
||||
system_meta.name
|
||||
);
|
||||
let conflicts = component_access_set.get_conflicts_single(&filter);
|
||||
if conflicts.is_empty() {
|
||||
component_access_set.add(filter);
|
||||
return;
|
||||
}
|
||||
|
||||
component_access_set.add(filter);
|
||||
let mut accesses = conflicts.format_conflict_list(world.as_unsafe_world_cell());
|
||||
// Access list may be empty (if access to all components requested)
|
||||
if !accesses.is_empty() {
|
||||
accesses.push(' ');
|
||||
}
|
||||
panic!("error[B0002]: ResMut<{}> in system {} conflicts with a previous system parameter. Consider removing the duplicate access or using `Without<IsResource>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::<T>(), system_meta.name);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1471,12 +1456,12 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
||||
|
||||
let combined_access = component_access_set.combined_access();
|
||||
assert!(
|
||||
!combined_access.has_resource_write(component_id),
|
||||
!combined_access.has_write(component_id),
|
||||
"error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(),
|
||||
system_meta.name,
|
||||
);
|
||||
component_access_set.add_unfiltered_resource_read(component_id);
|
||||
component_access_set.add_unfiltered_component_read(component_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1541,16 +1526,16 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
||||
system_meta.set_non_send();
|
||||
|
||||
let combined_access = component_access_set.combined_access();
|
||||
if combined_access.has_resource_write(component_id) {
|
||||
if combined_access.has_write(component_id) {
|
||||
panic!(
|
||||
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(), system_meta.name);
|
||||
} else if combined_access.has_resource_read(component_id) {
|
||||
} else if combined_access.has_read(component_id) {
|
||||
panic!(
|
||||
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
|
||||
DebugName::type_name::<T>(), system_meta.name);
|
||||
}
|
||||
component_access_set.add_unfiltered_resource_write(component_id);
|
||||
component_access_set.add_unfiltered_component_write(component_id);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -2773,13 +2758,10 @@ unsafe impl SystemParam for FilteredResources<'_, '_> {
|
||||
panic!("error[B0002]: FilteredResources in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002");
|
||||
}
|
||||
|
||||
if access.has_read_all_resources() {
|
||||
component_access_set.add_unfiltered_read_all_resources();
|
||||
} else {
|
||||
for component_id in access.resource_reads_and_writes() {
|
||||
component_access_set.add_unfiltered_resource_read(component_id);
|
||||
}
|
||||
}
|
||||
let mut filter = FilteredAccess::matches_everything();
|
||||
filter.access_mut().extend(access);
|
||||
filter.and_with(IS_RESOURCE);
|
||||
component_access_set.add(filter);
|
||||
}
|
||||
|
||||
unsafe fn get_param<'world, 'state>(
|
||||
@@ -2822,21 +2804,10 @@ unsafe impl SystemParam for FilteredResourcesMut<'_, '_> {
|
||||
panic!("error[B0002]: FilteredResourcesMut in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002");
|
||||
}
|
||||
|
||||
if access.has_read_all_resources() {
|
||||
component_access_set.add_unfiltered_read_all_resources();
|
||||
} else {
|
||||
for component_id in access.resource_reads() {
|
||||
component_access_set.add_unfiltered_resource_read(component_id);
|
||||
}
|
||||
}
|
||||
|
||||
if access.has_write_all_resources() {
|
||||
component_access_set.add_unfiltered_write_all_resources();
|
||||
} else {
|
||||
for component_id in access.resource_writes() {
|
||||
component_access_set.add_unfiltered_resource_write(component_id);
|
||||
}
|
||||
}
|
||||
let mut filter = FilteredAccess::matches_everything();
|
||||
filter.access_mut().extend(access);
|
||||
filter.and_with(IS_RESOURCE);
|
||||
component_access_set.add(filter);
|
||||
}
|
||||
|
||||
unsafe fn get_param<'world, 'state>(
|
||||
@@ -2941,7 +2912,10 @@ impl Display for SystemParamValidationError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::query::Without;
|
||||
use crate::resource::IsResource;
|
||||
use crate::system::assert_is_system;
|
||||
use crate::world::EntityMut;
|
||||
use core::cell::RefCell;
|
||||
|
||||
#[test]
|
||||
@@ -2960,6 +2934,44 @@ mod tests {
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn non_send_and_entities() {
|
||||
#[derive(Resource)]
|
||||
struct A(usize);
|
||||
fn my_system(mut ns: NonSendMut<A>, _: Query<EntityMut>) {
|
||||
ns.0 += 1;
|
||||
}
|
||||
assert_is_system(my_system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn res_and_entities() {
|
||||
#[derive(Resource)]
|
||||
struct A(usize);
|
||||
fn my_system(mut res: ResMut<A>, _: Query<EntityMut>) {
|
||||
res.0 += 1;
|
||||
}
|
||||
assert_is_system(my_system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_and_entities_filtered() {
|
||||
#[derive(Resource)]
|
||||
struct A(usize);
|
||||
fn res_system(mut res: ResMut<A>, _: Query<EntityMut, Without<IsResource>>) {
|
||||
res.0 += 1;
|
||||
}
|
||||
assert_is_system(res_system);
|
||||
|
||||
fn non_send_system(mut ns: NonSendMut<A>, _: Query<EntityMut, Without<A>>) {
|
||||
ns.0 += 1;
|
||||
}
|
||||
|
||||
assert_is_system(non_send_system);
|
||||
}
|
||||
|
||||
// Compile test for https://github.com/bevyengine/bevy/pull/2838.
|
||||
#[test]
|
||||
fn system_param_generic_bounds() {
|
||||
|
||||
@@ -144,7 +144,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> {
|
||||
.components()
|
||||
.get_valid_id(TypeId::of::<T>())?;
|
||||
self.access
|
||||
.has_component_read(id)
|
||||
.has_read(id)
|
||||
// SAFETY: We have read access
|
||||
.then(|| unsafe { self.entity.get() })
|
||||
.flatten()
|
||||
@@ -162,7 +162,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> {
|
||||
.components()
|
||||
.get_valid_id(TypeId::of::<T>())?;
|
||||
self.access
|
||||
.has_component_read(id)
|
||||
.has_read(id)
|
||||
// SAFETY: We have read access
|
||||
.then(|| unsafe { self.entity.get_ref() })
|
||||
.flatten()
|
||||
@@ -178,7 +178,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> {
|
||||
.components()
|
||||
.get_valid_id(TypeId::of::<T>())?;
|
||||
self.access
|
||||
.has_component_read(id)
|
||||
.has_read(id)
|
||||
// SAFETY: We have read access
|
||||
.then(|| unsafe { self.entity.get_change_ticks::<T>() })
|
||||
.flatten()
|
||||
@@ -193,7 +193,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> {
|
||||
#[inline]
|
||||
pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option<ComponentTicks> {
|
||||
self.access
|
||||
.has_component_read(component_id)
|
||||
.has_read(component_id)
|
||||
// SAFETY: We have read access
|
||||
.then(|| unsafe { self.entity.get_change_ticks_by_id(component_id) })
|
||||
.flatten()
|
||||
@@ -210,7 +210,7 @@ impl<'w, 's> FilteredEntityRef<'w, 's> {
|
||||
#[inline]
|
||||
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
|
||||
self.access
|
||||
.has_component_read(component_id)
|
||||
.has_read(component_id)
|
||||
// SAFETY: We have read access
|
||||
.then(|| unsafe { self.entity.get_by_id(component_id) })
|
||||
.flatten()
|
||||
@@ -575,7 +575,7 @@ impl<'w, 's> FilteredEntityMut<'w, 's> {
|
||||
.components()
|
||||
.get_valid_id(TypeId::of::<T>())?;
|
||||
self.access
|
||||
.has_component_write(id)
|
||||
.has_write(id)
|
||||
// SAFETY: We have permission to access the component mutable
|
||||
// and we promise to not create other references to the same component
|
||||
.then(|| unsafe { self.entity.get_mut() })
|
||||
@@ -608,7 +608,7 @@ impl<'w, 's> FilteredEntityMut<'w, 's> {
|
||||
.components()
|
||||
.get_valid_id(TypeId::of::<T>())?;
|
||||
self.access
|
||||
.has_component_write(id)
|
||||
.has_write(id)
|
||||
// SAFETY:
|
||||
// - We have write access
|
||||
// - Caller ensures `T` is a mutable component
|
||||
@@ -686,7 +686,7 @@ impl<'w, 's> FilteredEntityMut<'w, 's> {
|
||||
component_id: ComponentId,
|
||||
) -> Option<MutUntyped<'_>> {
|
||||
self.access
|
||||
.has_component_write(component_id)
|
||||
.has_write(component_id)
|
||||
// SAFETY: We have permission to access the component mutable
|
||||
// and we promise to not create other references to the same component
|
||||
.then(|| unsafe { self.entity.get_mut_by_id(component_id).ok() })
|
||||
|
||||
@@ -149,7 +149,7 @@ impl<'w, 's> FilteredResources<'w, 's> {
|
||||
/// Note that [`Self::get()`] may still return `Err` if the resource does not exist.
|
||||
pub fn has_read<R: Resource>(&self) -> bool {
|
||||
let component_id = self.world.components().component_id::<R>();
|
||||
component_id.is_some_and(|component_id| self.access.has_resource_read(component_id))
|
||||
component_id.is_some_and(|component_id| self.access.has_read(component_id))
|
||||
}
|
||||
|
||||
/// Gets a reference to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||
@@ -159,7 +159,7 @@ impl<'w, 's> FilteredResources<'w, 's> {
|
||||
.components()
|
||||
.valid_component_id::<R>()
|
||||
.ok_or(ResourceFetchError::NotRegistered)?;
|
||||
if !self.access.has_resource_read(component_id) {
|
||||
if !self.access.has_read(component_id) {
|
||||
return Err(ResourceFetchError::NoResourceAccess(component_id));
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ impl<'w, 's> FilteredResources<'w, 's> {
|
||||
|
||||
/// Gets a pointer to the resource with the given [`ComponentId`] if it exists and the `FilteredResources` has access to it.
|
||||
pub fn get_by_id(&self, component_id: ComponentId) -> Result<Ptr<'w>, ResourceFetchError> {
|
||||
if !self.access.has_resource_read(component_id) {
|
||||
if !self.access.has_read(component_id) {
|
||||
return Err(ResourceFetchError::NoResourceAccess(component_id));
|
||||
}
|
||||
// SAFETY: We have read access to this resource
|
||||
@@ -220,14 +220,7 @@ impl<'w, 's> From<&'w FilteredResourcesMut<'_, 's>> for FilteredResources<'w, 's
|
||||
|
||||
impl<'w> From<&'w World> for FilteredResources<'w, 'static> {
|
||||
fn from(value: &'w World) -> Self {
|
||||
const READ_ALL_RESOURCES: &Access = {
|
||||
const ACCESS: Access = {
|
||||
let mut access = Access::new();
|
||||
access.read_all_resources();
|
||||
access
|
||||
};
|
||||
&ACCESS
|
||||
};
|
||||
const READ_ALL_RESOURCES: &Access = const { &Access::new_read_all() };
|
||||
|
||||
let last_run = value.last_change_tick();
|
||||
let this_run = value.read_change_tick();
|
||||
@@ -417,14 +410,14 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> {
|
||||
/// Note that [`Self::get()`] may still return `Err` if the resource does not exist.
|
||||
pub fn has_read<R: Resource>(&self) -> bool {
|
||||
let component_id = self.world.components().component_id::<R>();
|
||||
component_id.is_some_and(|component_id| self.access.has_resource_read(component_id))
|
||||
component_id.is_some_and(|component_id| self.access.has_read(component_id))
|
||||
}
|
||||
|
||||
/// Returns `true` if the `FilteredResources` has write access to the given resource.
|
||||
/// Note that [`Self::get_mut()`] may still return `Err` if the resource does not exist.
|
||||
pub fn has_write<R: Resource>(&self) -> bool {
|
||||
let component_id = self.world.components().component_id::<R>();
|
||||
component_id.is_some_and(|component_id| self.access.has_resource_write(component_id))
|
||||
component_id.is_some_and(|component_id| self.access.has_write(component_id))
|
||||
}
|
||||
|
||||
/// Gets a reference to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||
@@ -489,7 +482,7 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> {
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
) -> Result<MutUntyped<'w>, ResourceFetchError> {
|
||||
if !self.access.has_resource_write(component_id) {
|
||||
if !self.access.has_write(component_id) {
|
||||
return Err(ResourceFetchError::NoResourceAccess(component_id));
|
||||
}
|
||||
|
||||
@@ -510,14 +503,7 @@ impl<'w, 's> FilteredResourcesMut<'w, 's> {
|
||||
|
||||
impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> {
|
||||
fn from(value: &'w mut World) -> Self {
|
||||
const WRITE_ALL_RESOURCES: &Access = {
|
||||
const ACCESS: Access = {
|
||||
let mut access = Access::new();
|
||||
access.write_all_resources();
|
||||
access
|
||||
};
|
||||
&ACCESS
|
||||
};
|
||||
const WRITE_ALL_RESOURCES: &Access = const { &Access::new_write_all() };
|
||||
|
||||
let last_run = value.last_change_tick();
|
||||
let this_run = value.change_tick();
|
||||
@@ -557,10 +543,7 @@ impl<'w> FilteredResourcesBuilder<'w> {
|
||||
|
||||
/// Add accesses required to read all resources.
|
||||
pub fn add_read_all(&mut self) -> &mut Self {
|
||||
self.access.read_all_resources();
|
||||
for &component_id in self.world.resource_entities().indices() {
|
||||
self.access.add_component_read(component_id);
|
||||
}
|
||||
self.access.read_all();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -575,8 +558,7 @@ impl<'w> FilteredResourcesBuilder<'w> {
|
||||
|
||||
/// Add accesses required to read the resource with the given [`ComponentId`].
|
||||
pub fn add_read_by_id(&mut self, component_id: ComponentId) -> &mut Self {
|
||||
self.access.add_resource_read(component_id);
|
||||
self.access.add_component_read(component_id);
|
||||
self.access.add_read(component_id);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -610,10 +592,7 @@ impl<'w> FilteredResourcesMutBuilder<'w> {
|
||||
|
||||
/// Add accesses required to read all resources.
|
||||
pub fn add_read_all(&mut self) -> &mut Self {
|
||||
self.access.read_all_resources();
|
||||
for &component_id in self.world.resource_entities().indices() {
|
||||
self.access.add_component_read(component_id);
|
||||
}
|
||||
self.access.read_all();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -628,17 +607,13 @@ impl<'w> FilteredResourcesMutBuilder<'w> {
|
||||
|
||||
/// Add accesses required to read the resource with the given [`ComponentId`].
|
||||
pub fn add_read_by_id(&mut self, component_id: ComponentId) -> &mut Self {
|
||||
self.access.add_resource_read(component_id);
|
||||
self.access.add_component_read(component_id);
|
||||
self.access.add_read(component_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add accesses required to get mutable access to all resources.
|
||||
pub fn add_write_all(&mut self) -> &mut Self {
|
||||
self.access.write_all_resources();
|
||||
for &component_id in self.world.resource_entities().indices() {
|
||||
self.access.add_component_write(component_id);
|
||||
}
|
||||
self.access.write_all();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -653,8 +628,7 @@ impl<'w> FilteredResourcesMutBuilder<'w> {
|
||||
|
||||
/// Add accesses required to get mutable access to the resource with the given [`ComponentId`].
|
||||
pub fn add_write_by_id(&mut self, component_id: ComponentId) -> &mut Self {
|
||||
self.access.add_resource_write(component_id);
|
||||
self.access.add_component_write(component_id);
|
||||
self.access.add_write(component_id);
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||
component_access_set: &mut FilteredAccessSet,
|
||||
world: &mut World,
|
||||
) {
|
||||
component_access_set.add_unfiltered_resource_read(state.resource_id);
|
||||
component_access_set.add_resource_read(state.resource_id);
|
||||
|
||||
<Query<'_, '_, D, F> as SystemParam>::init_access(
|
||||
&state.query_state,
|
||||
|
||||
@@ -89,3 +89,7 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
## Note
|
||||
|
||||
If you run this error while using resources, check out [B0001](https://bevy.org/learn/errors/b0002) for additional information.
|
||||
|
||||
@@ -42,3 +42,39 @@ fn main() {
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
## Resources as Components
|
||||
|
||||
Under the hood, resources *are* components. The consequences of this are usually nothing more than an implementation detail, however, you may run into an issue where the following system throws an error:
|
||||
|
||||
```rust,no_run
|
||||
# use bevy::prelude::*;
|
||||
# #[derive(Resource)]
|
||||
# struct MyResource;
|
||||
fn system(all_entities: Query<EntityMut>, res: Res<MyResource>) {}
|
||||
```
|
||||
|
||||
It's not possible to both query all entities and a resource when resources are stored on an entity.
|
||||
To fix this, you should either constrain the `all_entities` query (you usually don't need to query *every* single entity), or exclude `MyResource` with `Without<MyResource>` or `Without<IsResource>`.
|
||||
Here, `IsResource`, is a marker component on every single entity that holds a resource, so it's very convenient if you want to exclude resources.
|
||||
|
||||
Queries that might conflict with `Res` or `ResMut` include:
|
||||
|
||||
- `Query<()>`
|
||||
- `Query<Entity>`
|
||||
- `Query<EntityMut>`
|
||||
- `Query<EntityRef>`
|
||||
- `Query<EntityMutExcept>`
|
||||
- `Query<EntityRefExcept>`
|
||||
- `Query<Option<&T>>`
|
||||
|
||||
You can run into the same problem with non-send data:
|
||||
|
||||
```rust,no_run
|
||||
# use bevy::prelude::*;
|
||||
# #[derive(Resource)]
|
||||
# struct MyNonSend;
|
||||
fn system(all_entities: Query<EntityMut>, some_non_send: NonSend<MyNonSend>) {}
|
||||
```
|
||||
|
||||
This can be fixed by adding a `Without<MyNonSend>` filter to the query.
|
||||
|
||||
@@ -158,7 +158,7 @@ fn main() {
|
||||
query.iter_mut(&mut world).for_each(|filtered_entity| {
|
||||
let terms = filtered_entity
|
||||
.access()
|
||||
.try_iter_component_access()
|
||||
.try_iter_access()
|
||||
.unwrap()
|
||||
.map(|component_access| {
|
||||
let id = *component_access.index();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Resources as Components
|
||||
pull_requests: [20934]
|
||||
pull_requests: [20934, 22910, 22911, 22919, 22930]
|
||||
---
|
||||
|
||||
## `#[derive(Resource)]` implements the `Component` trait
|
||||
@@ -34,6 +34,35 @@ The `ReflectResource` is a ZST (zero-sized type) in 0.19 and only functions to s
|
||||
Instead, `#[reflect(Resource)]` also reflects the `Component` trait, so use `ReflectComponent` instead.
|
||||
This is likely to show up in code that uses reflection, like BRP (Bevy Reflect Protocol) and `bevy_scene`.
|
||||
|
||||
## Broad Queries and System Conflicts
|
||||
|
||||
Now that resources are components, they can be queried using 'broad' queries. These are queries that query all entities. Examples include:
|
||||
|
||||
- `Query<()>`
|
||||
- `Query<Entity>`
|
||||
- `Query<EntityMut>`
|
||||
- `Query<EntityRef>`
|
||||
- `Query<EntityMutExcept>`
|
||||
- `Query<EntityRefExcept>`
|
||||
- `Query<Option<&T>>`
|
||||
|
||||
These should rarely come up in real games, but if they do, they might conflict with resource access, i.e.
|
||||
|
||||
```rust
|
||||
fn system(entity_query: Query<EntityMut>, some_resource: Res<MyResource>) {} // err! entity_query conflicts with some_resource
|
||||
```
|
||||
|
||||
To fix this, you can narrow down the query by using either the `Without<MyResource>` or `Without<IsResource>` filter.
|
||||
The `IsResource` marker is attached to all resource entities, so it always filters them out.
|
||||
|
||||
The same is true for non-send data:
|
||||
|
||||
```rust
|
||||
fn system(entity_query: Query<EntityMut>, some_non_send: NonSend<MyNonSend>) {} // err! entity_query conflicts with some_resource
|
||||
```
|
||||
|
||||
This can be fixed by adding a `Without<MyNonSend>` filter to the query.
|
||||
|
||||
## Renaming Non-Send Resources to Non-Send Data
|
||||
|
||||
Previously there were two types of resources: `Send` resources and `!Send` resources.
|
||||
@@ -75,7 +104,43 @@ The registration process for components and resources is very similar and now th
|
||||
- `ComponentsQueuedRegistrator::queue_register_resource` was deprecated in favor of `ComponentsQueuedRegistrator::queue_register_component`.
|
||||
- `ComponentDescriptor::new_resource` was deprecated in favor of `ComponentDescriptor::new`
|
||||
- `ComponentDescriptor::new_resource` was deprecated in favor of `ComponentDescriptor::new`
|
||||
- `World::register_resource_with_descriptor was renamed to World::register_non_send_with_descriptor`.
|
||||
- `World::register_resource_with_descriptor` was renamed to `World::register_non_send_with_descriptor`.
|
||||
|
||||
## Access
|
||||
|
||||
Resources were also removed from `Access`, which keeps track what data any given query / system has access to.
|
||||
|
||||
- `Access::add_component_read` and `Access::add_resource_read` were deprecated in favor of `Access::add_read`.
|
||||
- `Access::add_component_write` and `Access::add_resource_write` were deprecated in favor of `Access::add_write`.
|
||||
- `Access::remove_component_read` was deprecated in favor of `Access::remove_read`.
|
||||
- `Access::remove_component_write` was deprecated in favor of `Access::remove_write`.
|
||||
- `Access::has_component_read` and `Access::has_resource_read` were deprecated in favor of `Access::has_read`.
|
||||
- `Access::has_any_component_read` and `Access::has_any_resource_read` were deprecated in favor of `Access::has_any_read`.
|
||||
- `Access::has_component_write` and `Access::has_resource_write` were deprecated in favor of `Access::has_write`.
|
||||
- `Access::has_any_component_write` and `Access::has_any_resource_write` were deprecated in favor of `Access::has_any_write`.
|
||||
- `Access::read_all_components` was deprecated in favor of `Access::read_all`.
|
||||
- `Access::write_all_components` was deprecated in favor of `Access::write_all`.
|
||||
- `Access::read_all_resources` and `Access::write_all_resources` were removed.
|
||||
- `Access::has_read_all_components` was deprecated in favor of `Access::has_read_all`.
|
||||
- `Access::has_write_all_components` was deprecated in favor of `Access::has_write_all`.
|
||||
- `Access::has_read_all_resources` and `Access::has_write_all_resources` were removed.
|
||||
- `Access::is_components_compatible` was deprecated in favor of `Access::is_compatible`.
|
||||
- `Access::is_resources_compatible` was removed.
|
||||
- `Access::is_subset_components` was deprecated in favor of `Access::is_subset`.
|
||||
- `Access::is_subset_resources` was removed.
|
||||
- `Access::resource_reads_and_writes`, `Access::resource_reads`, `Access::resource_writes` were removed.
|
||||
- `Access::try_iter_component_access` was deprecated in favor of `Access::try_iter_access`.
|
||||
- `FilteredAccess::add_component_read` was deprecated in favor of `FilteredAccess::add_read`.
|
||||
- `FilteredAccess::add_component_write` was deprecated in favor of `FilteredAccess::add_write`.
|
||||
- `FilteredAccess::add_resource_read` and `FilteredAccess::add_resource_write` were removed.
|
||||
- `FilteredAccess::read_all_components` was deprecated in favor of `FilteredAccess::read_all`.
|
||||
- `FilteredAccess::write_all_components` was deprecated in favor of `FilteredAccess::write_all`.
|
||||
- `FilteredAccessSet::add_unfiltered_resource_read` was deprecated in favor of `FilteredAccessSet::add_resource_read`.
|
||||
- `FilteredAccessSet::add_unfiltered_resource_write` was deprecated in favor of `FilteredAccessSet::add_resource_write`.
|
||||
|
||||
Due to the split storage it used to be possible to both access an entity and a resource in a `WorldQuery` implementor.
|
||||
This is no longer valid. In order to access multiple different entities for a `WorldQuery` implementation, use `WorldQuery::init_nested_access`.
|
||||
See the implementation of `WorldQuery` for `AssetChanged` for an example of how this can be done correctly.
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
|
||||
Reference in New Issue
Block a user