diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4aad33c9d6cde..9ffe5774de038 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1604,7 +1604,7 @@ mod tests { fn despawn_one_foo(mut commands: Commands, foos: Query>) { if let Some(e) = foos.iter().next() { - commands.entity(e).despawn(); + commands.entity(e).warn_if_missing().despawn(); }; } fn check_despawns(mut removed_foos: RemovedComponents) { diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index bbb9c5b682133..6e4efed251b6d 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -263,7 +263,7 @@ pub(crate) fn cleanup_finished_audio( } for (entity, sink) in &query_nonspatial_remove { if sink.sink.empty() { - commands.entity(entity).remove::<( + commands.entity(entity).ignore_if_missing().remove::<( AudioPlayer, AudioSink, PlaybackSettings, @@ -273,7 +273,7 @@ pub(crate) fn cleanup_finished_audio( } for (entity, sink) in &query_spatial_remove { if sink.sink.empty() { - commands.entity(entity).remove::<( + commands.entity(entity).ignore_if_missing().remove::<( AudioPlayer, SpatialAudioSink, PlaybackSettings, diff --git a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs index fbc3ecfec3f75..d90231711af3d 100644 --- a/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs +++ b/crates/bevy_core_pipeline/src/contrast_adaptive_sharpening/mod.rs @@ -250,7 +250,10 @@ fn prepare_cas_pipelines( mut removals: RemovedComponents, ) { for entity in removals.read() { - commands.entity(entity).remove::(); + commands + .entity(entity) + .ignore_if_missing() + .remove::(); } for (entity, view, denoise_cas) in &views { diff --git a/crates/bevy_core_pipeline/src/dof/mod.rs b/crates/bevy_core_pipeline/src/dof/mod.rs index 06cbbe3e9d312..614b324216943 100644 --- a/crates/bevy_core_pipeline/src/dof/mod.rs +++ b/crates/bevy_core_pipeline/src/dof/mod.rs @@ -832,7 +832,7 @@ fn extract_depth_of_field_settings( // Depth of field is nonsensical without a perspective projection. let Projection::Perspective(ref perspective_projection) = *projection else { // TODO: needs better strategy for cleaning up - entity_commands.remove::<( + entity_commands.ignore_if_missing().remove::<( DepthOfField, DepthOfFieldUniform, // components added in prepare systems (because `DepthOfFieldNode` does not query extracted components) diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index f9c543aeff03c..4783bbd411c7f 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -146,6 +146,7 @@ fn prepare_msaa_writeback_pipelines( // want this to silently break commands .entity(entity) + .ignore_if_missing() .remove::(); } } diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index 67422b4a7221a..54e847efe0499 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -386,7 +386,7 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut { queue: InternalQueue<'s>, entities: &'w Entities, + failure_handling_mode: FailureHandlingMode, } // SAFETY: All commands [`Command`] implement [`Send`] @@ -175,6 +176,7 @@ const _: () = { Commands { queue: InternalQueue::CommandQueue(f0), entities: f1, + failure_handling_mode: FailureHandlingMode::default(), } } } @@ -211,6 +213,7 @@ impl<'w, 's> Commands<'w, 's> { Self { queue: InternalQueue::CommandQueue(Deferred(queue)), entities, + failure_handling_mode: FailureHandlingMode::default(), } } @@ -228,6 +231,7 @@ impl<'w, 's> Commands<'w, 's> { Self { queue: InternalQueue::RawCommandQueue(queue), entities, + failure_handling_mode: FailureHandlingMode::default(), } } @@ -258,6 +262,7 @@ impl<'w, 's> Commands<'w, 's> { } }, entities: self.entities, + failure_handling_mode: self.failure_handling_mode, } } @@ -272,6 +277,60 @@ impl<'w, 's> Commands<'w, 's> { } } + /// Sets the [`Commands`] instance to ignore errors. + /// + /// Any subsequent commands that can fail will do so silently. + /// + /// # See also: + /// - [`log_on_error`](Self::log_on_error) + /// - [`warn_on_error`](Self::warn_on_error) + /// - [`panic_on_error`](Self::panic_on_error) (default) + pub fn ignore_on_error(&mut self) -> &mut Self { + self.failure_handling_mode = FailureHandlingMode::Ignore; + self + } + + /// Sets the [`Commands`] instance to log errors. + /// + /// Any subsequent commands that can fail will log each failure. + /// + /// # See also: + /// - [`ignore_on_error`](Self::ignore_on_error) + /// - [`warn_on_error`](Self::warn_on_error) + /// - [`panic_on_error`](Self::panic_on_error) (default) + pub fn log_on_error(&mut self) -> &mut Self { + self.failure_handling_mode = FailureHandlingMode::Log; + self + } + + /// Sets the [`Commands`] instance to send warnings upon encountering errors. + /// + /// Any subsequent commands that can fail will warn for each failure. + /// + /// # See also: + /// - [`ignore_on_error`](Self::ignore_on_error) + /// - [`log_on_error`](Self::log_on_error) + /// - [`panic_on_error`](Self::panic_on_error) (default) + pub fn warn_on_error(&mut self) -> &mut Self { + self.failure_handling_mode = FailureHandlingMode::Warn; + self + } + + /// Sets the [`Commands`] instance to panic upon encountering an error. + /// + /// Any subsequent commands that can fail will panic if they do. + /// + /// This is the default setting. + /// + /// # See also: + /// - [`ignore_on_error`](Self::ignore_on_error) + /// - [`log_on_error`](Self::log_on_error) + /// - [`warn_on_error`](Self::warn_on_error) + pub fn panic_on_error(&mut self) -> &mut Self { + self.failure_handling_mode = FailureHandlingMode::Panic; + self + } + /// Reserves a new empty [`Entity`] to be spawned, and returns its corresponding [`EntityCommands`]. /// /// See [`World::spawn_empty`] for more details. @@ -402,9 +461,17 @@ impl<'w, 's> Commands<'w, 's> { /// Returns the [`EntityCommands`] for the requested [`Entity`]. /// - /// # Panics + /// This method does not guarantee that commands queued by the `EntityCommands` + /// will be successful, since the entity could be despawned before they are executed. + /// + /// # Fallible /// - /// This method panics if the requested entity does not exist. + /// This command can fail if the entity does not exist. If the [`Commands`] instance + /// is not set to [`panic`] on failure, this command will return an invalid `EntityCommands`, + /// which will do nothing except [`warn`]/[`log`]/[`ignore`] the entity's non-existence + /// according to the instance's current setting. + /// + /// To get an [`Option`] instead, see [`get_entity`](Self::get_entity). /// /// # Example /// @@ -431,24 +498,24 @@ impl<'w, 's> Commands<'w, 's> { /// # bevy_ecs::system::assert_is_system(example_system); /// ``` /// - /// # See also - /// - /// - [`get_entity`](Self::get_entity) for the fallible version. + /// [`panic`]: Self::panic_on_error + /// [`warn`]: Self::warn_on_error + /// [`log`]: Self::log_on_error + /// [`ignore`]: Self::ignore_on_error #[inline] #[track_caller] pub fn entity(&mut self, entity: Entity) -> EntityCommands { - #[inline(never)] - #[cold] - #[track_caller] - fn panic_no_entity(entity: Entity) -> ! { - panic!( - "Attempting to create an EntityCommands for entity {entity:?}, which doesn't exist.", - ); + if !self.entities.contains(entity) { + match self.failure_handling_mode { + FailureHandlingMode::Ignore => (), + FailureHandlingMode::Log => info!("Attempted to create an EntityCommands for entity {entity}, which doesn't exist; returned invalid EntityCommands"), + FailureHandlingMode::Warn => warn!("Attempted to create an EntityCommands for entity {entity}, which doesn't exist; returned invalid EntityCommands"), + FailureHandlingMode::Panic => panic!("Attempted to create an EntityCommands for entity {entity}, which doesn't exist"), + }; } - - match self.get_entity(entity) { - Some(entity) => entity, - None => panic_no_entity(entity), + EntityCommands { + entity, + commands: self.reborrow(), } } @@ -456,8 +523,8 @@ impl<'w, 's> Commands<'w, 's> { /// /// Returns `None` if the entity does not exist. /// - /// This method does not guarantee that `EntityCommands` will be successfully applied, - /// since another command in the queue may delete the entity before them. + /// This method does not guarantee that commands queued by the `EntityCommands` + /// will be successful, since the entity could be despawned before they are executed. /// /// # Example /// @@ -481,7 +548,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// # See also /// - /// - [`entity`](Self::entity) for the panicking version. + /// - [`entity`](Self::entity) for the unwrapped version. #[inline] #[track_caller] pub fn get_entity(&mut self, entity: Entity) -> Option { @@ -628,18 +695,21 @@ impl<'w, 's> Commands<'w, 's> { /// and passing the bundle to [`insert`](EntityCommands::insert), /// but it is faster due to memory pre-allocation. /// - /// # Panics + /// # Fallible + /// This command can fail if any of the entities do not exist. It will [`panic`]/[`warn`]/[`log`]/[`ignore`] + /// according to the `Commands` instance's current setting. /// - /// This command panics if any of the given entities do not exist. - /// - /// For the non-panicking version, see [`try_insert_batch`](Self::try_insert_batch). + /// [`panic`]: Self::panic_on_error + /// [`warn`]: Self::warn_on_error + /// [`log`]: Self::log_on_error + /// [`ignore`]: Self::ignore_on_error #[track_caller] pub fn insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(insert_batch(batch)); + self.queue(insert_batch(batch, self.failure_handling_mode)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -655,18 +725,21 @@ impl<'w, 's> Commands<'w, 's> { /// and passing the bundle to [`insert_if_new`](EntityCommands::insert_if_new), /// but it is faster due to memory pre-allocation. /// - /// # Panics - /// - /// This command panics if any of the given entities do not exist. + /// # Fallible + /// This command can fail if any of the entities do not exist. It will [`panic`]/[`warn`]/[`log`]/[`ignore`] + /// according to the `Commands` instance's current setting. /// - /// For the non-panicking version, see [`try_insert_batch_if_new`](Self::try_insert_batch_if_new). + /// [`panic`]: Self::panic_on_error + /// [`warn`]: Self::warn_on_error + /// [`log`]: Self::log_on_error + /// [`ignore`]: Self::ignore_on_error #[track_caller] pub fn insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, B: Bundle, { - self.queue(insert_batch_if_new(batch)); + self.queue(insert_batch_if_new(batch, self.failure_handling_mode)); } /// Pushes a [`Command`] to the queue for adding a [`Bundle`] type to a batch of [`Entities`](Entity). @@ -684,7 +757,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// This command silently fails by ignoring any entities that do not exist. /// - /// For the panicking version, see [`insert_batch`](Self::insert_batch). + /// For the customizable version, see [`insert_batch`](Self::insert_batch). #[track_caller] pub fn try_insert_batch(&mut self, batch: I) where @@ -709,7 +782,7 @@ impl<'w, 's> Commands<'w, 's> { /// /// This command silently fails by ignoring any entities that do not exist. /// - /// For the panicking version, see [`insert_batch_if_new`](Self::insert_batch_if_new). + /// For the customizable version, see [`insert_batch_if_new`](Self::insert_batch_if_new). #[track_caller] pub fn try_insert_batch_if_new(&mut self, batch: I) where @@ -1026,7 +1099,7 @@ impl<'w, 's> Commands<'w, 's> { /// A [`Command`] which gets executed for a given [`Entity`]. /// -/// # Examples +/// # Example /// /// ``` /// # use std::collections::HashSet; @@ -1077,6 +1150,68 @@ impl<'w, 's> Commands<'w, 's> { /// assert_eq!(names, HashSet::from_iter(["Entity #0", "Entity #1"])); /// } /// ``` +/// +/// # Custom commands +/// +/// There are three ways to create a new `EntityCommand`: +/// +/// ## Function +/// +/// As seen above, a function can be used as an `EntityCommand` if it takes the parameters +/// `(Entity, &mut World)` and returns nothing: +/// +/// ```ignore +/// fn new_command(entity: Entity, world: &mut World) { +/// // Command code +/// } +/// commands.entity(entity).queue(new_command); +/// ``` +/// +/// Note that this approach does not allow the command to take additional parameters +/// (see below if that's what you need). +/// +/// ## Closure +/// +/// A closure can be used as an `EntityCommand` if it takes the parameters +/// `(Entity, &mut World)` and returns nothing. +/// +/// The most versatile form of this (and the way most built-in `EntityCommand`s are implemented) +/// is a function that returns such a closure. This allows you to take in parameters for the command: +/// +/// ```ignore +/// fn new_command(whatever_parameters: i32) -> impl EntityCommand { +/// move |entity: Entity, world: &mut World| { +/// // Command code (can access parameters here) +/// } +/// } +/// commands.entity(entity).queue(new_command(5)); +/// ``` +/// +/// You can also queue a closure directly if so desired: +/// +/// ```ignore +/// commands.entity(entity).queue(|entity: Entity, world: &mut World| { +/// // Command code +/// }); +/// ``` +/// +/// ## Struct +/// +/// A struct (or enum) can be used as an `EntityCommand` if it implements the trait +/// and its `apply` method: +/// +/// ```ignore +/// struct NewCommand { +/// // Fields act as parameters +/// whatever_parameters: i32, +/// } +/// impl EntityCommand for NewCommand { +/// fn apply(self, entity: Entity, world: &mut World) { +/// // Command code +/// } +/// } +/// commands.entity(entity).queue(NewCommand { whatever_parameters: 5 }); +/// ``` pub trait EntityCommand: Send + 'static { /// Executes this command for the given [`Entity`]. fn apply(self, entity: Entity, world: &mut World); @@ -1088,15 +1223,48 @@ pub trait EntityCommand: Send + 'static { /// 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 + fn with_entity(self, entity: Entity, failure_handling_mode: FailureHandlingMode) -> impl Command where Self: Sized, { - move |world: &mut World| self.apply(entity, world) + move |world: &mut World| { + if world.entities.contains(entity) { + self.apply(entity, world); + } else { + match failure_handling_mode { + FailureHandlingMode::Ignore => (), + FailureHandlingMode::Log => { + info!("Could not execute EntityCommand because its entity {entity} was missing"); + } + FailureHandlingMode::Warn => { + warn!("Could not execute EntityCommand because its entity {entity} was missing"); + } + FailureHandlingMode::Panic => { + panic!("Could not execute EntityCommand because its entity {entity} was missing"); + } + }; + } + } } } -/// A list of commands that will be run to modify an [entity](crate::entity). +/// A list of commands that will be run to modify an [`Entity`]. +/// +/// Most [`Commands`] (and thereby [`EntityCommands`]) are deferred: when you call the command, +/// if it requires mutable access to the [`World`] (that is, if it removes, adds, or changes something), +/// it's not executed immediately. Instead, the command is added to a "command queue." +/// The command queue is applied between [`Schedules`](bevy_ecs::schedule::Schedule), one by one, +/// so that each command can have exclusive access to the World. +/// +/// # Fallible +/// +/// Due to their deferred nature, an entity you're trying to change with an `EntityCommand` can be +/// despawned by the time the command is executed. Use the following commands to set how you +/// would like subsequent commands to respond if the entity is missing: +/// - [`ignore_if_missing`](Self::ignore_if_missing) +/// - [`log_if_missing`](Self::log_if_missing) +/// - [`warn_if_missing`](Self::warn_if_missing) +/// - [`panic_if_missing`](Self::panic_if_missing) (default) pub struct EntityCommands<'a> { pub(crate) entity: Entity, pub(crate) commands: Commands<'a, 'a>, @@ -1130,6 +1298,56 @@ impl<'a> EntityCommands<'a> { } } + /// Sets the [`EntityCommands`] instance to ignore commands if the entity doesn't exist + /// when a command is executed. + /// + /// # See also: + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn ignore_if_missing(&mut self) -> &mut Self { + self.commands.ignore_on_error(); + self + } + + /// Sets the [`EntityCommands`] instance to log if the entity doesn't exist + /// when a command is executed. + /// + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn log_if_missing(&mut self) -> &mut Self { + self.commands.log_on_error(); + self + } + + /// Sets the [`EntityCommands`] instance to warn if the entity doesn't exist + /// when a command is executed. + /// + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn warn_if_missing(&mut self) -> &mut Self { + self.commands.warn_on_error(); + self + } + + /// Sets the [`EntityCommands`] instance to panic if the entity doesn't exist + /// when a command is executed. + /// + /// This is the default setting. + /// + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + pub fn panic_if_missing(&mut self) -> &mut Self { + self.commands.panic_on_error(); + self + } + /// Get an [`EntityEntryCommands`] for the [`Component`] `T`, /// allowing you to modify it or insert it if it isn't already present. /// @@ -1167,12 +1385,6 @@ impl<'a> EntityCommands<'a> { /// This will overwrite any previous value(s) of the same component type. /// See [`EntityCommands::insert_if_new`] to keep the old value instead. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert`] instead. - /// /// # Example /// /// ``` @@ -1222,12 +1434,6 @@ impl<'a> EntityCommands<'a> { /// Similar to [`Self::insert`] but will only insert if the predicate returns true. /// This is useful for chaining method calls. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if`] instead. - /// /// # Example /// /// ``` @@ -1268,12 +1474,6 @@ impl<'a> EntityCommands<'a> { /// /// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present, /// as well as initialize it with a default value. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] instead. pub fn insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { self.queue(insert(bundle, InsertMode::Keep)) } @@ -1284,14 +1484,6 @@ impl<'a> EntityCommands<'a> { /// This is the same as [`EntityCommands::insert_if`], but in case of duplicate /// components will leave the old values instead of replacing them with new /// ones. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not - /// exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] - /// instead. pub fn insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self where F: FnOnce() -> bool, @@ -1307,12 +1499,6 @@ impl<'a> EntityCommands<'a> { /// /// See [`EntityWorldMut::insert_by_id`] for more information. /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. - /// - /// To avoid a panic in this case, use the command [`Self::try_insert_by_id`] instead. - /// /// # Safety /// /// - [`ComponentId`] must be from the same world as `self`. @@ -1323,11 +1509,8 @@ impl<'a> EntityCommands<'a> { component_id: ComponentId, value: T, ) -> &mut Self { - let caller = Location::caller(); // SAFETY: same invariants as parent call - self.queue(unsafe {insert_by_id(component_id, value, move |entity| { - panic!("error[B0003]: {caller}: Could not insert a component {component_id:?} (with type {}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::()); - })}) + self.queue(unsafe { insert_by_id(component_id, value) }) } /// Attempts to add a dynamic component to an entity. @@ -1338,13 +1521,24 @@ impl<'a> EntityCommands<'a> { /// /// - [`ComponentId`] must be from the same world as `self`. /// - `T` must have the same layout as the one passed during `component_id` creation. + /// + /// # Note + /// + /// [`EntityCommands::insert_by_id`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityCommands` instance. pub unsafe fn try_insert_by_id( &mut self, component_id: ComponentId, value: T, ) -> &mut Self { - // SAFETY: same invariants as parent call - self.queue(unsafe { insert_by_id(component_id, value, |_| {}) }) + self.queue_with_different_failure_handling_mode( + // SAFETY: same invariants as parent call + unsafe { insert_by_id(component_id, value) }, + FailureHandlingMode::Ignore, + ) } /// Tries to add a [`Bundle`] of components to the entity. @@ -1353,7 +1547,11 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// Unlike [`Self::insert`], this will not panic if the associated entity does not exist. + /// [`EntityCommands::insert`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityCommands` instance. /// /// # Example /// @@ -1397,12 +1595,23 @@ impl<'a> EntityCommands<'a> { /// ``` #[track_caller] pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue(try_insert(bundle, InsertMode::Replace)) + self.queue_with_different_failure_handling_mode( + try_insert(bundle, InsertMode::Replace), + FailureHandlingMode::Ignore, + ) } /// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true. /// This is useful for chaining method calls. /// + /// # Note + /// + /// [`EntityCommands::insert_if`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityCommands` instance. + /// /// # Example /// /// ``` @@ -1432,7 +1641,10 @@ impl<'a> EntityCommands<'a> { F: FnOnce() -> bool, { if condition() { - self.queue(try_insert(bundle, InsertMode::Replace)) + self.queue_with_different_failure_handling_mode( + try_insert(bundle, InsertMode::Replace), + FailureHandlingMode::Ignore, + ) } else { self } @@ -1447,8 +1659,11 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// Unlike [`Self::insert_if_new_and`], this will not panic if the associated entity does - /// not exist. + /// [`EntityCommands::insert_if_new_and`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityCommands` instance. /// /// # Example /// @@ -1492,9 +1707,16 @@ impl<'a> EntityCommands<'a> { /// /// # Note /// - /// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist. + /// [`EntityCommands::insert_if_new`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityCommands` instance. pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { - self.queue(try_insert(bundle, InsertMode::Keep)) + self.queue_with_different_failure_handling_mode( + try_insert(bundle, InsertMode::Keep), + FailureHandlingMode::Ignore, + ) } /// Removes a [`Bundle`] of components from the entity. @@ -1578,7 +1800,6 @@ impl<'a> EntityCommands<'a> { } /// Despawns the entity. - /// This will emit a warning if the entity does not exist. /// /// See [`World::despawn`] for more details. /// @@ -1610,11 +1831,21 @@ impl<'a> EntityCommands<'a> { } /// 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. + /// + /// This will ignore the command if the entity does not exist, + /// essentially performing the same function as [`Self::despawn`] + /// without respecting the instance's failure handling mode. + /// + /// # Note + /// + /// [`EntityCommands::despawn`] used to warn if the entity was missing, + /// and this was the silent version. + /// [`EntityCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityCommands::ignore_if_missing`] (or another variant) + /// to change the failure response for all commands from that `EntityCommands` instance. #[track_caller] pub fn try_despawn(&mut self) { - self.queue(try_despawn()); + self.queue_with_different_failure_handling_mode(try_despawn(), FailureHandlingMode::Ignore); } /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. @@ -1634,7 +1865,24 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(my_system); /// ``` pub fn queue(&mut self, command: impl EntityCommand) -> &mut Self { - self.commands.queue(command.with_entity(self.entity)); + self.commands + .queue(command.with_entity(self.entity, self.commands.failure_handling_mode)); + self + } + + /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. + /// + /// Allows commands to use a specific failure handling mode instead of the instance's internal setting. + /// Used to avoid breaking `try_` variants before their deprecation. + /// + /// TODO: remove alongside `try_` variants + fn queue_with_different_failure_handling_mode( + &mut self, + command: impl EntityCommand, + failure_handling_mode: FailureHandlingMode, + ) -> &mut Self { + self.commands + .queue(command.with_entity(self.entity, failure_handling_mode)); self } @@ -1683,10 +1931,6 @@ impl<'a> EntityCommands<'a> { } /// Logs the components of the entity at the info level. - /// - /// # Panics - /// - /// The command will panic when applied if the associated entity does not exist. pub fn log_components(&mut self) -> &mut Self { self.queue(log_components) } @@ -1806,14 +2050,59 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { } impl<'a, T: Component> EntityEntryCommands<'a, T> { - /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. + /// Sets the [`EntityEntryCommands`] instance to ignore commands if the entity doesn't exist + /// when a command is executed. + /// + /// # See also: + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn ignore_if_missing(&mut self) -> &mut Self { + self.entity_commands.ignore_if_missing(); + self + } + + /// Sets the [`EntityEntryCommands`] instance to log if the entity doesn't exist + /// when a command is executed. /// - /// See also [`or_insert_with`](Self::or_insert_with). + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn log_if_missing(&mut self) -> &mut Self { + self.entity_commands.log_if_missing(); + self + } + + /// Sets the [`EntityEntryCommands`] instance to warn if the entity doesn't exist + /// when a command is executed. /// - /// # Panics + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`panic_if_missing`](Self::panic_if_missing) (default) + pub fn warn_if_missing(&mut self) -> &mut Self { + self.entity_commands.warn_if_missing(); + self + } + + /// Sets the [`EntityEntryCommands`] instance to panic if the entity doesn't exist + /// when a command is executed. /// - /// Panics if the entity does not exist. - /// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version. + /// This is the default setting. + /// + /// # See also: + /// - [`ignore_if_missing`](Self::ignore_if_missing) + /// - [`log_if_missing`](Self::log_if_missing) + /// - [`warn_if_missing`](Self::warn_if_missing) + pub fn panic_if_missing(&mut self) -> &mut Self { + self.entity_commands.panic_if_missing(); + self + } + + /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. + /// + /// See also [`or_insert_with`](Self::or_insert_with). #[track_caller] pub fn or_insert(&mut self, default: T) -> &mut Self { self.entity_commands @@ -1823,24 +2112,28 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present. /// - /// Unlike [`or_insert`](Self::or_insert), this will not panic if the entity does not exist. - /// /// See also [`or_insert_with`](Self::or_insert_with). + /// + /// # Note + /// + /// [`EntityEntryCommands::or_insert`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityEntryCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityEntryCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityEntryCommands` instance. #[track_caller] pub fn or_try_insert(&mut self, default: T) -> &mut Self { self.entity_commands - .queue(try_insert(default, InsertMode::Keep)); + .queue_with_different_failure_handling_mode( + try_insert(default, InsertMode::Keep), + FailureHandlingMode::Ignore, + ); self } /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. /// /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). - /// - /// # Panics - /// - /// Panics if the entity does not exist. - /// See [`or_try_insert_with`](Self::or_try_insert_with) for a non-panicking version. #[track_caller] pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { self.or_insert(default()) @@ -1848,9 +2141,15 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present. /// - /// Unlike [`or_insert_with`](Self::or_insert_with), this will not panic if the entity does not exist. + /// See also [`or_insert`](Self::or_insert). /// - /// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert). + /// # Note + /// + /// [`EntityEntryCommands::or_insert_with`] used to panic if the entity was missing, + /// and this was the non-panicking version. + /// [`EntityEntryCommands`] no longer need to handle missing entities individually; + /// you can now use [`EntityEntryCommands::ignore_if_missing`] (or another variant) + /// to disable panicking for all commands from that `EntityEntryCommands` instance. #[track_caller] pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { self.or_try_insert(default()) @@ -1859,10 +2158,6 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// [Insert](EntityCommands::insert) `T::default` into this entity, if `T` is not already present. /// /// See also [`or_insert`](Self::or_insert) and [`or_from_world`](Self::or_from_world). - /// - /// # Panics - /// - /// Panics if the entity does not exist. #[track_caller] pub fn or_default(&mut self) -> &mut Self where @@ -1876,10 +2171,6 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> { /// [Insert](EntityCommands::insert) `T::from_world` into this entity, if `T` is not already present. /// /// See also [`or_insert`](Self::or_insert) and [`or_default`](Self::or_default). - /// - /// # Panics - /// - /// Panics if the entity does not exist. #[track_caller] pub fn or_from_world(&mut self) -> &mut Self where @@ -1967,11 +2258,12 @@ where } /// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities. -/// If any entities do not exist in the world, this command will panic. +/// If any entities do not exist in the world, this command will fail according to +/// `failure_handling_mode`. /// /// This is more efficient than inserting the bundles individually. #[track_caller] -fn insert_batch(batch: I) -> impl Command +fn insert_batch(batch: I, failure_handling_mode: FailureHandlingMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, B: Bundle, @@ -1982,6 +2274,7 @@ where world.insert_batch_with_caller( batch, InsertMode::Replace, + failure_handling_mode, #[cfg(feature = "track_change_detection")] caller, ); @@ -1989,11 +2282,12 @@ where } /// A [`Command`] that consumes an iterator to add a series of [`Bundles`](Bundle) to a set of entities. -/// If any entities do not exist in the world, this command will panic. +/// If any entities do not exist in the world, this command will fail according to +/// `failure_handling_mode`. /// /// This is more efficient than inserting the bundles individually. #[track_caller] -fn insert_batch_if_new(batch: I) -> impl Command +fn insert_batch_if_new(batch: I, failure_handling_mode: FailureHandlingMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, B: Bundle, @@ -2004,6 +2298,7 @@ where world.insert_batch_with_caller( batch, InsertMode::Keep, + failure_handling_mode, #[cfg(feature = "track_change_detection")] caller, ); @@ -2055,7 +2350,6 @@ where } /// A [`Command`] that despawns a specific entity. -/// This will emit a warning if the entity does not exist. /// /// # Note /// @@ -2070,7 +2364,6 @@ fn despawn() -> impl EntityCommand { } /// A [`Command`] that despawns a specific entity. -/// This will not emit a warning if the entity does not exist. /// /// # Note /// @@ -2087,37 +2380,33 @@ fn try_despawn() -> impl EntityCommand { /// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity. #[track_caller] fn insert(bundle: T, mode: InsertMode) -> impl EntityCommand { + #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - bundle, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); - } else { - panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), entity); - } + let mut entity = world.entity_mut(entity); + entity.insert_with_caller( + bundle, + mode, + #[cfg(feature = "track_change_detection")] + caller, + ); } } /// An [`EntityCommand`] that adds the component using its `FromWorld` implementation. #[track_caller] fn insert_from_world(mode: InsertMode) -> impl EntityCommand { + #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |entity: Entity, world: &mut World| { let value = T::from_world(world); - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - value, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); - } else { - panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), entity); - } + let mut entity = world.entity_mut(entity); + entity.insert_with_caller( + value, + mode, + #[cfg(feature = "track_change_detection")] + caller, + ); } } @@ -2128,14 +2417,13 @@ fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { #[cfg(feature = "track_change_detection")] let caller = Location::caller(); move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.insert_with_caller( - bundle, - mode, - #[cfg(feature = "track_change_detection")] - caller, - ); - } + let mut entity = world.entity_mut(entity); + entity.insert_with_caller( + bundle, + mode, + #[cfg(feature = "track_change_detection")] + caller, + ); } } @@ -2148,19 +2436,15 @@ fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { unsafe fn insert_by_id( component_id: ComponentId, value: T, - on_none_entity: impl FnOnce(Entity) + Send + 'static, ) -> impl EntityCommand { move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - // 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); - }); - } else { - on_none_entity(entity); - } + let mut entity = world.entity_mut(entity); + // 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); + }); } } @@ -2169,9 +2453,8 @@ unsafe fn insert_by_id( /// For a [`Bundle`] type `T`, this will remove any components in the bundle. /// Any components in the bundle that aren't found on the entity will be ignored. fn remove(entity: Entity, world: &mut World) { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove::(); - } + let mut entity = world.entity_mut(entity); + entity.remove::(); } /// An [`EntityCommand`] that removes components with a provided [`ComponentId`] from an entity. @@ -2180,25 +2463,22 @@ fn remove(entity: Entity, world: &mut World) { /// Panics if the provided [`ComponentId`] does not exist in the [`World`]. fn remove_by_id(component_id: ComponentId) -> impl EntityCommand { move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove_by_id(component_id); - } + let mut entity = world.entity_mut(entity); + entity.remove_by_id(component_id); } } /// An [`EntityCommand`] that remove all components in the bundle and remove all required components for each component in the bundle. fn remove_with_requires(entity: Entity, world: &mut World) { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.remove_with_requires::(); - } + let mut entity = world.entity_mut(entity); + entity.remove_with_requires::(); } /// An [`EntityCommand`] that removes all components associated with a provided entity. fn clear() -> impl EntityCommand { move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.clear(); - } + let mut entity = world.entity_mut(entity); + entity.clear(); } } @@ -2207,9 +2487,8 @@ fn clear() -> impl EntityCommand { /// For a [`Bundle`] type `T`, this will remove all components except those in the bundle. /// Any components in the bundle that aren't found on the entity will be ignored. fn retain(entity: Entity, world: &mut World) { - if let Ok(mut entity_mut) = world.get_entity_mut(entity) { - entity_mut.retain::(); - } + let mut entity = world.entity_mut(entity); + entity.retain::(); } /// A [`Command`] that inserts a [`Resource`] into the world using a value @@ -2252,9 +2531,8 @@ fn observe( observer: impl IntoObserverSystem, ) -> impl EntityCommand { move |entity: Entity, world: &mut World| { - if let Ok(mut entity) = world.get_entity_mut(entity) { - entity.observe(observer); - } + let mut entity = world.entity_mut(entity); + entity.observe(observer); } } @@ -2355,6 +2633,28 @@ mod tests { assert_eq!("*****", &world.get::>(entity).unwrap().0); } + #[test] + fn entity_commands_failure() { + #[derive(Component)] + struct X; + + let mut world = World::default(); + + let mut queue_a = CommandQueue::default(); + let mut queue_b = CommandQueue::default(); + + let mut commands_a = Commands::new(&mut queue_a, &world); + let mut commands_b = Commands::new(&mut queue_b, &world); + + let entity = commands_a.spawn_empty().id(); + + commands_a.entity(entity).despawn(); + commands_b.entity(entity).warn_if_missing().insert(X); + + queue_a.apply(&mut world); + queue_b.apply(&mut world); + } + #[test] fn commands() { let mut world = World::default(); @@ -2374,7 +2674,7 @@ mod tests { { let mut commands = Commands::new(&mut command_queue, &world); commands.entity(entity).despawn(); - commands.entity(entity).despawn(); // double despawn shouldn't panic + commands.entity(entity).warn_if_missing().despawn(); // shouldn't panic } command_queue.apply(&mut world); let results2 = world diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 491c3648dd043..38f5591405e9d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -52,7 +52,7 @@ use crate::{ }, }; use bevy_ptr::{OwningPtr, Ptr}; -use bevy_utils::tracing::warn; +use bevy_utils::tracing::{info, warn}; use core::{ any::TypeId, fmt, @@ -2573,6 +2573,7 @@ impl World { self.insert_batch_with_caller( batch, InsertMode::Replace, + FailureHandlingMode::Panic, #[cfg(feature = "track_change_detection")] Location::caller(), ); @@ -2603,6 +2604,7 @@ impl World { self.insert_batch_with_caller( batch, InsertMode::Keep, + FailureHandlingMode::Panic, #[cfg(feature = "track_change_detection")] Location::caller(), ); @@ -2620,6 +2622,7 @@ impl World { &mut self, iter: I, insert_mode: InsertMode, + failure_handling_mode: FailureHandlingMode, #[cfg(feature = "track_change_detection")] caller: &'static Location, ) where I: IntoIterator, @@ -2639,6 +2642,27 @@ impl World { archetype_id: ArchetypeId, } + let insert_batch_fail = |entity: Entity| { + match failure_handling_mode { + FailureHandlingMode::Ignore => (), + FailureHandlingMode::Log => info!( + "error[B0003]: Could not insert a bundle (of type `{}`) for entity {} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", + core::any::type_name::(), + entity, + ), + FailureHandlingMode::Warn => warn!( + "error[B0003]: Could not insert a bundle (of type `{}`) for entity {} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", + core::any::type_name::(), + entity, + ), + FailureHandlingMode::Panic => panic!( + "error[B0003]: Could not insert a bundle (of type `{}`) for entity {} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", + core::any::type_name::(), + entity, + ), + }; + }; + let mut batch = iter.into_iter(); if let Some((first_entity, first_bundle)) = batch.next() { @@ -2695,11 +2719,11 @@ impl World { ) }; } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), entity); + insert_batch_fail(entity); } } } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), first_entity); + insert_batch_fail(first_entity); } } } @@ -3898,6 +3922,20 @@ impl FromWorld for T { } } +/// How to respond if a function fails +#[derive(Default, Clone, Copy, PartialEq, Eq)] +pub enum FailureHandlingMode { + /// Do nothing + Ignore, + /// Send a benign message to the log + Log, + /// Send a more serious message to the log + Warn, + /// Stop the application + #[default] + Panic, +} + #[cfg(test)] mod tests { use super::{FromWorld, World}; diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index 8b17525baa9e3..4098349122afd 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -1361,7 +1361,7 @@ pub fn gamepad_connection_system( // Gamepad entities are left alive to preserve their state (e.g. [`GamepadSettings`]). // Instead of despawning, we remove Gamepad components that don't need to preserve state // and re-add them if they ever reconnect. - gamepad.remove::(); + gamepad.ignore_if_missing().remove::(); info!("Gamepad {:} disconnected.", id); } } diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_pbr/src/cluster/assign.rs index 69de548b57ae6..b4d6b66a4c651 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_pbr/src/cluster/assign.rs @@ -323,6 +323,7 @@ pub(crate) fn assign_objects_to_clusters( if visible_clusterable_objects.is_some() { commands .entity(view_entity) + .ignore_if_missing() .remove::(); } clusters.clear(); diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index bbab0dff08b55..18be4f6fb950b 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -555,7 +555,9 @@ pub fn extract_clusters( .get_entity(entity) .expect("Clusters entity wasn't synced."); if !camera.is_active { - entity_commands.remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>(); + entity_commands + .ignore_if_missing() + .remove::<(ExtractedClusterableObjects, ExtractedClusterConfig)>(); continue; } diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 5391d75c862d3..ec690021596b8 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -443,6 +443,7 @@ fn gather_light_probes( commands .get_entity(view_entity) .expect("View entity wasn't synced.") + .ignore_if_missing() .remove::>(); } else { commands diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 05cbc9cda9fe2..e8c324b39e134 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -645,7 +645,7 @@ pub fn extract_camera_previous_view_data( entity.insert(previous_view_data.clone()); } } else { - entity.remove::(); + entity.ignore_if_missing().remove::(); } } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index c2a61d16e08e2..b07ad8afbb59b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -403,6 +403,7 @@ pub fn extract_lights( commands .get_entity(entity) .expect("Light entity wasn't synced.") + .ignore_if_missing() .remove::<(ExtractedDirectionalLight, RenderCascadesVisibleEntities)>(); continue; } @@ -507,7 +508,7 @@ pub(crate) fn extracted_light_removed( mut commands: Commands, ) { if let Some(mut v) = commands.get_entity(trigger.entity()) { - v.remove::(); + v.ignore_if_missing().remove::(); } } @@ -520,7 +521,7 @@ pub(crate) fn remove_light_view_entities( for v in entities.0.values() { for e in v.iter().copied() { if let Some(mut v) = commands.get_entity(e) { - v.despawn(); + v.warn_if_missing().despawn(); } } } diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index fd47511da5d16..beb77f7b36150 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -543,7 +543,9 @@ fn extract_ssao_settings( if camera.is_active { entity_commands.insert(ssao_settings.clone()); } else { - entity_commands.remove::(); + entity_commands + .ignore_if_missing() + .remove::(); } } } diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index 76039bbbe448b..286db59e7221e 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -279,12 +279,17 @@ pub fn extract_volumetric_fog( if volumetric_lights.is_empty() { // TODO: needs better way to handle clean up in render world for (entity, ..) in view_targets.iter() { - commands - .entity(entity) - .remove::<(VolumetricFog, ViewVolumetricFogPipelines, ViewVolumetricFog)>(); + commands.entity(entity).ignore_if_missing().remove::<( + VolumetricFog, + ViewVolumetricFogPipelines, + ViewVolumetricFog, + )>(); } for (entity, ..) in fog_volumes.iter() { - commands.entity(entity).remove::(); + commands + .entity(entity) + .ignore_if_missing() + .remove::(); } return; } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 413933135c85a..4402eaa2a4c2e 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -157,7 +157,9 @@ fn apply_wireframe_material( ) { for e in removed_wireframes.read().chain(no_wireframes.iter()) { if let Some(mut commands) = commands.get_entity(e) { - commands.remove::>(); + commands + .ignore_if_missing() + .remove::>(); } } @@ -199,6 +201,7 @@ fn apply_global_wireframe_material( for e in &meshes_with_global_material { commands .entity(e) + .ignore_if_missing() .remove::>(); } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index f0603157fb4fe..7931ce20885d1 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1055,16 +1055,19 @@ pub fn extract_cameras( ) in query.iter() { if !camera.is_active { - commands.entity(render_entity).remove::<( - ExtractedCamera, - ExtractedView, - RenderVisibleEntities, - TemporalJitter, - RenderLayers, - Projection, - GpuCulling, - ViewUniformOffset, - )>(); + commands + .entity(render_entity) + .ignore_if_missing() + .remove::<( + ExtractedCamera, + ExtractedView, + RenderVisibleEntities, + TemporalJitter, + RenderLayers, + Projection, + GpuCulling, + ViewUniformOffset, + )>(); continue; } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index 64e744775ffaf..6879979efea50 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -207,7 +207,10 @@ fn extract_components( if let Some(component) = C::extract_component(query_item) { values.push((entity, component)); } else { - commands.entity(entity).remove::(); + commands + .entity(entity) + .ignore_if_missing() + .remove::(); } } *previous_len = values.len(); @@ -226,7 +229,10 @@ fn extract_visible_components( if let Some(component) = C::extract_component(query_item) { values.push((entity, component)); } else { - commands.entity(entity).remove::(); + commands + .entity(entity) + .ignore_if_missing() + .remove::(); } } } diff --git a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs index 6f2659fbaaf91..cd80532a44d8b 100644 --- a/crates/bevy_sprite/src/mesh2d/wireframe2d.rs +++ b/crates/bevy_sprite/src/mesh2d/wireframe2d.rs @@ -163,7 +163,9 @@ fn apply_wireframe_material( ) { for e in removed_wireframes.read().chain(no_wireframes.iter()) { if let Some(mut commands) = commands.get_entity(e) { - commands.remove::>(); + commands + .ignore_if_missing() + .remove::>(); } } @@ -213,6 +215,7 @@ fn apply_global_wireframe_material( for e in &meshes_with_global_material { commands .entity(e) + .ignore_if_missing() .remove::>(); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index a77d5cf144f2f..e5802130c4e34 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -552,6 +552,7 @@ pub fn extract_default_ui_camera_view( commands .get_entity(entity) .expect("Camera entity wasn't synced.") + .ignore_if_missing() .remove::<(DefaultCameraView, UiAntiAlias, BoxShadowSamples)>(); continue; } diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 049c797ea103c..ee70e6a22861a 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -73,7 +73,10 @@ fn update_clipping( } } else { // No inherited clipping rect, remove the component - commands.entity(entity).remove::(); + commands + .entity(entity) + .ignore_if_missing() + .remove::(); } } else if let Some(inherited_clip) = maybe_inherited_clip { // No previous calculated clip, add a new CalculatedClip component with the inherited clipping rect @@ -201,7 +204,10 @@ fn update_children_target_camera( commands.entity(child).try_insert(camera.clone()); } None => { - commands.entity(child).remove::(); + commands + .entity(child) + .ignore_if_missing() + .remove::(); } } updated_entities.insert(child); diff --git a/crates/bevy_window/src/system.rs b/crates/bevy_window/src/system.rs index d507e9e84139e..0ae701c513e43 100644 --- a/crates/bevy_window/src/system.rs +++ b/crates/bevy_window/src/system.rs @@ -46,7 +46,7 @@ pub fn close_when_requested( ) { // This was inserted by us on the last frame so now we can despawn the window for window in closing.iter() { - commands.entity(window).despawn(); + commands.entity(window).warn_if_missing().despawn(); } // Mark the window as closing so we can despawn it on the next frame for event in closed.read() { diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 113e5d4a44540..3aac3344ff4ea 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -210,7 +210,7 @@ pub fn create_monitors( true } else { info!("Monitor removed {:?}", entity); - commands.entity(*entity).despawn(); + commands.entity(*entity).warn_if_missing().despawn(); idx += 1; false }