mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +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,
|
||||
},
|
||||
world::{
|
||||
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,
|
||||
OnReplace, World,
|
||||
Command, EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
||||
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::component::ComponentId;
|
||||
use crate::storage::SparseSetIndex;
|
||||
use crate::world::World;
|
||||
use core::{fmt, fmt::Debug, marker::PhantomData};
|
||||
use fixedbitset::FixedBitSet;
|
||||
|
||||
|
@ -727,6 +729,25 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
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.
|
||||
///
|
||||
/// 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
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self::Individual(FixedBitSet::new())
|
||||
|
@ -1239,6 +1278,20 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
|
|||
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`.
|
||||
pub fn extend(&mut self, filtered_access_set: FilteredAccessSet<T>) {
|
||||
self.combined_access
|
||||
|
|
|
@ -6,7 +6,10 @@ use crate::{
|
|||
system::{
|
||||
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
|
||||
},
|
||||
world::{FromWorld, World},
|
||||
world::{
|
||||
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
|
||||
FilteredResourcesMutBuilder, FromWorld, World,
|
||||
},
|
||||
};
|
||||
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`.
|
||||
///
|
||||
/// [`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,
|
||||
/// 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)]
|
||||
mod tests {
|
||||
use crate as bevy_ecs;
|
||||
|
@ -546,6 +694,9 @@ mod tests {
|
|||
#[derive(Component)]
|
||||
struct C;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct R;
|
||||
|
||||
fn local_system(local: Local<u64>) -> u64 {
|
||||
*local
|
||||
}
|
||||
|
@ -774,4 +925,114 @@ mod tests {
|
|||
let output = world.run_system_once(system).unwrap();
|
||||
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`],
|
||||
//! but will only provide an empty value when used with an ordinary system:
|
||||
//!
|
||||
//! - [`FilteredResources`](crate::world::FilteredResources)
|
||||
//! - [`FilteredResourcesMut`](crate::world::FilteredResourcesMut)
|
||||
//! - [`DynSystemParam`]
|
||||
//! - [`Vec<P>`] where `P: SystemParam`
|
||||
//! - [`ParamSet<Vec<P>>`] where `P: SystemParam`
|
||||
|
|
|
@ -6,12 +6,15 @@ use crate::{
|
|||
component::{ComponentId, ComponentTicks, Components, Tick},
|
||||
entity::Entities,
|
||||
query::{
|
||||
Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter,
|
||||
QuerySingleError, QueryState, ReadOnlyQueryData,
|
||||
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError,
|
||||
QueryState, ReadOnlyQueryData,
|
||||
},
|
||||
storage::{ResourceData, SparseSetIndex},
|
||||
storage::ResourceData,
|
||||
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;
|
||||
pub use bevy_ecs_macros::{Resource, SystemParam};
|
||||
|
@ -349,21 +352,7 @@ fn assert_component_access_compatibility(
|
|||
if conflicts.is_empty() {
|
||||
return;
|
||||
}
|
||||
let accesses = match conflicts {
|
||||
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(", ")
|
||||
),
|
||||
};
|
||||
let accesses = conflicts.format_conflict_list(world);
|
||||
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)]
|
||||
mod tests {
|
||||
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 entity_ref;
|
||||
pub mod error;
|
||||
mod filtered_resource;
|
||||
mod identifier;
|
||||
mod spawn_batch;
|
||||
pub mod unsafe_world_cell;
|
||||
|
@ -22,6 +23,7 @@ pub use entity_ref::{
|
|||
EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry,
|
||||
FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry,
|
||||
};
|
||||
pub use filtered_resource::*;
|
||||
pub use identifier::WorldId;
|
||||
pub use spawn_batch::*;
|
||||
|
||||
|
|
Loading…
Reference in a new issue