Add Result handling to Commands and EntityCommands (#17043)

## Objective

Fixes #2004
Fixes #3845
Fixes #7118
Fixes #10166

## Solution

- The crux of this PR is the new `Command::with_error_handling` method.
This wraps the relevant command in another command that, when applied,
will apply the original command and handle any resulting errors.
- To enable this, `Command::apply` and `EntityCommand::apply` now return
`Result`.
- `Command::with_error_handling` takes as a parameter an error handler
of the form `fn(&mut World, CommandError)`, which it passes the error
to.
- `CommandError` is an enum that can be either `NoSuchEntity(Entity)` or
`CommandFailed(Box<dyn Error>)`.

### Closures
- Closure commands can now optionally return `Result`, which will be
passed to `with_error_handling`.

### Commands
- Fallible commands can be queued with `Commands::queue_fallible` and
`Commands::queue_fallible_with`, which call `with_error_handling` before
queuing them (using `Commands::queue` will queue them without error
handling).
- `Commands::queue_fallible_with` takes an `error_handler` parameter,
which will be used by `with_error_handling` instead of a command's
default.
- The `command` submodule provides unqueued forms of built-in fallible
commands so that you can use them with `queue_fallible_with`.
- There is also an `error_handler` submodule that provides simple error
handlers for convenience.

### Entity Commands
- `EntityCommand` now automatically checks if the entity exists before
executing the command, and returns `NoSuchEntity` if it doesn't.
- Since all entity commands might need to return an error, they are
always queued with error handling.
- `EntityCommands::queue_with` takes an `error_handler` parameter, which
will be used by `with_error_handling` instead of a command's default.
- The `entity_command` submodule provides unqueued forms of built-in
entity commands so that you can use them with `queue_with`.

### Defaults
- In the future, commands should all fail according to the global error
handling setting. That doesn't exist yet though.
- For this PR, commands all fail the way they do on `main`.
- Both now and in the future, the defaults can be overridden by
`Commands::override_error_handler` (or equivalent methods on
`EntityCommands` and `EntityEntryCommands`).
- `override_error_handler` takes an error handler (`fn(&mut World,
CommandError)`) and passes it to every subsequent command queued with
`Commands::queue_fallible` or `EntityCommands::queue`.
- The `_with` variants of the queue methods will still provide an error
handler directly to the command.
- An override can be reset with `reset_error_handler`.

## Future Work

- After a universal error handling mode is added, we can change all
commands to fail that way by default.
- Once we have all commands failing the same way (which would require
either the full removal of `try` variants or just making them useless
while they're deprecated), `queue_fallible_with_default` could be
removed, since its only purpose is to enable commands having different
defaults.
This commit is contained in:
JaySpruce 2025-01-07 10:50:52 -06:00 committed by GitHub
parent 5faff84c10
commit ee4414159b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1158 additions and 453 deletions

View File

@ -2,8 +2,9 @@ use core::hint::black_box;
use bevy_ecs::{
component::Component,
system::Commands,
world::{Command, CommandQueue, World},
result::Result,
system::{Command, Commands},
world::{CommandQueue, World},
};
use criterion::Criterion;
@ -136,16 +137,18 @@ struct FakeCommandA;
struct FakeCommandB(u64);
impl Command for FakeCommandA {
fn apply(self, world: &mut World) {
fn apply(self, world: &mut World) -> Result {
black_box(self);
black_box(world);
Ok(())
}
}
impl Command for FakeCommandB {
fn apply(self, world: &mut World) {
fn apply(self, world: &mut World) -> Result {
black_box(self);
black_box(world);
Ok(())
}
}
@ -180,9 +183,10 @@ 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) {
fn apply(self, world: &mut World) -> Result {
black_box(self);
black_box(world);
Ok(())
}
}

View File

@ -336,12 +336,12 @@ impl SparseSetIndex for BundleId {
}
}
// What to do on insertion if component already exists
/// What to do on insertion if a component already exists.
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum InsertMode {
pub enum InsertMode {
/// Any existing components of a matching type will be overwritten.
Replace,
/// Any existing components of a matching type will kept unchanged.
/// Any existing components of a matching type will be left unchanged.
Keep,
}

View File

@ -73,13 +73,13 @@ pub mod prelude {
IntoSystemSet, IntoSystemSetConfigs, Schedule, Schedules, SystemSet,
},
system::{
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem, Res, ResMut, Resource,
Single, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction,
WithParamWarnPolicy,
Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef,
IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem,
Res, ResMut, Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder,
SystemParamFunction, WithParamWarnPolicy,
},
world::{
Command, EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
},
};

View File

@ -0,0 +1,345 @@
//! This module contains the definition of the [`Command`] trait, as well as
//! blanket implementations of the trait for closures.
//!
//! It also contains functions that return closures for use with
//! [`Commands`](crate::system::Commands).
#[cfg(feature = "track_location")]
use core::panic::Location;
use crate::{
bundle::{Bundle, InsertMode},
entity::Entity,
event::{Event, Events},
observer::TriggerTargets,
result::Result,
schedule::ScheduleLabel,
system::{CommandError, IntoSystem, Resource, SystemId, SystemInput},
world::{FromWorld, SpawnBatchIter, World},
};
/// A [`World`] mutation.
///
/// Should be used with [`Commands::queue`](crate::system::Commands::queue).
///
/// # Usage
///
/// ```
/// # use bevy_ecs::prelude::*;
/// // Our world resource
/// #[derive(Resource, Default)]
/// struct Counter(u64);
///
/// // Our custom command
/// struct AddToCounter(u64);
///
/// impl Command for AddToCounter {
/// fn apply(self, world: &mut World) -> Result {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// counter.0 += self.0;
/// Ok(())
/// }
/// }
///
/// fn some_system(mut commands: Commands) {
/// 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 {
/// 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);
}
}
}
}
impl<F> Command for F
where
F: FnOnce(&mut World) + 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 {
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(())
}
fn apply_internal(self, world: &mut World) -> Result<(), CommandError> {
self(world)
}
}
/// A [`Command`] that consumes an iterator of [`Bundles`](Bundle) to spawn a series of entities.
///
/// This is more efficient than spawning the entities individually.
#[track_caller]
pub fn spawn_batch<I>(bundles_iter: I) -> impl Command
where
I: IntoIterator + Send + Sync + 'static,
I::Item: Bundle,
{
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| {
SpawnBatchIter::new(
world,
bundles_iter.into_iter(),
#[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 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.
///
/// This is more efficient than inserting the bundles individually.
#[track_caller]
pub fn try_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.try_insert_batch_with_caller(
batch,
mode,
#[cfg(feature = "track_location")]
caller,
);
}
}
/// A [`Command`] that inserts a [`Resource`] into the world using a value
/// created with the [`FromWorld`] trait.
#[track_caller]
pub fn init_resource<R: Resource + FromWorld>() -> impl Command {
move |world: &mut World| {
world.init_resource::<R>();
}
}
/// A [`Command`] that inserts a [`Resource`] into the world.
#[track_caller]
pub fn insert_resource<R: Resource>(resource: R) -> impl Command {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| {
world.insert_resource_with_caller(
resource,
#[cfg(feature = "track_location")]
caller,
);
}
}
/// A [`Command`] that removes a [`Resource`] from the world.
pub fn remove_resource<R: Resource>() -> impl Command {
move |world: &mut World| {
world.remove_resource::<R>();
}
}
/// A [`Command`] that runs the system corresponding to the given [`SystemId`].
pub fn run_system<O: 'static>(id: SystemId<(), O>) -> impl Command<Result> {
move |world: &mut World| -> Result {
world.run_system(id)?;
Ok(())
}
}
/// A [`Command`] that runs the system corresponding to the given [`SystemId`]
/// and provides the given input value.
pub fn run_system_with<I>(id: SystemId<I>, input: I::Inner<'static>) -> impl Command<Result>
where
I: SystemInput<Inner<'static>: Send> + 'static,
{
move |world: &mut World| -> Result {
world.run_system_with(id, input)?;
Ok(())
}
}
/// A [`Command`] that runs the given system,
/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource.
pub fn run_system_cached<M, S>(system: S) -> impl Command<Result>
where
M: 'static,
S: IntoSystem<(), (), M> + Send + 'static,
{
move |world: &mut World| -> Result {
world.run_system_cached(system)?;
Ok(())
}
}
/// A [`Command`] that runs the given system with the given input value,
/// caching its [`SystemId`] in a [`CachedSystemId`](crate::system::CachedSystemId) resource.
pub fn run_system_cached_with<I, M, S>(system: S, input: I::Inner<'static>) -> impl Command<Result>
where
I: SystemInput<Inner<'static>: Send> + Send + 'static,
M: 'static,
S: IntoSystem<I, (), M> + Send + 'static,
{
move |world: &mut World| -> Result {
world.run_system_cached_with(system, input)?;
Ok(())
}
}
/// A [`Command`] that removes a system previously registered with
/// [`Commands::register_system`](crate::system::Commands::register_system) or
/// [`World::register_system`].
pub fn unregister_system<I, O>(system_id: SystemId<I, O>) -> impl Command<Result>
where
I: SystemInput + Send + 'static,
O: Send + 'static,
{
move |world: &mut World| -> Result {
world.unregister_system(system_id)?;
Ok(())
}
}
/// A [`Command`] that removes a system previously registered with
/// [`World::register_system_cached`].
pub fn unregister_system_cached<I, O, M, S>(system: S) -> impl Command<Result>
where
I: SystemInput + Send + 'static,
O: 'static,
M: 'static,
S: IntoSystem<I, O, M> + Send + 'static,
{
move |world: &mut World| -> Result {
world.unregister_system_cached(system)?;
Ok(())
}
}
/// A [`Command`] that runs the schedule corresponding to the given [`ScheduleLabel`].
pub fn run_schedule(label: impl ScheduleLabel) -> impl Command<Result> {
move |world: &mut World| -> Result {
world.try_run_schedule(label)?;
Ok(())
}
}
/// A [`Command`] that sends a global [`Trigger`](crate::observer::Trigger) without any targets.
pub fn trigger(event: impl Event) -> impl Command {
move |world: &mut World| {
world.trigger(event);
}
}
/// A [`Command`] that sends a [`Trigger`](crate::observer::Trigger) for the given targets.
pub fn trigger_targets(
event: impl Event,
targets: impl TriggerTargets + Send + Sync + 'static,
) -> impl Command {
move |world: &mut World| {
world.trigger_targets(event, targets);
}
}
/// A [`Command`] that sends an arbitrary [`Event`].
#[track_caller]
pub fn send_event<E: Event>(event: E) -> impl Command {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |world: &mut World| {
let mut events = world.resource_mut::<Events<E>>();
events.send_with_caller(
event,
#[cfg(feature = "track_location")]
caller,
);
}
}

View File

@ -0,0 +1,329 @@
//! This module contains the definition of the [`EntityCommand`] trait, as well as
//! blanket implementations of the trait for closures.
//!
//! It also contains functions that return closures for use with
//! [`EntityCommands`](crate::system::EntityCommands).
use alloc::vec::Vec;
use log::info;
#[cfg(feature = "track_location")]
use core::panic::Location;
use crate::{
bundle::{Bundle, InsertMode},
component::{Component, ComponentId, ComponentInfo},
entity::{Entity, EntityCloneBuilder},
event::Event,
result::Result,
system::{Command, CommandError, IntoObserverSystem},
world::{EntityWorldMut, FromWorld, World},
};
use bevy_ptr::OwningPtr;
/// A [`Command`] which gets executed for a given [`Entity`].
///
/// # Examples
///
/// ```
/// # use std::collections::HashSet;
/// # use bevy_ecs::prelude::*;
/// use bevy_ecs::system::EntityCommand;
/// #
/// # #[derive(Component, PartialEq)]
/// # struct Name(String);
/// # impl Name {
/// # fn new(s: String) -> Self { Name(s) }
/// # fn as_str(&self) -> &str { &self.0 }
/// # }
///
/// #[derive(Resource, Default)]
/// struct Counter(i64);
///
/// /// A `Command` which names an entity based on a global counter.
/// fn count_name(entity: Entity, world: &mut World) {
/// // 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;
///
/// // Name the entity after the value of the counter.
/// world.entity_mut(entity).insert(Name::new(format!("Entity #{i}")));
/// }
///
/// // App creation boilerplate omitted...
/// # let mut world = World::new();
/// # world.init_resource::<Counter>();
/// #
/// # let mut setup_schedule = Schedule::default();
/// # setup_schedule.add_systems(setup);
/// # let mut assert_schedule = Schedule::default();
/// # assert_schedule.add_systems(assert_names);
/// #
/// # setup_schedule.run(&mut world);
/// # assert_schedule.run(&mut world);
///
/// fn setup(mut commands: Commands) {
/// commands.spawn_empty().queue(count_name);
/// commands.spawn_empty().queue(count_name);
/// }
///
/// fn assert_names(named: Query<&Name>) {
/// // We use a HashSet because we do not care about the order.
/// let names: HashSet<_> = named.iter().map(Name::as_str).collect();
/// 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 {
/// Executes this command for the given [`Entity`] and
/// returns a [`Result`] for error handling.
fn apply(self, entity: Entity, world: &mut World) -> Result;
/// 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,
{
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),
))
}
}
}
}
impl<F> EntityCommand for F
where
F: FnOnce(Entity, &mut World) + Send + 'static,
{
fn apply(self, id: Entity, world: &mut World) -> Result {
self(id, world);
Ok(())
}
}
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)
}
}
impl<F> EntityCommand<World> for F
where
F: FnOnce(EntityWorldMut) + 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))
}
}
/// 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> {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| {
entity.insert_with_caller(
bundle,
InsertMode::Replace,
#[cfg(feature = "track_location")]
caller,
);
}
}
/// 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> {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |mut entity: EntityWorldMut| {
entity.insert_with_caller(
bundle,
InsertMode::Keep,
#[cfg(feature = "track_location")]
caller,
);
}
}
/// 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> {
move |mut entity: EntityWorldMut| {
// SAFETY:
// - `component_id` safety is ensured by the caller
// - `ptr` is valid within the `make` block
OwningPtr::make(value, |ptr| unsafe {
entity.insert_by_id(component_id, ptr);
});
}
}
/// An [`EntityCommand`] that adds a component to an entity using
/// the component's [`FromWorld`] implementation.
#[track_caller]
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);
entity.insert_with_caller(
value,
mode,
#[cfg(feature = "track_location")]
caller,
);
}
}
/// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity.
pub fn remove<T: Bundle>() -> impl EntityCommand<World> {
move |mut entity: EntityWorldMut| {
entity.remove::<T>();
}
}
/// 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> {
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> {
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> {
move |mut entity: EntityWorldMut| {
entity.clear();
}
}
/// An [`EntityCommand`] that removes all components from an entity,
/// except for those in the given [`Bundle`].
pub fn retain<T: Bundle>() -> impl EntityCommand<World> {
move |mut entity: EntityWorldMut| {
entity.retain::<T>();
}
}
/// An [`EntityCommand`] that despawns an entity.
///
/// # Note
///
/// 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> {
#[cfg(feature = "track_location")]
let caller = Location::caller();
move |entity: EntityWorldMut| {
entity.despawn_with_caller(
#[cfg(feature = "track_location")]
caller,
);
}
}
/// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer)
/// 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> {
move |mut entity: EntityWorldMut| {
entity.observe(observer);
}
}
/// An [`EntityCommand`] that clones parts of an entity onto another entity,
/// configured through [`EntityCloneBuilder`].
pub fn clone_with(
target: Entity,
config: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static,
) -> impl EntityCommand<World> {
move |mut entity: EntityWorldMut| {
entity.clone_with(target, config);
}
}
/// 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> {
move |mut entity: EntityWorldMut| {
entity.clone_components::<B>(target);
}
}
/// 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> {
move |mut entity: EntityWorldMut| {
entity.move_components::<B>(target);
}
}
/// 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)
.map(ComponentInfo::name)
.collect();
info!("Entity {entity}: {debug_infos:?}");
}
}

View File

@ -0,0 +1,17 @@
//! 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

@ -0,0 +1,32 @@
//! 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)
use log::{error, warn};
use crate::{system::CommandError, world::World};
/// An error handler that does nothing.
pub fn silent() -> fn(&mut World, CommandError) {
|_, _| {}
}
/// An error handler that accepts an error and logs it with [`warn!`].
pub fn warn() -> fn(&mut World, CommandError) {
|_, error| warn!("{error}")
}
/// An error handler that accepts an error and logs it with [`error!`].
pub fn error() -> fn(&mut World, CommandError) {
|_, 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) {
|_, error| panic!("{error}")
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ use crate::system::{SystemBuffer, SystemMeta};
use core::{
fmt::Debug,
marker::PhantomData,
mem::{size_of, MaybeUninit},
panic::AssertUnwindSafe,
ptr::{addr_of_mut, NonNull},
@ -11,7 +12,7 @@ use alloc::{boxed::Box, vec::Vec};
use bevy_ptr::{OwningPtr, Unaligned};
use log::warn;
use crate::world::{Command, World};
use crate::{system::Command, world::World};
use super::DeferredWorld;
@ -75,9 +76,10 @@ unsafe impl Sync for CommandQueue {}
impl CommandQueue {
/// Push a [`Command`] onto the queue.
#[inline]
pub fn push<C>(&mut self, command: C)
pub fn push<C, M>(&mut self, command: C)
where
C: Command,
C: Command<M>,
M: 'static,
{
// SAFETY: self is guaranteed to live for the lifetime of this method
unsafe {
@ -154,17 +156,23 @@ impl RawCommandQueue {
///
/// * Caller ensures that `self` has not outlived the underlying queue
#[inline]
pub unsafe fn push<C>(&mut self, command: C)
pub unsafe fn push<C, M>(&mut self, command: C)
where
C: Command,
C: Command<M>,
M: 'static,
{
// 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<T: Command> {
struct Packed<C, M>
where
C: Command<M>,
M: 'static,
{
meta: CommandMeta,
command: T,
command: C,
phantom: PhantomData<M>,
}
let meta = CommandMeta {
@ -179,7 +187,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.
@ -197,7 +205,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>>());
bytes.reserve(size_of::<Packed<C, M>>());
// 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()`.
@ -209,15 +217,18 @@ 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>>()
.write_unaligned(Packed { meta, command });
ptr.cast::<Packed<C, M>>().write_unaligned(Packed {
meta,
command,
phantom: PhantomData,
});
}
// 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>>());
bytes.set_len(old_len + size_of::<Packed<C, M>>());
}
}
@ -344,8 +355,7 @@ impl SystemBuffer for CommandQueue {
#[cfg(test)]
mod test {
use super::*;
use crate as bevy_ecs;
use crate::system::Resource;
use crate::{self as bevy_ecs, result::Result, system::Resource};
use alloc::{borrow::ToOwned, string::String, sync::Arc};
use core::{
panic::AssertUnwindSafe,
@ -371,7 +381,9 @@ mod test {
}
impl Command for DropCheck {
fn apply(self, _: &mut World) {}
fn apply(self, _: &mut World) -> Result {
Ok(())
}
}
#[test]
@ -418,8 +430,9 @@ mod test {
struct SpawnCommand;
impl Command for SpawnCommand {
fn apply(self, world: &mut World) {
fn apply(self, world: &mut World) -> Result {
world.spawn_empty();
Ok(())
}
}
@ -447,7 +460,7 @@ mod test {
#[allow(dead_code)]
struct PanicCommand(String);
impl Command for PanicCommand {
fn apply(self, _: &mut World) {
fn apply(self, _: &mut World) -> Result {
panic!("command is panicking");
}
}
@ -523,7 +536,9 @@ mod test {
#[allow(dead_code)]
struct CommandWithPadding(u8, u16);
impl Command for CommandWithPadding {
fn apply(self, _: &mut World) {}
fn apply(self, _: &mut World) -> Result {
Ok(())
}
}
#[cfg(miri)]

View File

@ -43,6 +43,7 @@ use crate::{
observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
removal_detection::RemovedComponentEvents,
result::Result,
schedule::{Schedule, ScheduleLabel, Schedules},
storage::{ResourceData, Storages},
system::{Commands, Resource},
@ -69,42 +70,6 @@ use core::panic::Location;
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
/// A [`World`] mutation.
///
/// Should be used with [`Commands::queue`].
///
/// # Usage
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::world::Command;
/// // Our world resource
/// #[derive(Resource, Default)]
/// struct Counter(u64);
///
/// // Our custom command
/// struct AddToCounter(u64);
///
/// impl Command for AddToCounter {
/// fn apply(self, world: &mut World) {
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
/// counter.0 += self.0;
/// }
/// }
///
/// fn some_system(mut commands: Commands) {
/// commands.queue(AddToCounter(42));
/// }
/// ```
pub trait Command: 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);
}
/// Stores and exposes operations on [entities](Entity), [components](Component), resources,
/// and their associated metadata.
///

View File

@ -3,8 +3,8 @@ use bevy_ecs::{
bundle::Bundle,
entity::Entity,
event::Events,
system::{Commands, EntityCommands},
world::{Command, EntityWorldMut, World},
system::{Command, Commands, EntityCommands},
world::{EntityWorldMut, World},
};
use smallvec::{smallvec, SmallVec};
@ -471,11 +471,19 @@ impl ChildBuild for WorldChildBuilder<'_> {
}
fn queue_command<C: Command>(&mut self, command: C) -> &mut Self {
command.apply(self.world);
self.world.commands().queue(command);
self
}
}
impl WorldChildBuilder<'_> {
/// Calls the world's [`World::flush`] to apply any commands
/// queued by [`Self::queue_command`].
pub fn flush_world(&mut self) {
self.world.flush();
}
}
impl BuildChildren for EntityWorldMut<'_> {
type Builder<'a> = WorldChildBuilder<'a>;

View File

@ -250,7 +250,7 @@ mod tests {
use alloc::{borrow::ToOwned, string::String, vec, vec::Vec};
use bevy_ecs::{
component::Component,
system::Commands,
system::{error_handler, Commands},
world::{CommandQueue, World},
};
@ -310,6 +310,7 @@ 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();