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:
Chris Russell 2024-10-03 14:20:34 -04:00 committed by GitHub
parent 1e61092604
commit 46180a75f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1005 additions and 22 deletions

View file

@ -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,
}, },
}; };

View file

@ -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

View file

@ -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| {});
}
} }

View file

@ -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`

View file

@ -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::*;

View 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
}
}

View file

@ -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::*;