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:
parent
5faff84c10
commit
ee4414159b
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
345
crates/bevy_ecs/src/system/commands/command.rs
Normal file
345
crates/bevy_ecs/src/system/commands/command.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
329
crates/bevy_ecs/src/system/commands/entity_command.rs
Normal file
329
crates/bevy_ecs/src/system/commands/entity_command.rs
Normal 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:?}");
|
||||
}
|
||||
}
|
17
crates/bevy_ecs/src/system/commands/error.rs
Normal file
17
crates/bevy_ecs/src/system/commands/error.rs
Normal 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>),
|
||||
}
|
32
crates/bevy_ecs/src/system/commands/error_handler.rs
Normal file
32
crates/bevy_ecs/src/system/commands/error_handler.rs
Normal 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
@ -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)]
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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>;
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user