Skip to content

Commit 584d148

Browse files
authored
Allow World::entity family of functions to take multiple entities and get multiple references back (#15614)
# Objective Following the pattern established in #15593, we can reduce the API surface of `World` by providing a single function to grab both a singular entity reference, or multiple entity references. ## Solution The following functions can now also take multiple entity IDs and will return multiple entity references back: - `World::entity` - `World::get_entity` - `World::entity_mut` - `World::get_entity_mut` - `DeferredWorld::entity_mut` - `DeferredWorld::get_entity_mut` If you pass in X, you receive Y: - give a single `Entity`, receive a single `EntityRef`/`EntityWorldMut` (matches current behavior) - give a `[Entity; N]`/`&[Entity; N]` (array), receive an equally-sized `[EntityRef; N]`/`[EntityMut; N]` - give a `&[Entity]` (slice), receive a `Vec<EntityRef>`/`Vec<EntityMut>` - give a `&EntityHashSet`, receive a `EntityHashMap<EntityRef>`/`EntityHashMap<EntityMut>` Note that `EntityWorldMut` is only returned in the single-entity case, because having multiple at the same time would lead to UB. Also, `DeferredWorld` receives an `EntityMut` in the single-entity case because it does not allow structural access. ## Testing - Added doc-tests on `World::entity`, `World::entity_mut`, and `DeferredWorld::entity_mut` - Added tests for aliased mutability and entity existence --- ## Showcase <details> <summary>Click to view showcase</summary> The APIs for fetching `EntityRef`s and `EntityMut`s from the `World` have been unified. ```rust // This code will be referred to by subsequent code blocks. let world = World::new(); let e1 = world.spawn_empty().id(); let e2 = world.spawn_empty().id(); let e3 = world.spawn_empty().id(); ``` Querying for a single entity remains mostly the same: ```rust // 0.14 let eref: EntityRef = world.entity(e1); let emut: EntityWorldMut = world.entity_mut(e1); let eref: Option<EntityRef> = world.get_entity(e1); let emut: Option<EntityWorldMut> = world.get_entity_mut(e1); // 0.15 let eref: EntityRef = world.entity(e1); let emut: EntityWorldMut = world.entity_mut(e1); let eref: Result<EntityRef, Entity> = world.get_entity(e1); let emut: Result<EntityWorldMut, Entity> = world.get_entity_mut(e1); ``` Querying for multiple entities with an array has changed: ```rust // 0.14 let erefs: [EntityRef; 2] = world.many_entities([e1, e2]); let emuts: [EntityMut; 2] = world.many_entities_mut([e1, e2]); let erefs: Result<[EntityRef; 2], Entity> = world.get_many_entities([e1, e2]); let emuts: Result<[EntityMut; 2], QueryEntityError> = world.get_many_entities_mut([e1, e2]); // 0.15 let erefs: [EntityRef; 2] = world.entity([e1, e2]); let emuts: [EntityMut; 2] = world.entity_mut([e1, e2]); let erefs: Result<[EntityRef; 2], Entity> = world.get_entity([e1, e2]); let emuts: Result<[EntityMut; 2], EntityFetchError> = world.get_entity_mut([e1, e2]); ``` Querying for multiple entities with a slice has changed: ```rust let ids = vec![e1, e2, e3]); // 0.14 let erefs: Result<Vec<EntityRef>, Entity> = world.get_many_entities_dynamic(&ids[..]); let emuts: Result<Vec<EntityMut>, QueryEntityError> = world.get_many_entities_dynamic_mut(&ids[..]); // 0.15 let erefs: Result<Vec<EntityRef>, Entity> = world.get_entity(&ids[..]); let emuts: Result<Vec<EntityMut>, EntityFetchError> = world.get_entity_mut(&ids[..]); let erefs: Vec<EntityRef> = world.entity(&ids[..]); // Newly possible! let emuts: Vec<EntityMut> = world.entity_mut(&ids[..]); // Newly possible! ``` Querying for multiple entities with an `EntityHashSet` has changed: ```rust let set = EntityHashSet::from_iter([e1, e2, e3]); // 0.14 let emuts: Result<Vec<EntityMut>, QueryEntityError> = world.get_many_entities_from_set_mut(&set); // 0.15 let emuts: Result<EntityHashMap<EntityMut>, EntityFetchError> = world.get_entity_mut(&set); let erefs: Result<EntityHashMap<EntityRef>, EntityFetchError> = world.get_entity(&set); // Newly possible! let emuts: EntityHashMap<EntityMut> = world.entity_mut(&set); // Newly possible! let erefs: EntityHashMap<EntityRef> = world.entity(&set); // Newly possible! ``` </details> ## Migration Guide - `World::get_entity` now returns `Result<_, Entity>` instead of `Option<_>`. - Use `world.get_entity(..).ok()` to return to the previous behavior. - `World::get_entity_mut` and `DeferredWorld::get_entity_mut` now return `Result<_, EntityFetchError>` instead of `Option<_>`. - Use `world.get_entity_mut(..).ok()` to return to the previous behavior. - Type inference for `World::entity`, `World::entity_mut`, `World::get_entity`, `World::get_entity_mut`, `DeferredWorld::entity_mut`, and `DeferredWorld::get_entity_mut` has changed, and might now require the input argument's type to be explicitly written when inside closures. - The following functions have been deprecated, and should be replaced as such: - `World::many_entities` -> `World::entity::<[Entity; N]>` - `World::many_entities_mut` -> `World::entity_mut::<[Entity; N]>` - `World::get_many_entities` -> `World::get_entity::<[Entity; N]>` - `World::get_many_entities_dynamic` -> `World::get_entity::<&[Entity]>` - `World::get_many_entities_mut` -> `World::get_entity_mut::<[Entity; N]>` - The equivalent return type has changed from `Result<_, QueryEntityError>` to `Result<_, EntityFetchError>` - `World::get_many_entities_dynamic_mut` -> `World::get_entity_mut::<&[Entity]>1 - The equivalent return type has changed from `Result<_, QueryEntityError>` to `Result<_, EntityFetchError>` - `World::get_many_entities_from_set_mut` -> `World::get_entity_mut::<&EntityHashSet>` - The equivalent return type has changed from `Result<Vec<EntityMut>, QueryEntityError>` to `Result<EntityHashMap<EntityMut>, EntityFetchError>`. If necessary, you can still convert the `EntityHashMap` into a `Vec`.
1 parent 31409eb commit 584d148

File tree

18 files changed

+967
-284
lines changed

18 files changed

+967
-284
lines changed

crates/bevy_ecs/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,13 +1637,13 @@ mod tests {
16371637
"new entity is created immediately after world_a's max entity"
16381638
);
16391639
assert!(world_b.get::<A>(e1).is_none());
1640-
assert!(world_b.get_entity(e1).is_none());
1640+
assert!(world_b.get_entity(e1).is_err());
16411641

16421642
assert!(world_b.get::<A>(e2).is_none());
1643-
assert!(world_b.get_entity(e2).is_none());
1643+
assert!(world_b.get_entity(e2).is_err());
16441644

16451645
assert!(world_b.get::<A>(e3).is_none());
1646-
assert!(world_b.get_entity(e3).is_none());
1646+
assert!(world_b.get_entity(e3).is_err());
16471647

16481648
world_b.get_or_spawn(e1).unwrap().insert(B(1));
16491649
assert_eq!(
@@ -1694,7 +1694,7 @@ mod tests {
16941694

16951695
let high_non_existent_but_reserved_entity = Entity::from_raw(5);
16961696
assert!(
1697-
world_b.get_entity(high_non_existent_but_reserved_entity).is_none(),
1697+
world_b.get_entity(high_non_existent_but_reserved_entity).is_err(),
16981698
"entities between high-newly allocated entity and continuous block of existing entities don't exist"
16991699
);
17001700

crates/bevy_ecs/src/observer/entity_observer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl Component for ObservedBy {
1919
};
2020
for e in observed_by {
2121
let (total_entities, despawned_watched_entities) = {
22-
let Some(mut entity_mut) = world.get_entity_mut(e) else {
22+
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
2323
continue;
2424
};
2525
let Some(mut state) = entity_mut.get_mut::<ObserverState>() else {

crates/bevy_ecs/src/reflect/entity_commands.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ fn insert_reflect(
221221
.get_represented_type_info()
222222
.expect("component should represent a type.");
223223
let type_path = type_info.type_path();
224-
let Some(mut entity) = world.get_entity_mut(entity) else {
224+
let Ok(mut entity) = world.get_entity_mut(entity) else {
225225
panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003");
226226
};
227227
let Some(type_registration) = type_registry.get(type_info.type_id()) else {
@@ -284,7 +284,7 @@ fn remove_reflect(
284284
type_registry: &TypeRegistry,
285285
component_type_path: Cow<'static, str>,
286286
) {
287-
let Some(mut entity) = world.get_entity_mut(entity) else {
287+
let Ok(mut entity) = world.get_entity_mut(entity) else {
288288
return;
289289
};
290290
let Some(type_registration) = type_registry.get_with_type_path(&component_type_path) else {

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,7 +1757,7 @@ fn try_despawn() -> impl EntityCommand {
17571757
fn insert<T: Bundle>(bundle: T, mode: InsertMode) -> impl EntityCommand {
17581758
let caller = Location::caller();
17591759
move |entity: Entity, world: &mut World| {
1760-
if let Some(mut entity) = world.get_entity_mut(entity) {
1760+
if let Ok(mut entity) = world.get_entity_mut(entity) {
17611761
entity.insert_with_caller(
17621762
bundle,
17631763
mode,
@@ -1776,7 +1776,7 @@ fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityC
17761776
let caller = Location::caller();
17771777
move |entity: Entity, world: &mut World| {
17781778
let value = T::from_world(world);
1779-
if let Some(mut entity) = world.get_entity_mut(entity) {
1779+
if let Ok(mut entity) = world.get_entity_mut(entity) {
17801780
entity.insert_with_caller(
17811781
value,
17821782
mode,
@@ -1795,8 +1795,8 @@ fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityC
17951795
fn try_insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
17961796
#[cfg(feature = "track_change_detection")]
17971797
let caller = Location::caller();
1798-
move |entity, world: &mut World| {
1799-
if let Some(mut entity) = world.get_entity_mut(entity) {
1798+
move |entity: Entity, world: &mut World| {
1799+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18001800
entity.insert_with_caller(
18011801
bundle,
18021802
mode,
@@ -1818,8 +1818,8 @@ unsafe fn insert_by_id<T: Send + 'static>(
18181818
value: T,
18191819
on_none_entity: impl FnOnce(Entity) + Send + 'static,
18201820
) -> impl EntityCommand {
1821-
move |entity, world: &mut World| {
1822-
if let Some(mut entity) = world.get_entity_mut(entity) {
1821+
move |entity: Entity, world: &mut World| {
1822+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18231823
// SAFETY:
18241824
// - `component_id` safety is ensured by the caller
18251825
// - `ptr` is valid within the `make` block;
@@ -1837,7 +1837,7 @@ unsafe fn insert_by_id<T: Send + 'static>(
18371837
/// For a [`Bundle`] type `T`, this will remove any components in the bundle.
18381838
/// Any components in the bundle that aren't found on the entity will be ignored.
18391839
fn remove<T: Bundle>(entity: Entity, world: &mut World) {
1840-
if let Some(mut entity) = world.get_entity_mut(entity) {
1840+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18411841
entity.remove::<T>();
18421842
}
18431843
}
@@ -1848,23 +1848,23 @@ fn remove<T: Bundle>(entity: Entity, world: &mut World) {
18481848
/// Panics if the provided [`ComponentId`] does not exist in the [`World`].
18491849
fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
18501850
move |entity: Entity, world: &mut World| {
1851-
if let Some(mut entity) = world.get_entity_mut(entity) {
1851+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18521852
entity.remove_by_id(component_id);
18531853
}
18541854
}
18551855
}
18561856

18571857
/// An [`EntityCommand`] that remove all components in the bundle and remove all required components for each component in the bundle.
18581858
fn remove_with_requires<T: Bundle>(entity: Entity, world: &mut World) {
1859-
if let Some(mut entity) = world.get_entity_mut(entity) {
1859+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18601860
entity.remove_with_requires::<T>();
18611861
}
18621862
}
18631863

18641864
/// An [`EntityCommand`] that removes all components associated with a provided entity.
18651865
fn clear() -> impl EntityCommand {
18661866
move |entity: Entity, world: &mut World| {
1867-
if let Some(mut entity) = world.get_entity_mut(entity) {
1867+
if let Ok(mut entity) = world.get_entity_mut(entity) {
18681868
entity.clear();
18691869
}
18701870
}
@@ -1875,7 +1875,7 @@ fn clear() -> impl EntityCommand {
18751875
/// For a [`Bundle`] type `T`, this will remove all components except those in the bundle.
18761876
/// Any components in the bundle that aren't found on the entity will be ignored.
18771877
fn retain<T: Bundle>(entity: Entity, world: &mut World) {
1878-
if let Some(mut entity_mut) = world.get_entity_mut(entity) {
1878+
if let Ok(mut entity_mut) = world.get_entity_mut(entity) {
18791879
entity_mut.retain::<T>();
18801880
}
18811881
}
@@ -1919,8 +1919,8 @@ fn log_components(entity: Entity, world: &mut World) {
19191919
fn observe<E: Event, B: Bundle, M>(
19201920
observer: impl IntoObserverSystem<E, B, M>,
19211921
) -> impl EntityCommand {
1922-
move |entity, world: &mut World| {
1923-
if let Some(mut entity) = world.get_entity_mut(entity) {
1922+
move |entity: Entity, world: &mut World| {
1923+
if let Ok(mut entity) = world.get_entity_mut(entity) {
19241924
entity.observe_entity(observer);
19251925
}
19261926
}

crates/bevy_ecs/src/system/system.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ where
271271
/// let entity = world.run_system_once(|mut commands: Commands| {
272272
/// commands.spawn_empty().id()
273273
/// }).unwrap();
274-
/// # assert!(world.get_entity(entity).is_some());
274+
/// # assert!(world.get_entity(entity).is_ok());
275275
/// ```
276276
///
277277
/// ## Immediate Queries

crates/bevy_ecs/src/system/system_registry.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl World {
181181
O: 'static,
182182
{
183183
match self.get_entity_mut(id.entity) {
184-
Some(mut entity) => {
184+
Ok(mut entity) => {
185185
let registered_system = entity
186186
.take::<RegisteredSystem<I, O>>()
187187
.ok_or(RegisteredSystemError::SelfRemove(id))?;
@@ -191,7 +191,7 @@ impl World {
191191
system: registered_system.system,
192192
})
193193
}
194-
None => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
194+
Err(_) => Err(RegisteredSystemError::SystemIdNotRegistered(id)),
195195
}
196196
}
197197

@@ -327,7 +327,7 @@ impl World {
327327
// lookup
328328
let mut entity = self
329329
.get_entity_mut(id.entity)
330-
.ok_or(RegisteredSystemError::SystemIdNotRegistered(id))?;
330+
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
331331

332332
// take ownership of system trait object
333333
let RegisteredSystem {
@@ -350,7 +350,7 @@ impl World {
350350
};
351351

352352
// return ownership of system trait object (if entity still exists)
353-
if let Some(mut entity) = self.get_entity_mut(id.entity) {
353+
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
354354
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
355355
initialized,
356356
system,
@@ -398,7 +398,7 @@ impl World {
398398
}
399399

400400
self.resource_scope(|world, mut id: Mut<CachedSystemId<S::System>>| {
401-
if let Some(mut entity) = world.get_entity_mut(id.0.entity()) {
401+
if let Ok(mut entity) = world.get_entity_mut(id.0.entity()) {
402402
if !entity.contains::<RegisteredSystem<I, O>>() {
403403
entity.insert(system_bundle(Box::new(IntoSystem::into_system(system))));
404404
}
@@ -538,7 +538,7 @@ where
538538
O: Send + 'static,
539539
{
540540
fn apply(self, world: &mut World) {
541-
if let Some(mut entity) = world.get_entity_mut(self.entity) {
541+
if let Ok(mut entity) = world.get_entity_mut(self.entity) {
542542
entity.insert(system_bundle(self.system));
543543
}
544544
}

0 commit comments

Comments
 (0)