Skip to content

Commit c148665

Browse files
authored
QuerySingle family of system params (#15476)
# Objective Add the following system params: - `QuerySingle<D, F>` - Valid if only one matching entity exists, - `Option<QuerySingle<D, F>>` - Valid if zero or one matching entity exists. As @chescock pointed out, we don't need `Mut` variants. Fixes: #15264 ## Solution Implement the type and both variants of system params. Also implement `ReadOnlySystemParam` for readonly queries. Added a new ECS example `fallible_params` which showcases `SingleQuery` usage. In the future we might want to add `NonEmptyQuery`, `NonEmptyEventReader` and `Res` to it (or maybe just stop at mentioning it). ## Testing Tested with the example. There is a lot of warning spam so we might want to implement #15391.
1 parent 89925ee commit c148665

File tree

8 files changed

+363
-22
lines changed

8 files changed

+363
-22
lines changed

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -1892,6 +1892,17 @@ description = "Run systems only when one or multiple conditions are met"
18921892
category = "ECS (Entity Component System)"
18931893
wasm = false
18941894

1895+
[[example]]
1896+
name = "fallible_params"
1897+
path = "examples/ecs/fallible_params.rs"
1898+
doc-scrape-examples = true
1899+
1900+
[package.metadata.example.fallible_params]
1901+
name = "Fallible System Parameters"
1902+
description = "Systems are skipped if their parameters cannot be acquired"
1903+
category = "ECS (Entity Component System)"
1904+
wasm = false
1905+
18951906
[[example]]
18961907
name = "startup_system"
18971908
path = "examples/ecs/startup_system.rs"

crates/bevy_ecs/src/change_detection.rs

+9-12
Original file line numberDiff line numberDiff line change
@@ -543,11 +543,10 @@ impl<'w> From<TicksMut<'w>> for Ticks<'w> {
543543
///
544544
/// If you need a unique mutable borrow, use [`ResMut`] instead.
545545
///
546-
/// # Panics
546+
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
547+
/// This will cause systems that use this parameter to be skipped.
547548
///
548-
/// Panics when used as a [`SystemParameter`](crate::system::SystemParam) if the resource does not exist.
549-
///
550-
/// Use `Option<Res<T>>` instead if the resource might not always exist.
549+
/// Use [`Option<Res<T>>`] instead if the resource might not always exist.
551550
pub struct Res<'w, T: ?Sized + Resource> {
552551
pub(crate) value: &'w T,
553552
pub(crate) ticks: Ticks<'w>,
@@ -622,11 +621,10 @@ impl_debug!(Res<'w, T>, Resource);
622621
///
623622
/// If you need a shared borrow, use [`Res`] instead.
624623
///
625-
/// # Panics
626-
///
627-
/// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist.
624+
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
625+
/// This will cause systems that use this parameter to be skipped.
628626
///
629-
/// Use `Option<ResMut<T>>` instead if the resource might not always exist.
627+
/// Use [`Option<ResMut<T>>`] instead if the resource might not always exist.
630628
pub struct ResMut<'w, T: ?Sized + Resource> {
631629
pub(crate) value: &'w mut T,
632630
pub(crate) ticks: TicksMut<'w>,
@@ -684,11 +682,10 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
684682
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
685683
/// over to another thread.
686684
///
687-
/// # Panics
688-
///
689-
/// Panics when used as a `SystemParameter` if the resource does not exist.
685+
/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist.
686+
/// This will cause systems that use this parameter to be skipped.
690687
///
691-
/// Use `Option<NonSendMut<T>>` instead if the resource might not always exist.
688+
/// Use [`Option<NonSendMut<T>>`] instead if the resource might not always exist.
692689
pub struct NonSendMut<'w, T: ?Sized + 'static> {
693690
pub(crate) value: &'w mut T,
694691
pub(crate) ticks: TicksMut<'w>,

crates/bevy_ecs/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ pub mod prelude {
5858
},
5959
system::{
6060
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
61-
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
62-
Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
61+
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, QuerySingle, ReadOnlySystem,
62+
Res, ResMut, Resource, System, SystemIn, SystemInput, SystemParamBuilder,
63+
SystemParamFunction,
6364
},
6465
world::{
6566
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,

crates/bevy_ecs/src/system/query.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ use crate::{
88
},
99
world::unsafe_world_cell::UnsafeWorldCell,
1010
};
11-
use core::borrow::Borrow;
11+
use core::{
12+
borrow::Borrow,
13+
marker::PhantomData,
14+
ops::{Deref, DerefMut},
15+
};
1216

1317
/// [System parameter] that provides selective access to the [`Component`] data stored in a [`World`].
1418
///
@@ -1629,3 +1633,37 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
16291633
value.transmute_lens_filtered()
16301634
}
16311635
}
1636+
1637+
/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`].
1638+
///
1639+
/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists.
1640+
/// This will cause systems that use this parameter to be skipped.
1641+
///
1642+
/// Use [`Option<QuerySingle<D, F>>`] instead if zero or one matching entities can exist.
1643+
///
1644+
/// See [`Query`] for more details.
1645+
pub struct QuerySingle<'w, D: QueryData, F: QueryFilter = ()> {
1646+
pub(crate) item: D::Item<'w>,
1647+
pub(crate) _filter: PhantomData<F>,
1648+
}
1649+
1650+
impl<'w, D: QueryData, F: QueryFilter> Deref for QuerySingle<'w, D, F> {
1651+
type Target = D::Item<'w>;
1652+
1653+
fn deref(&self) -> &Self::Target {
1654+
&self.item
1655+
}
1656+
}
1657+
1658+
impl<'w, D: QueryData, F: QueryFilter> DerefMut for QuerySingle<'w, D, F> {
1659+
fn deref_mut(&mut self) -> &mut Self::Target {
1660+
&mut self.item
1661+
}
1662+
}
1663+
1664+
impl<'w, D: QueryData, F: QueryFilter> QuerySingle<'w, D, F> {
1665+
/// Returns the inner item with ownership.
1666+
pub fn into_inner(self) -> D::Item<'w> {
1667+
self.item
1668+
}
1669+
}

crates/bevy_ecs/src/system/system.rs

+4
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ pub trait System: Send + Sync + 'static {
104104
/// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`]
105105
/// should provide their own safety mechanism to prevent undefined behavior.
106106
///
107+
/// This method has to be called directly before [`System::run_unsafe`] with no other (relevant)
108+
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
109+
/// the validity of the param may change.
110+
///
107111
/// # Safety
108112
///
109113
/// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data

crates/bevy_ecs/src/system/system_param.rs

+149-7
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use crate::{
77
entity::Entities,
88
query::{
99
Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter,
10-
QueryState, ReadOnlyQueryData,
10+
QuerySingleError, QueryState, ReadOnlyQueryData,
1111
},
1212
storage::{ResourceData, SparseSetIndex},
13-
system::{Query, SystemMeta},
13+
system::{Query, QuerySingle, SystemMeta},
1414
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
1515
};
1616
use bevy_ecs_macros::impl_param_set;
@@ -227,12 +227,21 @@ pub unsafe trait SystemParam: Sized {
227227
/// The [`world`](UnsafeWorldCell) can only be used to read param's data
228228
/// and world metadata. No data can be written.
229229
///
230+
/// When using system parameters that require `change_tick` you can use
231+
/// [`UnsafeWorldCell::change_tick()`]. Even if this isn't the exact
232+
/// same tick used for [`SystemParam::get_param`], the world access
233+
/// ensures that the queried data will be the same in both calls.
234+
///
235+
/// This method has to be called directly before [`SystemParam::get_param`] with no other (relevant)
236+
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
237+
/// the validity of the param may change.
238+
///
230239
/// # Safety
231240
///
232241
/// - The passed [`UnsafeWorldCell`] must have read-only access to world data
233242
/// registered in [`init_state`](SystemParam::init_state).
234243
/// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state).
235-
/// - all `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
244+
/// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
236245
unsafe fn validate_param(
237246
_state: &Self::State,
238247
_system_meta: &SystemMeta,
@@ -356,6 +365,140 @@ fn assert_component_access_compatibility(
356365
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");
357366
}
358367

368+
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
369+
// this Query conflicts with any prior access, a panic will occur.
370+
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
371+
for QuerySingle<'a, D, F>
372+
{
373+
type State = QueryState<D, F>;
374+
type Item<'w, 's> = QuerySingle<'w, D, F>;
375+
376+
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
377+
Query::init_state(world, system_meta)
378+
}
379+
380+
unsafe fn new_archetype(
381+
state: &mut Self::State,
382+
archetype: &Archetype,
383+
system_meta: &mut SystemMeta,
384+
) {
385+
// SAFETY: Delegate to existing `SystemParam` implementations.
386+
unsafe { Query::new_archetype(state, archetype, system_meta) };
387+
}
388+
389+
#[inline]
390+
unsafe fn get_param<'w, 's>(
391+
state: &'s mut Self::State,
392+
system_meta: &SystemMeta,
393+
world: UnsafeWorldCell<'w>,
394+
change_tick: Tick,
395+
) -> Self::Item<'w, 's> {
396+
state.validate_world(world.id());
397+
// SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere.
398+
let result =
399+
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
400+
let single =
401+
result.expect("The query was expected to contain exactly one matching entity.");
402+
QuerySingle {
403+
item: single,
404+
_filter: PhantomData,
405+
}
406+
}
407+
408+
#[inline]
409+
unsafe fn validate_param(
410+
state: &Self::State,
411+
system_meta: &SystemMeta,
412+
world: UnsafeWorldCell,
413+
) -> bool {
414+
state.validate_world(world.id());
415+
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
416+
// and the query is read only.
417+
let result = unsafe {
418+
state.as_readonly().get_single_unchecked_manual(
419+
world,
420+
system_meta.last_run,
421+
world.change_tick(),
422+
)
423+
};
424+
result.is_ok()
425+
}
426+
}
427+
428+
// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
429+
// this Query conflicts with any prior access, a panic will occur.
430+
unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
431+
for Option<QuerySingle<'a, D, F>>
432+
{
433+
type State = QueryState<D, F>;
434+
type Item<'w, 's> = Option<QuerySingle<'w, D, F>>;
435+
436+
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
437+
QuerySingle::init_state(world, system_meta)
438+
}
439+
440+
unsafe fn new_archetype(
441+
state: &mut Self::State,
442+
archetype: &Archetype,
443+
system_meta: &mut SystemMeta,
444+
) {
445+
// SAFETY: Delegate to existing `SystemParam` implementations.
446+
unsafe { QuerySingle::new_archetype(state, archetype, system_meta) };
447+
}
448+
449+
#[inline]
450+
unsafe fn get_param<'w, 's>(
451+
state: &'s mut Self::State,
452+
system_meta: &SystemMeta,
453+
world: UnsafeWorldCell<'w>,
454+
change_tick: Tick,
455+
) -> Self::Item<'w, 's> {
456+
state.validate_world(world.id());
457+
// SAFETY: State ensures that the components it accesses are not accessible elsewhere.
458+
let result =
459+
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
460+
match result {
461+
Ok(single) => Some(QuerySingle {
462+
item: single,
463+
_filter: PhantomData,
464+
}),
465+
Err(QuerySingleError::NoEntities(_)) => None,
466+
Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e),
467+
}
468+
}
469+
470+
#[inline]
471+
unsafe fn validate_param(
472+
state: &Self::State,
473+
system_meta: &SystemMeta,
474+
world: UnsafeWorldCell,
475+
) -> bool {
476+
state.validate_world(world.id());
477+
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
478+
// and the query is read only.
479+
let result = unsafe {
480+
state.as_readonly().get_single_unchecked_manual(
481+
world,
482+
system_meta.last_run,
483+
world.change_tick(),
484+
)
485+
};
486+
!matches!(result, Err(QuerySingleError::MultipleEntities(_)))
487+
}
488+
}
489+
490+
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
491+
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
492+
for QuerySingle<'a, D, F>
493+
{
494+
}
495+
496+
// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
497+
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
498+
for Option<QuerySingle<'a, D, F>>
499+
{
500+
}
501+
359502
/// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access.
360503
///
361504
/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as
@@ -1172,11 +1315,10 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
11721315
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
11731316
/// over to another thread.
11741317
///
1175-
/// # Panics
1176-
///
1177-
/// Panics when used as a `SystemParameter` if the resource does not exist.
1318+
/// This [`SystemParam`] fails validation if non-send resource doesn't exist.
1319+
/// This will cause systems that use this parameter to be skipped.
11781320
///
1179-
/// Use `Option<NonSend<T>>` instead if the resource might not always exist.
1321+
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
11801322
pub struct NonSend<'w, T: 'static> {
11811323
pub(crate) value: &'w T,
11821324
ticks: ComponentTicks,

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ Example | Description
288288
[Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components
289289
[ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS
290290
[Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception
291+
[Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired
291292
[Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
292293
[Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
293294
[Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities

0 commit comments

Comments
 (0)