Skip to content

Commit

Permalink
Add missing insert API commands (#15166)
Browse files Browse the repository at this point in the history
# Objective

- Adds the missing API commands `insert_if_new_and` and
`try_insert_if_new_and` (resolves #15105)
- Adds some test coverage for existing insert commands

## Testing

- Implemented additional unit tests to add coverage
  • Loading branch information
crvarner authored Sep 16, 2024
1 parent 382917f commit 17b1bcd
Showing 1 changed file with 110 additions and 2 deletions.
112 changes: 110 additions & 2 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,13 +1017,36 @@ impl EntityCommands<'_> {
///
/// # 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(self, bundle: impl Bundle) -> Self {
self.add(insert(bundle, InsertMode::Keep))
}

/// Adds a [`Bundle`] of components to the entity without overwriting if the
/// predicate returns true.
///
/// 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(self, bundle: impl Bundle) -> Self {
self.add(insert(bundle, InsertMode::Keep))
pub fn insert_if_new_and<F>(self, bundle: impl Bundle, condition: F) -> Self
where
F: FnOnce() -> bool,
{
if condition() {
self.insert_if_new(bundle)
} else {
self
}
}

/// Adds a dynamic component to an entity.
Expand Down Expand Up @@ -1161,6 +1184,52 @@ impl EntityCommands<'_> {
}
}

/// Tries to add a [`Bundle`] of components to the entity without overwriting if the
/// predicate returns true.
///
/// This is the same as [`EntityCommands::try_insert_if`], but in case of duplicate
/// components will leave the old values instead of replacing them with new
/// ones.
///
/// # Note
///
/// Unlike [`Self::insert_if_new_and`], this will not panic if the associated entity does
/// not exist.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Resource)]
/// # struct PlayerEntity { entity: Entity }
/// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } }
/// #[derive(Component)]
/// struct StillLoadingStats;
/// #[derive(Component)]
/// struct Health(u32);
///
/// fn add_health_system(mut commands: Commands, player: Res<PlayerEntity>) {
/// commands.entity(player.entity)
/// .try_insert_if(Health(10), || player.is_spectator())
/// .remove::<StillLoadingStats>();
///
/// commands.entity(player.entity)
/// // This will not panic nor will it overwrite the component
/// .try_insert_if_new_and(Health(5), || player.is_spectator());
/// }
/// # bevy_ecs::system::assert_is_system(add_health_system);
/// ```
pub fn try_insert_if_new_and<F>(self, bundle: impl Bundle, condition: F) -> Self
where
F: FnOnce() -> bool,
{
if condition() {
self.try_insert_if_new(bundle)
} else {
self
}
}

/// Tries to add a [`Bundle`] of components to the entity without overwriting.
///
/// This is the same as [`EntityCommands::try_insert`], but in case of duplicate
Expand Down Expand Up @@ -1684,6 +1753,45 @@ mod tests {
assert_eq!(results3, vec![(42u32, 0u64), (0u32, 42u64)]);
}

#[test]
fn insert_components() {
let mut world = World::default();
let mut command_queue1 = CommandQueue::default();

// insert components
let entity = Commands::new(&mut command_queue1, &world)
.spawn(())
.insert_if(W(1u8), || true)
.insert_if(W(2u8), || false)
.insert_if_new(W(1u16))
.insert_if_new(W(2u16))
.insert_if_new_and(W(1u32), || false)
.insert_if_new_and(W(2u32), || true)
.insert_if_new_and(W(3u32), || true)
.id();
command_queue1.apply(&mut world);

let results = world
.query::<(&W<u8>, &W<u16>, &W<u32>)>()
.iter(&world)
.map(|(a, b, c)| (a.0, b.0, c.0))
.collect::<Vec<_>>();
assert_eq!(results, vec![(1u8, 1u16, 2u32)]);

// try to insert components after despawning entity
// in another command queue
Commands::new(&mut command_queue1, &world)
.entity(entity)
.try_insert_if_new_and(W(1u64), || true);

let mut command_queue2 = CommandQueue::default();
Commands::new(&mut command_queue2, &world)
.entity(entity)
.despawn();
command_queue2.apply(&mut world);
command_queue1.apply(&mut world);
}

#[test]
fn remove_components() {
let mut world = World::default();
Expand Down

0 comments on commit 17b1bcd

Please sign in to comment.