diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 419972fdfff79..ab949045ef5b6 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -58,7 +58,7 @@ pub mod prelude { bundle::Bundle, change_detection::{DetectChanges, DetectChangesMut, Mut, Ref}, component::{require, Component}, - entity::{Entity, EntityMapper}, + entity::{Entity, EntityBorrow, EntityMapper}, event::{Event, EventMutator, EventReader, EventWriter, Events}, name::{Name, NameOrEntity}, observer::{CloneEntityWithObserversExt, Observer, Trigger}, diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 816c92e76ca14..c7303382f68d8 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,11 +1,15 @@ use super::{QueryData, QueryFilter, ReadOnlyQueryData}; use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, + bundle::Bundle, component::Tick, entity::{Entities, Entity, EntityBorrow, EntitySet, EntitySetIterator}, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId}, storage::{Table, TableRow, Tables}, - world::unsafe_world_cell::UnsafeWorldCell, + world::{ + unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, + FilteredEntityMut, FilteredEntityRef, + }, }; use alloc::vec::Vec; use core::{ @@ -1105,6 +1109,36 @@ impl<'w, 's, D: QueryData, F: QueryFilter> FusedIterator for QueryIter<'w, 's, D // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, Entity, F> {} +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityRef<'_>, F> {} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator for QueryIter<'w, 's, EntityMut<'_>, F> {} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityRef<'_>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator + for QueryIter<'w, 's, FilteredEntityMut<'_>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator + for QueryIter<'w, 's, EntityRefExcept<'_, B>, F> +{ +} + +// SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. +unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator + for QueryIter<'w, 's, EntityMutExcept<'_, B>, F> +{ +} + impl<'w, 's, D: QueryData, F: QueryFilter> Debug for QueryIter<'w, 's, D, F> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("QueryIter").finish() diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 64dd16149a0ca..f8b8e55f59f37 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -3,7 +3,9 @@ use crate::{ bundle::{Bundle, BundleId, BundleInfo, BundleInserter, DynamicBundle, InsertMode}, change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType}, - entity::{Entities, Entity, EntityCloneBuilder, EntityLocation}, + entity::{ + Entities, Entity, EntityBorrow, EntityCloneBuilder, EntityLocation, TrustedEntityBorrow, + }, event::Event, observer::Observer, query::{Access, ReadOnlyQueryData}, @@ -17,7 +19,13 @@ use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::{HashMap, HashSet}; #[cfg(feature = "track_change_detection")] use core::panic::Location; -use core::{any::TypeId, marker::PhantomData, mem::MaybeUninit}; +use core::{ + any::TypeId, + cmp::Ordering, + hash::{Hash, Hasher}, + marker::PhantomData, + mem::MaybeUninit, +}; use thiserror::Error; use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE, ON_REPLACE}; @@ -369,6 +377,44 @@ impl<'a> TryFrom<&'a FilteredEntityMut<'_>> for EntityRef<'a> { } } +impl PartialEq for EntityRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityRef<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityRef<'_> { + /// [`EntityRef`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityRef<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityRef<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityRef<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityRef<'_> {} + /// Provides mutable access to a single entity and all of its components. /// /// Contrast with [`EntityWorldMut`], which allows adding and removing components, @@ -869,6 +915,44 @@ impl<'a> TryFrom<&'a mut FilteredEntityMut<'_>> for EntityMut<'a> { } } +impl PartialEq for EntityMut<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityMut<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityMut<'_> { + /// [`EntityMut`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityMut<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityMut<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityMut<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityMut<'_> {} + /// A mutable reference to a particular [`Entity`], and the entire world. /// /// This is essentially a performance-optimized `(Entity, &mut World)` tuple, @@ -2969,6 +3053,44 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> { } } +impl PartialEq for FilteredEntityRef<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for FilteredEntityRef<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for FilteredEntityRef<'_> { + /// [`FilteredEntityRef`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for FilteredEntityRef<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for FilteredEntityRef<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for FilteredEntityRef<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for FilteredEntityRef<'_> {} + /// Provides mutable access to a single entity and some of its components defined by the contained [`Access`]. /// /// 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> { } } +impl PartialEq for FilteredEntityMut<'_> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for FilteredEntityMut<'_> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for FilteredEntityMut<'_> { + /// [`FilteredEntityMut`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for FilteredEntityMut<'_> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for FilteredEntityMut<'_> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for FilteredEntityMut<'_> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for FilteredEntityMut<'_> {} + /// Error type returned by [`TryFrom`] conversions from filtered entity types /// ([`FilteredEntityRef`]/[`FilteredEntityMut`]) to full-access entity types /// ([`EntityRef`]/[`EntityMut`]). @@ -3361,6 +3521,44 @@ where } } +impl PartialEq for EntityRefExcept<'_, B> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityRefExcept<'_, B> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityRefExcept<'_, B> { + /// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityRefExcept<'_, B> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityRefExcept<'_, B> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityRefExcept<'_, B> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityRefExcept<'_, B> {} + /// Provides mutable access to all components of an entity, with the exception /// of an explicit set. /// @@ -3464,6 +3662,44 @@ where } } +impl PartialEq for EntityMutExcept<'_, B> { + fn eq(&self, other: &Self) -> bool { + self.entity() == other.entity() + } +} + +impl Eq for EntityMutExcept<'_, B> {} + +#[expect(clippy::non_canonical_partial_ord_impl)] +impl PartialOrd for EntityMutExcept<'_, B> { + /// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`], + /// and cannot discern between different worlds. + fn partial_cmp(&self, other: &Self) -> Option { + self.entity().partial_cmp(&other.entity()) + } +} + +impl Ord for EntityMutExcept<'_, B> { + fn cmp(&self, other: &Self) -> Ordering { + self.entity().cmp(&other.entity()) + } +} + +impl Hash for EntityMutExcept<'_, B> { + fn hash(&self, state: &mut H) { + self.entity().hash(state); + } +} + +impl EntityBorrow for EntityMutExcept<'_, B> { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. +unsafe impl TrustedEntityBorrow for EntityMutExcept<'_, B> {} + fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool where B: Bundle, diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 82593961c51c8..afc6e86be5516 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -8,7 +8,7 @@ use crate::{ bundle::Bundles, change_detection::{MaybeUnsafeCellLocation, MutUntyped, Ticks, TicksMut}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, - entity::{Entities, Entity, EntityLocation}, + entity::{Entities, Entity, EntityBorrow, EntityLocation}, observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReadOnlyQueryData}, @@ -1183,3 +1183,9 @@ unsafe fn get_ticks( StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity), } } + +impl EntityBorrow for UnsafeEntityCell<'_> { + fn entity(&self) -> Entity { + self.id() + } +} diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index d552000d0b5fd..1dc75c8b16645 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -19,7 +19,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, component::{Component, ComponentId, Mutable}, - entity::Entity, + entity::{Entity, EntityBorrow}, event::EventReader, prelude::{require, With}, query::Has, diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index 274b12bca84fa..99a988b9f32df 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -4,7 +4,7 @@ use crate::{ renderer::RenderContext, view::ExtractedWindows, }; -use bevy_ecs::{prelude::QueryState, world::World}; +use bevy_ecs::{entity::EntityBorrow, prelude::QueryState, world::World}; use bevy_utils::HashSet; use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp}; diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index b3948c5c513ca..15fb582fb7682 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -3,7 +3,7 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHash; use bevy_ecs::{ component::Component, - entity::Entity, + entity::{Entity, EntityBorrow, TrustedEntityBorrow}, observer::Trigger, query::With, reflect::ReflectComponent, @@ -140,6 +140,15 @@ impl From for RenderEntity { } } +impl EntityBorrow for RenderEntity { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +unsafe impl TrustedEntityBorrow for RenderEntity {} + /// Component added on the render world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. @@ -158,6 +167,15 @@ impl From for MainEntity { } } +impl EntityBorrow for MainEntity { + fn entity(&self) -> Entity { + self.id() + } +} + +// SAFETY: RenderEntity is a newtype around Entity that derives its comparison traits. +unsafe impl TrustedEntityBorrow for MainEntity {} + /// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. pub type MainEntityHashMap = hashbrown::HashMap; diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index cc1d68526a648..5bfdfc31a6ea2 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -3,7 +3,7 @@ use crate::{ }; use bevy_ecs::{ change_detection::DetectChangesMut, - entity::Entity, + entity::{Entity, EntityBorrow}, prelude::{Component, With}, query::QueryData, reflect::ReflectComponent, diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 17107d3679b88..af3994a766d9c 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -5,7 +5,7 @@ use crate::{ }; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, - entity::{Entity, EntityHashMap, EntityHashSet}, + entity::{Entity, EntityBorrow, EntityHashMap, EntityHashSet}, event::EventReader, query::With, removal_detection::RemovedComponents, diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 97e4cf3c3ea19..49bd325e600d5 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -1,7 +1,7 @@ use core::num::NonZero; use bevy_ecs::{ - entity::{Entity, VisitEntities, VisitEntitiesMut}, + entity::{Entity, EntityBorrow, VisitEntities, VisitEntitiesMut}, prelude::{Component, ReflectComponent}, }; use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2}; @@ -88,9 +88,8 @@ impl VisitEntitiesMut for WindowRef { )] pub struct NormalizedWindowRef(Entity); -impl NormalizedWindowRef { - /// Fetch the entity of this window reference - pub fn entity(&self) -> Entity { +impl EntityBorrow for NormalizedWindowRef { + fn entity(&self) -> Entity { self.0 } }