Skip to content

Commit

Permalink
Add QueryState::get_single_unchecked_manual and its family (#4841)
Browse files Browse the repository at this point in the history
# Objective

- Rebase of #3159.
- Fixes #3156
- add #[inline] to single related functions so that they matches with other function defs

## Solution

* added functions to QueryState
  *  get_single_unchecked_manual
  *  get_single_unchecked
  *  get_single
  *  get_single_mut
  *  single
  *  single_mut
* make Query::get_single use QueryState::get_single_unchecked_manual
* added #[inline]

---

## Changelog

### Added

Functions `QueryState::single`, `QueryState::get_single`, `QueryState::single_mut`, `QueryState::get_single_mut`, `QueryState::get_single_unchecked`, `QueryState::get_single_unchecked_manual`.

### Changed

`QuerySingleError` is now in the `state` module.

## Migration Guide

Change `query::QuerySingleError` to `state::QuerySingleError`


Co-authored-by: 2ne1ugly <chattermin@gmail.com>
Co-authored-by: 2ne1ugly <47616772+2ne1ugly@users.noreply.github.com>
  • Loading branch information
3 people committed May 30, 2022
1 parent e528b63 commit c02beab
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 43 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/query/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, QF: Fetch<'w, State = Q::Stat
filter: QueryFetch<'w, F>,
current_len: usize,
current_index: usize,
phantom: PhantomData<&'w Q>,
phantom: PhantomData<(&'w (), Q)>,
}

impl<'w, 's, Q: WorldQuery, QF, F: WorldQuery> Clone for QueryIterationCursor<'w, 's, Q, QF, F>
Expand Down
151 changes: 151 additions & 0 deletions crates/bevy_ecs/src/query/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,136 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
}
});
}

/// Returns a single immutable query result when there is exactly one entity matching
/// the query.
///
/// This can only be called for read-only queries,
/// see [`single_mut`](Self::single_mut) for write-queries.
///
/// # Panics
///
/// Panics if the number of query results is not exactly one. Use
/// [`get_single`](Self::get_single) to return a `Result` instead of panicking.
#[track_caller]
#[inline]
pub fn single<'w>(&mut self, world: &'w World) -> ROQueryItem<'w, Q> {
self.get_single(world).unwrap()
}

/// Returns a single immutable query result when there is exactly one entity matching
/// the query.
///
/// This can only be called for read-only queries,
/// see [`get_single_mut`](Self::get_single_mut) for write-queries.
///
/// If the number of query results is not exactly one, a [`QuerySingleError`] is returned
/// instead.
#[inline]
pub fn get_single<'w>(
&mut self,
world: &'w World,
) -> Result<ROQueryItem<'w, Q>, QuerySingleError> {
self.update_archetypes(world);

// SAFETY: query is read only
unsafe {
self.get_single_unchecked_manual::<ROQueryFetch<'w, Q>>(
world,
world.last_change_tick(),
world.read_change_tick(),
)
}
}

/// Returns a single mutable query result when there is exactly one entity matching
/// the query.
///
/// # Panics
///
/// Panics if the number of query results is not exactly one. Use
/// [`get_single_mut`](Self::get_single_mut) to return a `Result` instead of panicking.
#[track_caller]
#[inline]
pub fn single_mut<'w>(&mut self, world: &'w mut World) -> QueryItem<'w, Q> {
// SAFETY: query has unique world access
self.get_single_mut(world).unwrap()
}

/// Returns a single mutable query result when there is exactly one entity matching
/// the query.
///
/// If the number of query results is not exactly one, a [`QuerySingleError`] is returned
/// instead.
#[inline]
pub fn get_single_mut<'w>(
&mut self,
world: &'w mut World,
) -> Result<QueryItem<'w, Q>, QuerySingleError> {
self.update_archetypes(world);

// SAFETY: query has unique world access
unsafe {
self.get_single_unchecked_manual::<QueryFetch<'w, Q>>(
world,
world.last_change_tick(),
world.read_change_tick(),
)
}
}

/// Returns a query result when there is exactly one entity matching the query.
///
/// If the number of query results is not exactly one, a [`QuerySingleError`] is returned
/// instead.
///
/// # Safety
///
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
/// have unique access to the components they query.
#[inline]
pub unsafe fn get_single_unchecked<'w>(
&mut self,
world: &'w World,
) -> Result<QueryItem<'w, Q>, QuerySingleError> {
self.update_archetypes(world);

self.get_single_unchecked_manual::<QueryFetch<'w, Q>>(
world,
world.last_change_tick(),
world.read_change_tick(),
)
}

/// Returns a query result when there is exactly one entity matching the query,
/// where the last change and the current change tick are given.
///
/// If the number of query results is not exactly one, a [`QuerySingleError`] is returned
/// instead.
///
/// # Safety
///
/// This does not check for mutable query correctness. To be safe, make sure mutable queries
/// have unique access to the components they query.
#[inline]
pub unsafe fn get_single_unchecked_manual<'w, QF: Fetch<'w, State = Q::State>>(
&self,
world: &'w World,
last_change_tick: u32,
change_tick: u32,
) -> Result<QF::Item, QuerySingleError> {
let mut query = self.iter_unchecked_manual::<QF>(world, last_change_tick, change_tick);
let first = query.next();
let extra = query.next().is_some();

match (first, extra) {
(Some(r), false) => Ok(r),
(None, _) => Err(QuerySingleError::NoEntities(std::any::type_name::<Self>())),
(Some(_), _) => Err(QuerySingleError::MultipleEntities(std::any::type_name::<
Self,
>())),
}
}
}

/// An error that occurs when retrieving a specific [`Entity`]'s query result.
Expand Down Expand Up @@ -1074,3 +1204,24 @@ mod tests {
let _panics = query_state.get_many_mut(&mut world_2, []);
}
}

/// An error that occurs when evaluating a [`QueryState`] as a single expected resulted via
/// [`QueryState::single`] or [`QueryState::single_mut`].
#[derive(Debug)]
pub enum QuerySingleError {
NoEntities(&'static str),
MultipleEntities(&'static str),
}

impl std::error::Error for QuerySingleError {}

impl std::fmt::Display for QuerySingleError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
QuerySingleError::NoEntities(query) => write!(f, "No entities fit the query {}", query),
QuerySingleError::MultipleEntities(query) => {
write!(f, "Multiple entities fit the query {}!", query)
}
}
}
}
58 changes: 16 additions & 42 deletions crates/bevy_ecs/src/system/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
entity::Entity,
query::{
NopFetch, QueryCombinationIter, QueryEntityError, QueryFetch, QueryItem, QueryIter,
QueryState, ROQueryFetch, ROQueryItem, ReadOnlyFetch, WorldQuery,
QuerySingleError, QueryState, ROQueryFetch, ROQueryItem, ReadOnlyFetch, WorldQuery,
},
world::{Mut, World},
};
Expand Down Expand Up @@ -968,7 +968,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::QuerySingleError;
/// # use bevy_ecs::query::QuerySingleError;
/// # #[derive(Component)]
/// # struct PlayerScore(i32);
/// fn player_scoring_system(query: Query<&PlayerScore>) {
Expand All @@ -986,17 +986,14 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// }
/// # bevy_ecs::system::assert_is_system(player_scoring_system);
/// ```
#[inline]
pub fn get_single(&self) -> Result<ROQueryItem<'_, Q>, QuerySingleError> {
let mut query = self.iter();
let first = query.next();
let extra = query.next().is_some();

match (first, extra) {
(Some(r), false) => Ok(r),
(None, _) => Err(QuerySingleError::NoEntities(std::any::type_name::<Self>())),
(Some(_), _) => Err(QuerySingleError::MultipleEntities(std::any::type_name::<
Self,
>())),
unsafe {
self.state.get_single_unchecked_manual::<ROQueryFetch<Q>>(
self.world,
self.last_change_tick,
self.change_tick,
)
}
}

Expand Down Expand Up @@ -1051,17 +1048,14 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// }
/// # bevy_ecs::system::assert_is_system(regenerate_player_health_system);
/// ```
#[inline]
pub fn get_single_mut(&mut self) -> Result<QueryItem<'_, Q>, QuerySingleError> {
let mut query = self.iter_mut();
let first = query.next();
let extra = query.next().is_some();

match (first, extra) {
(Some(r), false) => Ok(r),
(None, _) => Err(QuerySingleError::NoEntities(std::any::type_name::<Self>())),
(Some(_), _) => Err(QuerySingleError::MultipleEntities(std::any::type_name::<
Self,
>())),
unsafe {
self.state.get_single_unchecked_manual::<QueryFetch<Q>>(
self.world,
self.last_change_tick,
self.change_tick,
)
}
}

Expand Down Expand Up @@ -1183,26 +1177,6 @@ impl std::fmt::Display for QueryComponentError {
}
}

/// An error that occurs when evaluating a [`Query`] as a single expected resulted via
/// [`Query::single`] or [`Query::single_mut`].
#[derive(Debug)]
pub enum QuerySingleError {
NoEntities(&'static str),
MultipleEntities(&'static str),
}

impl std::error::Error for QuerySingleError {}

impl std::fmt::Display for QuerySingleError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
QuerySingleError::NoEntities(query) => write!(f, "No entities fit the query {}", query),
QuerySingleError::MultipleEntities(query) => {
write!(f, "Multiple entities fit the query {}!", query)
}
}
}
}
impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F>
where
QueryFetch<'w, Q>: ReadOnlyFetch,
Expand Down

0 comments on commit c02beab

Please sign in to comment.