mirror of
https://github.com/bevyengine/bevy
synced 2024-11-25 14:10:19 +00:00
add get_single variant (#2793)
# Objective The vast majority of `.single()` usage I've seen is immediately followed by a `.unwrap()`. Since it seems most people use it without handling the error, I think making it easier to just get what you want fast while also having a more verbose alternative when you want to handle the error could help. ## Solution Instead of having a lot of `.unwrap()` everywhere, this PR introduces a `try_single()` variant that behaves like the current `.single()` and make the new `.single()` panic on error.
This commit is contained in:
parent
5ff96b8e7e
commit
51a5070cd2
6 changed files with 115 additions and 81 deletions
|
@ -574,7 +574,7 @@ mod tests {
|
|||
let (a, query, _) = system_state.get(&world);
|
||||
assert_eq!(*a, A(42), "returned resource matches initial value");
|
||||
assert_eq!(
|
||||
*query.single().unwrap(),
|
||||
*query.single(),
|
||||
B(7),
|
||||
"returned component matches initial value"
|
||||
);
|
||||
|
@ -601,7 +601,7 @@ mod tests {
|
|||
let (a, mut query) = system_state.get_mut(&mut world);
|
||||
assert_eq!(*a, A(42), "returned resource matches initial value");
|
||||
assert_eq!(
|
||||
*query.single_mut().unwrap(),
|
||||
*query.single_mut(),
|
||||
B(7),
|
||||
"returned component matches initial value"
|
||||
);
|
||||
|
@ -618,18 +618,18 @@ mod tests {
|
|||
let mut system_state: SystemState<Query<&A, Changed<A>>> = SystemState::new(&mut world);
|
||||
{
|
||||
let query = system_state.get(&world);
|
||||
assert_eq!(*query.single().unwrap(), A(1));
|
||||
assert_eq!(*query.single(), A(1));
|
||||
}
|
||||
|
||||
{
|
||||
let query = system_state.get(&world);
|
||||
assert!(query.single().is_err());
|
||||
assert!(query.get_single().is_err());
|
||||
}
|
||||
|
||||
world.entity_mut(entity).get_mut::<A>().unwrap().0 = 2;
|
||||
{
|
||||
let query = system_state.get(&world);
|
||||
assert_eq!(*query.single().unwrap(), A(2));
|
||||
assert_eq!(*query.single(), A(2));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -488,6 +488,33 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the result of a single-result query.
|
||||
///
|
||||
/// Assumes this query has only one result and panics if there are no or multiple results.
|
||||
/// Use [`Self::get_single`] to handle the error cases explicitly
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::{IntoSystem, Query, With};
|
||||
/// struct Player;
|
||||
/// struct Position(f32, f32);
|
||||
/// fn player_system(query: Query<&Position, With<Player>>) {
|
||||
/// let player_position = query.single();
|
||||
/// // do something with player_position
|
||||
/// }
|
||||
/// # let _check_that_its_a_system = player_system.system();
|
||||
/// ```
|
||||
///
|
||||
/// This can only be called for read-only queries, see [`Self::single_mut`] for write-queries.
|
||||
#[track_caller]
|
||||
pub fn single(&'s self) -> <Q::Fetch as Fetch<'w, 's>>::Item
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
self.get_single().unwrap()
|
||||
}
|
||||
|
||||
/// Gets the result of a single-result query.
|
||||
///
|
||||
/// If the query has exactly one result, returns the result inside `Ok`
|
||||
|
@ -497,27 +524,28 @@ where
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::{IntoSystem, With};
|
||||
/// # use bevy_ecs::system::{Query, QuerySingleError};
|
||||
/// # use bevy_ecs::prelude::IntoSystem;
|
||||
/// struct PlayerScore(i32);
|
||||
/// fn player_scoring_system(query: Query<&PlayerScore>) {
|
||||
/// match query.single() {
|
||||
/// Ok(PlayerScore(score)) => {
|
||||
/// // do something with score
|
||||
/// struct Player;
|
||||
/// struct Position(f32, f32);
|
||||
/// fn player_system(query: Query<&Position, With<Player>>) {
|
||||
/// match query.get_single() {
|
||||
/// Ok(position) => {
|
||||
/// // do something with position
|
||||
/// }
|
||||
/// Err(QuerySingleError::NoEntities(_)) => {
|
||||
/// // no PlayerScore
|
||||
/// // no position with Player
|
||||
/// }
|
||||
/// Err(QuerySingleError::MultipleEntities(_)) => {
|
||||
/// // multiple PlayerScore
|
||||
/// // multiple position with Player
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # let _check_that_its_a_system = player_scoring_system.system();
|
||||
/// # let _check_that_its_a_system = player_system.system();
|
||||
/// ```
|
||||
///
|
||||
/// This can only be called for read-only queries, see [`Self::single_mut`] for write-queries.
|
||||
pub fn single(&'s self) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QuerySingleError>
|
||||
/// This can only be called for read-only queries, see [`Self::get_single_mut`] for write-queries.
|
||||
pub fn get_single(&'s self) -> Result<<Q::Fetch as Fetch<'w, 's>>::Item, QuerySingleError>
|
||||
where
|
||||
Q::Fetch: ReadOnlyFetch,
|
||||
{
|
||||
|
@ -534,9 +562,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the query result if it is only a single result, otherwise panics
|
||||
/// If you want to handle the error case yourself you can use the [`Self::get_single_mut`] variant.
|
||||
#[track_caller]
|
||||
pub fn single_mut(&mut self) -> <Q::Fetch as Fetch<'_, '_>>::Item {
|
||||
self.get_single_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Gets the query result if it is only a single result, otherwise returns a
|
||||
/// [`QuerySingleError`].
|
||||
pub fn single_mut(&mut self) -> Result<<Q::Fetch as Fetch<'_, '_>>::Item, QuerySingleError> {
|
||||
pub fn get_single_mut(
|
||||
&mut self,
|
||||
) -> Result<<Q::Fetch as Fetch<'_, '_>>::Item, QuerySingleError> {
|
||||
let mut query = self.iter_mut();
|
||||
let first = query.next();
|
||||
let extra = query.next().is_some();
|
||||
|
|
|
@ -75,7 +75,7 @@ fn setup(
|
|||
|
||||
// System for rotating and translating the camera
|
||||
fn move_camera_system(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
|
||||
let mut camera_transform = camera_query.single_mut().unwrap();
|
||||
let mut camera_transform = camera_query.single_mut();
|
||||
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.5));
|
||||
*camera_transform = *camera_transform
|
||||
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
|
||||
|
@ -83,7 +83,7 @@ fn move_camera_system(time: Res<Time>, mut camera_query: Query<&mut Transform, W
|
|||
|
||||
// System for printing the number of sprites on every tick of the timer
|
||||
fn tick_system(time: Res<Time>, sprites_query: Query<&Sprite>, mut timer_query: Query<&mut Timer>) {
|
||||
let mut timer = timer_query.single_mut().unwrap();
|
||||
let mut timer = timer_query.single_mut();
|
||||
timer.tick(time.delta());
|
||||
|
||||
if timer.just_finished() {
|
||||
|
|
|
@ -352,7 +352,7 @@ 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>) {
|
||||
let mut text = query.single_mut().unwrap();
|
||||
let mut text = query.single_mut();
|
||||
text.sections[0].value = format!("Sugar Rush: {}", game.score);
|
||||
}
|
||||
|
||||
|
|
|
@ -185,32 +185,30 @@ fn paddle_movement_system(
|
|||
keyboard_input: Res<Input<KeyCode>>,
|
||||
mut query: Query<(&Paddle, &mut Transform)>,
|
||||
) {
|
||||
if let Ok((paddle, mut transform)) = query.single_mut() {
|
||||
let mut direction = 0.0;
|
||||
if keyboard_input.pressed(KeyCode::Left) {
|
||||
direction -= 1.0;
|
||||
}
|
||||
|
||||
if keyboard_input.pressed(KeyCode::Right) {
|
||||
direction += 1.0;
|
||||
}
|
||||
|
||||
let translation = &mut transform.translation;
|
||||
// move the paddle horizontally
|
||||
translation.x += direction * paddle.speed * TIME_STEP;
|
||||
// bound the paddle within the walls
|
||||
translation.x = translation.x.min(380.0).max(-380.0);
|
||||
let (paddle, mut transform) = query.single_mut();
|
||||
let mut direction = 0.0;
|
||||
if keyboard_input.pressed(KeyCode::Left) {
|
||||
direction -= 1.0;
|
||||
}
|
||||
|
||||
if keyboard_input.pressed(KeyCode::Right) {
|
||||
direction += 1.0;
|
||||
}
|
||||
|
||||
let translation = &mut transform.translation;
|
||||
// move the paddle horizontally
|
||||
translation.x += direction * paddle.speed * TIME_STEP;
|
||||
// bound the paddle within the walls
|
||||
translation.x = translation.x.min(380.0).max(-380.0);
|
||||
}
|
||||
|
||||
fn ball_movement_system(mut ball_query: Query<(&Ball, &mut Transform)>) {
|
||||
if let Ok((ball, mut transform)) = ball_query.single_mut() {
|
||||
transform.translation += ball.velocity * TIME_STEP;
|
||||
}
|
||||
let (ball, mut transform) = ball_query.single_mut();
|
||||
transform.translation += ball.velocity * TIME_STEP;
|
||||
}
|
||||
|
||||
fn scoreboard_system(scoreboard: Res<Scoreboard>, mut query: Query<&mut Text>) {
|
||||
let mut text = query.single_mut().unwrap();
|
||||
let mut text = query.single_mut();
|
||||
text.sections[1].value = format!("{}", scoreboard.score);
|
||||
}
|
||||
|
||||
|
@ -220,53 +218,52 @@ fn ball_collision_system(
|
|||
mut ball_query: Query<(&mut Ball, &Transform, &Sprite)>,
|
||||
collider_query: Query<(Entity, &Collider, &Transform, &Sprite)>,
|
||||
) {
|
||||
if let Ok((mut ball, ball_transform, sprite)) = ball_query.single_mut() {
|
||||
let ball_size = sprite.size;
|
||||
let velocity = &mut ball.velocity;
|
||||
let (mut ball, ball_transform, sprite) = ball_query.single_mut();
|
||||
let ball_size = sprite.size;
|
||||
let velocity = &mut ball.velocity;
|
||||
|
||||
// check collision with walls
|
||||
for (collider_entity, collider, transform, sprite) in collider_query.iter() {
|
||||
let collision = collide(
|
||||
ball_transform.translation,
|
||||
ball_size,
|
||||
transform.translation,
|
||||
sprite.size,
|
||||
);
|
||||
if let Some(collision) = collision {
|
||||
// scorable colliders should be despawned and increment the scoreboard on collision
|
||||
if let Collider::Scorable = *collider {
|
||||
scoreboard.score += 1;
|
||||
commands.entity(collider_entity).despawn();
|
||||
}
|
||||
// check collision with walls
|
||||
for (collider_entity, collider, transform, sprite) in collider_query.iter() {
|
||||
let collision = collide(
|
||||
ball_transform.translation,
|
||||
ball_size,
|
||||
transform.translation,
|
||||
sprite.size,
|
||||
);
|
||||
if let Some(collision) = collision {
|
||||
// scorable colliders should be despawned and increment the scoreboard on collision
|
||||
if let Collider::Scorable = *collider {
|
||||
scoreboard.score += 1;
|
||||
commands.entity(collider_entity).despawn();
|
||||
}
|
||||
|
||||
// reflect the ball when it collides
|
||||
let mut reflect_x = false;
|
||||
let mut reflect_y = false;
|
||||
// reflect the ball when it collides
|
||||
let mut reflect_x = false;
|
||||
let mut reflect_y = false;
|
||||
|
||||
// only reflect if the ball's velocity is going in the opposite direction of the
|
||||
// collision
|
||||
match collision {
|
||||
Collision::Left => reflect_x = velocity.x > 0.0,
|
||||
Collision::Right => reflect_x = velocity.x < 0.0,
|
||||
Collision::Top => reflect_y = velocity.y < 0.0,
|
||||
Collision::Bottom => reflect_y = velocity.y > 0.0,
|
||||
}
|
||||
// only reflect if the ball's velocity is going in the opposite direction of the
|
||||
// collision
|
||||
match collision {
|
||||
Collision::Left => reflect_x = velocity.x > 0.0,
|
||||
Collision::Right => reflect_x = velocity.x < 0.0,
|
||||
Collision::Top => reflect_y = velocity.y < 0.0,
|
||||
Collision::Bottom => reflect_y = velocity.y > 0.0,
|
||||
}
|
||||
|
||||
// reflect velocity on the x-axis if we hit something on the x-axis
|
||||
if reflect_x {
|
||||
velocity.x = -velocity.x;
|
||||
}
|
||||
// reflect velocity on the x-axis if we hit something on the x-axis
|
||||
if reflect_x {
|
||||
velocity.x = -velocity.x;
|
||||
}
|
||||
|
||||
// reflect velocity on the y-axis if we hit something on the y-axis
|
||||
if reflect_y {
|
||||
velocity.y = -velocity.y;
|
||||
}
|
||||
// reflect velocity on the y-axis if we hit something on the y-axis
|
||||
if reflect_y {
|
||||
velocity.y = -velocity.y;
|
||||
}
|
||||
|
||||
// break if this collide is on a solid, otherwise continue check whether a solid is
|
||||
// also in collision
|
||||
if let Collider::Solid = *collider {
|
||||
break;
|
||||
}
|
||||
// break if this collide is on a solid, otherwise continue check whether a solid is
|
||||
// also in collision
|
||||
if let Collider::Solid = *collider {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,6 +120,6 @@ fn setup(
|
|||
/// `time.seconds_since_startup()` as the `value` of the `TimeComponent`. This value will be
|
||||
/// accessed by the fragment shader and used to animate the shader.
|
||||
fn animate_shader(time: Res<Time>, mut query: Query<&mut TimeUniform>) {
|
||||
let mut time_uniform = query.single_mut().unwrap();
|
||||
let mut time_uniform = query.single_mut();
|
||||
time_uniform.value = time.seconds_since_startup() as f32;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue