Skip to content

Commit 9e2bd8a

Browse files
authored
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>`.
1 parent 60ea43d commit 9e2bd8a

File tree

4 files changed

+148
-202
lines changed

4 files changed

+148
-202
lines changed

crates/bevy_ecs/src/system/builder.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::{
77
query::{QueryData, QueryFilter, QueryState},
88
resource::Resource,
99
system::{
10-
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, When,
10+
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
11+
SystemParamValidationError, When,
1112
},
1213
world::{
1314
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
@@ -710,6 +711,36 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
710711
}
711712
}
712713

714+
/// A [`SystemParamBuilder`] for an [`Option`].
715+
#[derive(Clone)]
716+
pub struct OptionBuilder<T>(T);
717+
718+
// SAFETY: `OptionBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `Option<P>`
719+
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<Option<P>>
720+
for OptionBuilder<B>
721+
{
722+
fn build(self, world: &mut World, meta: &mut SystemMeta) -> <Option<P> as SystemParam>::State {
723+
self.0.build(world, meta)
724+
}
725+
}
726+
727+
/// A [`SystemParamBuilder`] for a [`Result`] of [`SystemParamValidationError`].
728+
#[derive(Clone)]
729+
pub struct ResultBuilder<T>(T);
730+
731+
// SAFETY: `ResultBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `Result<P, SystemParamValidationError>`
732+
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>>
733+
SystemParamBuilder<Result<P, SystemParamValidationError>> for ResultBuilder<B>
734+
{
735+
fn build(
736+
self,
737+
world: &mut World,
738+
meta: &mut SystemMeta,
739+
) -> <Result<P, SystemParamValidationError> as SystemParam>::State {
740+
self.0.build(world, meta)
741+
}
742+
}
743+
713744
/// A [`SystemParamBuilder`] for a [`When`].
714745
#[derive(Clone)]
715746
pub struct WhenBuilder<T>(T);

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 85 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -477,87 +477,12 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo
477477
}
478478
}
479479

480-
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
481-
// this Query conflicts with any prior access, a panic will occur.
482-
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
483-
for Option<Single<'a, D, F>>
484-
{
485-
type State = QueryState<D, F>;
486-
type Item<'w, 's> = Option<Single<'w, D, F>>;
487-
488-
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
489-
Single::init_state(world, system_meta)
490-
}
491-
492-
unsafe fn new_archetype(
493-
state: &mut Self::State,
494-
archetype: &Archetype,
495-
system_meta: &mut SystemMeta,
496-
) {
497-
// SAFETY: Delegate to existing `SystemParam` implementations.
498-
unsafe { Single::new_archetype(state, archetype, system_meta) };
499-
}
500-
501-
#[inline]
502-
unsafe fn get_param<'w, 's>(
503-
state: &'s mut Self::State,
504-
system_meta: &SystemMeta,
505-
world: UnsafeWorldCell<'w>,
506-
change_tick: Tick,
507-
) -> Self::Item<'w, 's> {
508-
state.validate_world(world.id());
509-
// SAFETY: State ensures that the components it accesses are not accessible elsewhere.
510-
// The caller ensures the world matches the one used in init_state.
511-
let query = unsafe {
512-
state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick)
513-
};
514-
match query.single_inner() {
515-
Ok(single) => Some(Single {
516-
item: single,
517-
_filter: PhantomData,
518-
}),
519-
Err(QuerySingleError::NoEntities(_)) => None,
520-
Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e),
521-
}
522-
}
523-
524-
#[inline]
525-
unsafe fn validate_param(
526-
state: &Self::State,
527-
system_meta: &SystemMeta,
528-
world: UnsafeWorldCell,
529-
) -> Result<(), SystemParamValidationError> {
530-
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
531-
// and the query is read only.
532-
// The caller ensures the world matches the one used in init_state.
533-
let query = unsafe {
534-
state.query_unchecked_manual_with_ticks(
535-
world,
536-
system_meta.last_run,
537-
world.change_tick(),
538-
)
539-
};
540-
match query.single_inner() {
541-
Ok(_) | Err(QuerySingleError::NoEntities(_)) => Ok(()),
542-
Err(QuerySingleError::MultipleEntities(_)) => Err(
543-
SystemParamValidationError::skipped::<Self>("Multiple matching entities"),
544-
),
545-
}
546-
}
547-
}
548-
549480
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
550481
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
551482
for Single<'a, D, F>
552483
{
553484
}
554485

555-
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
556-
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
557-
for Option<Single<'a, D, F>>
558-
{
559-
}
560-
561486
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
562487
// this Query conflicts with any prior access, a panic will occur.
563488
unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
@@ -931,40 +856,6 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
931856
}
932857
}
933858

934-
// SAFETY: Only reads a single World resource
935-
unsafe impl<'a, T: Resource> ReadOnlySystemParam for Option<Res<'a, T>> {}
936-
937-
// SAFETY: this impl defers to `Res`, which initializes and validates the correct world access.
938-
unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
939-
type State = ComponentId;
940-
type Item<'w, 's> = Option<Res<'w, T>>;
941-
942-
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
943-
Res::<T>::init_state(world, system_meta)
944-
}
945-
946-
#[inline]
947-
unsafe fn get_param<'w, 's>(
948-
&mut component_id: &'s mut Self::State,
949-
system_meta: &SystemMeta,
950-
world: UnsafeWorldCell<'w>,
951-
change_tick: Tick,
952-
) -> Self::Item<'w, 's> {
953-
world
954-
.get_resource_with_ticks(component_id)
955-
.map(|(ptr, ticks, caller)| Res {
956-
value: ptr.deref(),
957-
ticks: Ticks {
958-
added: ticks.added.deref(),
959-
changed: ticks.changed.deref(),
960-
last_run: system_meta.last_run,
961-
this_run: change_tick,
962-
},
963-
changed_by: caller.map(|caller| caller.deref()),
964-
})
965-
}
966-
}
967-
968859
// SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res
969860
// conflicts with any prior access, a panic will occur.
970861
unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
@@ -1045,37 +936,6 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
1045936
}
1046937
}
1047938

1048-
// SAFETY: this impl defers to `ResMut`, which initializes and validates the correct world access.
1049-
unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
1050-
type State = ComponentId;
1051-
type Item<'w, 's> = Option<ResMut<'w, T>>;
1052-
1053-
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1054-
ResMut::<T>::init_state(world, system_meta)
1055-
}
1056-
1057-
#[inline]
1058-
unsafe fn get_param<'w, 's>(
1059-
&mut component_id: &'s mut Self::State,
1060-
system_meta: &SystemMeta,
1061-
world: UnsafeWorldCell<'w>,
1062-
change_tick: Tick,
1063-
) -> Self::Item<'w, 's> {
1064-
world
1065-
.get_resource_mut_by_id(component_id)
1066-
.map(|value| ResMut {
1067-
value: value.value.deref_mut::<T>(),
1068-
ticks: TicksMut {
1069-
added: value.ticks.added,
1070-
changed: value.ticks.changed,
1071-
last_run: system_meta.last_run,
1072-
this_run: change_tick,
1073-
},
1074-
changed_by: value.changed_by,
1075-
})
1076-
}
1077-
}
1078-
1079939
/// SAFETY: only reads world
1080940
unsafe impl<'w> ReadOnlySystemParam for &'w World {}
1081941

@@ -1644,37 +1504,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
16441504
}
16451505
}
16461506

1647-
// SAFETY: Only reads a single World non-send resource
1648-
unsafe impl<T: 'static> ReadOnlySystemParam for Option<NonSend<'_, T>> {}
1649-
1650-
// SAFETY: this impl defers to `NonSend`, which initializes and validates the correct world access.
1651-
unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
1652-
type State = ComponentId;
1653-
type Item<'w, 's> = Option<NonSend<'w, T>>;
1654-
1655-
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1656-
NonSend::<T>::init_state(world, system_meta)
1657-
}
1658-
1659-
#[inline]
1660-
unsafe fn get_param<'w, 's>(
1661-
&mut component_id: &'s mut Self::State,
1662-
system_meta: &SystemMeta,
1663-
world: UnsafeWorldCell<'w>,
1664-
change_tick: Tick,
1665-
) -> Self::Item<'w, 's> {
1666-
world
1667-
.get_non_send_with_ticks(component_id)
1668-
.map(|(ptr, ticks, caller)| NonSend {
1669-
value: ptr.deref(),
1670-
ticks: ticks.read(),
1671-
last_run: system_meta.last_run,
1672-
this_run: change_tick,
1673-
changed_by: caller.map(|caller| caller.deref()),
1674-
})
1675-
}
1676-
}
1677-
16781507
// SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this
16791508
// NonSendMut conflicts with any prior access, a panic will occur.
16801509
unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
@@ -1753,32 +1582,6 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
17531582
}
17541583
}
17551584

1756-
// SAFETY: this impl defers to `NonSendMut`, which initializes and validates the correct world access.
1757-
unsafe impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
1758-
type State = ComponentId;
1759-
type Item<'w, 's> = Option<NonSendMut<'w, T>>;
1760-
1761-
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1762-
NonSendMut::<T>::init_state(world, system_meta)
1763-
}
1764-
1765-
#[inline]
1766-
unsafe fn get_param<'w, 's>(
1767-
&mut component_id: &'s mut Self::State,
1768-
system_meta: &SystemMeta,
1769-
world: UnsafeWorldCell<'w>,
1770-
change_tick: Tick,
1771-
) -> Self::Item<'w, 's> {
1772-
world
1773-
.get_non_send_with_ticks(component_id)
1774-
.map(|(ptr, ticks, caller)| NonSendMut {
1775-
value: ptr.assert_unique().deref_mut(),
1776-
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
1777-
changed_by: caller.map(|caller| caller.deref_mut()),
1778-
})
1779-
}
1780-
}
1781-
17821585
// SAFETY: Only reads World archetypes
17831586
unsafe impl<'a> ReadOnlySystemParam for &'a Archetypes {}
17841587

@@ -1916,6 +1719,91 @@ unsafe impl SystemParam for SystemChangeTick {
19161719
}
19171720
}
19181721

1722+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
1723+
unsafe impl<T: SystemParam> SystemParam for Option<T> {
1724+
type State = T::State;
1725+
1726+
type Item<'world, 'state> = Option<T::Item<'world, 'state>>;
1727+
1728+
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1729+
T::init_state(world, system_meta)
1730+
}
1731+
1732+
#[inline]
1733+
unsafe fn get_param<'world, 'state>(
1734+
state: &'state mut Self::State,
1735+
system_meta: &SystemMeta,
1736+
world: UnsafeWorldCell<'world>,
1737+
change_tick: Tick,
1738+
) -> Self::Item<'world, 'state> {
1739+
T::validate_param(state, system_meta, world)
1740+
.ok()
1741+
.map(|()| T::get_param(state, system_meta, world, change_tick))
1742+
}
1743+
1744+
unsafe fn new_archetype(
1745+
state: &mut Self::State,
1746+
archetype: &Archetype,
1747+
system_meta: &mut SystemMeta,
1748+
) {
1749+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
1750+
unsafe { T::new_archetype(state, archetype, system_meta) };
1751+
}
1752+
1753+
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
1754+
T::apply(state, system_meta, world);
1755+
}
1756+
1757+
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
1758+
T::queue(state, system_meta, world);
1759+
}
1760+
}
1761+
1762+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
1763+
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for Option<T> {}
1764+
1765+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
1766+
unsafe impl<T: SystemParam> SystemParam for Result<T, SystemParamValidationError> {
1767+
type State = T::State;
1768+
1769+
type Item<'world, 'state> = Result<T::Item<'world, 'state>, SystemParamValidationError>;
1770+
1771+
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1772+
T::init_state(world, system_meta)
1773+
}
1774+
1775+
#[inline]
1776+
unsafe fn get_param<'world, 'state>(
1777+
state: &'state mut Self::State,
1778+
system_meta: &SystemMeta,
1779+
world: UnsafeWorldCell<'world>,
1780+
change_tick: Tick,
1781+
) -> Self::Item<'world, 'state> {
1782+
T::validate_param(state, system_meta, world)
1783+
.map(|()| T::get_param(state, system_meta, world, change_tick))
1784+
}
1785+
1786+
unsafe fn new_archetype(
1787+
state: &mut Self::State,
1788+
archetype: &Archetype,
1789+
system_meta: &mut SystemMeta,
1790+
) {
1791+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
1792+
unsafe { T::new_archetype(state, archetype, system_meta) };
1793+
}
1794+
1795+
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
1796+
T::apply(state, system_meta, world);
1797+
}
1798+
1799+
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
1800+
T::queue(state, system_meta, world);
1801+
}
1802+
}
1803+
1804+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
1805+
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for Result<T, SystemParamValidationError> {}
1806+
19191807
/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid.
19201808
///
19211809
/// # Example

examples/ecs/fallible_params.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,12 @@ fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<
131131
}
132132

133133
/// System that moves the player, causing them to track a single enemy.
134-
/// The player will search for enemies if there are none.
135-
/// If there is one, player will track it.
136-
/// If there are too many enemies, the player will cease all action (the system will not run).
134+
/// If there is exactly one, player will track it.
135+
/// Otherwise, the player will search for enemies.
137136
fn track_targets(
138137
// `Single` ensures the system runs ONLY when exactly one matching entity exists.
139138
mut player: Single<(&mut Transform, &Player)>,
140-
// `Option<Single>` ensures that the system runs ONLY when zero or one matching entity exists.
139+
// `Option<Single>` never prevents the system from running, but will be `None` if there is not exactly one matching entity.
141140
enemy: Option<Single<&Transform, (With<Enemy>, Without<Player>)>>,
142141
time: Res<Time>,
143142
) {

0 commit comments

Comments
 (0)