Skip to content

Commit 3a66d88

Browse files
command based entry api with EntityCommands::entry (#15274)
# Objective It's convenient to be able to modify a component if it exist, and insert a default value if it doesn't. You can already do most of this with `EntityCommands::insert_if_new`, and all of this using a custom command. However, that does not spark joy in my opinion. Closes #10669 ## Solution Introduce a new commands type `EntityEntryCommands`, along with a method to access it, `EntityCommands::entry`. `EntityEntryCommands` exposes a subset of the entry API (`and_modify`, `or_insert`, etc), however it's not an enum so it doesn't allow pattern matching. Also, `or_insert` won't return the component because it's all based on commands. ## Testing Added a new test `entity_commands_entry`. --- ## Showcase ```rust commands .entity(player) .entry::<Level>() .and_modify(|mut lvl| lvl.0 += 1) .or_default(); ``` --------- Co-authored-by: Jan Hohenheim <[email protected]>
1 parent 1bb8007 commit 3a66d88

File tree

1 file changed

+209
-2
lines changed
  • crates/bevy_ecs/src/system/commands

1 file changed

+209
-2
lines changed

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 209 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
mod parallel_scope;
22

33
use core::panic::Location;
4+
use std::marker::PhantomData;
45

56
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
67
use crate::{
78
self as bevy_ecs,
89
bundle::{Bundle, InsertMode},
9-
component::{ComponentId, ComponentInfo},
10+
change_detection::Mut,
11+
component::{Component, ComponentId, ComponentInfo},
1012
entity::{Entities, Entity},
1113
event::{Event, SendEvent},
1214
observer::{Observer, TriggerEvent, TriggerTargets},
@@ -906,6 +908,38 @@ impl EntityCommands<'_> {
906908
}
907909
}
908910

911+
/// Get an [`EntityEntryCommands`] for the [`Component`] `T`,
912+
/// allowing you to modify it or insert it if it isn't already present.
913+
///
914+
/// See also [`insert_if_new`](Self::insert_if_new), which lets you insert a [`Bundle`] without overwriting it.
915+
///
916+
/// # Example
917+
///
918+
/// ```
919+
/// # use bevy_ecs::prelude::*;
920+
/// # #[derive(Resource)]
921+
/// # struct PlayerEntity { entity: Entity }
922+
/// #[derive(Component)]
923+
/// struct Level(u32);
924+
///
925+
/// fn level_up_system(mut commands: Commands, player: Res<PlayerEntity>) {
926+
/// commands
927+
/// .entity(player.entity)
928+
/// .entry::<Level>()
929+
/// // Modify the component if it exists
930+
/// .and_modify(|mut lvl| lvl.0 += 1)
931+
/// // Otherwise insert a default value
932+
/// .or_insert(Level(0));
933+
/// }
934+
/// # bevy_ecs::system::assert_is_system(level_up_system);
935+
/// ```
936+
pub fn entry<T: Component>(&mut self) -> EntityEntryCommands<T> {
937+
EntityEntryCommands {
938+
entity_commands: self.reborrow(),
939+
marker: PhantomData,
940+
}
941+
}
942+
909943
/// Adds a [`Bundle`] of components to the entity.
910944
///
911945
/// This will overwrite any previous value(s) of the same component type.
@@ -1010,6 +1044,9 @@ impl EntityCommands<'_> {
10101044
/// components will leave the old values instead of replacing them with new
10111045
/// ones.
10121046
///
1047+
/// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present,
1048+
/// as well as initialize it with a default value.
1049+
///
10131050
/// # Panics
10141051
///
10151052
/// The command will panic when applied if the associated entity does not exist.
@@ -1417,6 +1454,113 @@ impl EntityCommands<'_> {
14171454
}
14181455
}
14191456

1457+
/// A wrapper around [`EntityCommands`] with convenience methods for working with a specified component type.
1458+
pub struct EntityEntryCommands<'a, T> {
1459+
entity_commands: EntityCommands<'a>,
1460+
marker: PhantomData<T>,
1461+
}
1462+
1463+
impl<'a, T: Component> EntityEntryCommands<'a, T> {
1464+
/// Modify the component `T` if it exists, using the the function `modify`.
1465+
pub fn and_modify(mut self, modify: impl FnOnce(Mut<T>) + Send + Sync + 'static) -> Self {
1466+
self.entity_commands = self
1467+
.entity_commands
1468+
.queue(move |mut entity: EntityWorldMut| {
1469+
if let Some(value) = entity.get_mut() {
1470+
modify(value);
1471+
}
1472+
});
1473+
self
1474+
}
1475+
1476+
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
1477+
///
1478+
/// See also [`or_insert_with`](Self::or_insert_with).
1479+
///
1480+
/// # Panics
1481+
///
1482+
/// Panics if the entity does not exist.
1483+
/// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version.
1484+
#[track_caller]
1485+
pub fn or_insert(mut self, default: T) -> Self {
1486+
self.entity_commands = self
1487+
.entity_commands
1488+
.queue(insert(default, InsertMode::Keep));
1489+
self
1490+
}
1491+
1492+
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
1493+
///
1494+
/// Unlike [`or_insert`](Self::or_insert), this will not panic if the entity does not exist.
1495+
///
1496+
/// See also [`or_insert_with`](Self::or_insert_with).
1497+
#[track_caller]
1498+
pub fn or_try_insert(mut self, default: T) -> Self {
1499+
self.entity_commands = self
1500+
.entity_commands
1501+
.queue(try_insert(default, InsertMode::Keep));
1502+
self
1503+
}
1504+
1505+
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present.
1506+
///
1507+
/// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert).
1508+
///
1509+
/// # Panics
1510+
///
1511+
/// Panics if the entity does not exist.
1512+
/// See [`or_try_insert_with`](Self::or_try_insert_with) for a non-panicking version.
1513+
#[track_caller]
1514+
pub fn or_insert_with(self, default: impl Fn() -> T) -> Self {
1515+
self.or_insert(default())
1516+
}
1517+
1518+
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present.
1519+
///
1520+
/// Unlike [`or_insert_with`](Self::or_insert_with), this will not panic if the entity does not exist.
1521+
///
1522+
/// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert).
1523+
#[track_caller]
1524+
pub fn or_try_insert_with(self, default: impl Fn() -> T) -> Self {
1525+
self.or_try_insert(default())
1526+
}
1527+
1528+
/// [Insert](EntityCommands::insert) `T::default` into this entity, if `T` is not already present.
1529+
///
1530+
/// See also [`or_insert`](Self::or_insert) and [`or_from_world`](Self::or_from_world).
1531+
///
1532+
/// # Panics
1533+
///
1534+
/// Panics if the entity does not exist.
1535+
#[track_caller]
1536+
pub fn or_default(self) -> Self
1537+
where
1538+
T: Default,
1539+
{
1540+
#[allow(clippy::unwrap_or_default)]
1541+
// FIXME: use `expect` once stable
1542+
self.or_insert(T::default())
1543+
}
1544+
1545+
/// [Insert](EntityCommands::insert) `T::from_world` into this entity, if `T` is not already present.
1546+
///
1547+
/// See also [`or_insert`](Self::or_insert) and [`or_default`](Self::or_default).
1548+
///
1549+
/// # Panics
1550+
///
1551+
/// Panics if the entity does not exist.
1552+
#[track_caller]
1553+
pub fn or_from_world(mut self) -> Self
1554+
where
1555+
T: FromWorld,
1556+
{
1557+
self.entity_commands = self
1558+
.entity_commands
1559+
.queue(insert_from_world::<T>(InsertMode::Keep));
1560+
self
1561+
}
1562+
}
1563+
14201564
impl<F> Command for F
14211565
where
14221566
F: FnOnce(&mut World) + Send + 'static,
@@ -1525,6 +1669,25 @@ fn insert<T: Bundle>(bundle: T, mode: InsertMode) -> impl EntityCommand {
15251669
}
15261670
}
15271671

1672+
/// An [`EntityCommand`] that adds the component using its `FromWorld` implementation.
1673+
#[track_caller]
1674+
fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
1675+
let caller = Location::caller();
1676+
move |entity: Entity, world: &mut World| {
1677+
let value = T::from_world(world);
1678+
if let Some(mut entity) = world.get_entity_mut(entity) {
1679+
entity.insert_with_caller(
1680+
value,
1681+
mode,
1682+
#[cfg(feature = "track_change_detection")]
1683+
caller,
1684+
);
1685+
} else {
1686+
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", std::any::type_name::<T>(), entity);
1687+
}
1688+
}
1689+
}
1690+
15281691
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
15291692
/// Does nothing if the entity does not exist.
15301693
#[track_caller]
@@ -1660,7 +1823,7 @@ mod tests {
16601823
self as bevy_ecs,
16611824
component::Component,
16621825
system::{Commands, Resource},
1663-
world::{CommandQueue, World},
1826+
world::{CommandQueue, FromWorld, World},
16641827
};
16651828
use std::{
16661829
any::TypeId,
@@ -1697,6 +1860,50 @@ mod tests {
16971860
world.spawn((W(0u32), W(42u64)));
16981861
}
16991862

1863+
impl FromWorld for W<String> {
1864+
fn from_world(world: &mut World) -> Self {
1865+
let v = world.resource::<W<usize>>();
1866+
Self("*".repeat(v.0))
1867+
}
1868+
}
1869+
1870+
#[test]
1871+
fn entity_commands_entry() {
1872+
let mut world = World::default();
1873+
let mut queue = CommandQueue::default();
1874+
let mut commands = Commands::new(&mut queue, &world);
1875+
let entity = commands.spawn_empty().id();
1876+
commands
1877+
.entity(entity)
1878+
.entry::<W<u32>>()
1879+
.and_modify(|_| unreachable!());
1880+
queue.apply(&mut world);
1881+
assert!(!world.entity(entity).contains::<W<u32>>());
1882+
let mut commands = Commands::new(&mut queue, &world);
1883+
commands
1884+
.entity(entity)
1885+
.entry::<W<u32>>()
1886+
.or_insert(W(0))
1887+
.and_modify(|mut val| {
1888+
val.0 = 21;
1889+
});
1890+
queue.apply(&mut world);
1891+
assert_eq!(21, world.get::<W<u32>>(entity).unwrap().0);
1892+
let mut commands = Commands::new(&mut queue, &world);
1893+
commands
1894+
.entity(entity)
1895+
.entry::<W<u64>>()
1896+
.and_modify(|_| unreachable!())
1897+
.or_insert(W(42));
1898+
queue.apply(&mut world);
1899+
assert_eq!(42, world.get::<W<u64>>(entity).unwrap().0);
1900+
world.insert_resource(W(5_usize));
1901+
let mut commands = Commands::new(&mut queue, &world);
1902+
commands.entity(entity).entry::<W<String>>().or_from_world();
1903+
queue.apply(&mut world);
1904+
assert_eq!("*****", &world.get::<W<String>>(entity).unwrap().0);
1905+
}
1906+
17001907
#[test]
17011908
fn commands() {
17021909
let mut world = World::default();

0 commit comments

Comments
 (0)