Skip to content

Commit

Permalink
QuerySingle family of system params (bevyengine#15476)
Browse files Browse the repository at this point in the history
# 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: bevyengine#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 bevyengine#15391.
  • Loading branch information
MiniaczQ authored Sep 28, 2024
1 parent 89925ee commit c148665
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 22 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,17 @@ description = "Run systems only when one or multiple conditions are met"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "fallible_params"
path = "examples/ecs/fallible_params.rs"
doc-scrape-examples = true

[package.metadata.example.fallible_params]
name = "Fallible System Parameters"
description = "Systems are skipped if their parameters cannot be acquired"
category = "ECS (Entity Component System)"
wasm = false

[[example]]
name = "startup_system"
path = "examples/ecs/startup_system.rs"
Expand Down
21 changes: 9 additions & 12 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,10 @@ impl<'w> From<TicksMut<'w>> for Ticks<'w> {
///
/// If you need a unique mutable borrow, use [`ResMut`] instead.
///
/// # Panics
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
/// This will cause systems that use this parameter to be skipped.
///
/// Panics when used as a [`SystemParameter`](crate::system::SystemParam) if the resource does not exist.
///
/// Use `Option<Res<T>>` instead if the resource might not always exist.
/// Use [`Option<Res<T>>`] instead if the resource might not always exist.
pub struct Res<'w, T: ?Sized + Resource> {
pub(crate) value: &'w T,
pub(crate) ticks: Ticks<'w>,
Expand Down Expand Up @@ -622,11 +621,10 @@ impl_debug!(Res<'w, T>, Resource);
///
/// If you need a shared borrow, use [`Res`] instead.
///
/// # Panics
///
/// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist.
/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist.
/// This will cause systems that use this parameter to be skipped.
///
/// Use `Option<ResMut<T>>` instead if the resource might not always exist.
/// Use [`Option<ResMut<T>>`] instead if the resource might not always exist.
pub struct ResMut<'w, T: ?Sized + Resource> {
pub(crate) value: &'w mut T,
pub(crate) ticks: TicksMut<'w>,
Expand Down Expand Up @@ -684,11 +682,10 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
/// over to another thread.
///
/// # Panics
///
/// Panics when used as a `SystemParameter` if the resource does not exist.
/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist.
/// This will cause systems that use this parameter to be skipped.
///
/// Use `Option<NonSendMut<T>>` instead if the resource might not always exist.
/// Use [`Option<NonSendMut<T>>`] instead if the resource might not always exist.
pub struct NonSendMut<'w, T: ?Sized + 'static> {
pub(crate) value: &'w mut T,
pub(crate) ticks: TicksMut<'w>,
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ pub mod prelude {
},
system::{
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, QuerySingle, ReadOnlySystem,
Res, ResMut, Resource, System, SystemIn, SystemInput, SystemParamBuilder,
SystemParamFunction,
},
world::{
Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove,
Expand Down
40 changes: 39 additions & 1 deletion crates/bevy_ecs/src/system/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ use crate::{
},
world::unsafe_world_cell::UnsafeWorldCell,
};
use core::borrow::Borrow;
use core::{
borrow::Borrow,
marker::PhantomData,
ops::{Deref, DerefMut},
};

/// [System parameter] that provides selective access to the [`Component`] data stored in a [`World`].
///
Expand Down Expand Up @@ -1629,3 +1633,37 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
value.transmute_lens_filtered()
}
}

/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`].
///
/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists.
/// This will cause systems that use this parameter to be skipped.
///
/// Use [`Option<QuerySingle<D, F>>`] instead if zero or one matching entities can exist.
///
/// See [`Query`] for more details.
pub struct QuerySingle<'w, D: QueryData, F: QueryFilter = ()> {
pub(crate) item: D::Item<'w>,
pub(crate) _filter: PhantomData<F>,
}

impl<'w, D: QueryData, F: QueryFilter> Deref for QuerySingle<'w, D, F> {
type Target = D::Item<'w>;

fn deref(&self) -> &Self::Target {
&self.item
}
}

impl<'w, D: QueryData, F: QueryFilter> DerefMut for QuerySingle<'w, D, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.item
}
}

impl<'w, D: QueryData, F: QueryFilter> QuerySingle<'w, D, F> {
/// Returns the inner item with ownership.
pub fn into_inner(self) -> D::Item<'w> {
self.item
}
}
4 changes: 4 additions & 0 deletions crates/bevy_ecs/src/system/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ pub trait System: Send + Sync + 'static {
/// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`]
/// should provide their own safety mechanism to prevent undefined behavior.
///
/// This method has to be called directly before [`System::run_unsafe`] with no other (relevant)
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
/// the validity of the param may change.
///
/// # Safety
///
/// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data
Expand Down
156 changes: 149 additions & 7 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ use crate::{
entity::Entities,
query::{
Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter,
QueryState, ReadOnlyQueryData,
QuerySingleError, QueryState, ReadOnlyQueryData,
},
storage::{ResourceData, SparseSetIndex},
system::{Query, SystemMeta},
system::{Query, QuerySingle, SystemMeta},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
};
use bevy_ecs_macros::impl_param_set;
Expand Down Expand Up @@ -227,12 +227,21 @@ pub unsafe trait SystemParam: Sized {
/// The [`world`](UnsafeWorldCell) can only be used to read param's data
/// and world metadata. No data can be written.
///
/// When using system parameters that require `change_tick` you can use
/// [`UnsafeWorldCell::change_tick()`]. Even if this isn't the exact
/// same tick used for [`SystemParam::get_param`], the world access
/// ensures that the queried data will be the same in both calls.
///
/// This method has to be called directly before [`SystemParam::get_param`] with no other (relevant)
/// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior,
/// the validity of the param may change.
///
/// # Safety
///
/// - The passed [`UnsafeWorldCell`] must have read-only access to world data
/// registered in [`init_state`](SystemParam::init_state).
/// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state).
/// - all `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
/// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype).
unsafe fn validate_param(
_state: &Self::State,
_system_meta: &SystemMeta,
Expand Down Expand Up @@ -356,6 +365,140 @@ fn assert_component_access_compatibility(
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");
}

// 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 QuerySingle<'a, D, F>
{
type State = QueryState<D, F>;
type Item<'w, 's> = QuerySingle<'w, D, F>;

fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
Query::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 { Query::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 somewhere elsewhere.
let result =
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
let single =
result.expect("The query was expected to contain exactly one matching entity.");
QuerySingle {
item: single,
_filter: PhantomData,
}
}

#[inline]
unsafe fn validate_param(
state: &Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> bool {
state.validate_world(world.id());
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
// and the query is read only.
let result = unsafe {
state.as_readonly().get_single_unchecked_manual(
world,
system_meta.last_run,
world.change_tick(),
)
};
result.is_ok()
}
}

// 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<QuerySingle<'a, D, F>>
{
type State = QueryState<D, F>;
type Item<'w, 's> = Option<QuerySingle<'w, D, F>>;

fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
QuerySingle::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 { QuerySingle::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.
let result =
unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) };
match result {
Ok(single) => Some(QuerySingle {
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,
) -> bool {
state.validate_world(world.id());
// SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere
// and the query is read only.
let result = unsafe {
state.as_readonly().get_single_unchecked_manual(
world,
system_meta.last_run,
world.change_tick(),
)
};
!matches!(result, Err(QuerySingleError::MultipleEntities(_)))
}
}

// SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam
for QuerySingle<'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<QuerySingle<'a, D, F>>
{
}

/// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access.
///
/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as
Expand Down Expand Up @@ -1172,11 +1315,10 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
/// the scheduler to instead run the system on the main thread so that it doesn't send the resource
/// over to another thread.
///
/// # Panics
///
/// Panics when used as a `SystemParameter` if the resource does not exist.
/// This [`SystemParam`] fails validation if non-send resource doesn't exist.
/// This will cause systems that use this parameter to be skipped.
///
/// Use `Option<NonSend<T>>` instead if the resource might not always exist.
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
pub struct NonSend<'w, T: 'static> {
pub(crate) value: &'w T,
ticks: ComponentTicks,
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ Example | Description
[Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components
[ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS
[Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception
[Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired
[Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick
[Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types
[Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities
Expand Down
Loading

0 comments on commit c148665

Please sign in to comment.