Skip to content

Commit 94e162f

Browse files
Victoronzpcwalton
authored andcommitted
impl EntityBorrow for more types (bevyengine#16917)
# Objective Some types like `RenderEntity` and `MainEntity` are just wrappers around `Entity`, so they should be able to implement `EntityBorrow`/`TrustedEntityBorrow`. This allows using them with `EntitySet` functionality. The `EntityRef` family are more than direct wrappers around `Entity`, but can still benefit from being unique in a collection. ## Solution Implement `EntityBorrow` and `TrustedEntityBorrow` for simple `Entity` newtypes and `EntityRef` types. These impls are an explicit decision to have the `EntityRef` types compare like just `Entity`. `EntityWorldMut` is omitted from this impl, because it explicitly contains a `&mut World` as well, and we do not ever use more than one at a time. Add `EntityBorrow` to the `bevy_ecs` prelude. ## Migration Guide `NormalizedWindowRef::entity` has been replaced with an `EntityBorrow::entity` impl.
1 parent d6c4808 commit 94e162f

File tree

10 files changed

+307
-14
lines changed

10 files changed

+307
-14
lines changed

crates/bevy_ecs/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ pub mod prelude {
5858
bundle::Bundle,
5959
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
6060
component::{require, Component},
61-
entity::{Entity, EntityMapper},
61+
entity::{Entity, EntityBorrow, EntityMapper},
6262
event::{Event, EventMutator, EventReader, EventWriter, Events},
6363
name::{Name, NameOrEntity},
6464
observer::{CloneEntityWithObserversExt, Observer, Trigger},

crates/bevy_ecs/src/query/iter.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use super::{QueryData, QueryFilter, ReadOnlyQueryData};
22
use crate::{
33
archetype::{Archetype, ArchetypeEntity, Archetypes},
4+
bundle::Bundle,
45
component::Tick,
56
entity::{Entities, Entity, EntityBorrow, EntitySet, EntitySetIterator},
67
query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId},
78
storage::{Table, TableRow, Tables},
8-
world::unsafe_world_cell::UnsafeWorldCell,
9+
world::{
10+
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
11+
FilteredEntityMut, FilteredEntityRef,
12+
},
913
};
1014
use alloc::vec::Vec;
1115
use core::{
@@ -1105,6 +1109,36 @@ impl<'w, 's, D: QueryData, F: QueryFilter> FusedIterator for QueryIter<'w, 's, D
11051109
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
11061110
unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, Entity, F> {}
11071111

1112+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1113+
unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F> {}
1114+
1115+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1116+
unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F> {}
1117+
1118+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1119+
unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator
1120+
for QueryIter<'w, 's, FilteredEntityRef<'_>, F>
1121+
{
1122+
}
1123+
1124+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1125+
unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator
1126+
for QueryIter<'w, 's, FilteredEntityMut<'_>, F>
1127+
{
1128+
}
1129+
1130+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1131+
unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator
1132+
for QueryIter<'w, 's, EntityRefExcept<'_, B>, F>
1133+
{
1134+
}
1135+
1136+
// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once.
1137+
unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator
1138+
for QueryIter<'w, 's, EntityMutExcept<'_, B>, F>
1139+
{
1140+
}
1141+
11081142
impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> {
11091143
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
11101144
f.debug_struct("QueryIter").finish()

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 238 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use crate::{
33
bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode},
44
change_detection::MutUntyped,
55
component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType},
6-
entity::{Entities, Entity, EntityCloneBuilder, EntityLocation},
6+
entity::{
7+
Entities, Entity, EntityBorrow, EntityCloneBuilder, EntityLocation, TrustedEntityBorrow,
8+
},
79
event::Event,
810
observer::Observer,
911
query::{Access, ReadOnlyQueryData},
@@ -17,7 +19,13 @@ use bevy_ptr::{OwningPtr, Ptr};
1719
use bevy_utils::{HashMap, HashSet};
1820
#[cfg(feature = "track_change_detection")]
1921
use core::panic::Location;
20-
use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit};
22+
use core::{
23+
any::TypeId,
24+
cmp::Ordering,
25+
hash::{Hash, Hasher},
26+
marker::PhantomData,
27+
mem::MaybeUninit,
28+
};
2129
use thiserror::Error;
2230

2331
use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE};
@@ -369,6 +377,44 @@ impl<'a> TryFrom<&'a FilteredEntityMut<'_>> for EntityRef<'a> {
369377
}
370378
}
371379

380+
impl PartialEq for EntityRef<'_> {
381+
fn eq(&self, other: &Self) -> bool {
382+
self.entity() == other.entity()
383+
}
384+
}
385+
386+
impl Eq for EntityRef<'_> {}
387+
388+
#[expect(clippy::non_canonical_partial_ord_impl)]
389+
impl PartialOrd for EntityRef<'_> {
390+
/// [`EntityRef`]'s comparison trait implementations match the underlying [`Entity`],
391+
/// and cannot discern between different worlds.
392+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
393+
self.entity().partial_cmp(&other.entity())
394+
}
395+
}
396+
397+
impl Ord for EntityRef<'_> {
398+
fn cmp(&self, other: &Self) -> Ordering {
399+
self.entity().cmp(&other.entity())
400+
}
401+
}
402+
403+
impl Hash for EntityRef<'_> {
404+
fn hash<H: Hasher>(&self, state: &mut H) {
405+
self.entity().hash(state);
406+
}
407+
}
408+
409+
impl EntityBorrow for EntityRef<'_> {
410+
fn entity(&self) -> Entity {
411+
self.id()
412+
}
413+
}
414+
415+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
416+
unsafe impl TrustedEntityBorrow for EntityRef<'_> {}
417+
372418
/// Provides mutable access to a single entity and all of its components.
373419
///
374420
/// Contrast with [`EntityWorldMut`], which allows adding and removing components,
@@ -869,6 +915,44 @@ impl<'a> TryFrom<&'a mut FilteredEntityMut<'_>> for EntityMut<'a> {
869915
}
870916
}
871917

918+
impl PartialEq for EntityMut<'_> {
919+
fn eq(&self, other: &Self) -> bool {
920+
self.entity() == other.entity()
921+
}
922+
}
923+
924+
impl Eq for EntityMut<'_> {}
925+
926+
#[expect(clippy::non_canonical_partial_ord_impl)]
927+
impl PartialOrd for EntityMut<'_> {
928+
/// [`EntityMut`]'s comparison trait implementations match the underlying [`Entity`],
929+
/// and cannot discern between different worlds.
930+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
931+
self.entity().partial_cmp(&other.entity())
932+
}
933+
}
934+
935+
impl Ord for EntityMut<'_> {
936+
fn cmp(&self, other: &Self) -> Ordering {
937+
self.entity().cmp(&other.entity())
938+
}
939+
}
940+
941+
impl Hash for EntityMut<'_> {
942+
fn hash<H: Hasher>(&self, state: &mut H) {
943+
self.entity().hash(state);
944+
}
945+
}
946+
947+
impl EntityBorrow for EntityMut<'_> {
948+
fn entity(&self) -> Entity {
949+
self.id()
950+
}
951+
}
952+
953+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
954+
unsafe impl TrustedEntityBorrow for EntityMut<'_> {}
955+
872956
/// A mutable reference to a particular [`Entity`], and the entire world.
873957
///
874958
/// This is essentially a performance-optimized `(Entity, &mut World)` tuple,
@@ -2969,6 +3053,44 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> {
29693053
}
29703054
}
29713055

3056+
impl PartialEq for FilteredEntityRef<'_> {
3057+
fn eq(&self, other: &Self) -> bool {
3058+
self.entity() == other.entity()
3059+
}
3060+
}
3061+
3062+
impl Eq for FilteredEntityRef<'_> {}
3063+
3064+
#[expect(clippy::non_canonical_partial_ord_impl)]
3065+
impl PartialOrd for FilteredEntityRef<'_> {
3066+
/// [`FilteredEntityRef`]'s comparison trait implementations match the underlying [`Entity`],
3067+
/// and cannot discern between different worlds.
3068+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3069+
self.entity().partial_cmp(&other.entity())
3070+
}
3071+
}
3072+
3073+
impl Ord for FilteredEntityRef<'_> {
3074+
fn cmp(&self, other: &Self) -> Ordering {
3075+
self.entity().cmp(&other.entity())
3076+
}
3077+
}
3078+
3079+
impl Hash for FilteredEntityRef<'_> {
3080+
fn hash<H: Hasher>(&self, state: &mut H) {
3081+
self.entity().hash(state);
3082+
}
3083+
}
3084+
3085+
impl EntityBorrow for FilteredEntityRef<'_> {
3086+
fn entity(&self) -> Entity {
3087+
self.id()
3088+
}
3089+
}
3090+
3091+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
3092+
unsafe impl TrustedEntityBorrow for FilteredEntityRef<'_> {}
3093+
29723094
/// Provides mutable access to a single entity and some of its components defined by the contained [`Access`].
29733095
///
29743096
/// To define the access when used as a [`QueryData`](crate::query::QueryData),
@@ -3258,6 +3380,44 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a> {
32583380
}
32593381
}
32603382

3383+
impl PartialEq for FilteredEntityMut<'_> {
3384+
fn eq(&self, other: &Self) -> bool {
3385+
self.entity() == other.entity()
3386+
}
3387+
}
3388+
3389+
impl Eq for FilteredEntityMut<'_> {}
3390+
3391+
#[expect(clippy::non_canonical_partial_ord_impl)]
3392+
impl PartialOrd for FilteredEntityMut<'_> {
3393+
/// [`FilteredEntityMut`]'s comparison trait implementations match the underlying [`Entity`],
3394+
/// and cannot discern between different worlds.
3395+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3396+
self.entity().partial_cmp(&other.entity())
3397+
}
3398+
}
3399+
3400+
impl Ord for FilteredEntityMut<'_> {
3401+
fn cmp(&self, other: &Self) -> Ordering {
3402+
self.entity().cmp(&other.entity())
3403+
}
3404+
}
3405+
3406+
impl Hash for FilteredEntityMut<'_> {
3407+
fn hash<H: Hasher>(&self, state: &mut H) {
3408+
self.entity().hash(state);
3409+
}
3410+
}
3411+
3412+
impl EntityBorrow for FilteredEntityMut<'_> {
3413+
fn entity(&self) -> Entity {
3414+
self.id()
3415+
}
3416+
}
3417+
3418+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
3419+
unsafe impl TrustedEntityBorrow for FilteredEntityMut<'_> {}
3420+
32613421
/// Error type returned by [`TryFrom`] conversions from filtered entity types
32623422
/// ([`FilteredEntityRef`]/[`FilteredEntityMut`]) to full-access entity types
32633423
/// ([`EntityRef`]/[`EntityMut`]).
@@ -3361,6 +3521,44 @@ where
33613521
}
33623522
}
33633523

3524+
impl<B: Bundle> PartialEq for EntityRefExcept<'_, B> {
3525+
fn eq(&self, other: &Self) -> bool {
3526+
self.entity() == other.entity()
3527+
}
3528+
}
3529+
3530+
impl<B: Bundle> Eq for EntityRefExcept<'_, B> {}
3531+
3532+
#[expect(clippy::non_canonical_partial_ord_impl)]
3533+
impl<B: Bundle> PartialOrd for EntityRefExcept<'_, B> {
3534+
/// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`],
3535+
/// and cannot discern between different worlds.
3536+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3537+
self.entity().partial_cmp(&other.entity())
3538+
}
3539+
}
3540+
3541+
impl<B: Bundle> Ord for EntityRefExcept<'_, B> {
3542+
fn cmp(&self, other: &Self) -> Ordering {
3543+
self.entity().cmp(&other.entity())
3544+
}
3545+
}
3546+
3547+
impl<B: Bundle> Hash for EntityRefExcept<'_, B> {
3548+
fn hash<H: Hasher>(&self, state: &mut H) {
3549+
self.entity().hash(state);
3550+
}
3551+
}
3552+
3553+
impl<B: Bundle> EntityBorrow for EntityRefExcept<'_, B> {
3554+
fn entity(&self) -> Entity {
3555+
self.id()
3556+
}
3557+
}
3558+
3559+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
3560+
unsafe impl<B: Bundle> TrustedEntityBorrow for EntityRefExcept<'_, B> {}
3561+
33643562
/// Provides mutable access to all components of an entity, with the exception
33653563
/// of an explicit set.
33663564
///
@@ -3464,6 +3662,44 @@ where
34643662
}
34653663
}
34663664

3665+
impl<B: Bundle> PartialEq for EntityMutExcept<'_, B> {
3666+
fn eq(&self, other: &Self) -> bool {
3667+
self.entity() == other.entity()
3668+
}
3669+
}
3670+
3671+
impl<B: Bundle> Eq for EntityMutExcept<'_, B> {}
3672+
3673+
#[expect(clippy::non_canonical_partial_ord_impl)]
3674+
impl<B: Bundle> PartialOrd for EntityMutExcept<'_, B> {
3675+
/// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`],
3676+
/// and cannot discern between different worlds.
3677+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3678+
self.entity().partial_cmp(&other.entity())
3679+
}
3680+
}
3681+
3682+
impl<B: Bundle> Ord for EntityMutExcept<'_, B> {
3683+
fn cmp(&self, other: &Self) -> Ordering {
3684+
self.entity().cmp(&other.entity())
3685+
}
3686+
}
3687+
3688+
impl<B: Bundle> Hash for EntityMutExcept<'_, B> {
3689+
fn hash<H: Hasher>(&self, state: &mut H) {
3690+
self.entity().hash(state);
3691+
}
3692+
}
3693+
3694+
impl<B: Bundle> EntityBorrow for EntityMutExcept<'_, B> {
3695+
fn entity(&self) -> Entity {
3696+
self.id()
3697+
}
3698+
}
3699+
3700+
// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity.
3701+
unsafe impl<B: Bundle> TrustedEntityBorrow for EntityMutExcept<'_, B> {}
3702+
34673703
fn bundle_contains_component<B>(components: &Components, query_id: ComponentId) -> bool
34683704
where
34693705
B: Bundle,

crates/bevy_ecs/src/world/unsafe_world_cell.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
bundle::Bundles,
99
change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut},
1010
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
11-
entity::{Entities, Entity, EntityLocation},
11+
entity::{Entities, Entity, EntityBorrow, EntityLocation},
1212
observer::Observers,
1313
prelude::Component,
1414
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
@@ -1183,3 +1183,9 @@ unsafe fn get_ticks(
11831183
StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity),
11841184
}
11851185
}
1186+
1187+
impl EntityBorrow for UnsafeEntityCell<'_> {
1188+
fn entity(&self) -> Entity {
1189+
self.id()
1190+
}
1191+
}

crates/bevy_render/src/camera/camera.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use bevy_derive::{Deref, DerefMut};
1919
use bevy_ecs::{
2020
change_detection::DetectChanges,
2121
component::{Component, ComponentId, Mutable},
22-
entity::Entity,
22+
entity::{Entity, EntityBorrow},
2323
event::EventReader,
2424
prelude::{require, With},
2525
query::Has,

crates/bevy_render/src/camera/camera_driver_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
renderer::RenderContext,
55
view::ExtractedWindows,
66
};
7-
use bevy_ecs::{prelude::QueryState, world::World};
7+
use bevy_ecs::{entity::EntityBorrow, prelude::QueryState, world::World};
88
use bevy_utils::HashSet;
99
use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp};
1010

0 commit comments

Comments
 (0)