Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add QueryState::get_single_unchecked_manual and its family #4841

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change shouldn't be needed; what happens if you remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alice-i-cecile Compilation error that I mentioned in Discord. @BoxyUwU suggested this fix for it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for future context &'w Q requires Q: 'w to hold which is unnecessary, this change makes that not required

}

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 @@ -940,6 +940,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 @@ -1070,3 +1200,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