Skip to content

Commit

Permalink
Change World::try_despawn and World::try_insert_batch to return `…
Browse files Browse the repository at this point in the history
…Result` (#17376)

## Objective

Most `try` methods on `World` return a `Result`, but `try_despawn` and
`try_insert_batch` don't. Since Bevy's error handling is advancing,
these should be brought in line.

## Solution

- Added `TryDespawnError` and `TryInsertBatchError`.
- `try_despawn`, `try_insert_batch`, and `try_insert_batch_if_new` now
return their respective errors.
- Fixed slightly incorrect behavior in `try_insert_batch_with_caller`.
- The method was always meant to continue with the rest of the batch if
an entity was missing, but that only worked after the first entity; if
the first entity was missing, the method would exit early. This has been
resolved.

## Migration Guide
- `World::try_despawn` now returns a `Result` rather than a `bool`.
- `World::try_insert_batch` and `World::try_insert_batch_if_new` now
return a `Result` where they previously returned nothing.
  • Loading branch information
JaySpruce authored Jan 21, 2025
1 parent 44ad3bf commit fe24652
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 136 deletions.
8 changes: 6 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1878,7 +1878,9 @@ mod tests {

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.try_insert_batch(values);
let error = world.try_insert_batch(values).unwrap_err();

assert_eq!(e1, error.entities[0]);

assert_eq!(
world.get::<A>(e0),
Expand All @@ -1900,7 +1902,9 @@ mod tests {

let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

world.try_insert_batch_if_new(values);
let error = world.try_insert_batch_if_new(values).unwrap_err();

assert_eq!(e1, error.entities[0]);

assert_eq!(
world.get::<A>(e0),
Expand Down
33 changes: 7 additions & 26 deletions crates/bevy_ecs/src/system/commands/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,46 +126,27 @@ where
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will panic.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
pub fn insert_batch<I, B>(batch: I, mode: InsertMode) -> impl Command
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| {
world.insert_batch_with_caller(
batch,
mode,
#[cfg(feature = "track_location")]
caller,
);
}
}

/// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities.
/// If any entities do not exist in the world, this command will ignore them.
/// If any entities do not exist in the world, this command will return a
/// [`TryInsertBatchError`](crate::world::error::TryInsertBatchError).
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
pub fn try_insert_batch<I, B>(batch: I, mode: InsertMode) -> impl Command
pub fn insert_batch<I, B>(batch: I, insert_mode: InsertMode) -> impl Command<Result>
where
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| {
move |world: &mut World| -> Result {
world.try_insert_batch_with_caller(
batch,
mode,
insert_mode,
#[cfg(feature = "track_location")]
caller,
);
)?;
Ok(())
}
}

Expand Down
21 changes: 13 additions & 8 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ impl<'w, 's> Commands<'w, 's> {
/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
/// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`.
Expand All @@ -732,7 +732,7 @@ impl<'w, 's> Commands<'w, 's> {
/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
/// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`.
Expand All @@ -759,7 +759,7 @@ impl<'w, 's> Commands<'w, 's> {
/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
/// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, overwriting any existing components shared by the `Bundle`.
Expand All @@ -769,7 +769,7 @@ impl<'w, 's> Commands<'w, 's> {
/// and passing the bundle to [`insert`](EntityCommands::insert),
/// but it is faster due to memory pre-allocation.
///
/// This command silently fails by ignoring any entities that do not exist.
/// This command will send a warning if any of the given entities do not exist.
///
/// For the panicking version, see [`insert_batch`](Self::insert_batch).
#[track_caller]
Expand All @@ -778,13 +778,16 @@ impl<'w, 's> Commands<'w, 's> {
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(command::try_insert_batch(batch, InsertMode::Replace));
self.queue(
command::insert_batch(batch, InsertMode::Replace)
.handle_error_with(error_handler::warn()),
);
}

/// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity).
///
/// A batch can be any type that implements [`IntoIterator`] containing `(Entity, Bundle)` tuples,
/// such as a [`Vec<(Entity, Bundle)>`] or an array `[(Entity, Bundle); N]`.
/// such as a [`Vec<(Entity, Bundle)>`](alloc::vec::Vec) or an array `[(Entity, Bundle); N]`.
///
/// When the command is applied, for each `(Entity, Bundle)` pair in the given batch,
/// the `Bundle` is added to the `Entity`, except for any components already present on the `Entity`.
Expand All @@ -794,7 +797,7 @@ impl<'w, 's> Commands<'w, 's> {
/// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new),
/// but it is faster due to memory pre-allocation.
///
/// This command silently fails by ignoring any entities that do not exist.
/// This command will send a warning if any of the given entities do not exist.
///
/// For the panicking version, see [`insert_batch_if_new`](Self::insert_batch_if_new).
#[track_caller]
Expand All @@ -803,7 +806,9 @@ impl<'w, 's> Commands<'w, 's> {
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
B: Bundle,
{
self.queue(command::try_insert_batch(batch, InsertMode::Keep));
self.queue(
command::insert_batch(batch, InsertMode::Keep).handle_error_with(error_handler::warn()),
);
}

/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value.
Expand Down
27 changes: 27 additions & 0 deletions crates/bevy_ecs/src/world/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Contains error types returned by bevy's schedule.
use alloc::vec::Vec;
use thiserror::Error;

use crate::{
Expand All @@ -15,6 +16,32 @@ use crate::{
#[error("The schedule with the label {0:?} was not found.")]
pub struct TryRunScheduleError(pub InternedScheduleLabel);

/// The error type returned by [`World::try_despawn`] if the provided entity does not exist.
///
/// [`World::try_despawn`]: crate::world::World::try_despawn
#[derive(Error, Debug, Clone, Copy)]
#[error("Could not despawn the entity with ID {entity} because it {details}")]
pub struct TryDespawnError {
/// The entity's ID.
pub entity: Entity,
/// Details on why the entity does not exist, if available.
pub details: EntityDoesNotExistDetails,
}

/// The error type returned by [`World::try_insert_batch`] and [`World::try_insert_batch_if_new`]
/// if any of the provided entities do not exist.
///
/// [`World::try_insert_batch`]: crate::world::World::try_insert_batch
/// [`World::try_insert_batch_if_new`]: crate::world::World::try_insert_batch_if_new
#[derive(Error, Debug, Clone)]
#[error("Could not insert bundles of type {bundle_type} into the entities with the following IDs because they do not exist: {entities:?}")]
pub struct TryInsertBatchError {
/// The bundles' type name.
pub bundle_type: &'static str,
/// The IDs of the provided entities that do not exist.
pub entities: Vec<Entity>,
}

/// An error that occurs when dynamically retrieving components from an entity.
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityComponentError {
Expand Down
Loading

0 comments on commit fe24652

Please sign in to comment.