diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index c481da091a..d33eafb604 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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, }, }; diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index cf504c2606..927e8ebcab 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -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 Access { AccessConflicts::Individual(conflicts) } + /// Returns the indices of the resources this has access to. + pub fn resource_reads_and_writes(&self) -> impl Iterator + '_ { + 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 + '_ { + 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 + '_ { + 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::>() + .join(", ") + ), + } + } + /// An [`AccessConflicts`] which represents the absence of any conflict pub(crate) fn empty() -> Self { Self::Individual(FixedBitSet::new()) @@ -1239,6 +1278,20 @@ impl FilteredAccessSet { 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) { self.combined_access diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index a9411dd176..91aa67f3ee 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -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> } } +/// A [`SystemParamBuilder`] for a [`FilteredResources`]. +/// See the [`FilteredResources`] docs for examples. +pub struct FilteredResourcesParamBuilder(T); + +impl FilteredResourcesParamBuilder { + /// 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> { + /// 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> for FilteredResourcesParamBuilder +{ + fn build( + self, + world: &mut World, + meta: &mut SystemMeta, + ) -> 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); + +impl FilteredResourcesMutParamBuilder { + /// 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> { + /// 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> for FilteredResourcesMutParamBuilder +{ + fn build( + self, + world: &mut World, + meta: &mut SystemMeta, + ) -> 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 { *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::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: Res, _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::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: ResMut, _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, _fr: FilteredResources| {}); + } + + #[test] + fn filtered_resource_mut_conflicts_read_with_res() { + let mut world = World::new(); + ( + ParamBuilder::resource(), + FilteredResourcesMutParamBuilder::new(|builder| { + builder.add_read::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: Res, _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::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: ResMut, _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::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: Res, _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, _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::(); + }), + ) + .build_state(&mut world) + .build_system(|_r: ResMut, _fr: FilteredResourcesMut| {}); + } } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index acf28fac37..dd6a950f72 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -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

`] where `P: SystemParam` //! - [`ParamSet>`] where `P: SystemParam` diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index bfa2fe108d..9d06aec04d 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -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::>() - .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` 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; + + 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; + + 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::*; diff --git a/crates/bevy_ecs/src/world/filtered_resource.rs b/crates/bevy_ecs/src/world/filtered_resource.rs new file mode 100644 index 0000000000..527957d87c --- /dev/null +++ b/crates/bevy_ecs/src/world/filtered_resource.rs @@ -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::().add_read::(); +/// }),) +/// .build_state(&mut world) +/// .build_system(resource_system); +/// +/// world.init_resource::(); +/// world.init_resource::(); +/// +/// fn resource_system(res: FilteredResources) { +/// // The resource exists, but we have no access, so we can't read it. +/// assert!(res.get::().is_none()); +/// // The resource doesn't exist, so we can't read it. +/// assert!(res.get::().is_none()); +/// // The resource exists and we have access, so we can read it. +/// let c = res.get::().unwrap(); +/// // The type parameter can be left out if it can be determined from use. +/// let c: Ref = 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::(); +/// # world.init_resource::(); +/// # +/// let system = ( +/// FilteredResourcesParamBuilder::new(|builder| { +/// builder.add_read::(); +/// }), +/// 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, res_mut_b: ResMut) { +/// let res_a_2: Ref = filtered.get::().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::(); +/// # +/// let system = ( +/// FilteredResourcesParamBuilder::new(|builder| { +/// builder.add_read::(); +/// }), +/// 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) { } +/// # +/// # world.run_system_once(system); +/// ``` +#[derive(Clone, Copy)] +pub struct FilteredResources<'w, 's> { + world: UnsafeWorldCell<'w>, + access: &'s Access, + 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, + 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 { + 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(&self) -> bool { + let component_id = self.world.components().resource_id::(); + 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(&self) -> Option> { + let component_id = self.world.components().resource_id::()?; + 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> { + 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> 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> = 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::().add_read::().add_write::(); +/// }),) +/// .build_state(&mut world) +/// .build_system(resource_system); +/// +/// world.init_resource::(); +/// world.init_resource::(); +/// world.init_resource::(); +/// +/// 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::().is_none()); +/// assert!(res.get_mut::().is_none()); +/// // The resource doesn't exist, so we can't read it or write it. +/// assert!(res.get::().is_none()); +/// assert!(res.get_mut::().is_none()); +/// // The resource exists and we have read access, so we can read it but not write it. +/// let c = res.get::().unwrap(); +/// assert!(res.get_mut::().is_none()); +/// // The resource exists and we have write access, so we can read it or write it. +/// let d = res.get::().unwrap(); +/// let d = res.get_mut::().unwrap(); +/// // The type parameter can be left out if it can be determined from use. +/// let c: Ref = 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::(); +/// # world.init_resource::(); +/// # world.init_resource::(); +/// # +/// let system = ( +/// FilteredResourcesMutParamBuilder::new(|builder| { +/// builder.add_read::().add_write::(); +/// }), +/// 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, res_mut_c: ResMut) { +/// let res_a_2: Ref = filtered.get::().unwrap(); +/// let res_mut_b: Mut = filtered.get_mut::().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::(); +/// # +/// let system = ( +/// FilteredResourcesMutParamBuilder::new(|builder| { +/// builder.add_write::(); +/// }), +/// 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) { } +/// # +/// # world.run_system_once(system); +/// ``` +pub struct FilteredResourcesMut<'w, 's> { + world: UnsafeWorldCell<'w>, + access: &'s Access, + 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, + 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 { + 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(&self) -> bool { + let component_id = self.world.components().resource_id::(); + 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(&self) -> bool { + let component_id = self.world.components().resource_id::(); + 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(&self) -> Option> { + 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> { + 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(&mut self) -> Option> { + // 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> { + // 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(mut self) -> Option> { + // 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> { + // 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(&mut self) -> Option> { + let component_id = self.world.components().resource_id::()?; + // 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::() }) + } + + /// 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> { + 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> = 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, +} + +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 { + &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(&mut self) -> &mut Self { + let component_id = self.world.components.register_resource::(); + 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 { + 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, +} + +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 { + &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(&mut self) -> &mut Self { + let component_id = self.world.components.register_resource::(); + 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(&mut self) -> &mut Self { + let component_id = self.world.components.register_resource::(); + 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 { + self.access + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8a665e51ec..7dae46a97a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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::*;