Improved Command Errors (#17215)

# Objective

Rework / build on #17043 to simplify the implementation. #17043 should
be merged first, and the diff from this PR will get much nicer after it
is merged (this PR is net negative LOC).

## Solution

1. Command and EntityCommand have been vastly simplified. No more marker
components. Just one function.
2. Command and EntityCommand are now generic on the return type. This
enables result-less commands to exist, and allows us to statically
distinguish between fallible and infallible commands, which allows us to
skip the "error handling overhead" for cases that don't need it.
3. There are now only two command queue variants: `queue` and
`queue_fallible`. `queue` accepts commands with no return type.
`queue_fallible` accepts commands that return a Result (specifically,
one that returns an error that can convert to
`bevy_ecs::result::Error`).
4. I've added the concept of the "default error handler", which is used
by `queue_fallible`. This is a simple direct call to the `panic()` error
handler by default. Users that want to override this can enable the
`configurable_error_handler` cargo feature, then initialize the
GLOBAL_ERROR_HANDLER value on startup. This is behind a flag because
there might be minor overhead with `OnceLock` and I'm guessing this will
be a niche feature. We can also do perf testing with OnceLock if someone
really wants it to be used unconditionally, but I don't personally feel
the need to do that.
5. I removed the "temporary error handler" on Commands (and all code
associated with it). It added more branching, made Commands bigger /
more expensive to initialize (note that we construct it at high
frequencies / treat it like a pointer type), made the code harder to
follow, and introduced a bunch of additional functions. We instead rely
on the new default error handler used in `queue_fallible` for most
things. In the event that a custom handler is required,
`handle_error_with` can be used.
6. EntityCommand now _only_ supports functions that take
`EntityWorldMut` (and all existing entity commands have been ported).
Removing the marker component from EntityCommand hinged on this change,
but I strongly believe this is for the best anyway, as this sets the
stage for more efficient batched entity commands.
7. I added `EntityWorldMut::resource` and the other variants for more
ergonomic resource access on `EntityWorldMut` (removes the need for
entity.world_scope, which also incurs entity-lookup overhead).

## Open Questions

1. I believe we could merge `queue` and `queue_fallible` into a single
`queue` which accepts both fallible and infallible commands (via the
introduction of a `QueueCommand` trait). Is this desirable?
This commit is contained in:
Carter Anderson 2025-01-09 20:15:50 -08:00 committed by GitHub
parent 145f5f4394
commit 4bca7f1b6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 526 additions and 618 deletions

View File

@ -2,7 +2,6 @@ use core::hint::black_box;
use bevy_ecs::{
component::Component,
result::Result,
system::{Command, Commands},
world::{CommandQueue, World},
};
@ -137,18 +136,16 @@ struct FakeCommandA;
struct FakeCommandB(u64);
impl Command for FakeCommandA {
fn apply(self, world: &mut World) -> Result {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
Ok(())
}
}
impl Command for FakeCommandB {
fn apply(self, world: &mut World) -> Result {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
Ok(())
}
}
@ -183,10 +180,9 @@ pub fn fake_commands(criterion: &mut Criterion) {
struct SizedCommand<T: Default + Send + Sync + 'static>(T);
impl<T: Default + Send + Sync + 'static> Command for SizedCommand<T> {
fn apply(self, world: &mut World) -> Result {
fn apply(self, world: &mut World) {
black_box(self);
black_box(world);
Ok(())
}
}

View File

@ -28,6 +28,9 @@ bevy_reflect = ["dep:bevy_reflect"]
## Extends reflection support to functions.
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
## Use the configurable global error handler as the default error handler
configurable_error_handler = []
# Debugging Features
## Enables `tracing` integration, allowing spans and other metrics to be reported

View File

@ -169,10 +169,8 @@ pub trait ReflectCommandExt {
impl ReflectCommandExt for EntityCommands<'_> {
fn insert_reflect(&mut self, component: Box<dyn PartialReflect>) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.insert_reflect(component);
}
self.queue(move |mut entity: EntityWorldMut| {
entity.insert_reflect(component);
})
}
@ -180,19 +178,15 @@ impl ReflectCommandExt for EntityCommands<'_> {
&mut self,
component: Box<dyn PartialReflect>,
) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.insert_reflect_with_registry::<T>(component);
}
self.queue(move |mut entity: EntityWorldMut| {
entity.insert_reflect_with_registry::<T>(component);
})
}
fn remove_reflect(&mut self, component_type_path: impl Into<Cow<'static, str>>) -> &mut Self {
let component_type_path: Cow<'static, str> = component_type_path.into();
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.remove_reflect(component_type_path);
}
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_reflect(component_type_path);
})
}
@ -201,10 +195,8 @@ impl ReflectCommandExt for EntityCommands<'_> {
component_type_path: impl Into<Cow<'static, str>>,
) -> &mut Self {
let component_type_path: Cow<'static, str> = component_type_path.into();
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.remove_reflect_with_registry::<T>(component_type_path);
}
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_reflect_with_registry::<T>(component_type_path);
})
}
}

View File

@ -12,9 +12,9 @@ use crate::{
entity::Entity,
event::{Event, Events},
observer::TriggerTargets,
result::Result,
result::{Error, Result},
schedule::ScheduleLabel,
system::{CommandError, IntoSystem, Resource, SystemId, SystemInput},
system::{error_handler, IntoSystem, Resource, SystemId, SystemInput},
world::{FromWorld, SpawnBatchIter, World},
};
@ -22,6 +22,8 @@ use crate::{
///
/// Should be used with [`Commands::queue`](crate::system::Commands::queue).
///
/// The `Out` generic parameter is the returned "output" of the command.
///
/// # Usage
///
/// ```
@ -34,10 +36,9 @@ use crate::{
/// struct AddToCounter(u64);
///
/// impl Command for AddToCounter {
/// fn apply(self, world: &mut World) -> Result {
/// fn apply(self, world: &mut World) {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// counter.0 += self.0;
/// Ok(())
/// }
/// }
///
@ -45,91 +46,60 @@ use crate::{
/// commands.queue(AddToCounter(42));
/// }
/// ```
///
/// # Note on Generic
///
/// The `Marker` generic is necessary to allow multiple blanket implementations
/// of `Command` for closures, like so:
/// ```ignore (This would conflict with the real implementations)
/// impl Command for FnOnce(&mut World)
/// impl Command<Result> for FnOnce(&mut World) -> Result
/// ```
/// Without the generic, Rust would consider the two implementations to be conflicting.
///
/// The type used for `Marker` has no connection to anything else in the implementation.
pub trait Command<Marker = ()>: Send + 'static {
pub trait Command<Out = ()>: Send + 'static {
/// Applies this command, causing it to mutate the provided `world`.
///
/// This method is used to define what a command "does" when it is ultimately applied.
/// Because this method takes `self`, you can store data or settings on the type that implements this trait.
/// This data is set by the system or other source of the command, and then ultimately read in this method.
fn apply(self, world: &mut World) -> Result;
/// Applies this command and converts any resulting error into a [`CommandError`].
///
/// Overwriting this method allows an implementor to return a `CommandError` directly
/// and avoid erasing the error's type.
fn apply_internal(self, world: &mut World) -> Result<(), CommandError>
where
Self: Sized,
{
match self.apply(world) {
Ok(_) => Ok(()),
Err(error) => Err(CommandError::CommandFailed(error)),
}
}
/// Returns a new [`Command`] that, when applied, will apply the original command
/// and pass any resulting error to the provided `error_handler`.
fn with_error_handling(
self,
error_handler: Option<fn(&mut World, CommandError)>,
) -> impl Command
where
Self: Sized,
{
move |world: &mut World| {
if let Err(error) = self.apply_internal(world) {
// TODO: Pass the error to the global error handler if `error_handler` is `None`.
let error_handler = error_handler.unwrap_or(|_, error| panic!("{error}"));
error_handler(world, error);
}
}
}
fn apply(self, world: &mut World) -> Out;
}
impl<F> Command for F
impl<F, Out> Command<Out> for F
where
F: FnOnce(&mut World) + Send + 'static,
F: FnOnce(&mut World) -> Out + Send + 'static,
{
fn apply(self, world: &mut World) -> Result {
self(world);
Ok(())
}
}
impl<F> Command<Result> for F
where
F: FnOnce(&mut World) -> Result + Send + 'static,
{
fn apply(self, world: &mut World) -> Result {
fn apply(self, world: &mut World) -> Out {
self(world)
}
}
/// Necessary to avoid erasing the type of the `CommandError` in
/// [`EntityCommand::with_entity`](crate::system::EntityCommand::with_entity).
impl<F> Command<(Result, CommandError)> for F
where
F: FnOnce(&mut World) -> Result<(), CommandError> + Send + 'static,
{
fn apply(self, world: &mut World) -> Result {
self(world)?;
Ok(())
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
pub trait HandleError<Out = ()> {
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command;
/// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error(self) -> impl Command
where
Self: Sized,
{
self.handle_error_with(error_handler::default())
}
}
fn apply_internal(self, world: &mut World) -> Result<(), CommandError> {
self(world)
impl<C: Command<Result<T, E>>, T, E: Into<Error>> HandleError<Result<T, E>> for C {
fn handle_error_with(self, error_handler: fn(&mut World, Error)) -> impl Command {
move |world: &mut World| match self.apply(world) {
Ok(_) => {}
Err(err) => (error_handler)(world, err.into()),
}
}
}
impl<C: Command> HandleError for C {
#[inline]
fn handle_error_with(self, _error_handler: fn(&mut World, Error)) -> impl Command {
self
}
#[inline]
fn handle_error(self) -> impl Command
where
Self: Sized,
{
self
}
}

View File

@ -16,12 +16,16 @@ use crate::{
entity::{Entity, EntityCloneBuilder},
event::Event,
result::Result,
system::{Command, CommandError, IntoObserverSystem},
world::{EntityWorldMut, FromWorld, World},
system::{command::HandleError, Command, IntoObserverSystem},
world::{error::EntityFetchError, EntityWorldMut, FromWorld, World},
};
use bevy_ptr::OwningPtr;
/// A [`Command`] which gets executed for a given [`Entity`].
/// A command which gets executed for a given [`Entity`].
///
/// Should be used with [`EntityCommands::queue`](crate::system::EntityCommands::queue).
///
/// The `Out` generic parameter is the returned "output" of the command.
///
/// # Examples
///
@ -41,14 +45,16 @@ use bevy_ptr::OwningPtr;
/// struct Counter(i64);
///
/// /// A `Command` which names an entity based on a global counter.
/// fn count_name(entity: Entity, world: &mut World) {
/// fn count_name(mut entity: EntityWorldMut) {
/// // Get the current value of the counter, and increment it for next time.
/// let mut counter = world.resource_mut::<Counter>();
/// let i = counter.0;
/// counter.0 += 1;
///
/// let i = {
/// let mut counter = entity.resource_mut::<Counter>();
/// let i = counter.0;
/// counter.0 += 1;
/// i
/// };
/// // Name the entity after the value of the counter.
/// world.entity_mut(entity).insert(Name::new(format!("Entity #{i}")));
/// entity.insert(Name::new(format!("Entity #{i}")));
/// }
///
/// // App creation boilerplate omitted...
@ -74,96 +80,81 @@ use bevy_ptr::OwningPtr;
/// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"]));
/// }
/// ```
///
/// # Note on Generic
///
/// The `Marker` generic is necessary to allow multiple blanket implementations
/// of `EntityCommand` for closures, like so:
/// ```ignore (This would conflict with the real implementations)
/// impl EntityCommand for FnOnce(Entity, &mut World)
/// impl EntityCommand<World> for FnOnce(EntityWorldMut)
/// impl EntityCommand<Result> for FnOnce(Entity, &mut World) -> Result
/// impl EntityCommand<(World, Result)> for FnOnce(EntityWorldMut) -> Result
/// ```
/// Without the generic, Rust would consider the implementations to be conflicting.
///
/// The type used for `Marker` has no connection to anything else in the implementation.
pub trait EntityCommand<Marker = ()>: Send + 'static {
pub trait EntityCommand<Out = ()>: Send + 'static {
/// Executes this command for the given [`Entity`] and
/// returns a [`Result`] for error handling.
fn apply(self, entity: Entity, world: &mut World) -> Result;
fn apply(self, entity: EntityWorldMut) -> Out;
}
/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that
/// internally runs the [`EntityCommand`] on that entity.
///
// NOTE: This is a separate trait from `EntityCommand` because "result-returning entity commands" and
// "non-result returning entity commands" require different implementations, so they cannot be automatically
// implemented. And this isn't the type of implementation that we want to thrust on people implementing
// EntityCommand.
pub trait CommandWithEntity<Out> {
/// Passes in a specific entity to an [`EntityCommand`], resulting in a [`Command`] that
/// internally runs the [`EntityCommand`] on that entity.
fn with_entity(self, entity: Entity) -> impl Command<Out> + HandleError<Out>;
}
/// Returns a [`Command`] which executes this [`EntityCommand`] for the given [`Entity`].
///
/// This method is called when adding an [`EntityCommand`] to a command queue via [`Commands`](crate::system::Commands).
/// You can override the provided implementation if you can return a `Command` with a smaller memory
/// footprint than `(Entity, Self)`.
/// In most cases the provided implementation is sufficient.
#[must_use = "commands do nothing unless applied to a `World`"]
fn with_entity(self, entity: Entity) -> impl Command<(Result, CommandError)>
where
Self: Sized,
impl<C: EntityCommand> CommandWithEntity<Result<(), EntityFetchError>> for C {
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<(), EntityFetchError>> + HandleError<Result<(), EntityFetchError>>
{
move |world: &mut World| -> Result<(), CommandError> {
if world.entities().contains(entity) {
match self.apply(entity, world) {
Ok(_) => Ok(()),
Err(error) => Err(CommandError::CommandFailed(error)),
}
} else {
Err(CommandError::NoSuchEntity(
entity,
world
.entities()
.entity_does_not_exist_error_details_message(entity),
))
}
move |world: &mut World| -> Result<(), EntityFetchError> {
let entity = world.get_entity_mut(entity)?;
self.apply(entity);
Ok(())
}
}
}
impl<F> EntityCommand for F
where
F: FnOnce(Entity, &mut World) + Send + 'static,
impl<
C: EntityCommand<Result<T, Err>>,
T,
Err: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static,
> CommandWithEntity<Result<T, EntityCommandError<Err>>> for C
{
fn apply(self, id: Entity, world: &mut World) -> Result {
self(id, world);
Ok(())
fn with_entity(
self,
entity: Entity,
) -> impl Command<Result<T, EntityCommandError<Err>>> + HandleError<Result<T, EntityCommandError<Err>>>
{
move |world: &mut World| {
let entity = world.get_entity_mut(entity)?;
self.apply(entity)
.map_err(EntityCommandError::CommandFailed)
}
}
}
impl<F> EntityCommand<Result> for F
where
F: FnOnce(Entity, &mut World) -> Result + Send + 'static,
{
fn apply(self, id: Entity, world: &mut World) -> Result {
self(id, world)
}
/// An error that occurs when running an [`EntityCommand`] on a specific entity.
#[derive(thiserror::Error, Debug)]
pub enum EntityCommandError<E> {
/// The entity this [`EntityCommand`] tried to run on could not be fetched.
#[error(transparent)]
EntityFetchError(#[from] EntityFetchError),
/// An error that occurred while running the [`EntityCommand`].
#[error("{0}")]
CommandFailed(E),
}
impl<F> EntityCommand<World> for F
impl<Out, F> EntityCommand<Out> for F
where
F: FnOnce(EntityWorldMut) + Send + 'static,
F: FnOnce(EntityWorldMut) -> Out + Send + 'static,
{
fn apply(self, id: Entity, world: &mut World) -> Result {
self(world.entity_mut(id));
Ok(())
}
}
impl<F> EntityCommand<(World, Result)> for F
where
F: FnOnce(EntityWorldMut) -> Result + Send + 'static,
{
fn apply(self, id: Entity, world: &mut World) -> Result {
self(world.entity_mut(id))
fn apply(self, entity: EntityWorldMut) -> Out {
self(entity)
}
}
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity,
/// replacing any that were already present.
#[track_caller]
pub fn insert(bundle: impl Bundle) -> impl EntityCommand<World> {
pub fn insert(bundle: impl Bundle) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| {
@ -179,7 +170,7 @@ pub fn insert(bundle: impl Bundle) -> impl EntityCommand<World> {
/// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity,
/// except for any that were already present.
#[track_caller]
pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand<World> {
pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| {
@ -194,10 +185,7 @@ pub fn insert_if_new(bundle: impl Bundle) -> impl EntityCommand<World> {
/// An [`EntityCommand`] that adds a dynamic component to an entity.
#[track_caller]
pub fn insert_by_id<T: Send + 'static>(
component_id: ComponentId,
value: T,
) -> impl EntityCommand<World> {
pub fn insert_by_id<T: Send + 'static>(component_id: ComponentId, value: T) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
// SAFETY:
// - `component_id` safety is ensured by the caller
@ -214,9 +202,8 @@ pub fn insert_by_id<T: Send + 'static>(
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |entity: Entity, world: &mut World| {
let value = T::from_world(world);
let mut entity = world.entity_mut(entity);
move |mut entity: EntityWorldMut| {
let value = entity.world_scope(|world| T::from_world(world));
entity.insert_with_caller(
value,
mode,
@ -227,7 +214,7 @@ pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl Ent
}
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
pub fn remove<T: Bundle>() -> impl EntityCommand<World> {
pub fn remove<T: Bundle>() -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.remove::<T>();
}
@ -235,21 +222,21 @@ pub fn remove<T: Bundle>() -> impl EntityCommand<World> {
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity,
/// as well as the required components for each component removed.
pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand<World> {
pub fn remove_with_requires<T: Bundle>() -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.remove_with_requires::<T>();
}
}
/// An [`EntityCommand`] that removes a dynamic component from an entity.
pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand<World> {
pub fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.remove_by_id(component_id);
}
}
/// An [`EntityCommand`] that removes all components from an entity.
pub fn clear() -> impl EntityCommand<World> {
pub fn clear() -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clear();
}
@ -257,7 +244,7 @@ pub fn clear() -> impl EntityCommand<World> {
/// An [`EntityCommand`] that removes all components from an entity,
/// except for those in the given [`Bundle`].
pub fn retain<T: Bundle>() -> impl EntityCommand<World> {
pub fn retain<T: Bundle>() -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.retain::<T>();
}
@ -269,7 +256,7 @@ pub fn retain<T: Bundle>() -> impl EntityCommand<World> {
///
/// This won't clean up external references to the entity (such as parent-child relationships
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state.
pub fn despawn() -> impl EntityCommand<World> {
pub fn despawn() -> impl EntityCommand {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |entity: EntityWorldMut| {
@ -284,7 +271,7 @@ pub fn despawn() -> impl EntityCommand<World> {
/// listening for events of type `E` targeting an entity
pub fn observe<E: Event, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>,
) -> impl EntityCommand<World> {
) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.observe(observer);
}
@ -295,7 +282,7 @@ pub fn observe<E: Event, B: Bundle, M>(
pub fn clone_with(
target: Entity,
config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static,
) -> impl EntityCommand<World> {
) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clone_with(target, config);
}
@ -303,7 +290,7 @@ pub fn clone_with(
/// An [`EntityCommand`] that clones the specified components of an entity
/// and inserts them into another entity.
pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand<World> {
pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.clone_components::<B>(target);
}
@ -311,7 +298,7 @@ pub fn clone_components<B: Bundle>(target: Entity) -> impl EntityCommand<World>
/// An [`EntityCommand`] that clones the specified components of an entity
/// and inserts them into another entity, then removes them from the original entity.
pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand<World> {
pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand {
move |mut entity: EntityWorldMut| {
entity.move_components::<B>(target);
}
@ -319,11 +306,12 @@ pub fn move_components<B: Bundle>(target: Entity) -> impl EntityCommand<World> {
/// An [`EntityCommand`] that logs the components of an entity.
pub fn log_components() -> impl EntityCommand {
move |entity: Entity, world: &mut World| {
let debug_infos: Vec<_> = world
.inspect_entity(entity)
move |entity: EntityWorldMut| {
let debug_infos: Vec<_> = entity
.world()
.inspect_entity(entity.id())
.map(ComponentInfo::name)
.collect();
info!("Entity {entity}: {debug_infos:?}");
info!("Entity {}: {debug_infos:?}", entity.id());
}
}

View File

@ -1,17 +0,0 @@
//! This module contains the error type used by commands.
use alloc::boxed::Box;
use thiserror::Error;
use crate::entity::{Entity, EntityDoesNotExistDetails};
/// An error that occurs when executing a command.
#[derive(Error, Debug)]
pub enum CommandError {
/// The entity with the given ID does not exist.
#[error("Command failed because the entity with ID {0} {1}")]
NoSuchEntity(Entity, EntityDoesNotExistDetails),
/// The command returned an error.
#[error("Command returned an error: {0}")]
CommandFailed(Box<dyn core::error::Error + Send + Sync + 'static>),
}

View File

@ -1,32 +1,61 @@
//! This module contains convenience functions that return simple error handlers
//! for use with the following methods:
//! - [`Commands::queue_fallible_with`](super::Commands::queue_fallible_with)
//! - [`Commands::override_error_handler`](super::Commands::override_error_handler)
//! - [`EntityCommands::queue_with`](super::EntityCommands::queue_with)
//! - [`EntityCommands::override_error_handler`](super::EntityCommands::override_error_handler)
//! - [`EntityEntryCommands::override_error_handler`](super::EntityEntryCommands::override_error_handler)
//! for use with [`Commands::queue_handled`](super::Commands::queue_handled) and [`EntityCommands::queue_handled`](super::EntityCommands::queue_handled).
use crate::{result::Error, world::World};
use log::{error, warn};
use crate::{system::CommandError, world::World};
/// An error handler that does nothing.
pub fn silent() -> fn(&mut World, CommandError) {
pub fn silent() -> fn(&mut World, Error) {
|_, _| {}
}
/// An error handler that accepts an error and logs it with [`warn!`].
pub fn warn() -> fn(&mut World, CommandError) {
pub fn warn() -> fn(&mut World, Error) {
|_, error| warn!("{error}")
}
/// An error handler that accepts an error and logs it with [`error!`].
pub fn error() -> fn(&mut World, CommandError) {
pub fn error() -> fn(&mut World, Error) {
|_, error| error!("{error}")
}
/// An error handler that accepts an error and panics with the error in
/// the panic message.
pub fn panic() -> fn(&mut World, CommandError) {
pub fn panic() -> fn(&mut World, Error) {
|_, error| panic!("{error}")
}
/// The default error handler. This defaults to [`panic()`]. If the
/// `configurable_error_handler` cargo feature is enabled, then
/// `GLOBAL_ERROR_HANDLER` will be used instead, enabling error handler customization.
#[cfg(not(feature = "configurable_error_handler"))]
#[inline]
pub fn default() -> fn(&mut World, Error) {
panic()
}
/// A global error handler. This can be set at startup, as long as it is set before
/// any uses. This should generally be configured _before_ initializing the app.
///
/// If the `configurable_error_handler` cargo feature is enabled, this will be used
/// by default.
///
/// This should be set in the following way:
///
/// ```
/// # use bevy_ecs::system::error_handler::{GLOBAL_ERROR_HANDLER, warn};
/// GLOBAL_ERROR_HANDLER.set(warn());
/// // initialize Bevy App here
/// ```
#[cfg(feature = "configurable_error_handler")]
pub static GLOBAL_ERROR_HANDLER: std::sync::OnceLock<fn(&mut World, Error)> =
std::sync::OnceLock::new();
/// The default error handler. This defaults to [`panic()`]. If the
/// `configurable_error_handler` cargo feature is enabled, then
/// [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization.
#[cfg(feature = "configurable_error_handler")]
#[inline]
pub fn default() -> fn(&mut World, Error) {
*GLOBAL_ERROR_HANDLER.get_or_init(|| panic())
}

View File

@ -1,6 +1,5 @@
pub mod command;
pub mod entity_command;
pub mod error;
pub mod error_handler;
#[cfg(feature = "std")]
@ -8,7 +7,6 @@ mod parallel_scope;
pub use command::Command;
pub use entity_command::EntityCommand;
pub use error::CommandError;
#[cfg(feature = "std")]
pub use parallel_scope::*;
@ -28,10 +26,11 @@ use crate::{
entity::{Entities, Entity, EntityCloneBuilder},
event::Event,
observer::{Observer, TriggerTargets},
result::Error,
schedule::ScheduleLabel,
system::{
input::SystemInput, Deferred, IntoObserverSystem, IntoSystem, RegisteredSystem, Resource,
SystemId,
command::HandleError, entity_command::CommandWithEntity, input::SystemInput, Deferred,
IntoObserverSystem, IntoSystem, RegisteredSystem, Resource, SystemId,
},
world::{
command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, CommandQueue,
@ -92,24 +91,14 @@ use crate::{
///
/// # Error handling
///
/// Commands return a [`Result`](crate::result::Result), which gets passed to
/// Commands can return a [`Result`](crate::result::Result), which can be passed to
/// an error handler. Error handlers are functions/closures of the form
/// `fn(&mut World, CommandError)`.
///
/// By default, if a command returns an error, it will be passed to the
/// global error handler. Currently, the global error handler just panics;
/// in the future, this will be configurable.
/// The default error handler panics. It can be configured by enabling the `configurable_error_handler`
/// cargo feature, then setting the `GLOBAL_ERROR_HANDLER`.
///
/// [`Commands::override_error_handler`] allows you to override a [`Commands`]
/// instance's default error handler. This method takes an error handler
/// which will be used by all subsequent commands queued through either
/// [`Commands::queue_fallible`] or built-in commands' dedicated methods.
///
/// [`Commands::queue_fallible_with`] allows you to provide an error handler
/// directly to a command. This is unaffected by any default or override.
/// If you would like to use this method with built-in commands,
/// the [`command`] module provides unqueued forms of built-in commands
/// that you can queue manually.
/// Alternatively, you can customize the error handler for a specific command by calling [`Commands::queue_handled`].
///
/// The [`error_handler`] module provides some simple error handlers for convenience.
///
@ -117,12 +106,6 @@ use crate::{
pub struct Commands<'w, 's> {
queue: InternalQueue<'s>,
entities: &'w Entities,
/// This can be set using [`Commands::override_error_handler`] to override
/// the global error handler for all subsequent commands, which would be
/// more convenient than using [`Commands::queue_fallible_with`] to override
/// each command individually if you wanted to use the same error handler for
/// all of them.
error_handler_override: Option<fn(&mut World, CommandError)>,
}
// SAFETY: All commands [`Command`] implement [`Send`]
@ -218,7 +201,6 @@ const _: () = {
Commands {
queue: InternalQueue::CommandQueue(f0),
entities: f1,
error_handler_override: None,
}
}
}
@ -255,7 +237,6 @@ impl<'w, 's> Commands<'w, 's> {
Self {
queue: InternalQueue::CommandQueue(Deferred(queue)),
entities,
error_handler_override: None,
}
}
@ -273,7 +254,6 @@ impl<'w, 's> Commands<'w, 's> {
Self {
queue: InternalQueue::RawCommandQueue(queue),
entities,
error_handler_override: None,
}
}
@ -304,7 +284,6 @@ impl<'w, 's> Commands<'w, 's> {
}
},
entities: self.entities,
error_handler_override: self.error_handler_override,
}
}
@ -569,17 +548,16 @@ impl<'w, 's> Commands<'w, 's> {
/// Pushes a generic [`Command`] to the command queue.
///
/// If the [`Command`] returns a [`Result`], it will be handled using the [default error handler](error_handler::default).
///
/// To use a custom error handler, see [`Commands::queue_handled`].
///
/// The command can be:
/// - A custom struct that implements [`Command`].
/// - A closure or function that matches one of the following signatures:
/// - [`(&mut World)`](World)
/// - [`(&mut World)`](World)`->`[`Result`](crate::result::Result)
/// - A built-in command from the [`command`] module.
///
/// Commands can return a [`Result`], but this method will ignore them.
/// If you want to queue a command with error handling,
/// use [`Commands::queue_fallible`] or [`Commands::queue_fallible_with`].
///
/// # Example
///
/// ```
@ -587,18 +565,19 @@ impl<'w, 's> Commands<'w, 's> {
/// #[derive(Resource, Default)]
/// struct Counter(u64);
///
/// struct AddToCounter(u64);
/// struct AddToCounter(String);
///
/// impl Command for AddToCounter {
/// impl Command<Result> for AddToCounter {
/// fn apply(self, world: &mut World) -> Result {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// counter.0 += self.0;
/// let amount: u64 = self.0.parse()?;
/// counter.0 += amount;
/// Ok(())
/// }
/// }
///
/// fn add_three_to_counter_system(mut commands: Commands) {
/// commands.queue(AddToCounter(3));
/// commands.queue(AddToCounter("3".to_string()));
/// }
/// fn add_twenty_five_to_counter_system(mut commands: Commands) {
/// commands.queue(|world: &mut World| {
@ -609,7 +588,61 @@ impl<'w, 's> Commands<'w, 's> {
/// # bevy_ecs::system::assert_is_system(add_three_to_counter_system);
/// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system);
/// ```
pub fn queue<C: Command<M>, M: 'static>(&mut self, command: C) {
pub fn queue<C: Command<T> + HandleError<T>, T>(&mut self, command: C) {
self.queue_internal(command.handle_error());
}
/// Pushes a generic [`Command`] to the command queue. If the command returns a [`Result`] the given
/// `error_handler` will be used to handle error cases.
///
/// To implicitly use the default error handler, see [`Commands::queue`].
///
/// The command can be:
/// - A custom struct that implements [`Command`].
/// - A closure or function that matches one of the following signatures:
/// - [`(&mut World)`](World)
/// - [`(&mut World)`](World) `->` [`Result`]
/// - A built-in command from the [`command`] module.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::error_handler;
/// #[derive(Resource, Default)]
/// struct Counter(u64);
///
/// struct AddToCounter(String);
///
/// impl Command<Result> for AddToCounter {
/// fn apply(self, world: &mut World) -> Result {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// let amount: u64 = self.0.parse()?;
/// counter.0 += amount;
/// Ok(())
/// }
/// }
///
/// fn add_three_to_counter_system(mut commands: Commands) {
/// commands.queue_handled(AddToCounter("3".to_string()), error_handler::warn());
/// }
/// fn add_twenty_five_to_counter_system(mut commands: Commands) {
/// commands.queue(|world: &mut World| {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// counter.0 += 25;
/// });
/// }
/// # bevy_ecs::system::assert_is_system(add_three_to_counter_system);
/// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system);
/// ```
pub fn queue_handled<C: Command<T> + HandleError<T>, T>(
&mut self,
command: C,
error_handler: fn(&mut World, Error),
) {
self.queue_internal(command.handle_error_with(error_handler));
}
fn queue_internal(&mut self, command: impl Command) {
match &mut self.queue {
InternalQueue::CommandQueue(queue) => {
queue.push(command);
@ -624,75 +657,6 @@ impl<'w, 's> Commands<'w, 's> {
}
}
/// Pushes a generic [`Command`] to the command queue with error handling.
///
/// The command can be:
/// - A custom struct that implements [`Command`].
/// - A closure or function that has the signature [`(&mut World)`](World)`->`[`Result`](crate::result::Result).
/// - A built-in command from the [`command`] module.
///
/// If the command returns an error, it will panic by default. You can use
/// [`Commands::queue_fallible_with`] to override an individual command's error handler,
/// or you can use [`Commands::override_error_handler`] to override the default
/// error handler for all subsequent commands queued by this [`Commands`] instance.
pub fn queue_fallible<C: Command<M>, M: 'static>(&mut self, command: C) {
self.queue(command.with_error_handling(self.error_handler_override));
}
/// Pushes a generic [`Command`] to the command queue with a particular error handler.
///
/// The command can be:
/// - A custom struct that implements [`Command`].
/// - A closure or function that has the signature [`(&mut World)`](World)`->`[`Result`](crate::result::Result).
/// - A built-in command from the [`command`] module.
///
/// If the command returns an error, it will be passed to `error_handler`.
///
/// See the [`command`] module for built-in fallible commands that can be
/// queued manually, as well as the [`error_handler`] module for simple
/// error handlers.
pub fn queue_fallible_with<C: Command<M>, M: 'static>(
&mut self,
command: C,
error_handler: fn(&mut World, CommandError),
) {
self.queue(command.with_error_handling(Some(error_handler)));
}
/// Pushes a generic [`Command`] to the command queue with error handling.
///
/// If the command returns an error, it will be passed to the [`Commands`] instance's
/// error handler override if set, or `error_handler` otherwise.
// TODO: This is only useful for commands that fail differently (non-panic) by default, but
// still want to be overridden by the Commands instance's setting. It can be removed once
// all commands obey the global error handler by default.
fn queue_fallible_with_default<C: Command<M>, M: 'static>(
&mut self,
command: C,
error_handler: fn(&mut World, CommandError),
) {
let error_handler = self.error_handler_override.unwrap_or(error_handler);
self.queue(command.with_error_handling(Some(error_handler)));
}
/// Sets the [`Commands`] instance to use a custom error handler when encountering an error.
///
/// This will apply to all subsequent commands. You can use [`Self::reset_error_handler`] to undo this.
///
/// `fn()` can be a closure if it doesn't capture its environment.
pub fn override_error_handler(&mut self, error_handler: fn(&mut World, CommandError)) {
self.error_handler_override = Some(error_handler);
}
/// Resets the [`Commands`] instance's error handler, allowing commands
/// to respond to errors in their default manner.
///
/// This is only useful if the instance's error handler was previously overridden
/// by [`Self::override_error_handler`].
pub fn reset_error_handler(&mut self) {
self.error_handler_override = None;
}
/// Pushes a [`Command`] to the queue for creating entities, if needed,
/// and for adding a bundle to each entity.
///
@ -934,7 +898,7 @@ impl<'w, 's> Commands<'w, 's> {
/// execution of the system happens later. To get the output of a system, use
/// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command.
pub fn run_system(&mut self, id: SystemId) {
self.queue_fallible_with_default(command::run_system(id), error_handler::warn());
self.queue(command::run_system(id).handle_error_with(error_handler::warn()));
}
/// Runs the system corresponding to the given [`SystemId`].
@ -950,10 +914,7 @@ impl<'w, 's> Commands<'w, 's> {
where
I: SystemInput<Inner<'static>: Send> + 'static,
{
self.queue_fallible_with_default(
command::run_system_with(id, input),
error_handler::warn(),
);
self.queue(command::run_system_with(id, input).handle_error_with(error_handler::warn()));
}
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
@ -1027,10 +988,7 @@ impl<'w, 's> Commands<'w, 's> {
I: SystemInput + Send + 'static,
O: Send + 'static,
{
self.queue_fallible_with_default(
command::unregister_system(system_id),
error_handler::warn(),
);
self.queue(command::unregister_system(system_id).handle_error_with(error_handler::warn()));
}
/// Removes a system previously registered with [`World::register_system_cached`].
@ -1045,9 +1003,8 @@ impl<'w, 's> Commands<'w, 's> {
&mut self,
system: S,
) {
self.queue_fallible_with_default(
command::unregister_system_cached(system),
error_handler::warn(),
self.queue(
command::unregister_system_cached(system).handle_error_with(error_handler::warn()),
);
}
@ -1059,7 +1016,7 @@ impl<'w, 's> Commands<'w, 's> {
&mut self,
system: S,
) {
self.queue_fallible_with_default(command::run_system_cached(system), error_handler::warn());
self.queue(command::run_system_cached(system).handle_error_with(error_handler::warn()));
}
/// Similar to [`Self::run_system_with`], but caching the [`SystemId`] in a
@ -1072,9 +1029,8 @@ impl<'w, 's> Commands<'w, 's> {
M: 'static,
S: IntoSystem<I, (), M> + Send + 'static,
{
self.queue_fallible_with_default(
command::run_system_cached_with(system, input),
error_handler::warn(),
self.queue(
command::run_system_cached_with(system, input).handle_error_with(error_handler::warn()),
);
}
@ -1167,7 +1123,7 @@ impl<'w, 's> Commands<'w, 's> {
/// # assert_eq!(world.resource::<Counter>().0, 1);
/// ```
pub fn run_schedule(&mut self, label: impl ScheduleLabel) {
self.queue_fallible_with_default(command::run_schedule(label), error_handler::warn());
self.queue(command::run_schedule(label).handle_error_with(error_handler::warn()));
}
}
@ -1189,24 +1145,14 @@ impl<'w, 's> Commands<'w, 's> {
///
/// # Error handling
///
/// Entity commands return a [`Result`](crate::result::Result), which gets passed to
/// [`EntityCommands`] can return a [`Result`](crate::result::Result), which can be passed to
/// an error handler. Error handlers are functions/closures of the form
/// `fn(&mut World, CommandError)`.
///
/// By default, if a command returns an error, it will be passed to the
/// global error handler. Currently, the global error handler just panics;
/// in the future, this will be configurable.
/// The default error handler panics. It can be configured by enabling the `configurable_error_handler`
/// cargo feature, then setting the `GLOBAL_ERROR_HANDLER`.
///
/// [`EntityCommands::override_error_handler`] allows you to override an [`EntityCommands`]
/// instance's default error handler. This method takes an error handler
/// which will be used by all subsequent commands queued through either
/// [`EntityCommands::queue`] or built-in commands' dedicated methods.
///
/// [`EntityCommands::queue_with`] allows you to provide an error handler
/// directly to a command. This is unaffected by any default or override.
/// If you would like to use this method with built-in commands,
/// the [`entity_command`] module provides unqueued forms of built-in entity commands
/// that you can queue manually.
/// Alternatively, you can customize the error handler for a specific command by calling [`EntityCommands::queue_handled`].
///
/// The [`error_handler`] module provides some simple error handlers for convenience.
pub struct EntityCommands<'a> {
@ -1454,7 +1400,7 @@ impl<'a> EntityCommands<'a> {
component_id: ComponentId,
value: T,
) -> &mut Self {
self.queue_with_default(
self.queue_handled(
entity_command::insert_by_id(component_id, value),
error_handler::silent(),
)
@ -1510,7 +1456,7 @@ impl<'a> EntityCommands<'a> {
/// ```
#[track_caller]
pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue_with_default(entity_command::insert(bundle), error_handler::silent())
self.queue_handled(entity_command::insert(bundle), error_handler::silent())
}
/// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true.
@ -1609,7 +1555,7 @@ impl<'a> EntityCommands<'a> {
/// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist.
#[track_caller]
pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self {
self.queue_with_default(
self.queue_handled(
entity_command::insert_if_new(bundle),
error_handler::silent(),
)
@ -1654,7 +1600,53 @@ impl<'a> EntityCommands<'a> {
where
T: Bundle,
{
self.queue_with_default(entity_command::remove::<T>(), error_handler::silent())
self.queue_handled(entity_command::remove::<T>(), error_handler::warn())
}
/// Removes a [`Bundle`] of components from the entity.
///
/// # Note
///
/// Unlike [`Self::remove`], this will not panic if the associated entity does not exist.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Resource)]
/// # struct PlayerEntity { entity: Entity }
/// #[derive(Component)]
/// struct Health(u32);
/// #[derive(Component)]
/// struct Strength(u32);
/// #[derive(Component)]
/// struct Defense(u32);
///
/// #[derive(Bundle)]
/// struct CombatBundle {
/// health: Health,
/// strength: Strength,
/// }
///
/// fn remove_combat_stats_system(mut commands: Commands, player: Res<PlayerEntity>) {
/// commands
/// .entity(player.entity)
/// // You can remove individual components:
/// .try_remove::<Defense>()
/// // You can also remove pre-defined Bundles of components:
/// .try_remove::<CombatBundle>()
/// // You can also remove tuples of components and bundles.
/// // This is equivalent to the calls above:
/// .try_remove::<(Defense, CombatBundle)>();
/// }
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
/// ```
pub fn try_remove<T>(&mut self) -> &mut Self
where
T: Bundle,
{
self.queue_handled(entity_command::remove::<T>(), error_handler::silent())
}
/// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity.
@ -1682,10 +1674,7 @@ impl<'a> EntityCommands<'a> {
/// # bevy_ecs::system::assert_is_system(remove_with_requires_system);
/// ```
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
self.queue_with_default(
entity_command::remove_with_requires::<T>(),
error_handler::silent(),
)
self.queue(entity_command::remove_with_requires::<T>())
}
/// Removes a dynamic [`Component`] from the entity if it exists.
@ -1694,15 +1683,12 @@ impl<'a> EntityCommands<'a> {
///
/// Panics if the provided [`ComponentId`] does not exist in the [`World`].
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
self.queue_with_default(
entity_command::remove_by_id(component_id),
error_handler::silent(),
)
self.queue(entity_command::remove_by_id(component_id))
}
/// Removes all components associated with the entity.
pub fn clear(&mut self) -> &mut Self {
self.queue_with_default(entity_command::clear(), error_handler::silent())
self.queue(entity_command::clear())
}
/// Despawns the entity.
@ -1735,38 +1721,30 @@ impl<'a> EntityCommands<'a> {
/// ```
#[track_caller]
pub fn despawn(&mut self) {
self.queue_with_default(entity_command::despawn(), error_handler::warn());
self.queue_handled(entity_command::despawn(), error_handler::warn());
}
/// Despawns the entity.
///
/// This will not emit a warning if the entity does not exist, essentially performing
/// the same function as [`Self::despawn`] without emitting warnings.
#[track_caller]
pub fn try_despawn(&mut self) {
self.queue_with_default(entity_command::despawn(), error_handler::silent());
self.queue_handled(entity_command::despawn(), error_handler::silent());
}
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
///
/// If the [`EntityCommand`] returns a [`Result`], it will be handled using the [default error handler](error_handler::default).
///
/// To use a custom error handler, see [`EntityCommands::queue_handled`].
///
/// The command can be:
/// - A custom struct that implements [`EntityCommand`].
/// - A closure or function that matches one of the following signatures:
/// - `(Entity, &mut World)`
/// - `(Entity, &mut World) ->`[`Result`](crate::result::Result)
/// - A closure or function that matches the following signature:
/// - [`(EntityWorldMut)`](EntityWorldMut)
/// - [`(EntityWorldMut)`](EntityWorldMut)`->`[`Result`](crate::result::Result)
/// - [`(EntityWorldMut)`](EntityWorldMut) `->` [`Result`]
/// - A built-in command from the [`entity_command`] module.
///
/// All entity commands are fallible, because they must return an error if the entity
/// doesn't exist when the command is executed. Therefore, all entity commands are
/// queued with error handling.
///
/// If the command returns an error, it will panic by default. You can use
/// [`EntityCommands::queue_with`] to override an individual command's error handler,
/// or you can use [`EntityCommands::override_error_handler`] to override the default
/// error handler for all subsequent commands queued by this [`EntityCommands`] instance.
///
/// # Examples
///
/// ```
@ -1781,83 +1759,53 @@ impl<'a> EntityCommands<'a> {
/// # }
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
pub fn queue<M: 'static>(&mut self, command: impl EntityCommand<M>) -> &mut Self {
self.commands
.queue_fallible(command.with_entity(self.entity));
pub fn queue<C: EntityCommand<T> + CommandWithEntity<M>, T, M>(
&mut self,
command: C,
) -> &mut Self {
self.commands.queue(command.with_entity(self.entity));
self
}
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
/// If the command returns a [`Result`] the given `error_handler` will be used to handle error cases.
///
/// To implicitly use the default error handler, see [`EntityCommands::queue`].
///
/// The command can be:
/// - A custom struct that implements [`EntityCommand`].
/// - A closure or function that matches one of the following signatures:
/// - `(Entity, &mut World)`
/// - `(Entity, &mut World) ->`[`Result`](crate::result::Result)
/// - A closure or function that matches the following signature:
/// - [`(EntityWorldMut)`](EntityWorldMut)
/// - [`(EntityWorldMut)`](EntityWorldMut)`->`[`Result`](crate::result::Result)
/// - [`(EntityWorldMut)`](EntityWorldMut) `->` [`Result`]
/// - A built-in command from the [`entity_command`] module.
///
/// All entity commands are fallible, because they must return an error if the entity
/// doesn't exist when the command is executed. Therefore, all entity commands are
/// queued with error handling.
/// # Examples
///
/// If the command returns an error, it will be passed to `error_handler`.
///
/// See the [`entity_command`] module for built-in entity commands that can be
/// queued manually, as well as the [`error_handler`] module for simple
/// error handlers.
pub fn queue_with<M: 'static>(
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::system::error_handler;
/// # fn my_system(mut commands: Commands) {
/// commands
/// .spawn_empty()
/// // Closures with this signature implement `EntityCommand`.
/// .queue_handled(
/// |entity: EntityWorldMut| -> Result {
/// let value: usize = "100".parse()?;
/// println!("Successfully parsed the value {} for entity {}", value, entity.id());
/// Ok(())
/// },
/// error_handler::warn()
/// );
/// # }
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
pub fn queue_handled<C: EntityCommand<T> + CommandWithEntity<M>, T, M>(
&mut self,
command: impl EntityCommand<M>,
error_handler: fn(&mut World, CommandError),
command: C,
error_handler: fn(&mut World, Error),
) -> &mut Self {
self.commands
.queue_fallible_with(command.with_entity(self.entity), error_handler);
self
}
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
///
/// If the command returns an error, it will be passed to the [`EntityCommands`] instance's
/// error handler override if set, or `default_error_handler` otherwise.
// TODO: This is only useful for commands that fail differently (non-panic) by default, but
// still want to be overridden by the EntityCommands instance's setting. It can be removed once
// all commands obey the global error handler by default.
fn queue_with_default<M: 'static>(
&mut self,
command: impl EntityCommand<M>,
default_error_handler: fn(&mut World, CommandError),
) -> &mut Self {
let error_handler = self
.commands
.error_handler_override
.unwrap_or(default_error_handler);
self.commands
.queue_fallible_with(command.with_entity(self.entity), error_handler);
self
}
/// Sets the [`EntityCommands`] instance to use a custom error handler when encountering an error.
///
/// This will apply to all subsequent commands. You can use [`Self::reset_error_handler`] to undo this.
///
/// `fn()` can be a closure if it doesn't capture its environment.
pub fn override_error_handler(
&mut self,
error_handler: fn(&mut World, CommandError),
) -> &mut Self {
self.commands.override_error_handler(error_handler);
self
}
/// Resets the [`EntityCommands`] instance's error handler, allowing commands
/// to respond to errors in their default manner.
///
/// This is only useful if the instance's error handler was previously overridden
/// by [`Self::override_error_handler`].
pub fn reset_error_handler(&mut self) -> &mut Self {
self.commands.reset_error_handler();
.queue_handled(command.with_entity(self.entity), error_handler);
self
}
@ -1902,7 +1850,7 @@ impl<'a> EntityCommands<'a> {
where
T: Bundle,
{
self.queue_with_default(entity_command::retain::<T>(), error_handler::panic())
self.queue(entity_command::retain::<T>())
}
/// Logs the components of the entity at the info level.
@ -1938,7 +1886,7 @@ impl<'a> EntityCommands<'a> {
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.queue_with_default(entity_command::observe(observer), error_handler::silent())
self.queue(entity_command::observe(observer))
}
/// Clones parts of an entity (components, observers, etc.) onto another entity,
@ -1986,10 +1934,7 @@ impl<'a> EntityCommands<'a> {
target: Entity,
config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static,
) -> &mut Self {
self.queue_with_default(
entity_command::clone_with(target, config),
error_handler::silent(),
)
self.queue(entity_command::clone_with(target, config))
}
/// Spawns a clone of this entity and returns the [`EntityCommands`] of the clone.
@ -2085,10 +2030,7 @@ impl<'a> EntityCommands<'a> {
///
/// The command will panic when applied if the target entity does not exist.
pub fn clone_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
self.queue_with_default(
entity_command::clone_components::<B>(target),
error_handler::silent(),
)
self.queue(entity_command::clone_components::<B>(target))
}
/// Clones the specified components of this entity and inserts them into another entity,
@ -2101,10 +2043,7 @@ impl<'a> EntityCommands<'a> {
///
/// The command will panic when applied if the target entity does not exist.
pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self {
self.queue_with_default(
entity_command::move_components::<B>(target),
error_handler::silent(),
)
self.queue(entity_command::move_components::<B>(target))
}
}
@ -2128,29 +2067,6 @@ impl<'a, T: Component<Mutability = Mutable>> EntityEntryCommands<'a, T> {
}
impl<'a, T: Component> EntityEntryCommands<'a, T> {
/// Sets the [`EntityEntryCommands`] instance to use a custom error handler when encountering an error.
///
/// This will apply to all subsequent commands. You can use [`Self::reset_error_handler`] to undo this.
///
/// `fn()` can be a closure if it doesn't capture its environment.
pub fn override_error_handler(
&mut self,
error_handler: fn(&mut World, CommandError),
) -> &mut Self {
self.entity_commands.override_error_handler(error_handler);
self
}
/// Resets the [`EntityEntryCommands`] instance's error handler, allowing commands
/// to respond to errors in their default manner.
///
/// This is only useful if the instance's error handler was previously overridden
/// by [`Self::override_error_handler`].
pub fn reset_error_handler(&mut self) -> &mut Self {
self.entity_commands.reset_error_handler();
self
}
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
///
/// See also [`or_insert_with`](Self::or_insert_with).

View File

@ -1,21 +1,17 @@
use crate::system::{SystemBuffer, SystemMeta};
use crate::{
system::{Command, SystemBuffer, SystemMeta},
world::{DeferredWorld, World},
};
use alloc::{boxed::Box, vec::Vec};
use bevy_ptr::{OwningPtr, Unaligned};
use core::{
fmt::Debug,
marker::PhantomData,
mem::{size_of, MaybeUninit},
panic::AssertUnwindSafe,
ptr::{addr_of_mut, NonNull},
};
use alloc::{boxed::Box, vec::Vec};
use bevy_ptr::{OwningPtr, Unaligned};
use log::warn;
use crate::{system::Command, world::World};
use super::DeferredWorld;
struct CommandMeta {
/// SAFETY: The `value` must point to a value of type `T: Command`,
/// where `T` is some specific type that was used to produce this metadata.
@ -76,11 +72,7 @@ unsafe impl Sync for CommandQueue {}
impl CommandQueue {
/// Push a [`Command`] onto the queue.
#[inline]
pub fn push<C, M>(&mut self, command: C)
where
C: Command<M>,
M: 'static,
{
pub fn push(&mut self, command: impl Command) {
// SAFETY: self is guaranteed to live for the lifetime of this method
unsafe {
self.get_raw().push(command);
@ -156,23 +148,14 @@ impl RawCommandQueue {
///
/// * Caller ensures that `self` has not outlived the underlying queue
#[inline]
pub unsafe fn push<C, M>(&mut self, command: C)
where
C: Command<M>,
M: 'static,
{
pub unsafe fn push<C: Command>(&mut self, command: C) {
// Stores a command alongside its metadata.
// `repr(C)` prevents the compiler from reordering the fields,
// while `repr(packed)` prevents the compiler from inserting padding bytes.
#[repr(C, packed)]
struct Packed<C, M>
where
C: Command<M>,
M: 'static,
{
struct Packed<C: Command> {
meta: CommandMeta,
command: C,
phantom: PhantomData<M>,
}
let meta = CommandMeta {
@ -187,7 +170,7 @@ impl RawCommandQueue {
Some(mut world) => {
// SAFETY: Caller ensures pointer is not null
let world = unsafe { world.as_mut() };
_ = command.apply(world);
command.apply(world);
// The command may have queued up world commands, which we flush here to ensure they are also picked up.
// If the current command queue already the World Command queue, this will still behave appropriately because the global cursor
// is still at the current `stop`, ensuring only the newly queued Commands will be applied.
@ -205,7 +188,7 @@ impl RawCommandQueue {
let old_len = bytes.len();
// Reserve enough bytes for both the metadata and the command itself.
bytes.reserve(size_of::<Packed<C, M>>());
bytes.reserve(size_of::<Packed<C>>());
// Pointer to the bytes at the end of the buffer.
// SAFETY: We know it is within bounds of the allocation, due to the call to `.reserve()`.
@ -217,18 +200,15 @@ impl RawCommandQueue {
// The call to `reserve()` ensures that the buffer has enough space to fit a value of type `C`,
// and it is valid to write any bit pattern since the underlying buffer is of type `MaybeUninit<u8>`.
unsafe {
ptr.cast::<Packed<C, M>>().write_unaligned(Packed {
meta,
command,
phantom: PhantomData,
});
ptr.cast::<Packed<C>>()
.write_unaligned(Packed { meta, command });
}
// Extend the length of the buffer to include the data we just wrote.
// SAFETY: The new length is guaranteed to fit in the vector's capacity,
// due to the call to `.reserve()` above.
unsafe {
bytes.set_len(old_len + size_of::<Packed<C, M>>());
bytes.set_len(old_len + size_of::<Packed<C>>());
}
}
@ -355,7 +335,7 @@ impl SystemBuffer for CommandQueue {
#[cfg(test)]
mod test {
use super::*;
use crate::{self as bevy_ecs, result::Result, system::Resource};
use crate::{self as bevy_ecs, system::Resource};
use alloc::{borrow::ToOwned, string::String, sync::Arc};
use core::{
panic::AssertUnwindSafe,
@ -381,9 +361,7 @@ mod test {
}
impl Command for DropCheck {
fn apply(self, _: &mut World) -> Result {
Ok(())
}
fn apply(self, _: &mut World) {}
}
#[test]
@ -430,9 +408,8 @@ mod test {
struct SpawnCommand;
impl Command for SpawnCommand {
fn apply(self, world: &mut World) -> Result {
fn apply(self, world: &mut World) {
world.spawn_empty();
Ok(())
}
}
@ -460,7 +437,7 @@ mod test {
#[allow(dead_code)]
struct PanicCommand(String);
impl Command for PanicCommand {
fn apply(self, _: &mut World) -> Result {
fn apply(self, _: &mut World) {
panic!("command is panicking");
}
}
@ -536,9 +513,7 @@ mod test {
#[allow(dead_code)]
struct CommandWithPadding(u8, u16);
impl Command for CommandWithPadding {
fn apply(self, _: &mut World) -> Result {
Ok(())
}
fn apply(self, _: &mut World) {}
}
#[cfg(miri)]

View File

@ -11,7 +11,7 @@ use crate::{
query::{Access, ReadOnlyQueryData},
removal_detection::RemovedComponentEvents,
storage::Storages,
system::IntoObserverSystem,
system::{IntoObserverSystem, Resource},
world::{error::EntityComponentError, DeferredWorld, Mut, World},
};
use alloc::vec::Vec;
@ -1322,6 +1322,45 @@ impl<'w> EntityWorldMut<'w> {
unsafe { self.into_unsafe_entity_cell().get_mut() }
}
/// Gets a reference to the resource of the given type
///
/// # Panics
///
/// Panics if the resource does not exist.
/// Use [`get_resource`](EntityWorldMut::get_resource) instead if you want to handle this case.
#[inline]
#[track_caller]
pub fn resource<R: Resource>(&self) -> &R {
self.world.resource::<R>()
}
/// Gets a mutable reference to the resource of the given type
///
/// # Panics
///
/// Panics if the resource does not exist.
/// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case.
///
/// If you want to instead insert a value if the resource does not exist,
/// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with).
#[inline]
#[track_caller]
pub fn resource_mut<R: Resource>(&mut self) -> Mut<'_, R> {
self.world.resource_mut::<R>()
}
/// Gets a reference to the resource of the given type if it exists
#[inline]
pub fn get_resource<R: Resource>(&self) -> Option<&R> {
self.world.get_resource()
}
/// Gets a mutable reference to the resource of the given type if it exists
#[inline]
pub fn get_resource_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> {
self.world.get_resource_mut()
}
/// Retrieves the change ticks for the given component. This can be useful for implementing change
/// detection in custom runtimes.
///

View File

@ -351,15 +351,15 @@ impl BuildChildren for EntityCommands<'_> {
if children.contains(&parent) {
panic!("Entity cannot be a child of itself.");
}
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).add_children(&children);
self.queue(move |mut entity: EntityWorldMut| {
entity.add_children(&children);
})
}
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
let child = self.commands().spawn(bundle).id();
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).add_child(child);
self.queue(move |mut entity: EntityWorldMut| {
entity.add_child(child);
})
}
@ -369,8 +369,8 @@ impl BuildChildren for EntityCommands<'_> {
panic!("Cannot add entity as a child of itself.");
}
let children = SmallVec::<[Entity; 8]>::from_slice(children);
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).add_children(&children);
self.queue(move |mut entity: EntityWorldMut| {
entity.add_children(&children);
})
}
@ -380,15 +380,15 @@ impl BuildChildren for EntityCommands<'_> {
panic!("Cannot insert entity as a child of itself.");
}
let children = SmallVec::<[Entity; 8]>::from_slice(children);
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).insert_children(index, &children);
self.queue(move |mut entity: EntityWorldMut| {
entity.insert_children(index, &children);
})
}
fn remove_children(&mut self, children: &[Entity]) -> &mut Self {
let children = SmallVec::<[Entity; 8]>::from_slice(children);
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).remove_children(&children);
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_children(&children);
})
}
@ -397,14 +397,14 @@ impl BuildChildren for EntityCommands<'_> {
if child == parent {
panic!("Cannot add entity as a child of itself.");
}
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).add_child(child);
self.queue(move |mut entity: EntityWorldMut| {
entity.add_child(child);
})
}
fn clear_children(&mut self) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).clear_children();
self.queue(move |mut entity: EntityWorldMut| {
entity.clear_children();
})
}
@ -414,8 +414,8 @@ impl BuildChildren for EntityCommands<'_> {
panic!("Cannot replace entity as a child of itself.");
}
let children = SmallVec::<[Entity; 8]>::from_slice(children);
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).replace_children(&children);
self.queue(move |mut entity: EntityWorldMut| {
entity.replace_children(&children);
})
}
@ -424,14 +424,16 @@ impl BuildChildren for EntityCommands<'_> {
if child == parent {
panic!("Cannot set parent to itself");
}
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(parent).add_child(entity);
self.queue(move |mut entity: EntityWorldMut| {
entity.world_scope(|world| {
world.entity_mut(parent).add_child(child);
});
})
}
fn remove_parent(&mut self) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
world.entity_mut(entity).remove_parent();
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_parent();
})
}
}

View File

@ -5,7 +5,7 @@ use crate::{
use bevy_ecs::{
component::ComponentCloneHandler,
entity::{ComponentCloneCtx, Entity, EntityCloneBuilder},
system::EntityCommands,
system::{error_handler, EntityCommands},
world::{DeferredWorld, EntityWorldMut, World},
};
use log::debug;
@ -68,32 +68,44 @@ impl DespawnRecursiveExt for EntityCommands<'_> {
/// This will emit warnings for any entity that does not exist.
fn despawn_recursive(mut self) {
let warn = true;
self.queue(move |entity: Entity, world: &mut World| {
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "DespawnRecursive",
entity = tracing::field::debug(entity),
warn = tracing::field::debug(warn)
)
.entered();
despawn_with_children_recursive(world, entity, warn);
});
self.queue_handled(
move |mut entity: EntityWorldMut| {
let id = entity.id();
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "DespawnRecursive",
entity = tracing::field::debug(id),
warn = tracing::field::debug(warn)
)
.entered();
entity.world_scope(|world| {
despawn_with_children_recursive(world, id, warn);
});
},
error_handler::warn(),
);
}
fn despawn_descendants(&mut self) -> &mut Self {
let warn = true;
self.queue(move |entity: Entity, world: &mut World| {
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "DespawnChildrenRecursive",
entity = tracing::field::debug(entity),
warn = tracing::field::debug(warn)
)
.entered();
despawn_children_recursive(world, entity, warn);
});
self.queue_handled(
move |mut entity: EntityWorldMut| {
let id = entity.id();
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "DespawnChildrenRecursive",
entity = tracing::field::debug(id),
warn = tracing::field::debug(warn)
)
.entered();
entity.world_scope(|world| {
despawn_children_recursive(world, id, warn);
});
},
error_handler::warn(),
);
self
}
@ -101,32 +113,44 @@ impl DespawnRecursiveExt for EntityCommands<'_> {
/// This will never emit warnings.
fn try_despawn_recursive(mut self) {
let warn = false;
self.queue(move |entity: Entity, world: &mut World| {
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "TryDespawnRecursive",
entity = tracing::field::debug(entity),
warn = tracing::field::debug(warn)
)
.entered();
despawn_with_children_recursive(world, entity, warn);
});
self.queue_handled(
move |mut entity: EntityWorldMut| {
let id = entity.id();
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "TryDespawnRecursive",
entity = tracing::field::debug(id),
warn = tracing::field::debug(warn)
)
.entered();
entity.world_scope(|world| {
despawn_with_children_recursive(world, id, warn);
});
},
error_handler::silent(),
);
}
fn try_despawn_descendants(&mut self) -> &mut Self {
let warn = false;
self.queue(move |entity: Entity, world: &mut World| {
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "TryDespawnChildrenRecursive",
entity = tracing::field::debug(entity),
warn = tracing::field::debug(warn)
)
.entered();
despawn_children_recursive(world, entity, warn);
});
self.queue_handled(
move |mut entity: EntityWorldMut| {
let id = entity.id();
#[cfg(feature = "trace")]
let _span = tracing::info_span!(
"command",
name = "TryDespawnChildrenRecursive",
entity = tracing::field::debug(id),
warn = tracing::field::debug(warn)
)
.entered();
entity.world_scope(|world| {
despawn_children_recursive(world, id, warn);
});
},
error_handler::silent(),
);
self
}
}
@ -250,7 +274,7 @@ mod tests {
use alloc::{borrow::ToOwned, string::String, vec, vec::Vec};
use bevy_ecs::{
component::Component,
system::{error_handler, Commands},
system::Commands,
world::{CommandQueue, World},
};
@ -310,7 +334,6 @@ mod tests {
{
let mut commands = Commands::new(&mut queue, &world);
commands.override_error_handler(error_handler::silent());
commands.entity(parent_entity).despawn_recursive();
// despawning the same entity twice should not panic
commands.entity(parent_entity).despawn_recursive();

View File

@ -514,7 +514,7 @@ pub(crate) fn extracted_light_removed(
mut commands: Commands,
) {
if let Some(mut v) = commands.get_entity(trigger.target()) {
v.remove::<LightViewEntities>();
v.try_remove::<LightViewEntities>();
}
}

View File

@ -2,11 +2,7 @@
//! while preserving [`GlobalTransform`].
use crate::prelude::{GlobalTransform, Transform};
use bevy_ecs::{
entity::Entity,
system::EntityCommands,
world::{EntityWorldMut, World},
};
use bevy_ecs::{entity::Entity, system::EntityCommands, world::EntityWorldMut};
use bevy_hierarchy::BuildChildren;
/// Collection of methods similar to [`BuildChildren`], but preserving each
@ -35,18 +31,14 @@ pub trait BuildChildrenTransformExt {
impl BuildChildrenTransformExt for EntityCommands<'_> {
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.set_parent_in_place(parent);
}
self.queue(move |mut entity: EntityWorldMut| {
entity.set_parent_in_place(parent);
})
}
fn remove_parent_in_place(&mut self) -> &mut Self {
self.queue(move |entity: Entity, world: &mut World| {
if let Ok(mut entity) = world.get_entity_mut(entity) {
entity.remove_parent_in_place();
}
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_parent_in_place();
})
}
}