Generic SystemParam
impls for Option
and Result
(#18766)
# Objective Provide a generic `impl SystemParam for Option<P>` that uses system parameter validation. This immediately gives useful impls for params like `EventReader` and `GizmosState` that are defined in terms of `Res`. It also allows third-party system parameters to be usable with `Option`, which was previously impossible due to orphan rules. Note that this is a behavior change for `Option<Single>`. It currently fails validation if there are multiple matching entities, but with this change it will pass validation and produce `None`. Also provide an impl for `Result<P, SystemParamValidationError>`. This allows systems to inspect the error if necessary, either for bubbling it up or for checking the `skipped` flag. Fixes #12634 Fixes #14949 Related to #18516 ## Solution Add generic `SystemParam` impls for `Option` and `Result`, and remove the impls for specific types. Update documentation and `fallible_params` example with the new semantics for `Option<Single>`.
This commit is contained in:
parent
60ea43d01d
commit
9e2bd8ac18
@ -7,7 +7,8 @@ use crate::{
|
||||
query::{QueryData, QueryFilter, QueryState},
|
||||
resource::Resource,
|
||||
system::{
|
||||
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, When,
|
||||
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
|
||||
SystemParamValidationError, When,
|
||||
},
|
||||
world::{
|
||||
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
|
||||
@ -710,6 +711,36 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SystemParamBuilder`] for an [`Option`].
|
||||
#[derive(Clone)]
|
||||
pub struct OptionBuilder<T>(T);
|
||||
|
||||
// SAFETY: `OptionBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `Option<P>`
|
||||
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<Option<P>>
|
||||
for OptionBuilder<B>
|
||||
{
|
||||
fn build(self, world: &mut World, meta: &mut SystemMeta) -> <Option<P> as SystemParam>::State {
|
||||
self.0.build(world, meta)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SystemParamBuilder`] for a [`Result`] of [`SystemParamValidationError`].
|
||||
#[derive(Clone)]
|
||||
pub struct ResultBuilder<T>(T);
|
||||
|
||||
// SAFETY: `ResultBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `Result<P, SystemParamValidationError>`
|
||||
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>>
|
||||
SystemParamBuilder<Result<P, SystemParamValidationError>> for ResultBuilder<B>
|
||||
{
|
||||
fn build(
|
||||
self,
|
||||
world: &mut World,
|
||||
meta: &mut SystemMeta,
|
||||
) -> <Result<P, SystemParamValidationError> as SystemParam>::State {
|
||||
self.0.build(world, meta)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SystemParamBuilder`] for a [`When`].
|
||||
#[derive(Clone)]
|
||||
pub struct WhenBuilder<T>(T);
|
||||
|
@ -477,87 +477,12 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
||||
// this Query conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||
for Option<Single<'a, D, F>>
|
||||
{
|
||||
type State = QueryState<D, F>;
|
||||
type Item<'w, 's> = Option<Single<'w, D, F>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
Single::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
unsafe fn new_archetype(
|
||||
state: &mut Self::State,
|
||||
archetype: &Archetype,
|
||||
system_meta: &mut SystemMeta,
|
||||
) {
|
||||
// SAFETY: Delegate to existing `SystemParam` implementations.
|
||||
unsafe { Single::new_archetype(state, archetype, system_meta) };
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
state.validate_world(world.id());
|
||||
// SAFETY: State ensures that the components it accesses are not accessible elsewhere.
|
||||
// The caller ensures the world matches the one used in init_state.
|
||||
let query = unsafe {
|
||||
state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick)
|
||||
};
|
||||
match query.single_inner() {
|
||||
Ok(single) => Some(Single {
|
||||
item: single,
|
||||
_filter: PhantomData,
|
||||
}),
|
||||
Err(QuerySingleError::NoEntities(_)) => None,
|
||||
Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn validate_param(
|
||||
state: &Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell,
|
||||
) -> Result<(), SystemParamValidationError> {
|
||||
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
|
||||
// and the query is read only.
|
||||
// The caller ensures the world matches the one used in init_state.
|
||||
let query = unsafe {
|
||||
state.query_unchecked_manual_with_ticks(
|
||||
world,
|
||||
system_meta.last_run,
|
||||
world.change_tick(),
|
||||
)
|
||||
};
|
||||
match query.single_inner() {
|
||||
Ok(_) | Err(QuerySingleError::NoEntities(_)) => Ok(()),
|
||||
Err(QuerySingleError::MultipleEntities(_)) => Err(
|
||||
SystemParamValidationError::skipped::<Self>("Multiple matching entities"),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
|
||||
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
|
||||
for Single<'a, D, F>
|
||||
{
|
||||
}
|
||||
|
||||
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
|
||||
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
|
||||
for Option<Single<'a, D, F>>
|
||||
{
|
||||
}
|
||||
|
||||
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
|
||||
// this Query conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||
@ -931,40 +856,6 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Only reads a single World resource
|
||||
unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option<Res<'a, T>> {}
|
||||
|
||||
// SAFETY: this impl defers to `Res`, which initializes and validates the correct world access.
|
||||
unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
|
||||
type State = ComponentId;
|
||||
type Item<'w, 's> = Option<Res<'w, T>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
Res::<T>::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.get_resource_with_ticks(component_id)
|
||||
.map(|(ptr, ticks, caller)| Res {
|
||||
value: ptr.deref(),
|
||||
ticks: Ticks {
|
||||
added: ticks.added.deref(),
|
||||
changed: ticks.changed.deref(),
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
changed_by: caller.map(|caller| caller.deref()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res
|
||||
// conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
||||
@ -1045,37 +936,6 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access.
|
||||
unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
|
||||
type State = ComponentId;
|
||||
type Item<'w, 's> = Option<ResMut<'w, T>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
ResMut::<T>::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.get_resource_mut_by_id(component_id)
|
||||
.map(|value| ResMut {
|
||||
value: value.value.deref_mut::<T>(),
|
||||
ticks: TicksMut {
|
||||
added: value.ticks.added,
|
||||
changed: value.ticks.changed,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
changed_by: value.changed_by,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: only reads world
|
||||
unsafe impl<'w> ReadOnlySystemParam for &'w World {}
|
||||
|
||||
@ -1644,37 +1504,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Only reads a single World non-send resource
|
||||
unsafe impl<T: 'static> ReadOnlySystemParam for Option<NonSend<'_, T>> {}
|
||||
|
||||
// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access.
|
||||
unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
|
||||
type State = ComponentId;
|
||||
type Item<'w, 's> = Option<NonSend<'w, T>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
NonSend::<T>::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.get_non_send_with_ticks(component_id)
|
||||
.map(|(ptr, ticks, caller)| NonSend {
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.read(),
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
changed_by: caller.map(|caller| caller.deref()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this
|
||||
// NonSendMut conflicts with any prior access, a panic will occur.
|
||||
unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
||||
@ -1753,32 +1582,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access.
|
||||
unsafe impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
|
||||
type State = ComponentId;
|
||||
type Item<'w, 's> = Option<NonSendMut<'w, T>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
NonSendMut::<T>::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.get_non_send_with_ticks(component_id)
|
||||
.map(|(ptr, ticks, caller)| NonSendMut {
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
||||
changed_by: caller.map(|caller| caller.deref_mut()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Only reads World archetypes
|
||||
unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {}
|
||||
|
||||
@ -1916,6 +1719,91 @@ unsafe impl SystemParam for SystemChangeTick {
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
|
||||
unsafe impl<T: SystemParam> SystemParam for Option<T> {
|
||||
type State = T::State;
|
||||
|
||||
type Item<'world, 'state> = Option<T::Item<'world, 'state>>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
T::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'world, 'state>(
|
||||
state: &'state mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'world>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'world, 'state> {
|
||||
T::validate_param(state, system_meta, world)
|
||||
.ok()
|
||||
.map(|()| T::get_param(state, system_meta, world, change_tick))
|
||||
}
|
||||
|
||||
unsafe fn new_archetype(
|
||||
state: &mut Self::State,
|
||||
archetype: &Archetype,
|
||||
system_meta: &mut SystemMeta,
|
||||
) {
|
||||
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
|
||||
unsafe { T::new_archetype(state, archetype, system_meta) };
|
||||
}
|
||||
|
||||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
T::apply(state, system_meta, world);
|
||||
}
|
||||
|
||||
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
|
||||
T::queue(state, system_meta, world);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
|
||||
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for Option<T> {}
|
||||
|
||||
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
|
||||
unsafe impl<T: SystemParam> SystemParam for Result<T, SystemParamValidationError> {
|
||||
type State = T::State;
|
||||
|
||||
type Item<'world, 'state> = Result<T::Item<'world, 'state>, SystemParamValidationError>;
|
||||
|
||||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
|
||||
T::init_state(world, system_meta)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'world, 'state>(
|
||||
state: &'state mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'world>,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'world, 'state> {
|
||||
T::validate_param(state, system_meta, world)
|
||||
.map(|()| T::get_param(state, system_meta, world, change_tick))
|
||||
}
|
||||
|
||||
unsafe fn new_archetype(
|
||||
state: &mut Self::State,
|
||||
archetype: &Archetype,
|
||||
system_meta: &mut SystemMeta,
|
||||
) {
|
||||
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
|
||||
unsafe { T::new_archetype(state, archetype, system_meta) };
|
||||
}
|
||||
|
||||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
T::apply(state, system_meta, world);
|
||||
}
|
||||
|
||||
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
|
||||
T::queue(state, system_meta, world);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
|
||||
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for Result<T, SystemParamValidationError> {}
|
||||
|
||||
/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -131,13 +131,12 @@ fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<
|
||||
}
|
||||
|
||||
/// System that moves the player, causing them to track a single enemy.
|
||||
/// The player will search for enemies if there are none.
|
||||
/// If there is one, player will track it.
|
||||
/// If there are too many enemies, the player will cease all action (the system will not run).
|
||||
/// If there is exactly one, player will track it.
|
||||
/// Otherwise, the player will search for enemies.
|
||||
fn track_targets(
|
||||
// `Single` ensures the system runs ONLY when exactly one matching entity exists.
|
||||
mut player: Single<(&mut Transform, &Player)>,
|
||||
// `Option<Single>` ensures that the system runs ONLY when zero or one matching entity exists.
|
||||
// `Option<Single>` never prevents the system from running, but will be `None` if there is not exactly one matching entity.
|
||||
enemy: Option<Single<&Transform, (With<Enemy>, Without<Player>)>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
|
28
release-content/migration-guides/generic-option-parameter.md
Normal file
28
release-content/migration-guides/generic-option-parameter.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Generic `Option` Parameter
|
||||
pull_requests: [18766]
|
||||
---
|
||||
|
||||
`Option<Single<D, F>>` will now resolve to `None` if there are multiple entities matching the query.
|
||||
Previously, it would only resolve to `None` if there were no entities, and would skip the system if there were multiple.
|
||||
|
||||
We have introduced a blanket `impl SystemParam for Option` that resolves to `None` if the parameter is invalid.
|
||||
This allows third-party system parameters to work with `Option`, and makes the behavior more consistent.
|
||||
|
||||
If you want a system to run when there are no matching entities but skip when there are multiple,
|
||||
you will need to use `Query<D, F>` and call `single()` yourself.
|
||||
|
||||
```rust
|
||||
// 0.16
|
||||
fn my_system(single: Option<Single<&Player>>) {
|
||||
}
|
||||
|
||||
// 0.17
|
||||
fn my_system(query: Query<&Player>) {
|
||||
let result = query.single();
|
||||
if matches!(r, Err(QuerySingleError(MultipleEntities(_)))) {
|
||||
return;
|
||||
}
|
||||
let single: Option<&Player> = r.ok();
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue
Block a user