Skip to content
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
10 changes: 5 additions & 5 deletions crates/bevy_ecs/src/system/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
Expand All @@ -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"
);
Expand All @@ -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));
}
}

Expand Down
61 changes: 49 additions & 12 deletions crates/bevy_ecs/src/system/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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,
{
Expand All @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions examples/2d/many_sprites.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ 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());
}

// 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() {
Expand Down
2 changes: 1 addition & 1 deletion examples/game/alien_cake_addict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
115 changes: 56 additions & 59 deletions examples/game/breakout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (paddle, mut transform) = query.single_mut();
let mut direction = 0.0;
if keyboard_input.pressed(KeyCode::Left) {
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);
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);
}

Expand All @@ -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;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/shader/animate_shader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}