diff --git a/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.rs b/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.rs new file mode 100644 index 0000000000000..f56c6eecd125a --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.rs @@ -0,0 +1,19 @@ +//@no-rustfix + +use bevy_ecs::prelude::*; +use bevy_ecs::world::FilteredEntityMut; +fn main() { + let mut world = World::new(); + let parent = world.spawn_empty().id(); + let _ = world.spawn(ChildOf(parent)).id(); + + let children_id = world.register_component::(); + + let mut query = QueryBuilder::::new(&mut world) + .data::<&Children>() + .build(); + let mut filtered_entity = query.single_mut(&mut world).unwrap(); + + let _borrows_r1 = filtered_entity.get_relationship_targets_by_id(children_id); + let _borrows_r1_mutably = filtered_entity.get_mut_by_id(children_id); +} \ No newline at end of file diff --git a/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.stderr b/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.stderr new file mode 100644 index 0000000000000..edc396f6aa425 --- /dev/null +++ b/crates/bevy_ecs/compile_fail/tests/ui/filtered_entity_mut_get_relationship_target_by_id.stderr @@ -0,0 +1,13 @@ +error[E0502]: cannot borrow `filtered_entity` as mutable because it is also borrowed as immutable + --> tests/ui/filtered_entity_mut_get_relationship_target_by_id.rs:18:31 + | +17 | let _borrows_r1 = filtered_entity.get_relationship_targets_by_id(children_id); + | --------------- immutable borrow occurs here +18 | let _borrows_r1_mutably = filtered_entity.get_mut_by_id(children_id); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +19 | } + | - immutable borrow might be used here, when `_borrows_r1` is dropped and runs the destructor for type `Option>` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0502`. diff --git a/crates/bevy_ecs/src/world/entity_access/entity_mut.rs b/crates/bevy_ecs/src/world/entity_access/entity_mut.rs index c7ecdb4adb62f..61ddfba4564aa 100644 --- a/crates/bevy_ecs/src/world/entity_access/entity_mut.rs +++ b/crates/bevy_ecs/src/world/entity_access/entity_mut.rs @@ -591,6 +591,70 @@ impl<'w> EntityMut<'w> { unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } } + /// Gets the "target" entity of this entity via the [`Relationship`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Examples + /// + /// ## Single [`ComponentId`] + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # let mut world = World::new(); + /// let parent = world.spawn_empty().id(); + /// let child = world.spawn(ChildOf(parent)).id(); + /// + /// // Grab the component ID for `ChildOf` in whatever way you like. + /// let component_id = world.register_component::(); + /// + /// // Then, get the target entity via the 'ChildOf' relationship by ID. + /// let target = world.entity(child).get_relationship_by_id(component_id).unwrap(); + /// assert_eq!(target, parent); + /// ``` + /// + /// [`Relationship`]: crate::relationship::Relationship + pub fn get_relationship_by_id(&self, relationship_id: ComponentId) -> Option { + self.as_readonly().get_relationship_by_id(relationship_id) + } + + /// Gets an iterator to the "related" entities of this entity via the [`RelationshipTarget`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Examples + /// + /// ## Single [`ComponentId`] + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # let mut world = World::new(); + /// let parent = world.spawn_empty().id(); + /// let child = world.spawn(ChildOf(parent)).id(); + /// + /// // Grab the component ID for `Children` in whatever way you like. + /// let component_id = world.register_component::(); + /// + /// // Then, get the target entities via the 'Children' relationship by ID. + /// let targets: Vec = world.entity(parent).get_relationship_targets_by_id(component_id).unwrap().collect(); + /// assert_eq!(targets, vec![child]); + /// ``` + /// + /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget + pub fn get_relationship_targets_by_id( + &self, + relationship_target_id: ComponentId, + ) -> Option + use<'_>> { + self.as_readonly() + .get_relationship_targets_by_id(relationship_target_id) + } + /// Consumes `self` and returns untyped mutable reference(s) /// to component(s) with lifetime `'w` for the current entity, based on the /// given [`ComponentId`]s. diff --git a/crates/bevy_ecs/src/world/entity_access/entity_ref.rs b/crates/bevy_ecs/src/world/entity_access/entity_ref.rs index 9978c775bc4b7..6e3557c66802e 100644 --- a/crates/bevy_ecs/src/world/entity_access/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_access/entity_ref.rs @@ -256,6 +256,74 @@ impl<'w> EntityRef<'w> { unsafe { component_ids.fetch_ref(self.cell) } } + /// Gets the "target" entity of this entity via the [`Relationship`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Examples + /// + /// ## Single [`ComponentId`] + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # let mut world = World::new(); + /// let parent = world.spawn_empty().id(); + /// let child = world.spawn(ChildOf(parent)).id(); + /// + /// // Grab the component ID for `ChildOf` in whatever way you like. + /// let component_id = world.register_component::(); + /// + /// // Then, get the target entity via the 'ChildOf' relationship by ID. + /// let target = world.entity(child).get_relationship_by_id(component_id).unwrap(); + /// assert_eq!(target, parent); + /// ``` + /// + /// [`Relationship`]: crate::relationship::Relationship + pub fn get_relationship_by_id(&self, relationship_id: ComponentId) -> Option { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.cell.get_relationship_by_id(relationship_id) } + } + + /// Gets an iterator to the "related" entities of this entity via the [`RelationshipTarget`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Examples + /// + /// ## Single [`ComponentId`] + /// + /// ``` + /// # use bevy_ecs::prelude::*; + /// # + /// # let mut world = World::new(); + /// let parent = world.spawn_empty().id(); + /// let child = world.spawn(ChildOf(parent)).id(); + /// + /// // Grab the component ID for `Children` in whatever way you like. + /// let component_id = world.register_component::(); + /// + /// // Then, get the target entities via the 'Children' relationship by ID. + /// let targets: Vec = world.entity(parent).get_relationship_targets_by_id(component_id).unwrap().collect(); + /// assert_eq!(targets, vec![child]); + /// ``` + /// + /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget + pub fn get_relationship_targets_by_id( + &self, + relationship_target_id: ComponentId, + ) -> Option + use<'w>> { + // SAFETY: We have read-only access to all components of this entity. + unsafe { + self.cell + .get_relationship_targets_by_id(relationship_target_id) + } + } + /// Returns read-only components for the current entity that match the query `Q`. /// /// # Panics diff --git a/crates/bevy_ecs/src/world/entity_access/filtered.rs b/crates/bevy_ecs/src/world/entity_access/filtered.rs index 4459fc262e517..72fb76fd1ceed 100644 --- a/crates/bevy_ecs/src/world/entity_access/filtered.rs +++ b/crates/bevy_ecs/src/world/entity_access/filtered.rs @@ -199,6 +199,42 @@ impl<'w, 's> FilteredEntityRef<'w, 's> { .flatten() } + /// Gets the "target" entity of this entity via the [`Relationship`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// [`Relationship`]: crate::relationship::Relationship + pub fn get_relationship_by_id(&self, relationship_id: ComponentId) -> Option { + self.access + .has_component_read(relationship_id) + // SAFETY: We have read access + .then(|| unsafe { self.entity.get_relationship_by_id(relationship_id) }) + .flatten() + } + + /// Gets an iterator to the "related" entities of this entity via the [`RelationshipTarget`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget + pub fn get_relationship_targets_by_id( + &self, + relationship_target_id: ComponentId, + ) -> Option + use<'w>> { + self.access + .has_component_read(relationship_target_id) + // SAFETY: We have read access + .then(|| unsafe { + self.entity + .get_relationship_targets_by_id(relationship_target_id) + }) + .flatten() + } + /// Returns the source code location from which this entity has been spawned. pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() @@ -648,6 +684,32 @@ impl<'w, 's> FilteredEntityMut<'w, 's> { .flatten() } + /// Gets the "target" entity of this entity via the [`Relationship`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// [`Relationship`]: crate::relationship::Relationship + pub fn get_relationship_by_id(&self, relationship_id: ComponentId) -> Option { + self.as_readonly().get_relationship_by_id(relationship_id) + } + + /// Gets an iterator to the "related" entities of this entity via the [`RelationshipTarget`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget + pub fn get_relationship_targets_by_id( + &self, + relationship_target_id: ComponentId, + ) -> Option + use<'_>> { + self.as_readonly() + .get_relationship_targets_by_id(relationship_target_id) + } + /// Returns the source code location from which this entity has last been spawned. pub fn spawned_by(&self) -> MaybeLocation { self.entity.spawned_by() diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index b3d807d36fb35..22b70b122e773 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -1,6 +1,7 @@ //! Contains types that allow disjoint mutable access to a [`World`]. use super::{Mut, Ref, World, WorldId}; +use crate::relationship::RelationshipAccessor; use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, @@ -1136,6 +1137,70 @@ impl<'w> UnsafeEntityCell<'w> { } } + /// Gets the "target" entity of this entity via the [`Relationship`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Safety + /// It is the caller's responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the relationship component + /// - no other mutable references to the component exist at the same time + /// + /// [`Relationship`]: crate::relationship::Relationship + #[inline] + pub unsafe fn get_relationship_by_id(self, relationship_id: ComponentId) -> Option { + let ptr = self.get_by_id(relationship_id)?; + let RelationshipAccessor::Relationship { + entity_field_offset, + .. + } = self + .world + .components() + .get_info(relationship_id)? + .relationship_accessor()? + else { + return None; + }; + // Safety: + // - offset is in bounds, aligned and has the same lifetime as the original pointer. + // - value at offset is guaranteed to be a valid Entity + let target: Entity = unsafe { *ptr.byte_add(*entity_field_offset).deref() }; + Some(target) + } + + /// Gets an iterator to the "related" entities of this entity via the [`RelationshipTarget`] component with the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Safety + /// It is the caller's responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the relationship component + /// - no other mutable references to the component exist at the same time + /// + /// [`RelationshipTarget`]: crate::relationship::RelationshipTarget + #[inline] + pub unsafe fn get_relationship_targets_by_id( + self, + relationship_target_id: ComponentId, + ) -> Option + use<'w>> { + let ptr = self.get_by_id(relationship_target_id)?; + let RelationshipAccessor::RelationshipTarget { iter, .. } = self + .world + .components() + .get_info(relationship_target_id)? + .relationship_accessor()? + else { + return None; + }; + // Safety: `ptr` contains value of the same type as the one this accessor was registered for. + let targets = unsafe { iter(ptr) }; + Some(targets) + } + /// Returns the source code location from which this entity has been spawned. pub fn spawned_by(self) -> MaybeLocation { self.world() diff --git a/release-content/migration-guides/dynamic_relationships_api.md b/release-content/migration-guides/dynamic_relationships_api.md index 9317ad8a32d4f..5aff6455b37f6 100644 --- a/release-content/migration-guides/dynamic_relationships_api.md +++ b/release-content/migration-guides/dynamic_relationships_api.md @@ -1,9 +1,15 @@ --- title: API for working with `Relationships` and `RelationshipTargets` in type-erased contexts -pull_requests: [21601] +pull_requests: [21601, 21639] --- `ComponentDescriptor` now stores additional data for working with relationships in dynamic contexts. This resulted in changes to `ComponentDescriptor::new_with_layout`: - Now requires additional parameter `relationship_accessor`, which should be set to `None` for all existing code creating `ComponentDescriptors`. + +`UnsafeEntityCell`, `EntityRef`, `EntityMut`, `FilteredEntityRef`, `FilteredEntityMut` can now access relationship values +in dynamic contexts with the new public methods: + +- `get_relationship_by_id` +- `get_relationship_targets_by_id