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] - Query::get_unique #1263

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 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
58 changes: 57 additions & 1 deletion crates/bevy_ecs/src/system/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
world::{Mut, World},
};
use bevy_tasks::TaskPool;
use std::any::TypeId;
use std::{any::TypeId, fmt::Debug};
use thiserror::Error;

/// Provides scoped access to a World according to a given [WorldQuery] and query filter
Expand Down Expand Up @@ -212,6 +212,42 @@ where
Err(QueryComponentError::MissingWriteAccess)
}
}

pub fn single(&self) -> Result<<Q::Fetch as Fetch<'_>>::Item, UniqueQueryError<'_, Q>>
where
Q::Fetch: ReadOnlyFetch,
{
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(UniqueQueryError::NoEntities(std::any::type_name::<Self>())),
(Some(r), _) => Err(UniqueQueryError::MultipleEntities {
result: r,
query_name: std::any::type_name::<Self>(),
}),
}
}

/// See [`Query::single`]
pub fn single_mut(
&mut self,
) -> Result<<Q::Fetch as Fetch<'_>>::Item, UniqueQueryError<'_, Q>> {
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(UniqueQueryError::NoEntities(std::any::type_name::<Self>())),
(Some(r), _) => Err(UniqueQueryError::MultipleEntities {
result: r,
query_name: std::any::type_name::<Self>(),
}),
}
}
}

/// An error that occurs when retrieving a specific [Entity]'s component from a [Query]
Expand All @@ -226,3 +262,23 @@ pub enum QueryComponentError {
#[error("The requested entity does not exist.")]
NoSuchEntity,
}

#[derive(Error)]
pub enum UniqueQueryError<'a, Q: WorldQuery> {
TheRawMeatball marked this conversation as resolved.
Show resolved Hide resolved
#[error("No entities fit the query {0}")]
NoEntities(&'static str),
Copy link

@Bromeon Bromeon Mar 8, 2021

Choose a reason for hiding this comment

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

Would it make sense to have the same layout for NoEntities as for MultipleEntities,
i.e NoEntities { query_name: &'static str }?

This removes the slightly strange asymmetry and would allow to extend the error information in the future.

#[error("Multiple entities fit the query {query_name}!")]
MultipleEntities {
result: <Q::Fetch as Fetch<'a>>::Item,
query_name: &'static str,
},
}

impl<'a, Q: WorldQuery> Debug for UniqueQueryError<'a, Q> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoEntities(_) => f.debug_tuple("NoEntities").finish(),
Self::MultipleEntities { .. } => f.debug_tuple("MultipleEntities").finish(),
}
}
}
5 changes: 2 additions & 3 deletions examples/game/alien_cake_addict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,8 @@ fn rotate_bonus(game: Res<Game>, time: Res<Time>, mut transforms: Query<&mut Tra

// update the score displayed during the game
fn scoreboard_system(game: Res<Game>, mut query: Query<&mut Text>) {
for mut text in query.iter_mut() {
text.sections[0].value = format!("Sugar Rush: {}", game.score);
}
let mut text = query.single_mut().unwrap();
text.sections[0].value = format!("Sugar Rush: {}", game.score);
}

// restart the game when pressing spacebar
Expand Down
11 changes: 5 additions & 6 deletions examples/game/breakout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fn paddle_movement_system(
keyboard_input: Res<Input<KeyCode>>,
mut query: Query<(&Paddle, &mut Transform)>,
) {
for (paddle, mut transform) in query.iter_mut() {
if let Ok((paddle, mut transform)) = query.single_mut() {
let mut direction = 0.0;
if keyboard_input.pressed(KeyCode::Left) {
direction -= 1.0;
Expand All @@ -196,15 +196,14 @@ fn ball_movement_system(time: Res<Time>, mut ball_query: Query<(&Ball, &mut Tran
// clamp the timestep to stop the ball from escaping when the game starts
let delta_seconds = f32::min(0.2, time.delta_seconds());

for (ball, mut transform) in ball_query.iter_mut() {
if let Ok((ball, mut transform)) = ball_query.single_mut() {
transform.translation += ball.velocity * delta_seconds;
}
}

fn scoreboard_system(scoreboard: Res<Scoreboard>, mut query: Query<&mut Text>) {
for mut text in query.iter_mut() {
text.sections[1].value = scoreboard.score.to_string();
}
let mut text = query.single_mut().unwrap();
text.sections[0].value = format!("Score: {}", scoreboard.score);
}

fn ball_collision_system(
Expand All @@ -213,7 +212,7 @@ fn ball_collision_system(
mut ball_query: Query<(&mut Ball, &Transform, &Sprite)>,
collider_query: Query<(Entity, &Collider, &Transform, &Sprite)>,
) {
for (mut ball, ball_transform, sprite) in ball_query.iter_mut() {
if let Ok((mut ball, ball_transform, sprite)) = ball_query.single_mut() {
let ball_size = sprite.size;
let velocity = &mut ball.velocity;

Expand Down