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
					
				| @ -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 New Issue
	
	Block a user
	 Chris Russell
						Chris Russell