mirror of
https://github.com/bevyengine/bevy
synced 2024-11-15 01:18:01 +00:00
System param for dynamic resources (#15189)
# Objective Support accessing dynamic resources in a dynamic system, including accessing them by component id. This is similar to how dynamic components can be queried using `Query<FilteredEntityMut>`. ## Solution Create `FilteredResources` and `FilteredResourcesMut` types that act similar to `FilteredEntityRef` and `FilteredEntityMut` and that can be used as system parameters. ## Example ```rust // Use `FilteredResourcesParamBuilder` to declare access to resources. let system = (FilteredResourcesParamBuilder::new(|builder| { builder.add_read::<B>().add_read::<C>(); }),) .build_state(&mut world) .build_system(resource_system); world.init_resource::<A>(); world.init_resource::<C>(); fn resource_system(res: FilteredResources) { // The resource exists, but we have no access, so we can't read it. assert!(res.get::<A>().is_none()); // The resource doesn't exist, so we can't read it. assert!(res.get::<B>().is_none()); // The resource exists and we have access, so we can read it. let c = res.get::<C>().unwrap(); // The type parameter can be left out if it can be determined from use. let c: Res<C> = res.get().unwrap(); } ``` ## Future Work As a follow-up PR, `ReflectResource` can be modified to take `impl Into<FilteredResources>`, similar to how `ReflectComponent` takes `impl Into<FilteredEntityRef>`. That will allow dynamic resources to be accessed using reflection.
This commit is contained in:
parent
1e61092604
commit
46180a75f8
7 changed files with 1005 additions and 22 deletions
|
@ -63,8 +63,8 @@ pub mod prelude {
|
||||||
SystemParamFunction, WithParamWarnPolicy,
|
SystemParamFunction, WithParamWarnPolicy,
|
||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,
|
Command, EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
||||||
OnReplace, World,
|
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
use crate::component::ComponentId;
|
||||||
use crate::storage::SparseSetIndex;
|
use crate::storage::SparseSetIndex;
|
||||||
|
use crate::world::World;
|
||||||
use core::{fmt, fmt::Debug, marker::PhantomData};
|
use core::{fmt, fmt::Debug, marker::PhantomData};
|
||||||
use fixedbitset::FixedBitSet;
|
use fixedbitset::FixedBitSet;
|
||||||
|
|
||||||
|
@ -727,6 +729,25 @@ impl<T: SparseSetIndex> Access<T> {
|
||||||
AccessConflicts::Individual(conflicts)
|
AccessConflicts::Individual(conflicts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the indices of the resources this has access to.
|
||||||
|
pub fn resource_reads_and_writes(&self) -> impl Iterator<Item = T> + '_ {
|
||||||
|
self.resource_read_and_writes
|
||||||
|
.ones()
|
||||||
|
.map(T::get_sparse_set_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the indices of the resources this has non-exclusive access to.
|
||||||
|
pub fn resource_reads(&self) -> impl Iterator<Item = T> + '_ {
|
||||||
|
self.resource_read_and_writes
|
||||||
|
.difference(&self.resource_writes)
|
||||||
|
.map(T::get_sparse_set_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the indices of the resources this has exclusive access to.
|
||||||
|
pub fn resource_writes(&self) -> impl Iterator<Item = T> + '_ {
|
||||||
|
self.resource_writes.ones().map(T::get_sparse_set_index)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the indices of the components that this has an archetypal access to.
|
/// 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),
|
/// These are components whose values are not accessed (and thus will never cause conflicts),
|
||||||
|
@ -863,6 +884,24 @@ impl AccessConflicts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn format_conflict_list(&self, world: &World) -> String {
|
||||||
|
match self {
|
||||||
|
AccessConflicts::All => String::new(),
|
||||||
|
AccessConflicts::Individual(indices) => format!(
|
||||||
|
" {}",
|
||||||
|
indices
|
||||||
|
.ones()
|
||||||
|
.map(|index| world
|
||||||
|
.components
|
||||||
|
.get_info(ComponentId::get_sparse_set_index(index))
|
||||||
|
.unwrap()
|
||||||
|
.name())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(", ")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An [`AccessConflicts`] which represents the absence of any conflict
|
/// An [`AccessConflicts`] which represents the absence of any conflict
|
||||||
pub(crate) fn empty() -> Self {
|
pub(crate) fn empty() -> Self {
|
||||||
Self::Individual(FixedBitSet::new())
|
Self::Individual(FixedBitSet::new())
|
||||||
|
@ -1239,6 +1278,20 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
|
||||||
self.add(filter);
|
self.add(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds read access to all resources to the set.
|
||||||
|
pub(crate) fn add_unfiltered_read_all_resources(&mut self) {
|
||||||
|
let mut filter = FilteredAccess::default();
|
||||||
|
filter.access.read_all_resources();
|
||||||
|
self.add(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds write access to all resources to the set.
|
||||||
|
pub(crate) fn add_unfiltered_write_all_resources(&mut self) {
|
||||||
|
let mut filter = FilteredAccess::default();
|
||||||
|
filter.access.write_all_resources();
|
||||||
|
self.add(filter);
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds all of the accesses from the passed set to `self`.
|
/// Adds all of the accesses from the passed set to `self`.
|
||||||
pub fn extend(&mut self, filtered_access_set: FilteredAccessSet<T>) {
|
pub fn extend(&mut self, filtered_access_set: FilteredAccessSet<T>) {
|
||||||
self.combined_access
|
self.combined_access
|
||||||
|
|
|
@ -6,7 +6,10 @@ use crate::{
|
||||||
system::{
|
system::{
|
||||||
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
|
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
|
||||||
},
|
},
|
||||||
world::{FromWorld, World},
|
world::{
|
||||||
|
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
|
||||||
|
FilteredResourcesMutBuilder, FromWorld, World,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
@ -77,6 +80,10 @@ use super::{init_query_param, Res, ResMut, Resource, SystemState};
|
||||||
///
|
///
|
||||||
/// [`LocalBuilder`] can build a [`Local`] to supply the initial value for the `Local`.
|
/// [`LocalBuilder`] can build a [`Local`] to supply the initial value for the `Local`.
|
||||||
///
|
///
|
||||||
|
/// [`FilteredResourcesParamBuilder`] can build a [`FilteredResources`],
|
||||||
|
/// and [`FilteredResourcesMutParamBuilder`] can build a [`FilteredResourcesMut`],
|
||||||
|
/// to configure the resources that can be accessed.
|
||||||
|
///
|
||||||
/// [`DynParamBuilder`] can build a [`DynSystemParam`] to determine the type of the inner parameter,
|
/// [`DynParamBuilder`] can build a [`DynSystemParam`] to determine the type of the inner parameter,
|
||||||
/// and to supply any `SystemParamBuilder` it needs.
|
/// and to supply any `SystemParamBuilder` it needs.
|
||||||
///
|
///
|
||||||
|
@ -526,6 +533,147 @@ unsafe impl<'s, T: FromWorld + Send + 'static> SystemParamBuilder<Local<'s, T>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`SystemParamBuilder`] for a [`FilteredResources`].
|
||||||
|
/// See the [`FilteredResources`] docs for examples.
|
||||||
|
pub struct FilteredResourcesParamBuilder<T>(T);
|
||||||
|
|
||||||
|
impl<T> FilteredResourcesParamBuilder<T> {
|
||||||
|
/// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`].
|
||||||
|
pub fn new(f: T) -> Self
|
||||||
|
where
|
||||||
|
T: FnOnce(&mut FilteredResourcesBuilder),
|
||||||
|
{
|
||||||
|
Self(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FilteredResourcesParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesBuilder) + 'a>> {
|
||||||
|
/// Creates a [`SystemParamBuilder`] for a [`FilteredResources`] that accepts a callback to configure the [`FilteredResourcesBuilder`].
|
||||||
|
/// This boxes the callback so that it has a common type.
|
||||||
|
pub fn new_box(f: impl FnOnce(&mut FilteredResourcesBuilder) + 'a) -> Self {
|
||||||
|
Self(Box::new(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Resource ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this FilteredResources
|
||||||
|
// conflicts with any prior access, a panic will occur.
|
||||||
|
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)>
|
||||||
|
SystemParamBuilder<FilteredResources<'w, 's>> for FilteredResourcesParamBuilder<T>
|
||||||
|
{
|
||||||
|
fn build(
|
||||||
|
self,
|
||||||
|
world: &mut World,
|
||||||
|
meta: &mut SystemMeta,
|
||||||
|
) -> <FilteredResources<'w, 's> as SystemParam>::State {
|
||||||
|
let mut builder = FilteredResourcesBuilder::new(world);
|
||||||
|
(self.0)(&mut builder);
|
||||||
|
let access = builder.build();
|
||||||
|
|
||||||
|
let combined_access = meta.component_access_set.combined_access();
|
||||||
|
let conflicts = combined_access.get_conflicts(&access);
|
||||||
|
if !conflicts.is_empty() {
|
||||||
|
let accesses = conflicts.format_conflict_list(world);
|
||||||
|
let system_name = &meta.name;
|
||||||
|
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://bevyengine.org/learn/errors/#b0002");
|
||||||
|
}
|
||||||
|
|
||||||
|
if access.has_read_all_resources() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_read_all_resources();
|
||||||
|
meta.archetype_component_access.read_all_resources();
|
||||||
|
} else {
|
||||||
|
for component_id in access.resource_reads_and_writes() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_resource_read(component_id);
|
||||||
|
|
||||||
|
let archetype_component_id = world.initialize_resource_internal(component_id).id();
|
||||||
|
meta.archetype_component_access
|
||||||
|
.add_resource_read(archetype_component_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`SystemParamBuilder`] for a [`FilteredResourcesMut`].
|
||||||
|
/// See the [`FilteredResourcesMut`] docs for examples.
|
||||||
|
pub struct FilteredResourcesMutParamBuilder<T>(T);
|
||||||
|
|
||||||
|
impl<T> FilteredResourcesMutParamBuilder<T> {
|
||||||
|
/// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`].
|
||||||
|
pub fn new(f: T) -> Self
|
||||||
|
where
|
||||||
|
T: FnOnce(&mut FilteredResourcesMutBuilder),
|
||||||
|
{
|
||||||
|
Self(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FilteredResourcesMutParamBuilder<Box<dyn FnOnce(&mut FilteredResourcesMutBuilder) + 'a>> {
|
||||||
|
/// Creates a [`SystemParamBuilder`] for a [`FilteredResourcesMut`] that accepts a callback to configure the [`FilteredResourcesMutBuilder`].
|
||||||
|
/// This boxes the callback so that it has a common type.
|
||||||
|
pub fn new_box(f: impl FnOnce(&mut FilteredResourcesMutBuilder) + 'a) -> Self {
|
||||||
|
Self(Box::new(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: Resource ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this FilteredResources
|
||||||
|
// conflicts with any prior access, a panic will occur.
|
||||||
|
unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
|
||||||
|
SystemParamBuilder<FilteredResourcesMut<'w, 's>> for FilteredResourcesMutParamBuilder<T>
|
||||||
|
{
|
||||||
|
fn build(
|
||||||
|
self,
|
||||||
|
world: &mut World,
|
||||||
|
meta: &mut SystemMeta,
|
||||||
|
) -> <FilteredResourcesMut<'w, 's> as SystemParam>::State {
|
||||||
|
let mut builder = FilteredResourcesMutBuilder::new(world);
|
||||||
|
(self.0)(&mut builder);
|
||||||
|
let access = builder.build();
|
||||||
|
|
||||||
|
let combined_access = meta.component_access_set.combined_access();
|
||||||
|
let conflicts = combined_access.get_conflicts(&access);
|
||||||
|
if !conflicts.is_empty() {
|
||||||
|
let accesses = conflicts.format_conflict_list(world);
|
||||||
|
let system_name = &meta.name;
|
||||||
|
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://bevyengine.org/learn/errors/#b0002");
|
||||||
|
}
|
||||||
|
|
||||||
|
if access.has_read_all_resources() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_read_all_resources();
|
||||||
|
meta.archetype_component_access.read_all_resources();
|
||||||
|
} else {
|
||||||
|
for component_id in access.resource_reads() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_resource_read(component_id);
|
||||||
|
|
||||||
|
let archetype_component_id = world.initialize_resource_internal(component_id).id();
|
||||||
|
meta.archetype_component_access
|
||||||
|
.add_resource_read(archetype_component_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if access.has_write_all_resources() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_write_all_resources();
|
||||||
|
meta.archetype_component_access.write_all_resources();
|
||||||
|
} else {
|
||||||
|
for component_id in access.resource_writes() {
|
||||||
|
meta.component_access_set
|
||||||
|
.add_unfiltered_resource_write(component_id);
|
||||||
|
|
||||||
|
let archetype_component_id = world.initialize_resource_internal(component_id).id();
|
||||||
|
meta.archetype_component_access
|
||||||
|
.add_resource_write(archetype_component_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate as bevy_ecs;
|
use crate as bevy_ecs;
|
||||||
|
@ -546,6 +694,9 @@ mod tests {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct C;
|
struct C;
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct R;
|
||||||
|
|
||||||
fn local_system(local: Local<u64>) -> u64 {
|
fn local_system(local: Local<u64>) -> u64 {
|
||||||
*local
|
*local
|
||||||
}
|
}
|
||||||
|
@ -774,4 +925,114 @@ mod tests {
|
||||||
let output = world.run_system_once(system).unwrap();
|
let output = world.run_system_once(system).unwrap();
|
||||||
assert_eq!(output, 101);
|
assert_eq!(output, 101);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filtered_resource_conflicts_read_with_res() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource(),
|
||||||
|
FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
builder.add_read::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: Res<R>, _fr: FilteredResources| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_conflicts_read_with_resmut() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource_mut(),
|
||||||
|
FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
builder.add_read::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: ResMut<R>, _fr: FilteredResources| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_conflicts_read_all_with_resmut() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource_mut(),
|
||||||
|
FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
builder.add_read_all();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: ResMut<R>, _fr: FilteredResources| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filtered_resource_mut_conflicts_read_with_res() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource(),
|
||||||
|
FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
builder.add_read::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_mut_conflicts_read_with_resmut() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource_mut(),
|
||||||
|
FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
builder.add_read::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: ResMut<R>, _fr: FilteredResourcesMut| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_mut_conflicts_write_with_res() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource(),
|
||||||
|
FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
builder.add_write::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_mut_conflicts_write_all_with_res() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource(),
|
||||||
|
FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
builder.add_write_all();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: Res<R>, _fr: FilteredResourcesMut| {});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn filtered_resource_mut_conflicts_write_with_resmut() {
|
||||||
|
let mut world = World::new();
|
||||||
|
(
|
||||||
|
ParamBuilder::resource_mut(),
|
||||||
|
FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
builder.add_write::<R>();
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.build_state(&mut world)
|
||||||
|
.build_system(|_r: ResMut<R>, _fr: FilteredResourcesMut| {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,8 @@
|
||||||
//! In addition, the following parameters can be used when constructing a dynamic system with [`SystemParamBuilder`],
|
//! In addition, the following parameters can be used when constructing a dynamic system with [`SystemParamBuilder`],
|
||||||
//! but will only provide an empty value when used with an ordinary system:
|
//! but will only provide an empty value when used with an ordinary system:
|
||||||
//!
|
//!
|
||||||
|
//! - [`FilteredResources`](crate::world::FilteredResources)
|
||||||
|
//! - [`FilteredResourcesMut`](crate::world::FilteredResourcesMut)
|
||||||
//! - [`DynSystemParam`]
|
//! - [`DynSystemParam`]
|
||||||
//! - [`Vec<P>`] where `P: SystemParam`
|
//! - [`Vec<P>`] where `P: SystemParam`
|
||||||
//! - [`ParamSet<Vec<P>>`] where `P: SystemParam`
|
//! - [`ParamSet<Vec<P>>`] where `P: SystemParam`
|
||||||
|
|
|
@ -6,12 +6,15 @@ use crate::{
|
||||||
component::{ComponentId, ComponentTicks, Components, Tick},
|
component::{ComponentId, ComponentTicks, Components, Tick},
|
||||||
entity::Entities,
|
entity::Entities,
|
||||||
query::{
|
query::{
|
||||||
Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter,
|
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError,
|
||||||
QuerySingleError, QueryState, ReadOnlyQueryData,
|
QueryState, ReadOnlyQueryData,
|
||||||
},
|
},
|
||||||
storage::{ResourceData, SparseSetIndex},
|
storage::ResourceData,
|
||||||
system::{Query, Single, SystemMeta},
|
system::{Query, Single, SystemMeta},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
world::{
|
||||||
|
unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut,
|
||||||
|
FromWorld, World,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use bevy_ecs_macros::impl_param_set;
|
use bevy_ecs_macros::impl_param_set;
|
||||||
pub use bevy_ecs_macros::{Resource, SystemParam};
|
pub use bevy_ecs_macros::{Resource, SystemParam};
|
||||||
|
@ -349,21 +352,7 @@ fn assert_component_access_compatibility(
|
||||||
if conflicts.is_empty() {
|
if conflicts.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let accesses = match conflicts {
|
let accesses = conflicts.format_conflict_list(world);
|
||||||
AccessConflicts::All => "",
|
|
||||||
AccessConflicts::Individual(indices) => &format!(
|
|
||||||
" {}",
|
|
||||||
indices
|
|
||||||
.ones()
|
|
||||||
.map(|index| world
|
|
||||||
.components
|
|
||||||
.get_info(ComponentId::get_sparse_set_index(index))
|
|
||||||
.unwrap()
|
|
||||||
.name())
|
|
||||||
.collect::<Vec<&str>>()
|
|
||||||
.join(", ")
|
|
||||||
),
|
|
||||||
};
|
|
||||||
panic!("error[B0001]: Query<{query_type}, {filter_type}> in system {system_name} accesses component(s){accesses} in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001");
|
panic!("error[B0001]: Query<{query_type}, {filter_type}> in system {system_name} accesses component(s){accesses} in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2447,6 +2436,57 @@ unsafe impl SystemParam for DynSystemParam<'_, '_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: When initialized with `init_state`, `get_param` returns a `FilteredResources` with no access.
|
||||||
|
// Therefore, `init_state` trivially registers all access, and no accesses can conflict.
|
||||||
|
// Note that the safety requirements for non-empty access are handled by the `SystemParamBuilder` impl that builds them.
|
||||||
|
unsafe impl SystemParam for FilteredResources<'_, '_> {
|
||||||
|
type State = Access<ComponentId>;
|
||||||
|
|
||||||
|
type Item<'world, 'state> = FilteredResources<'world, 'state>;
|
||||||
|
|
||||||
|
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
|
||||||
|
Access::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_param<'world, 'state>(
|
||||||
|
state: &'state mut Self::State,
|
||||||
|
system_meta: &SystemMeta,
|
||||||
|
world: UnsafeWorldCell<'world>,
|
||||||
|
change_tick: Tick,
|
||||||
|
) -> Self::Item<'world, 'state> {
|
||||||
|
// SAFETY: The caller ensures that `world` has access to anything registered in `init_state` or `build`,
|
||||||
|
// and the builder registers `access` in `build`.
|
||||||
|
unsafe { FilteredResources::new(world, state, system_meta.last_run, change_tick) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: FilteredResources only reads resources.
|
||||||
|
unsafe impl ReadOnlySystemParam for FilteredResources<'_, '_> {}
|
||||||
|
|
||||||
|
// SAFETY: When initialized with `init_state`, `get_param` returns a `FilteredResourcesMut` with no access.
|
||||||
|
// Therefore, `init_state` trivially registers all access, and no accesses can conflict.
|
||||||
|
// Note that the safety requirements for non-empty access are handled by the `SystemParamBuilder` impl that builds them.
|
||||||
|
unsafe impl SystemParam for FilteredResourcesMut<'_, '_> {
|
||||||
|
type State = Access<ComponentId>;
|
||||||
|
|
||||||
|
type Item<'world, 'state> = FilteredResourcesMut<'world, 'state>;
|
||||||
|
|
||||||
|
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {
|
||||||
|
Access::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_param<'world, 'state>(
|
||||||
|
state: &'state mut Self::State,
|
||||||
|
system_meta: &SystemMeta,
|
||||||
|
world: UnsafeWorldCell<'world>,
|
||||||
|
change_tick: Tick,
|
||||||
|
) -> Self::Item<'world, 'state> {
|
||||||
|
// SAFETY: The caller ensures that `world` has access to anything registered in `init_state` or `build`,
|
||||||
|
// and the builder registers `access` in `build`.
|
||||||
|
unsafe { FilteredResourcesMut::new(world, state, system_meta.last_run, change_tick) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
625
crates/bevy_ecs/src/world/filtered_resource.rs
Normal file
625
crates/bevy_ecs/src/world/filtered_resource.rs
Normal file
|
@ -0,0 +1,625 @@
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
change_detection::{Mut, MutUntyped, Ref, Ticks, TicksMut},
|
||||||
|
component::{ComponentId, Tick},
|
||||||
|
query::Access,
|
||||||
|
system::Resource,
|
||||||
|
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||||
|
};
|
||||||
|
use bevy_ptr::Ptr;
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
use bevy_ptr::UnsafeCellDeref;
|
||||||
|
|
||||||
|
/// Provides read-only access to a set of [`Resource`]s defined by the contained [`Access`].
|
||||||
|
///
|
||||||
|
/// Use [`FilteredResourcesMut`] if you need mutable access to some resources.
|
||||||
|
///
|
||||||
|
/// To be useful as a [`SystemParam`](crate::system::SystemParam),
|
||||||
|
/// this must be configured using a [`FilteredResourcesParamBuilder`](crate::system::FilteredResourcesParamBuilder)
|
||||||
|
/// to build the system using a [`SystemParamBuilder`](crate::prelude::SystemParamBuilder).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct B;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct C;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// // Use `FilteredResourcesParamBuilder` to declare access to resources.
|
||||||
|
/// let system = (FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_read::<B>().add_read::<C>();
|
||||||
|
/// }),)
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(resource_system);
|
||||||
|
///
|
||||||
|
/// world.init_resource::<A>();
|
||||||
|
/// world.init_resource::<C>();
|
||||||
|
///
|
||||||
|
/// fn resource_system(res: FilteredResources) {
|
||||||
|
/// // The resource exists, but we have no access, so we can't read it.
|
||||||
|
/// assert!(res.get::<A>().is_none());
|
||||||
|
/// // The resource doesn't exist, so we can't read it.
|
||||||
|
/// assert!(res.get::<B>().is_none());
|
||||||
|
/// // The resource exists and we have access, so we can read it.
|
||||||
|
/// let c = res.get::<C>().unwrap();
|
||||||
|
/// // The type parameter can be left out if it can be determined from use.
|
||||||
|
/// let c: Ref<C> = res.get().unwrap();
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This can be used alongside ordinary [`Res`](crate::system::Res) and [`ResMut`](crate::system::ResMut) parameters if they do not conflict.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct B;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// # world.init_resource::<A>();
|
||||||
|
/// # world.init_resource::<B>();
|
||||||
|
/// #
|
||||||
|
/// let system = (
|
||||||
|
/// FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_read::<A>();
|
||||||
|
/// }),
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// )
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(resource_system);
|
||||||
|
///
|
||||||
|
/// // Read access to A does not conflict with read access to A or write access to B.
|
||||||
|
/// fn resource_system(filtered: FilteredResources, res_a: Res<A>, res_mut_b: ResMut<B>) {
|
||||||
|
/// let res_a_2: Ref<A> = filtered.get::<A>().unwrap();
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// But it will conflict if it tries to read the same resource that another parameter writes.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// # world.init_resource::<A>();
|
||||||
|
/// #
|
||||||
|
/// let system = (
|
||||||
|
/// FilteredResourcesParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_read::<A>();
|
||||||
|
/// }),
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// )
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(invalid_resource_system);
|
||||||
|
///
|
||||||
|
/// // Read access to A conflicts with write access to A.
|
||||||
|
/// fn invalid_resource_system(filtered: FilteredResources, res_mut_a: ResMut<A>) { }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct FilteredResources<'w, 's> {
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
access: &'s Access<ComponentId>,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's> FilteredResources<'w, 's> {
|
||||||
|
/// Creates a new [`FilteredResources`].
|
||||||
|
/// # Safety
|
||||||
|
/// It is the callers responsibility to ensure that nothing else may access the any resources in the `world` in a way that conflicts with `access`.
|
||||||
|
pub(crate) unsafe fn new(
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
access: &'s Access<ComponentId>,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
access,
|
||||||
|
last_run,
|
||||||
|
this_run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying [`Access`].
|
||||||
|
pub fn access(&self) -> &Access<ComponentId> {
|
||||||
|
self.access
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the `FilteredResources` has access to the given resource.
|
||||||
|
/// Note that [`Self::get()`] may still return `None` if the resource does not exist.
|
||||||
|
pub fn has_read<R: Resource>(&self) -> bool {
|
||||||
|
let component_id = self.world.components().resource_id::<R>();
|
||||||
|
component_id.is_some_and(|component_id| self.access.has_resource_read(component_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn get<R: Resource>(&self) -> Option<Ref<'w, R>> {
|
||||||
|
let component_id = self.world.components().resource_id::<R>()?;
|
||||||
|
if !self.access.has_resource_read(component_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: We have read access to this resource
|
||||||
|
unsafe { self.world.get_resource_with_ticks(component_id) }.map(
|
||||||
|
|(value, ticks, _caller)| Ref {
|
||||||
|
// SAFETY: `component_id` was obtained from the type ID of `R`.
|
||||||
|
value: unsafe { value.deref() },
|
||||||
|
// SAFETY: We have read access to the resource, so no mutable reference can exist.
|
||||||
|
ticks: unsafe { Ticks::from_tick_cells(ticks, self.last_run, self.this_run) },
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY: We have read access to the resource, so no mutable reference can exist.
|
||||||
|
changed_by: unsafe { _caller.deref() },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> Option<Ptr<'w>> {
|
||||||
|
if !self.access.has_resource_read(component_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: We have read access to this resource
|
||||||
|
unsafe { self.world.get_resource_by_id(component_id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's> From<FilteredResourcesMut<'w, 's>> for FilteredResources<'w, 's> {
|
||||||
|
fn from(resources: FilteredResourcesMut<'w, 's>) -> Self {
|
||||||
|
// SAFETY:
|
||||||
|
// - `FilteredResourcesMut` guarantees exclusive access to all resources in the new `FilteredResources`.
|
||||||
|
unsafe {
|
||||||
|
FilteredResources::new(
|
||||||
|
resources.world,
|
||||||
|
resources.access,
|
||||||
|
resources.last_run,
|
||||||
|
resources.this_run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's> From<&'w FilteredResourcesMut<'_, 's>> for FilteredResources<'w, 's> {
|
||||||
|
fn from(resources: &'w FilteredResourcesMut<'_, 's>) -> Self {
|
||||||
|
// SAFETY:
|
||||||
|
// - `FilteredResourcesMut` guarantees exclusive access to all components in the new `FilteredResources`.
|
||||||
|
unsafe {
|
||||||
|
FilteredResources::new(
|
||||||
|
resources.world,
|
||||||
|
resources.access,
|
||||||
|
resources.last_run,
|
||||||
|
resources.this_run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> From<&'w World> for FilteredResources<'w, 'static> {
|
||||||
|
fn from(value: &'w World) -> Self {
|
||||||
|
static READ_ALL_RESOURCES: OnceLock<Access<ComponentId>> = OnceLock::new();
|
||||||
|
let access = READ_ALL_RESOURCES.get_or_init(|| {
|
||||||
|
let mut access = Access::new();
|
||||||
|
access.read_all_resources();
|
||||||
|
access
|
||||||
|
});
|
||||||
|
|
||||||
|
let last_run = value.last_change_tick();
|
||||||
|
let this_run = value.read_change_tick();
|
||||||
|
// SAFETY: We have a reference to the entire world, so nothing else can alias with read access to all resources.
|
||||||
|
unsafe {
|
||||||
|
Self::new(
|
||||||
|
value.as_unsafe_world_cell_readonly(),
|
||||||
|
access,
|
||||||
|
last_run,
|
||||||
|
this_run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> From<&'w mut World> for FilteredResources<'w, 'static> {
|
||||||
|
fn from(value: &'w mut World) -> Self {
|
||||||
|
Self::from(&*value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides mutable access to a set of [`Resource`]s defined by the contained [`Access`].
|
||||||
|
///
|
||||||
|
/// Use [`FilteredResources`] if you only need read-only access to resources.
|
||||||
|
///
|
||||||
|
/// To be useful as a [`SystemParam`](crate::system::SystemParam),
|
||||||
|
/// this must be configured using a [`FilteredResourcesMutParamBuilder`](crate::system::FilteredResourcesMutParamBuilder)
|
||||||
|
/// to build the system using a [`SystemParamBuilder`](crate::prelude::SystemParamBuilder).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct B;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct C;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct D;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// // Use `FilteredResourcesMutParamBuilder` to declare access to resources.
|
||||||
|
/// let system = (FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_write::<B>().add_read::<C>().add_write::<D>();
|
||||||
|
/// }),)
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(resource_system);
|
||||||
|
///
|
||||||
|
/// world.init_resource::<A>();
|
||||||
|
/// world.init_resource::<C>();
|
||||||
|
/// world.init_resource::<D>();
|
||||||
|
///
|
||||||
|
/// fn resource_system(mut res: FilteredResourcesMut) {
|
||||||
|
/// // The resource exists, but we have no access, so we can't read it or write it.
|
||||||
|
/// assert!(res.get::<A>().is_none());
|
||||||
|
/// assert!(res.get_mut::<A>().is_none());
|
||||||
|
/// // The resource doesn't exist, so we can't read it or write it.
|
||||||
|
/// assert!(res.get::<B>().is_none());
|
||||||
|
/// assert!(res.get_mut::<B>().is_none());
|
||||||
|
/// // The resource exists and we have read access, so we can read it but not write it.
|
||||||
|
/// let c = res.get::<C>().unwrap();
|
||||||
|
/// assert!(res.get_mut::<C>().is_none());
|
||||||
|
/// // The resource exists and we have write access, so we can read it or write it.
|
||||||
|
/// let d = res.get::<D>().unwrap();
|
||||||
|
/// let d = res.get_mut::<D>().unwrap();
|
||||||
|
/// // The type parameter can be left out if it can be determined from use.
|
||||||
|
/// let c: Ref<C> = res.get().unwrap();
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This can be used alongside ordinary [`Res`](crate::system::ResMut) and [`ResMut`](crate::system::ResMut) parameters if they do not conflict.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct B;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct C;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// # world.init_resource::<A>();
|
||||||
|
/// # world.init_resource::<B>();
|
||||||
|
/// # world.init_resource::<C>();
|
||||||
|
/// #
|
||||||
|
/// let system = (
|
||||||
|
/// FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_read::<A>().add_write::<B>();
|
||||||
|
/// }),
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// )
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(resource_system);
|
||||||
|
///
|
||||||
|
/// // Read access to A does not conflict with read access to A or write access to C.
|
||||||
|
/// // Write access to B does not conflict with access to A or C.
|
||||||
|
/// fn resource_system(mut filtered: FilteredResourcesMut, res_a: Res<A>, res_mut_c: ResMut<C>) {
|
||||||
|
/// let res_a_2: Ref<A> = filtered.get::<A>().unwrap();
|
||||||
|
/// let res_mut_b: Mut<B> = filtered.get_mut::<B>().unwrap();
|
||||||
|
/// }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// But it will conflict if it tries to read the same resource that another parameter writes,
|
||||||
|
/// or write the same resource that another parameter reads.
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use bevy_ecs::{prelude::*, system::*};
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Default, Resource)]
|
||||||
|
/// # struct A;
|
||||||
|
/// #
|
||||||
|
/// # let mut world = World::new();
|
||||||
|
/// # world.init_resource::<A>();
|
||||||
|
/// #
|
||||||
|
/// let system = (
|
||||||
|
/// FilteredResourcesMutParamBuilder::new(|builder| {
|
||||||
|
/// builder.add_write::<A>();
|
||||||
|
/// }),
|
||||||
|
/// ParamBuilder,
|
||||||
|
/// )
|
||||||
|
/// .build_state(&mut world)
|
||||||
|
/// .build_system(invalid_resource_system);
|
||||||
|
///
|
||||||
|
/// // Read access to A conflicts with write access to A.
|
||||||
|
/// fn invalid_resource_system(filtered: FilteredResourcesMut, res_a: Res<A>) { }
|
||||||
|
/// #
|
||||||
|
/// # world.run_system_once(system);
|
||||||
|
/// ```
|
||||||
|
pub struct FilteredResourcesMut<'w, 's> {
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
access: &'s Access<ComponentId>,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's> FilteredResourcesMut<'w, 's> {
|
||||||
|
/// Creates a new [`FilteredResources`].
|
||||||
|
/// # Safety
|
||||||
|
/// It is the callers responsibility to ensure that nothing else may access the any resources in the `world` in a way that conflicts with `access`.
|
||||||
|
pub(crate) unsafe fn new(
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
access: &'s Access<ComponentId>,
|
||||||
|
last_run: Tick,
|
||||||
|
this_run: Tick,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
access,
|
||||||
|
last_run,
|
||||||
|
this_run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets read-only access to all of the resources this `FilteredResourcesMut` can access.
|
||||||
|
pub fn as_readonly(&self) -> FilteredResources<'_, 's> {
|
||||||
|
FilteredResources::from(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new instance with a shorter lifetime.
|
||||||
|
/// This is useful if you have `&mut FilteredResourcesMut`, but you need `FilteredResourcesMut`.
|
||||||
|
pub fn reborrow(&mut self) -> FilteredResourcesMut<'_, 's> {
|
||||||
|
// SAFETY: We have exclusive access to this access for the duration of `'_`, so there cannot be anything else that conflicts.
|
||||||
|
unsafe { Self::new(self.world, self.access, self.last_run, self.this_run) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying [`Access`].
|
||||||
|
pub fn access(&self) -> &Access<ComponentId> {
|
||||||
|
self.access
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the `FilteredResources` has read access to the given resource.
|
||||||
|
/// Note that [`Self::get()`] may still return `None` if the resource does not exist.
|
||||||
|
pub fn has_read<R: Resource>(&self) -> bool {
|
||||||
|
let component_id = self.world.components().resource_id::<R>();
|
||||||
|
component_id.is_some_and(|component_id| self.access.has_resource_read(component_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the `FilteredResources` has write access to the given resource.
|
||||||
|
/// Note that [`Self::get_mut()`] may still return `None` if the resource does not exist.
|
||||||
|
pub fn has_write<R: Resource>(&self) -> bool {
|
||||||
|
let component_id = self.world.components().resource_id::<R>();
|
||||||
|
component_id.is_some_and(|component_id| self.access.has_resource_write(component_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn get<R: Resource>(&self) -> Option<Ref<'_, R>> {
|
||||||
|
self.as_readonly().get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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) -> Option<Ptr<'_>> {
|
||||||
|
self.as_readonly().get_by_id(component_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn get_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> {
|
||||||
|
// SAFETY: We have exclusive access to the resources in `access` for `'_`, and we shorten the returned lifetime to that.
|
||||||
|
unsafe { self.get_mut_unchecked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable pointer to the resource with the given [`ComponentId`] if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
|
||||||
|
// SAFETY: We have exclusive access to the resources in `access` for `'_`, and we shorten the returned lifetime to that.
|
||||||
|
unsafe { self.get_mut_by_id_unchecked(component_id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes self and gets mutable access to resource of the given type with the world `'w` lifetime if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn into_mut<R: Resource>(mut self) -> Option<Mut<'w, R>> {
|
||||||
|
// SAFETY: This consumes self, so we have exclusive access to the resources in `access` for the entirety of `'w`.
|
||||||
|
unsafe { self.get_mut_unchecked() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes self and gets mutable access to resource with the given [`ComponentId`] with the world `'w` lifetime if it exists and the `FilteredResources` has access to it.
|
||||||
|
pub fn into_mut_by_id(mut self, component_id: ComponentId) -> Option<MutUntyped<'w>> {
|
||||||
|
// SAFETY: This consumes self, so we have exclusive access to the resources in `access` for the entirety of `'w`.
|
||||||
|
unsafe { self.get_mut_by_id_unchecked(component_id) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable pointer to the resource of the given type if it exists and the `FilteredResources` has access to it.
|
||||||
|
/// # Safety
|
||||||
|
/// It is the callers responsibility to ensure that there are no conflicting borrows of anything in `access` for the duration of the returned value.
|
||||||
|
unsafe fn get_mut_unchecked<R: Resource>(&mut self) -> Option<Mut<'w, R>> {
|
||||||
|
let component_id = self.world.components().resource_id::<R>()?;
|
||||||
|
// SAFETY: THe caller ensures that there are no conflicting borrows.
|
||||||
|
unsafe { self.get_mut_by_id_unchecked(component_id) }
|
||||||
|
// SAFETY: The underlying type of the resource is `R`.
|
||||||
|
.map(|ptr| unsafe { ptr.with_type::<R>() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable pointer to the resource with the given [`ComponentId`] if it exists and the `FilteredResources` has access to it.
|
||||||
|
/// # Safety
|
||||||
|
/// It is the callers responsibility to ensure that there are no conflicting borrows of anything in `access` for the duration of the returned value.
|
||||||
|
unsafe fn get_mut_by_id_unchecked(
|
||||||
|
&mut self,
|
||||||
|
component_id: ComponentId,
|
||||||
|
) -> Option<MutUntyped<'w>> {
|
||||||
|
if !self.access.has_resource_write(component_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: We have access to this resource in `access`, and the caller ensures that there are no conflicting borrows for the duration of the returned value.
|
||||||
|
unsafe { self.world.get_resource_with_ticks(component_id) }.map(
|
||||||
|
|(value, ticks, _caller)| MutUntyped {
|
||||||
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
|
value: unsafe { value.assert_unique() },
|
||||||
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
|
ticks: unsafe { TicksMut::from_tick_cells(ticks, self.last_run, self.this_run) },
|
||||||
|
#[cfg(feature = "track_change_detection")]
|
||||||
|
// SAFETY: We have exclusive access to the underlying storage.
|
||||||
|
changed_by: unsafe { _caller.deref_mut() },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> From<&'w mut World> for FilteredResourcesMut<'w, 'static> {
|
||||||
|
fn from(value: &'w mut World) -> Self {
|
||||||
|
static WRITE_ALL_RESOURCES: OnceLock<Access<ComponentId>> = OnceLock::new();
|
||||||
|
let access = WRITE_ALL_RESOURCES.get_or_init(|| {
|
||||||
|
let mut access = Access::new();
|
||||||
|
access.write_all_resources();
|
||||||
|
access
|
||||||
|
});
|
||||||
|
|
||||||
|
let last_run = value.last_change_tick();
|
||||||
|
let this_run = value.change_tick();
|
||||||
|
// SAFETY: We have a mutable reference to the entire world, so nothing else can alias with mutable access to all resources.
|
||||||
|
unsafe {
|
||||||
|
Self::new(
|
||||||
|
value.as_unsafe_world_cell_readonly(),
|
||||||
|
access,
|
||||||
|
last_run,
|
||||||
|
this_run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder struct to define the access for a [`FilteredResources`].
|
||||||
|
///
|
||||||
|
/// This is passed to a callback in [`FilteredResourcesParamBuilder`](crate::system::FilteredResourcesParamBuilder).
|
||||||
|
pub struct FilteredResourcesBuilder<'w> {
|
||||||
|
world: &'w mut World,
|
||||||
|
access: Access<ComponentId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> FilteredResourcesBuilder<'w> {
|
||||||
|
/// Creates a new builder with no access.
|
||||||
|
pub fn new(world: &'w mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
access: Access::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying [`Access`].
|
||||||
|
pub fn access(&self) -> &Access<ComponentId> {
|
||||||
|
&self.access
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to read all resources.
|
||||||
|
pub fn add_read_all(&mut self) -> &mut Self {
|
||||||
|
self.access.read_all_resources();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to read the resource of the given type.
|
||||||
|
pub fn add_read<R: Resource>(&mut self) -> &mut Self {
|
||||||
|
let component_id = self.world.components.register_resource::<R>();
|
||||||
|
self.add_read_by_id(component_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an [`Access`] that represents the accesses of the builder.
|
||||||
|
pub fn build(self) -> Access<ComponentId> {
|
||||||
|
self.access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder struct to define the access for a [`FilteredResourcesMut`].
|
||||||
|
///
|
||||||
|
/// This is passed to a callback in [`FilteredResourcesMutParamBuilder`](crate::system::FilteredResourcesMutParamBuilder).
|
||||||
|
pub struct FilteredResourcesMutBuilder<'w> {
|
||||||
|
world: &'w mut World,
|
||||||
|
access: Access<ComponentId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> FilteredResourcesMutBuilder<'w> {
|
||||||
|
/// Creates a new builder with no access.
|
||||||
|
pub fn new(world: &'w mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
access: Access::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying [`Access`].
|
||||||
|
pub fn access(&self) -> &Access<ComponentId> {
|
||||||
|
&self.access
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to read all resources.
|
||||||
|
pub fn add_read_all(&mut self) -> &mut Self {
|
||||||
|
self.access.read_all_resources();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to read the resource of the given type.
|
||||||
|
pub fn add_read<R: Resource>(&mut self) -> &mut Self {
|
||||||
|
let component_id = self.world.components.register_resource::<R>();
|
||||||
|
self.add_read_by_id(component_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to get mutable access to all resources.
|
||||||
|
pub fn add_write_all(&mut self) -> &mut Self {
|
||||||
|
self.access.write_all_resources();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add accesses required to get mutable access to the resource of the given type.
|
||||||
|
pub fn add_write<R: Resource>(&mut self) -> &mut Self {
|
||||||
|
let component_id = self.world.components.register_resource::<R>();
|
||||||
|
self.add_write_by_id(component_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an [`Access`] that represents the accesses of the builder.
|
||||||
|
pub fn build(self) -> Access<ComponentId> {
|
||||||
|
self.access
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ mod component_constants;
|
||||||
mod deferred_world;
|
mod deferred_world;
|
||||||
mod entity_ref;
|
mod entity_ref;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
mod filtered_resource;
|
||||||
mod identifier;
|
mod identifier;
|
||||||
mod spawn_batch;
|
mod spawn_batch;
|
||||||
pub mod unsafe_world_cell;
|
pub mod unsafe_world_cell;
|
||||||
|
@ -22,6 +23,7 @@ pub use entity_ref::{
|
||||||
EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry,
|
EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry,
|
||||||
FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry,
|
FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry,
|
||||||
};
|
};
|
||||||
|
pub use filtered_resource::*;
|
||||||
pub use identifier::WorldId;
|
pub use identifier::WorldId;
|
||||||
pub use spawn_batch::*;
|
pub use spawn_batch::*;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue