Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-Vec data structures in relations #17447

Merged
merged 13 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benches/benches/bevy_ecs/world/entity_hash.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy_ecs::entity::{Entity, EntityHashSet};
use bevy_ecs::entity::{hash_set::EntityHashSet, Entity};
use criterion::{BenchmarkId, Criterion, Throughput};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_core_pipeline/src/oit/resolve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, Handle};
use bevy_derive::Deref;
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet},
prelude::*,
};
use bevy_image::BevyDefault as _;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ derive_more = { version = "1", default-features = false, features = [
] }
nonmax = { version = "0.5", default-features = false }
arrayvec = { version = "0.7.4", default-features = false, optional = true }
smallvec = { version = "1", features = ["union"] }
smallvec = { version = "1", features = ["union", "const_generics"] }
indexmap = { version = "2.5.0", default-features = false }
variadics_please = { version = "1.1", default-features = false }
critical-section = { version = "1.2.0", optional = true }
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_ecs/src/entity/hash_map.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! Contains the [`EntityHashMap`] type, a [`HashMap`] pre-configured to use [`EntityHash`] hashing.
//!
//! This module is a lightweight wrapper around [`hashbrown`](bevy_utils::hashbrown)'s [`HashMap`] that is more performant for [`Entity`] keys.

use core::{
fmt::{self, Debug, Formatter},
iter::FusedIterator,
Expand Down
44 changes: 29 additions & 15 deletions crates/bevy_ecs/src/entity/hash_set.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//! Contains the [`EntityHashSet`] type, a [`HashSet`] pre-configured to use [`EntityHash`] hashing.
//!
//! This module is a lightweight wrapper around [`hashbrown`](bevy_utils::hashbrown)'s [`HashSet`] that is more performant for [`Entity`] keys.

use core::{
fmt::{self, Debug, Formatter},
iter::FusedIterator,
Expand Down Expand Up @@ -38,6 +42,16 @@ impl EntityHashSet {
Self(HashSet::with_capacity_and_hasher(n, EntityHash))
}

/// Returns the number of elements in the set.
pub fn len(&self) -> usize {
self.0.len()
}

/// Returns `true` if the set contains no elements.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

Comment on lines +45 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these not available via the Deref to the inner hashbrown set?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just relying on the Deref impl was failing for these, as the compiler was getting confused about the circular trait methods due to the same name / signature.

Copy link
Contributor

@Victoronz Victoronz Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, that is because of method resolution!
when len is an inherent method on self, then self.len() in a len() trait method impl works, but if the inherent len() is down a step in the deref chain, the trait method on self resolves first, resulting in a cycle.
The solution should be any of self.0.len()/(*self).len()/self.deref().len() in the trait method impl.

/// Returns the inner [`HashSet`].
pub fn into_inner(self) -> HashSet<Entity, EntityHash> {
self.0
Expand All @@ -54,8 +68,8 @@ impl EntityHashSet {
/// The iterator element type is `&'a Entity`.
///
/// Equivalent to [`HashSet::iter`].
pub fn iter(&self) -> Iter<'_> {
Iter(self.0.iter(), PhantomData)
pub fn iter(&self) -> EntityHashSetIter<'_> {
EntityHashSetIter(self.0.iter(), PhantomData)
}

/// Drains elements which are true under the given predicate,
Expand Down Expand Up @@ -84,10 +98,10 @@ impl DerefMut for EntityHashSet {
impl<'a> IntoIterator for &'a EntityHashSet {
type Item = &'a Entity;

type IntoIter = Iter<'a>;
type IntoIter = EntityHashSetIter<'a>;

fn into_iter(self) -> Self::IntoIter {
Iter((&self.0).into_iter(), PhantomData)
EntityHashSetIter((&self.0).into_iter(), PhantomData)
}
}

Expand Down Expand Up @@ -186,61 +200,61 @@ impl FromIterator<Entity> for EntityHashSet {
/// This struct is created by the [`iter`] method on [`EntityHashSet`]. See its documentation for more.
///
/// [`iter`]: EntityHashSet::iter
pub struct Iter<'a, S = EntityHash>(hash_set::Iter<'a, Entity>, PhantomData<S>);
pub struct EntityHashSetIter<'a, S = EntityHash>(hash_set::Iter<'a, Entity>, PhantomData<S>);
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

impl<'a> Iter<'a> {
impl<'a> EntityHashSetIter<'a> {
/// Returns the inner [`Iter`](hash_set::Iter).
pub fn into_inner(self) -> hash_set::Iter<'a, Entity> {
self.0
}
}

impl<'a> Deref for Iter<'a> {
impl<'a> Deref for EntityHashSetIter<'a> {
type Target = hash_set::Iter<'a, Entity>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for Iter<'_> {
impl DerefMut for EntityHashSetIter<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl<'a> Iterator for Iter<'a> {
impl<'a> Iterator for EntityHashSetIter<'a> {
type Item = &'a Entity;

fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}

impl ExactSizeIterator for Iter<'_> {}
impl ExactSizeIterator for EntityHashSetIter<'_> {}

impl FusedIterator for Iter<'_> {}
impl FusedIterator for EntityHashSetIter<'_> {}

impl Clone for Iter<'_> {
impl Clone for EntityHashSetIter<'_> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}

impl Debug for Iter<'_> {
impl Debug for EntityHashSetIter<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_tuple("Iter").field(&self.0).field(&self.1).finish()
}
}

impl Default for Iter<'_> {
impl Default for EntityHashSetIter<'_> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}

// SAFETY: Iter stems from a correctly behaving `HashSet<Entity, EntityHash>`.
unsafe impl EntitySetIterator for Iter<'_> {}
unsafe impl EntitySetIterator for EntityHashSetIter<'_> {}

/// Owning iterator over the items of an [`EntityHashSet`].
///
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
world::World,
};

use super::{EntityHashMap, VisitEntitiesMut};
use super::{hash_map::EntityHashMap, VisitEntitiesMut};

/// Operation to map all contained [`Entity`] fields in a type to new values.
///
Expand Down Expand Up @@ -71,7 +71,7 @@ impl<T: VisitEntitiesMut> MapEntities for T {
///
/// ```
/// # use bevy_ecs::entity::{Entity, EntityMapper};
/// # use bevy_ecs::entity::EntityHashMap;
/// # use bevy_ecs::entity::hash_map::EntityHashMap;
/// #
/// pub struct SimpleEntityMapper {
/// map: EntityHashMap<Entity>,
Expand Down Expand Up @@ -194,7 +194,7 @@ impl<'m> SceneEntityMapper<'m> {
#[cfg(test)]
mod tests {
use crate::{
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
entity::{hash_map::EntityHashMap, Entity, EntityMapper, SceneEntityMapper},
world::World,
};

Expand Down
7 changes: 2 additions & 5 deletions crates/bevy_ecs/src/entity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,8 @@ pub use visit_entities::*;
mod hash;
pub use hash::*;

mod hash_map;
mod hash_set;

pub use hash_map::EntityHashMap;
pub use hash_set::EntityHashSet;
pub mod hash_map;
pub mod hash_set;

use crate::{
archetype::{ArchetypeId, ArchetypeRow},
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/entity/visit_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl VisitEntitiesMut for Entity {
mod tests {
use crate::{
self as bevy_ecs,
entity::{EntityHashMap, MapEntities, SceneEntityMapper},
entity::{hash_map::EntityHashMap, MapEntities, SceneEntityMapper},
world::World,
};
use alloc::{string::String, vec, vec::Vec};
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/observer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub use runner::*;
use crate::{
archetype::ArchetypeFlags,
component::ComponentId,
entity::EntityHashMap,
entity::hash_map::EntityHashMap,
prelude::*,
system::IntoObserverSystem,
world::{DeferredWorld, *},
Expand Down
12 changes: 11 additions & 1 deletion crates/bevy_ecs/src/relationship/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,22 @@ pub trait Relationship: Component + Sized {
}
}

/// The iterator type for the source entities in a [`RelationshipTarget`] collection,
/// as defined in the [`RelationshipSourceCollection`] trait.
pub type SourceIter<'w, R> =
<<R as RelationshipTarget>::Collection as RelationshipSourceCollection>::SourceIter<'w>;

/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type.
/// See the [`Relationship`] documentation for more information.
pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
/// The [`Relationship`] that populates this [`RelationshipTarget`] collection.
type Relationship: Relationship<RelationshipTarget = Self>;
/// The collection type that stores the "source" entities for this [`RelationshipTarget`] component.
///
/// Check the list of types which implement [`RelationshipSourceCollection`] for the data structures that can be used inside of your component.
/// If you need a new collection type, you can implement the [`RelationshipSourceCollection`] trait
/// for a type you own which wraps the collection you want to use (to avoid the orphan rule),
/// or open an issue on the Bevy repository to request first-party support for your collection type.
type Collection: RelationshipSourceCollection;

/// Returns a reference to the stored [`RelationshipTarget::Collection`].
Expand Down Expand Up @@ -210,7 +220,7 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {

/// Iterates the entities stored in this collection.
#[inline]
fn iter(&self) -> impl DoubleEndedIterator<Item = Entity> {
fn iter(&self) -> SourceIter<'_, Self> {
self.collection().iter()
}

Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_ecs/src/relationship/relationship_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::{
use alloc::collections::VecDeque;
use smallvec::SmallVec;

use super::SourceIter;

impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// If the given `entity` contains the `R` [`Relationship`] component, returns the
/// target entity of that relationship.
Expand Down Expand Up @@ -59,6 +61,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
) -> impl Iterator<Item = Entity> + 'w
where
<D as QueryData>::ReadOnly: WorldQuery<Item<'w> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
self.iter_descendants_depth_first(entity).filter(|entity| {
self.get(*entity)
Expand Down Expand Up @@ -114,6 +117,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
) -> DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
DescendantDepthFirstIter::new(self, entity)
}
Expand Down Expand Up @@ -195,6 +199,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget>
DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
/// Returns a new [`DescendantDepthFirstIter`].
pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self {
Expand All @@ -211,6 +216,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator
for DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: WorldQuery<Item<'w> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
type Item = Entity;

Expand Down
Loading
Loading