Skip to content

Commit fecf2d2

Browse files
Provide a safe abstraction for split access to entities and commands (#18215)
# Objective Currently experimenting with manually implementing `Relationship`/`RelationshipTarget` to support associated edge data, which means I need to replace the default hook implementations provided by those traits. However, copying them over for editing revealed that `UnsafeWorldCell::get_raw_command_queue` is `pub(crate)`, and I would like to not have to clone the source collection, like the default impl. So instead, I've taken to providing a safe abstraction for being able to access entities and queue commands simultaneously. ## Solution Added `World::entities_and_commands` and `DeferredWorld::entities_and_commands`, which can be used like so: ```rust let eid: Entity = /* ... */; let (mut fetcher, mut commands) = world.entities_and_commands(); let emut = fetcher.get_mut(eid).unwrap(); commands.entity(eid).despawn(); ``` ## Testing - Added a new test for each of the added functions. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 770059c commit fecf2d2

File tree

4 files changed

+256
-49
lines changed

4 files changed

+256
-49
lines changed

crates/bevy_ecs/src/relationship/mod.rs

Lines changed: 34 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -207,29 +207,23 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
207207
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
208208
// note: think of this as "on_drop"
209209
fn on_replace(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
210-
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
211-
// copying the RelationshipTarget collection
212-
// SAFETY: This only reads the Self component and queues Remove commands
213-
unsafe {
214-
let world = world.as_unsafe_world_cell();
215-
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
216-
let mut commands = world.get_raw_command_queue();
217-
for source_entity in relationship_target.iter() {
218-
if world.get_entity(source_entity).is_ok() {
219-
commands.push(
220-
entity_command::remove::<Self::Relationship>()
221-
.with_entity(source_entity)
222-
.handle_error_with(error_handler::silent()),
223-
);
224-
} else {
225-
warn!(
226-
"{}Tried to despawn non-existent entity {}",
227-
caller
228-
.map(|location| format!("{location}: "))
229-
.unwrap_or_default(),
230-
source_entity
231-
);
232-
}
210+
let (entities, mut commands) = world.entities_and_commands();
211+
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
212+
for source_entity in relationship_target.iter() {
213+
if entities.get(source_entity).is_ok() {
214+
commands.queue(
215+
entity_command::remove::<Self::Relationship>()
216+
.with_entity(source_entity)
217+
.handle_error_with(error_handler::silent()),
218+
);
219+
} else {
220+
warn!(
221+
"{}Tried to despawn non-existent entity {}",
222+
caller
223+
.map(|location| format!("{location}: "))
224+
.unwrap_or_default(),
225+
source_entity
226+
);
233227
}
234228
}
235229
}
@@ -238,29 +232,23 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
238232
/// that entity is despawned.
239233
// note: think of this as "on_drop"
240234
fn on_despawn(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
241-
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
242-
// copying the RelationshipTarget collection
243-
// SAFETY: This only reads the Self component and queues despawn commands
244-
unsafe {
245-
let world = world.as_unsafe_world_cell();
246-
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
247-
let mut commands = world.get_raw_command_queue();
248-
for source_entity in relationship_target.iter() {
249-
if world.get_entity(source_entity).is_ok() {
250-
commands.push(
251-
entity_command::despawn()
252-
.with_entity(source_entity)
253-
.handle_error_with(error_handler::silent()),
254-
);
255-
} else {
256-
warn!(
257-
"{}Tried to despawn non-existent entity {}",
258-
caller
259-
.map(|location| format!("{location}: "))
260-
.unwrap_or_default(),
261-
source_entity
262-
);
263-
}
235+
let (entities, mut commands) = world.entities_and_commands();
236+
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
237+
for source_entity in relationship_target.iter() {
238+
if entities.get(source_entity).is_ok() {
239+
commands.queue(
240+
entity_command::despawn()
241+
.with_entity(source_entity)
242+
.handle_error_with(error_handler::silent()),
243+
);
244+
} else {
245+
warn!(
246+
"{}Tried to despawn non-existent entity {}",
247+
caller
248+
.map(|location| format!("{location}: "))
249+
.unwrap_or_default(),
250+
source_entity
251+
);
264252
}
265253
}
266254
}

crates/bevy_ecs/src/world/deferred_world.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
resource::Resource,
1414
system::{Commands, Query},
1515
traversal::Traversal,
16-
world::{error::EntityMutableFetchError, WorldEntityFetch},
16+
world::{error::EntityMutableFetchError, EntityFetcher, WorldEntityFetch},
1717
};
1818

1919
use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLACE};
@@ -341,6 +341,53 @@ impl<'w> DeferredWorld<'w> {
341341
self.get_entity_mut(entities).unwrap()
342342
}
343343

344+
/// Simultaneously provides access to entity data and a command queue, which
345+
/// will be applied when the [`World`] is next flushed.
346+
///
347+
/// This allows using borrowed entity data to construct commands where the
348+
/// borrow checker would otherwise prevent it.
349+
///
350+
/// See [`World::entities_and_commands`] for the non-deferred version.
351+
///
352+
/// # Example
353+
///
354+
/// ```rust
355+
/// # use bevy_ecs::{prelude::*, world::DeferredWorld};
356+
/// #[derive(Component)]
357+
/// struct Targets(Vec<Entity>);
358+
/// #[derive(Component)]
359+
/// struct TargetedBy(Entity);
360+
///
361+
/// # let mut _world = World::new();
362+
/// # let e1 = _world.spawn_empty().id();
363+
/// # let e2 = _world.spawn_empty().id();
364+
/// # let eid = _world.spawn(Targets(vec![e1, e2])).id();
365+
/// let mut world: DeferredWorld = // ...
366+
/// # DeferredWorld::from(&mut _world);
367+
/// let (entities, mut commands) = world.entities_and_commands();
368+
///
369+
/// let entity = entities.get(eid).unwrap();
370+
/// for &target in entity.get::<Targets>().unwrap().0.iter() {
371+
/// commands.entity(target).insert(TargetedBy(eid));
372+
/// }
373+
/// # _world.flush();
374+
/// # assert_eq!(_world.get::<TargetedBy>(e1).unwrap().0, eid);
375+
/// # assert_eq!(_world.get::<TargetedBy>(e2).unwrap().0, eid);
376+
/// ```
377+
pub fn entities_and_commands(&mut self) -> (EntityFetcher, Commands) {
378+
let cell = self.as_unsafe_world_cell();
379+
// SAFETY: `&mut self` gives mutable access to the entire world, and prevents simultaneous access.
380+
let fetcher = unsafe { EntityFetcher::new(cell) };
381+
// SAFETY:
382+
// - `&mut self` gives mutable access to the entire world, and prevents simultaneous access.
383+
// - Command queue access does not conflict with entity access.
384+
let raw_queue = unsafe { cell.get_raw_command_queue() };
385+
// SAFETY: `&mut self` ensures the commands does not outlive the world.
386+
let commands = unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities()) };
387+
388+
(fetcher, commands)
389+
}
390+
344391
/// Returns [`Query`] for the given [`QueryState`], which is used to efficiently
345392
/// run queries on the [`World`] by storing and reusing the [`QueryState`].
346393
///

crates/bevy_ecs/src/world/entity_fetch.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,98 @@ use core::mem::MaybeUninit;
33

44
use crate::{
55
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet, Entity, EntityDoesNotExistError},
6+
error::Result,
67
world::{
78
error::EntityMutableFetchError, unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef,
89
EntityWorldMut,
910
},
1011
};
1112

13+
/// Provides a safe interface for non-structural access to the entities in a [`World`].
14+
///
15+
/// This cannot add or remove components, or spawn or despawn entities,
16+
/// making it relatively safe to access in concert with other ECS data.
17+
/// This type can be constructed via [`World::entities_and_commands`],
18+
/// or [`DeferredWorld::entities_and_commands`].
19+
///
20+
/// [`World`]: crate::world::World
21+
/// [`World::entities_and_commands`]: crate::world::World::entities_and_commands
22+
/// [`DeferredWorld::entities_and_commands`]: crate::world::DeferredWorld::entities_and_commands
23+
pub struct EntityFetcher<'w> {
24+
cell: UnsafeWorldCell<'w>,
25+
}
26+
27+
impl<'w> EntityFetcher<'w> {
28+
// SAFETY:
29+
// - The given `cell` has mutable access to all entities.
30+
// - No other references to entities exist at the same time.
31+
pub(crate) unsafe fn new(cell: UnsafeWorldCell<'w>) -> Self {
32+
Self { cell }
33+
}
34+
35+
/// Returns [`EntityRef`]s that expose read-only operations for the given
36+
/// `entities`, returning [`Err`] if any of the given entities do not exist.
37+
///
38+
/// This function supports fetching a single entity or multiple entities:
39+
/// - Pass an [`Entity`] to receive a single [`EntityRef`].
40+
/// - Pass a slice of [`Entity`]s to receive a [`Vec<EntityRef>`].
41+
/// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityRef`]s.
42+
/// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an
43+
/// [`EntityHashMap<EntityRef>`](crate::entity::hash_map::EntityHashMap).
44+
///
45+
/// # Errors
46+
///
47+
/// If any of the given `entities` do not exist in the world, the first
48+
/// [`Entity`] found to be missing will return an [`EntityDoesNotExistError`].
49+
///
50+
/// # Examples
51+
///
52+
/// For examples, see [`World::entity`].
53+
///
54+
/// [`World::entity`]: crate::world::World::entity
55+
#[inline]
56+
pub fn get<F: WorldEntityFetch>(
57+
&self,
58+
entities: F,
59+
) -> Result<F::Ref<'_>, EntityDoesNotExistError> {
60+
// SAFETY: `&self` gives read access to all entities, and prevents mutable access.
61+
unsafe { entities.fetch_ref(self.cell) }
62+
}
63+
64+
/// Returns [`EntityMut`]s that expose read and write operations for the
65+
/// given `entities`, returning [`Err`] if any of the given entities do not
66+
/// exist.
67+
///
68+
/// This function supports fetching a single entity or multiple entities:
69+
/// - Pass an [`Entity`] to receive a single [`EntityMut`].
70+
/// - This reference type allows for structural changes to the entity,
71+
/// such as adding or removing components, or despawning the entity.
72+
/// - Pass a slice of [`Entity`]s to receive a [`Vec<EntityMut>`].
73+
/// - Pass an array of [`Entity`]s to receive an equally-sized array of [`EntityMut`]s.
74+
/// - Pass a reference to a [`EntityHashSet`](crate::entity::hash_map::EntityHashMap) to receive an
75+
/// [`EntityHashMap<EntityMut>`](crate::entity::hash_map::EntityHashMap).
76+
/// # Errors
77+
///
78+
/// - Returns [`EntityMutableFetchError::EntityDoesNotExist`] if any of the given `entities` do not exist in the world.
79+
/// - Only the first entity found to be missing will be returned.
80+
/// - Returns [`EntityMutableFetchError::AliasedMutability`] if the same entity is requested multiple times.
81+
///
82+
/// # Examples
83+
///
84+
/// For examples, see [`DeferredWorld::entity_mut`].
85+
///
86+
/// [`DeferredWorld::entity_mut`]: crate::world::DeferredWorld::entity_mut
87+
#[inline]
88+
pub fn get_mut<F: WorldEntityFetch>(
89+
&mut self,
90+
entities: F,
91+
) -> Result<F::DeferredMut<'_>, EntityMutableFetchError> {
92+
// SAFETY: `&mut self` gives mutable access to all entities,
93+
// and prevents any other access to entities.
94+
unsafe { entities.fetch_deferred_mut(self.cell) }
95+
}
96+
}
97+
1298
/// Types that can be used to fetch [`Entity`] references from a [`World`].
1399
///
14100
/// Provided implementations are:

crates/bevy_ecs/src/world/mod.rs

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub use crate::{
2121
pub use bevy_ecs_macros::FromWorld;
2222
pub use component_constants::*;
2323
pub use deferred_world::DeferredWorld;
24-
pub use entity_fetch::WorldEntityFetch;
24+
pub use entity_fetch::{EntityFetcher, WorldEntityFetch};
2525
pub use entity_ref::{
2626
DynamicComponentFetch, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut,
2727
Entry, FilteredEntityMut, FilteredEntityRef, OccupiedEntry, TryFromFilteredError, VacantEntry,
@@ -1015,6 +1015,52 @@ impl World {
10151015
})
10161016
}
10171017

1018+
/// Simultaneously provides access to entity data and a command queue, which
1019+
/// will be applied when the world is next flushed.
1020+
///
1021+
/// This allows using borrowed entity data to construct commands where the
1022+
/// borrow checker would otherwise prevent it.
1023+
///
1024+
/// See [`DeferredWorld::entities_and_commands`] for the deferred version.
1025+
///
1026+
/// # Example
1027+
///
1028+
/// ```rust
1029+
/// # use bevy_ecs::{prelude::*, world::DeferredWorld};
1030+
/// #[derive(Component)]
1031+
/// struct Targets(Vec<Entity>);
1032+
/// #[derive(Component)]
1033+
/// struct TargetedBy(Entity);
1034+
///
1035+
/// let mut world: World = // ...
1036+
/// # World::new();
1037+
/// # let e1 = world.spawn_empty().id();
1038+
/// # let e2 = world.spawn_empty().id();
1039+
/// # let eid = world.spawn(Targets(vec![e1, e2])).id();
1040+
/// let (entities, mut commands) = world.entities_and_commands();
1041+
///
1042+
/// let entity = entities.get(eid).unwrap();
1043+
/// for &target in entity.get::<Targets>().unwrap().0.iter() {
1044+
/// commands.entity(target).insert(TargetedBy(eid));
1045+
/// }
1046+
/// # world.flush();
1047+
/// # assert_eq!(world.get::<TargetedBy>(e1).unwrap().0, eid);
1048+
/// # assert_eq!(world.get::<TargetedBy>(e2).unwrap().0, eid);
1049+
/// ```
1050+
pub fn entities_and_commands(&mut self) -> (EntityFetcher, Commands) {
1051+
let cell = self.as_unsafe_world_cell();
1052+
// SAFETY: `&mut self` gives mutable access to the entire world, and prevents simultaneous access.
1053+
let fetcher = unsafe { EntityFetcher::new(cell) };
1054+
// SAFETY:
1055+
// - `&mut self` gives mutable access to the entire world, and prevents simultaneous access.
1056+
// - Command queue access does not conflict with entity access.
1057+
let raw_queue = unsafe { cell.get_raw_command_queue() };
1058+
// SAFETY: `&mut self` ensures the commands does not outlive the world.
1059+
let commands = unsafe { Commands::new_raw_from_entities(raw_queue, cell.entities()) };
1060+
1061+
(fetcher, commands)
1062+
}
1063+
10181064
/// Spawns a new [`Entity`] and returns a corresponding [`EntityWorldMut`], which can be used
10191065
/// to add components to the entity or retrieve its id.
10201066
///
@@ -3692,7 +3738,7 @@ mod tests {
36923738
entity_disabling::{DefaultQueryFilters, Disabled},
36933739
ptr::OwningPtr,
36943740
resource::Resource,
3695-
world::error::EntityMutableFetchError,
3741+
world::{error::EntityMutableFetchError, DeferredWorld},
36963742
};
36973743
use alloc::{
36983744
borrow::ToOwned,
@@ -4388,4 +4434,44 @@ mod tests {
43884434
world.remove_resource::<DefaultQueryFilters>();
43894435
assert_eq!(2, world.query::<&Foo>().iter(&world).count());
43904436
}
4437+
4438+
#[test]
4439+
fn entities_and_commands() {
4440+
#[derive(Component, PartialEq, Debug)]
4441+
struct Foo(u32);
4442+
4443+
let mut world = World::new();
4444+
4445+
let eid = world.spawn(Foo(35)).id();
4446+
4447+
let (mut fetcher, mut commands) = world.entities_and_commands();
4448+
let emut = fetcher.get_mut(eid).unwrap();
4449+
commands.entity(eid).despawn();
4450+
assert_eq!(emut.get::<Foo>().unwrap(), &Foo(35));
4451+
4452+
world.flush();
4453+
4454+
assert!(world.get_entity(eid).is_err());
4455+
}
4456+
4457+
#[test]
4458+
fn entities_and_commands_deferred() {
4459+
#[derive(Component, PartialEq, Debug)]
4460+
struct Foo(u32);
4461+
4462+
let mut world = World::new();
4463+
4464+
let eid = world.spawn(Foo(1)).id();
4465+
4466+
let mut dworld = DeferredWorld::from(&mut world);
4467+
4468+
let (mut fetcher, mut commands) = dworld.entities_and_commands();
4469+
let emut = fetcher.get_mut(eid).unwrap();
4470+
commands.entity(eid).despawn();
4471+
assert_eq!(emut.get::<Foo>().unwrap(), &Foo(1));
4472+
4473+
world.flush();
4474+
4475+
assert!(world.get_entity(eid).is_err());
4476+
}
43914477
}

0 commit comments

Comments
 (0)