diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index e768a7b149c41..477bee30545df 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -5,7 +5,7 @@ ), }, entities: { - 4294967296: ( + 4294967297: ( components: { "bevy_ecs::name::Name": "joe", "bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)), @@ -23,7 +23,7 @@ ), }, ), - 4294967297: ( + 4294967298: ( components: { "scene::ComponentA": ( x: 3.0, @@ -32,4 +32,4 @@ }, ), }, -) \ No newline at end of file +) diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 3f547d80d984b..a299a6526c2db 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -32,6 +32,7 @@ bevy_platform = { path = "../crates/bevy_platform", default-features = false, fe glam = "0.29" rand = "0.8" rand_chacha = "0.3" +nonmax = { version = "0.5", default-features = false } # Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here # because the benches do not actually open any windows. diff --git a/benches/benches/bevy_ecs/world/entity_hash.rs b/benches/benches/bevy_ecs/world/entity_hash.rs index 7e8dfb4a21f2f..3562510c90fda 100644 --- a/benches/benches/bevy_ecs/world/entity_hash.rs +++ b/benches/benches/bevy_ecs/world/entity_hash.rs @@ -17,9 +17,10 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity { let generation = 1.0 + -(1.0 - x).log2() * 2.0; // this is not reliable, but we're internal so a hack is ok - let bits = ((generation as u64) << 32) | (id as u64); + let id = id as u64 + 1; + let bits = ((generation as u64) << 32) | id; let e = Entity::from_bits(bits); - assert_eq!(e.index(), id as u32); + assert_eq!(e.index(), !(id as u32)); assert_eq!(e.generation(), generation as u32); e } diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 283b984186150..e6e2a0bb903ef 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -1,9 +1,10 @@ use core::hint::black_box; +use nonmax::NonMaxU32; use bevy_ecs::{ bundle::{Bundle, NoBundleEffect}, component::Component, - entity::Entity, + entity::{Entity, EntityRow}, system::{Query, SystemState}, world::World, }; @@ -53,7 +54,9 @@ pub fn world_entity(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); black_box(world.entity(entity)); } }); @@ -74,7 +77,9 @@ pub fn world_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(world.get::(entity).is_some()); } }); @@ -84,7 +89,9 @@ pub fn world_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(world.get::(entity).is_some()); } }); @@ -106,7 +113,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -131,7 +140,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -142,7 +153,9 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + let entity = + // SAFETY: Range is exclusive. + Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); assert!(query.get(&world, entity).is_ok()); } }); @@ -169,7 +182,10 @@ pub fn world_query_get(criterion: &mut Criterion) { bencher.iter(|| { for i in 0..entity_count { - let entity = Entity::from_raw(i); + // SAFETY: Range is exclusive. + let entity = Entity::from_raw(EntityRow::new(unsafe { + NonMaxU32::new_unchecked(i) + })); assert!(query.get(&world, entity).is_ok()); } }); diff --git a/crates/bevy_ecs/src/entity/map_entities.rs b/crates/bevy_ecs/src/entity/map_entities.rs index c75817fbc81c8..6e2c11160c052 100644 --- a/crates/bevy_ecs/src/entity/map_entities.rs +++ b/crates/bevy_ecs/src/entity/map_entities.rs @@ -171,7 +171,7 @@ impl> MapEntities for SmallVec { /// fn get_mapped(&mut self, entity: Entity) -> Entity { /// self.map.get(&entity).copied().unwrap_or(entity) /// } -/// +/// /// fn set_mapped(&mut self, source: Entity, target: Entity) { /// self.map.insert(source, target); /// } @@ -228,7 +228,7 @@ impl EntityMapper for SceneEntityMapper<'_> { // this new entity reference is specifically designed to never represent any living entity let new = Entity::from_raw_and_generation( - self.dead_start.index(), + self.dead_start.row(), IdentifierMask::inc_masked_high_by(self.dead_start.generation, self.generations), ); @@ -331,6 +331,7 @@ impl<'m> SceneEntityMapper<'m> { #[cfg(test)] mod tests { + use crate::{ entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper}, world::World, @@ -338,14 +339,11 @@ mod tests { #[test] fn entity_mapper() { - const FIRST_IDX: u32 = 1; - const SECOND_IDX: u32 = 2; - let mut map = EntityHashMap::default(); let mut world = World::new(); let mut mapper = SceneEntityMapper::new(&mut map, &mut world); - let mapped_ent = Entity::from_raw(FIRST_IDX); + let mapped_ent = Entity::from_raw_u32(1).unwrap(); let dead_ref = mapper.get_mapped(mapped_ent); assert_eq!( @@ -354,7 +352,7 @@ mod tests { "should persist the allocated mapping from the previous line" ); assert_eq!( - mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(), + mapper.get_mapped(Entity::from_raw_u32(2).unwrap()).index(), dead_ref.index(), "should re-use the same index for further dead refs" ); @@ -372,7 +370,7 @@ mod tests { let mut world = World::new(); let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| { - mapper.get_mapped(Entity::from_raw(0)) + mapper.get_mapped(Entity::from_raw_u32(0).unwrap()) }); // Next allocated entity should be a further generation on the same index diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 3126c9d198777..02311edf426dd 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -45,6 +45,7 @@ use bevy_reflect::Reflect; use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; pub use clone_entities::*; +use derive_more::derive::Display; pub use entity_set::*; pub use map_entities::*; @@ -67,6 +68,7 @@ pub mod unique_array; pub mod unique_slice; pub mod unique_vec; +use nonmax::NonMaxU32; pub use unique_array::{UniqueEntityArray, UniqueEntityEquivalentArray}; pub use unique_slice::{UniqueEntityEquivalentSlice, UniqueEntitySlice}; pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec}; @@ -103,6 +105,86 @@ use bevy_platform::sync::atomic::AtomicIsize as AtomicIdCursor; #[cfg(not(target_has_atomic = "64"))] type IdCursor = isize; +/// This represents the row or "index" of an [`Entity`] within the [`Entities`] table. +/// This is a lighter weight version of [`Entity`]. +/// +/// This is a unique identifier for an entity in the world. +/// This differs from [`Entity`] in that [`Entity`] is unique for all entities total (unless the [`Entity::generation`] wraps), +/// but this is only unique for entities that are active. +/// +/// This can be used over [`Entity`] to improve performance in some cases, +/// but improper use can cause this to identify a different entity than intended. +/// Use with caution. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(opaque))] +#[cfg_attr(feature = "bevy_reflect", reflect(Hash, PartialEq, Debug, Clone))] +#[repr(transparent)] +pub struct EntityRow(NonMaxU32); + +impl EntityRow { + const PLACEHOLDER: Self = Self(NonMaxU32::MAX); + + /// Constructs a new [`EntityRow`] from its index. + pub const fn new(index: NonMaxU32) -> Self { + Self(index) + } + + /// Gets the index of the entity. + #[inline(always)] + pub const fn index(self) -> u32 { + self.0.get() + } + + /// Gets a some bits that represent this value. + /// The bits are opaque and should not be regarded as meaningful. + #[inline(always)] + const fn to_bits(self) -> u32 { + // SAFETY: NonMax is repr transparent. + unsafe { mem::transmute::(self.0) } + } + + /// Reconstruct an [`EntityRow`] previously destructured with [`EntityRow::to_bits`]. + /// + /// Only useful when applied to results from `to_bits` in the same instance of an application. + /// + /// # Panics + /// + /// This method will likely panic if given `u32` values that did not come from [`EntityRow::to_bits`]. + #[inline] + const fn from_bits(bits: u32) -> Self { + Self::try_from_bits(bits).expect("Attempted to initialize invalid bits as an entity row") + } + + /// Reconstruct an [`EntityRow`] previously destructured with [`EntityRow::to_bits`]. + /// + /// Only useful when applied to results from `to_bits` in the same instance of an application. + /// + /// This method is the fallible counterpart to [`EntityRow::from_bits`]. + #[inline(always)] + const fn try_from_bits(bits: u32) -> Option { + match NonZero::::new(bits) { + // SAFETY: NonMax and NonZero are repr transparent. + Some(underlying) => Some(Self(unsafe { + mem::transmute::, NonMaxU32>(underlying) + })), + None => None, + } + } +} + +impl SparseSetIndex for EntityRow { + #[inline] + fn sparse_set_index(&self) -> usize { + self.index() as usize + } + + #[inline] + fn get_sparse_set_index(value: usize) -> Self { + Self::from_bits(value as u32) + } +} + /// Lightweight identifier of an [entity](crate::entity). /// /// The identifier is implemented using a [generational index]: a combination of an index and a generation. @@ -186,10 +268,10 @@ pub struct Entity { // Do not reorder the fields here. The ordering is explicitly used by repr(C) // to make this struct equivalent to a u64. #[cfg(target_endian = "little")] - index: u32, + row: EntityRow, generation: NonZero, #[cfg(target_endian = "big")] - index: u32, + row: EntityRow, } // By not short-circuiting in comparisons, we get better codegen. @@ -244,13 +326,16 @@ impl Hash for Entity { } impl Entity { - /// Construct an [`Entity`] from a raw `index` value and a non-zero `generation` value. + /// Construct an [`Entity`] from a raw `row` value and a non-zero `generation` value. /// Ensure that the generation value is never greater than `0x7FFF_FFFF`. #[inline(always)] - pub(crate) const fn from_raw_and_generation(index: u32, generation: NonZero) -> Entity { + pub(crate) const fn from_raw_and_generation( + row: EntityRow, + generation: NonZero, + ) -> Entity { debug_assert!(generation.get() <= HIGH_MASK); - Self { index, generation } + Self { row, generation } } /// An entity ID with a placeholder value. This may or may not correspond to an actual entity, @@ -287,9 +372,9 @@ impl Entity { /// } /// } /// ``` - pub const PLACEHOLDER: Self = Self::from_raw(u32::MAX); + pub const PLACEHOLDER: Self = Self::from_raw(EntityRow::PLACEHOLDER); - /// Creates a new entity ID with the specified `index` and a generation of 1. + /// Creates a new entity ID with the specified `row` and a generation of 1. /// /// # Note /// @@ -302,8 +387,19 @@ impl Entity { /// `Entity` lines up between instances, but instead insert a secondary identifier as /// a component. #[inline(always)] - pub const fn from_raw(index: u32) -> Entity { - Self::from_raw_and_generation(index, NonZero::::MIN) + pub const fn from_raw(row: EntityRow) -> Entity { + Self::from_raw_and_generation(row, NonZero::::MIN) + } + + /// This is equivalent to [`from_raw`](Self::from_raw) except that it takes a `u32` instead of an [`EntityRow`]. + /// + /// Returns `None` if the row is `u32::MAX`. + #[inline(always)] + pub const fn from_raw_u32(row: u32) -> Option { + match NonMaxU32::new(row) { + Some(row) => Some(Self::from_raw(EntityRow::new(row))), + None => None, + } } /// Convert to a form convenient for passing outside of rust. @@ -314,7 +410,7 @@ impl Entity { /// No particular structure is guaranteed for the returned bits. #[inline(always)] pub const fn to_bits(self) -> u64 { - IdentifierMask::pack_into_u64(self.index, self.generation.get()) + IdentifierMask::pack_into_u64(self.row.to_bits(), self.generation.get()) } /// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`]. @@ -346,10 +442,12 @@ impl Entity { let kind = id.kind() as u8; if kind == (IdKind::Entity as u8) { - return Ok(Self { - index: id.low(), - generation: id.high(), - }); + if let Some(row) = EntityRow::try_from_bits(id.low()) { + return Ok(Self { + row, + generation: id.high(), + }); + } } } @@ -357,18 +455,25 @@ impl Entity { } /// Return a transiently unique identifier. + /// See also [`EntityRow`]. /// - /// No two simultaneously-live entities share the same index, but dead entities' indices may collide + /// No two simultaneously-live entities share the same row, but dead entities' indices may collide /// with both live and dead entities. Useful for compactly representing entities within a /// specific snapshot of the world, such as when serializing. #[inline] + pub const fn row(self) -> EntityRow { + self.row + } + + /// Equivalent to `self.row().index()`. See [`Self::row`] for details. + #[inline] pub const fn index(self) -> u32 { - self.index + self.row.index() } - /// Returns the generation of this Entity's index. The generation is incremented each time an - /// entity with a given index is despawned. This serves as a "count" of the number of times a - /// given index has been reused (index, generation) pairs uniquely identify a given Entity. + /// Returns the generation of this Entity's row. The generation is incremented each time an + /// entity with a given row is despawned. This serves as a "count" of the number of times a + /// given row has been reused (row, generation) pairs uniquely identify a given Entity. #[inline] pub const fn generation(self) -> u32 { // Mask so not to expose any flags @@ -471,12 +576,12 @@ impl fmt::Display for Entity { impl SparseSetIndex for Entity { #[inline] fn sparse_set_index(&self) -> usize { - self.index() as usize + self.row().sparse_set_index() } #[inline] fn get_sparse_set_index(value: usize) -> Self { - Entity::from_raw(value as u32) + Entity::from_raw(EntityRow::get_sparse_set_index(value)) } } @@ -486,7 +591,7 @@ pub struct ReserveEntitiesIterator<'a> { meta: &'a [EntityMeta], // Reserved indices formerly in the freelist to hand out. - freelist_indices: core::slice::Iter<'a, u32>, + freelist_indices: core::slice::Iter<'a, EntityRow>, // New Entity indices to hand out, outside the range of meta.len(). new_indices: core::ops::Range, @@ -498,10 +603,16 @@ impl<'a> Iterator for ReserveEntitiesIterator<'a> { fn next(&mut self) -> Option { self.freelist_indices .next() - .map(|&index| { - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + .map(|&row| { + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) + }) + .or_else(|| { + self.new_indices.next().map(|index| { + // SAFETY: This came from an exclusive range so the max can't be hit. + let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(index)) }; + Entity::from_raw(row) + }) }) - .or_else(|| self.new_indices.next().map(Entity::from_raw)) } fn size_hint(&self) -> (usize, Option) { @@ -568,7 +679,7 @@ pub struct Entities { /// [`reserve_entity`]: Entities::reserve_entity /// [`reserve_entities`]: Entities::reserve_entities /// [`flush`]: Entities::flush - pending: Vec, + pending: Vec, free_cursor: AtomicIdCursor, } @@ -644,17 +755,21 @@ impl Entities { let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed); if n > 0 { // Allocate from the freelist. - let index = self.pending[(n - 1) as usize]; - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + let row = self.pending[(n - 1) as usize]; + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) } else { // Grab a new ID, outside the range of `meta.len()`. `flush()` must // eventually be called to make it valid. // // As `self.free_cursor` goes more and more negative, we return IDs farther // and farther beyond `meta.len()`. - Entity::from_raw( - u32::try_from(self.meta.len() as IdCursor - n).expect("too many entities"), - ) + let raw = self.meta.len() as IdCursor - n; + if raw >= u32::MAX as IdCursor { + panic!("too many entities"); + } + // SAFETY: We just checked the bounds + let row = unsafe { EntityRow::new(NonMaxU32::new_unchecked(raw as u32)) }; + Entity::from_raw(row) } } @@ -669,14 +784,17 @@ impl Entities { /// Allocate an entity ID directly. pub fn alloc(&mut self) -> Entity { self.verify_flushed(); - if let Some(index) = self.pending.pop() { + if let Some(row) = self.pending.pop() { let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; - Entity::from_raw_and_generation(index, self.meta[index as usize].generation) + Entity::from_raw_and_generation(row, self.meta[row.index() as usize].generation) } else { - let index = u32::try_from(self.meta.len()).expect("too many entities"); + let index = u32::try_from(self.meta.len()) + .ok() + .and_then(NonMaxU32::new) + .expect("too many entities"); self.meta.push(EntityMeta::EMPTY); - Entity::from_raw(index) + Entity::from_raw(EntityRow::new(index)) } } @@ -696,13 +814,13 @@ impl Entities { if meta.generation == NonZero::::MIN { warn!( "Entity({}) generation wrapped on Entities::free, aliasing may occur", - entity.index + entity.row() ); } let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location); - self.pending.push(entity.index()); + self.pending.push(entity.row()); let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; @@ -734,7 +852,7 @@ impl Entities { // This will return false for entities which have been freed, even if // not reallocated since the generation is incremented in `free` pub fn contains(&self, entity: Entity) -> bool { - self.resolve_from_id(entity.index()) + self.resolve_from_id(entity.row()) .is_some_and(|e| e.generation() == entity.generation()) } @@ -800,17 +918,17 @@ impl Entities { /// Note: This method may return [`Entities`](Entity) which are currently free /// Note that [`contains`](Entities::contains) will correctly return false for freed /// entities, since it checks the generation - pub fn resolve_from_id(&self, index: u32) -> Option { - let idu = index as usize; + pub fn resolve_from_id(&self, row: EntityRow) -> Option { + let idu = row.index() as usize; if let Some(&EntityMeta { generation, .. }) = self.meta.get(idu) { - Some(Entity::from_raw_and_generation(index, generation)) + Some(Entity::from_raw_and_generation(row, generation)) } else { // `id` is outside of the meta list - check whether it is reserved but not yet flushed. let free_cursor = self.free_cursor.load(Ordering::Relaxed); // If this entity was manually created, then free_cursor might be positive // Returning None handles that case correctly let num_pending = usize::try_from(-free_cursor).ok()?; - (idu < self.meta.len() + num_pending).then_some(Entity::from_raw(index)) + (idu < self.meta.len() + num_pending).then_some(Entity::from_raw(row)) } } @@ -839,8 +957,10 @@ impl Entities { let new_meta_len = old_meta_len + -current_free_cursor as usize; self.meta.resize(new_meta_len, EntityMeta::EMPTY); for (index, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) { + // SAFETY: the index is less than the meta length, which can not exceeded u32::MAX + let row = EntityRow::new(unsafe { NonMaxU32::new_unchecked(index as u32) }); init( - Entity::from_raw_and_generation(index as u32, meta.generation), + Entity::from_raw_and_generation(row, meta.generation), &mut meta.location, ); } @@ -849,10 +969,10 @@ impl Entities { 0 }; - for index in self.pending.drain(new_free_cursor..) { - let meta = &mut self.meta[index as usize]; + for row in self.pending.drain(new_free_cursor..) { + let meta = &mut self.meta[row.index() as usize]; init( - Entity::from_raw_and_generation(index, meta.generation), + Entity::from_raw_and_generation(row, meta.generation), &mut meta.location, ); } @@ -1064,9 +1184,14 @@ mod tests { #[test] fn entity_bits_roundtrip() { + let r = EntityRow::new(NonMaxU32::new(0xDEADBEEF).unwrap()); + assert_eq!(EntityRow::from_bits(r.to_bits()), r); + // Generation cannot be greater than 0x7FFF_FFFF else it will be an invalid Entity id - let e = - Entity::from_raw_and_generation(0xDEADBEEF, NonZero::::new(0x5AADF00D).unwrap()); + let e = Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(0xDEADBEEF).unwrap()), + NonZero::::new(0x5AADF00D).unwrap(), + ); assert_eq!(Entity::from_bits(e.to_bits()), e); } @@ -1099,18 +1224,18 @@ mod tests { #[test] fn entity_const() { - const C1: Entity = Entity::from_raw(42); + const C1: Entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); assert_eq!(42, C1.index()); assert_eq!(1, C1.generation()); const C2: Entity = Entity::from_bits(0x0000_00ff_0000_00cc); - assert_eq!(0x0000_00cc, C2.index()); + assert_eq!(!0x0000_00cc, C2.index()); assert_eq!(0x0000_00ff, C2.generation()); - const C3: u32 = Entity::from_raw(33).index(); + const C3: u32 = Entity::from_raw(EntityRow::new(NonMaxU32::new(33).unwrap())).index(); assert_eq!(33, C3); - const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation(); + const C4: u32 = Entity::from_bits(0x00dd_00ff_1111_1111).generation(); assert_eq!(0x00dd_00ff, C4); } @@ -1146,65 +1271,139 @@ mod tests { )] fn entity_comparison() { assert_eq!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(789).unwrap() + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(123, NonZero::::new(789).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(789).unwrap() + ) ); assert_ne!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()), - Entity::from_raw_and_generation(456, NonZero::::new(123).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ), + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(456).unwrap()), + NonZero::::new(123).unwrap() + ) ); // ordering is by generation then by index assert!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - >= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) >= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - <= Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) <= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) ); assert!( - !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - < Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) + !(Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + )) ); assert!( - !(Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap()) - > Entity::from_raw_and_generation(123, NonZero::::new(456).unwrap())) + !(Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(123).unwrap()), + NonZero::::new(456).unwrap() + )) ); assert!( - Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) - < Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(9).unwrap()), + NonZero::::new(1).unwrap() + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(9).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(9).unwrap()) - > Entity::from_raw_and_generation(9, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(9).unwrap() + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(9).unwrap()), + NonZero::::new(1).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) - < Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(1).unwrap() + ) > Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + NonZero::::new(1).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(1, NonZero::::new(1).unwrap()) - <= Entity::from_raw_and_generation(2, NonZero::::new(1).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(1).unwrap() + ) >= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + NonZero::::new(1).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) - > Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + NonZero::::new(2).unwrap() + ) < Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(2).unwrap() + ) ); assert!( - Entity::from_raw_and_generation(2, NonZero::::new(2).unwrap()) - >= Entity::from_raw_and_generation(1, NonZero::::new(2).unwrap()) + Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(2).unwrap()), + NonZero::::new(2).unwrap() + ) <= Entity::from_raw_and_generation( + EntityRow::new(NonMaxU32::new(1).unwrap()), + NonZero::::new(2).unwrap() + ) ); } @@ -1216,12 +1415,16 @@ mod tests { let hash = EntityHash; let first_id = 0xC0FFEE << 8; - let first_hash = hash.hash_one(Entity::from_raw(first_id)); + let first_hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(first_id).unwrap(), + ))); for i in 1..=255 { let id = first_id + i; - let hash = hash.hash_one(Entity::from_raw(id)); - assert_eq!(hash.wrapping_sub(first_hash) as u32, i); + let hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(id).unwrap(), + ))); + assert_eq!(first_hash.wrapping_sub(hash) as u32, i); } } @@ -1232,20 +1435,24 @@ mod tests { let hash = EntityHash; let first_id = 0xC0FFEE; - let first_hash = hash.hash_one(Entity::from_raw(first_id)) >> 57; + let first_hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(first_id).unwrap(), + ))) >> 57; for bit in 0..u32::BITS { let id = first_id ^ (1 << bit); - let hash = hash.hash_one(Entity::from_raw(id)) >> 57; + let hash = hash.hash_one(Entity::from_raw(EntityRow::new( + NonMaxU32::new(id).unwrap(), + ))) >> 57; assert_ne!(hash, first_hash); } } #[test] fn entity_debug() { - let entity = Entity::from_raw(42); + let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); let string = format!("{:?}", entity); - assert_eq!(string, "42v1#4294967338"); + assert_eq!(string, "42v1#8589934549"); let entity = Entity::PLACEHOLDER; let string = format!("{:?}", entity); @@ -1254,7 +1461,7 @@ mod tests { #[test] fn entity_display() { - let entity = Entity::from_raw(42); + let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap())); let string = format!("{}", entity); assert_eq!(string, "42v1"); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 44acc052deb73..15bfb49f6795f 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -488,10 +488,9 @@ mod tests { results.lock().unwrap().push((e, i)); }); results.lock().unwrap().sort(); - assert_eq!( - &*results.lock().unwrap(), - &[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] - ); + let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]; + expected.sort(); + assert_eq!(&*results.lock().unwrap(), &expected); } #[test] @@ -509,10 +508,9 @@ mod tests { .par_iter(&world) .for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i))); results.lock().unwrap().sort(); - assert_eq!( - &*results.lock().unwrap(), - &[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] - ); + let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]; + expected.sort(); + assert_eq!(&*results.lock().unwrap(), &expected); } #[test] @@ -1542,8 +1540,8 @@ mod tests { let mut world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); - let _ = query.get(&world_a, Entity::from_raw(0)); - let _ = query.get(&world_b, Entity::from_raw(0)); + let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap()); + let _ = query.get(&world_b, Entity::from_raw_u32(0).unwrap()); } #[test] @@ -1784,7 +1782,7 @@ mod tests { fn try_insert_batch() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); + let e1 = Entity::from_raw_u32(1).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; @@ -1808,7 +1806,7 @@ mod tests { fn try_insert_batch_if_new() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw(1); + let e1 = Entity::from_raw_u32(1).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 783dafd2d1eba..2343e66aa1c85 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -1075,7 +1075,7 @@ mod tests { world.add_observer(|_: Trigger, mut res: ResMut| res.observed("add_2")); world.spawn(A).flush(); - assert_eq!(vec!["add_1", "add_2"], world.resource::().0); + assert_eq!(vec!["add_2", "add_1"], world.resource::().0); // Our A entity plus our two observers assert_eq!(world.entities().len(), 3); } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 0591bac58c76c..8f3eb1b3fb09c 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -993,7 +993,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// ``` @@ -1031,7 +1031,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); /// ``` @@ -1087,7 +1087,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1133,7 +1133,7 @@ impl QueryState { /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1461,7 +1461,7 @@ impl QueryState { /// /// # assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// # let wrong_entity = Entity::from_raw(57); + /// # let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// # let invalid_entity = world.spawn_empty().id(); /// /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity); @@ -1782,7 +1782,7 @@ impl QueryState { /// /// fn my_system(query: Query<&A>) -> Result { /// let a = query.single()?; - /// + /// /// // Do something with `a` /// Ok(()) /// } @@ -1903,7 +1903,7 @@ mod tests { let world_2 = World::new(); let mut query_state = world_1.query::(); - let _panics = query_state.get(&world_2, Entity::from_raw(0)); + let _panics = query_state.get(&world_2, Entity::from_raw_u32(0).unwrap()); } #[test] diff --git a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs index 49e3725f6a6b8..91c7ac3a9d0e0 100644 --- a/crates/bevy_ecs/src/relationship/relationship_source_collection.rs +++ b/crates/bevy_ecs/src/relationship/relationship_source_collection.rs @@ -709,7 +709,7 @@ mod tests { let collection = rel_target.collection(); // Insertions should maintain ordering - assert!(collection.iter().eq(&[b, c, d])); + assert!(collection.iter().eq(&[d, c, b])); world.entity_mut(c).despawn(); @@ -717,7 +717,7 @@ mod tests { let collection = rel_target.collection(); // Removals should maintain ordering - assert!(collection.iter().eq(&[b, d])); + assert!(collection.iter().eq(&[d, b])); } #[test] diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 6c809df849b3e..69305e8a141ce 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -659,10 +659,11 @@ mod tests { use super::SparseSets; use crate::{ component::{Component, ComponentDescriptor, ComponentId, ComponentInfo}, - entity::Entity, + entity::{Entity, EntityRow}, storage::SparseSet, }; use alloc::{vec, vec::Vec}; + use nonmax::NonMaxU32; #[derive(Debug, Eq, PartialEq)] struct Foo(usize); @@ -670,11 +671,11 @@ mod tests { #[test] fn sparse_set() { let mut set = SparseSet::::default(); - let e0 = Entity::from_raw(0); - let e1 = Entity::from_raw(1); - let e2 = Entity::from_raw(2); - let e3 = Entity::from_raw(3); - let e4 = Entity::from_raw(4); + let e0 = Entity::from_raw(EntityRow::new(NonMaxU32::new(0).unwrap())); + let e1 = Entity::from_raw(EntityRow::new(NonMaxU32::new(1).unwrap())); + let e2 = Entity::from_raw(EntityRow::new(NonMaxU32::new(2).unwrap())); + let e3 = Entity::from_raw(EntityRow::new(NonMaxU32::new(3).unwrap())); + let e4 = Entity::from_raw(EntityRow::new(NonMaxU32::new(4).unwrap())); set.insert(e1, Foo(1)); set.insert(e2, Foo(2)); diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index 0f80b77f513c4..fd8eb410e4f33 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -823,11 +823,12 @@ mod tests { use crate::{ change_detection::MaybeLocation, component::{Component, ComponentIds, Components, ComponentsRegistrator, Tick}, - entity::Entity, + entity::{Entity, EntityRow}, ptr::OwningPtr, storage::{TableBuilder, TableId, TableRow, Tables}, }; use alloc::vec::Vec; + use nonmax::NonMaxU32; #[derive(Component)] struct W(T); @@ -856,7 +857,9 @@ mod tests { let mut table = TableBuilder::with_capacity(0, columns.len()) .add_column(components.get_info(component_id).unwrap()) .build(); - let entities = (0..200).map(Entity::from_raw).collect::>(); + let entities = (0..200) + .map(|index| Entity::from_raw(EntityRow::new(NonMaxU32::new(index).unwrap()))) + .collect::>(); for entity in &entities { // SAFETY: we allocate and immediately set data afterwards unsafe { diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 627a02f6ea552..b097e229177f7 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -933,7 +933,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// type Item = &'a Entity; /// type IntoIter = UniqueEntityIter>; - /// + /// /// fn into_iter(self) -> Self::IntoIter { /// // SAFETY: `Friends` ensures that it unique_list contains only unique entities. /// unsafe { UniqueEntityIter::from_iterator_unchecked(self.unique_list.iter()) } @@ -1414,7 +1414,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!( /// match query.get_many([wrong_entity]).unwrap_err() { @@ -1466,7 +1466,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = Entity::from_raw_u32(365).unwrap(); /// /// assert_eq!( /// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() { @@ -1665,7 +1665,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// let entities: [Entity; 3] = entities.try_into().unwrap(); /// /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// @@ -1740,7 +1740,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); /// /// world.spawn(A(73)); - /// let wrong_entity = Entity::from_raw(57); + /// let wrong_entity = Entity::from_raw_u32(57).unwrap(); /// let invalid_entity = world.spawn_empty().id(); /// /// @@ -2363,7 +2363,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// * `&mut T` -> `&T` /// * `&mut T` -> `Ref` /// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef) - /// + /// /// [`EntityLocation`]: crate::entity::EntityLocation /// [`&Archetype`]: crate::archetype::Archetype /// diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index ce5fa259a1a33..b59f08604ba7b 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -1490,13 +1490,14 @@ mod tests { "Deserialized value does not match original" ); } + use super::*; #[test] fn serialization_tests() { test_serialize_deserialize(BrpQueryRow { components: Default::default(), - entity: Entity::from_raw(0), + entity: Entity::from_raw_u32(0).unwrap(), has: Default::default(), }); test_serialize_deserialize(BrpListWatchingResponse::default()); @@ -1510,7 +1511,7 @@ mod tests { ..Default::default() }); test_serialize_deserialize(BrpListParams { - entity: Entity::from_raw(0), + entity: Entity::from_raw_u32(0).unwrap(), }); } } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 5deac09aaa073..c9e594107ec66 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -509,10 +509,10 @@ mod tests { let mut entities = builder.build().entities.into_iter(); // Assert entities are ordered - assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap()); - assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap()); assert_eq!(entity_d, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_c, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_b, entities.next().map(|e| e.entity).unwrap()); + assert_eq!(entity_a, entities.next().map(|e| e.entity).unwrap()); } #[test] @@ -539,7 +539,7 @@ mod tests { assert_eq!(scene.entities.len(), 2); let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity]; scene_entities.sort(); - assert_eq!(scene_entities, [entity_a_b, entity_a]); + assert_eq!(scene_entities, [entity_a, entity_a_b]); } #[test] @@ -621,9 +621,9 @@ mod tests { .build(); assert_eq!(scene.entities.len(), 3); - assert!(scene.entities[0].components[0].represents::()); + assert!(scene.entities[2].components[0].represents::()); assert!(scene.entities[1].components[0].represents::()); - assert_eq!(scene.entities[2].components.len(), 0); + assert_eq!(scene.entities[0].components.len(), 0); } #[test] diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index dce5ad971e105..3bf8ca9f64c44 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -912,7 +912,7 @@ mod tests { app.update(); let world = app.world_mut(); - let spawned_root = world.entity(spawned).get::().unwrap()[0]; + let spawned_root = world.entity(spawned).get::().unwrap()[1]; let children = world.entity(spawned_root).get::().unwrap(); assert_eq!(children.len(), 3); assert!(world.entity(children[0]).get::().is_some()); diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index ead8933a49966..9eff75f9016c5 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -638,21 +638,21 @@ mod tests { ), }, entities: { - 4294967296: ( + 8589934589: ( components: { + "bevy_scene::serde::tests::Bar": (345), + "bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967297: ( + 8589934590: ( components: { "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967298: ( + 8589934591: ( components: { - "bevy_scene::serde::tests::Bar": (345), - "bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Foo": (123), }, ), @@ -675,18 +675,18 @@ mod tests { ), }, entities: { - 4294967296: ( + 8589934591: ( components: { "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967297: ( + 8589934590: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), }, ), - 4294967298: ( + 8589934589: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), @@ -815,7 +815,7 @@ mod tests { assert_eq!( vec![ - 0, 1, 128, 128, 128, 128, 16, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, + 0, 1, 255, 255, 255, 255, 31, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 @@ -856,11 +856,12 @@ mod tests { assert_eq!( vec![ - 146, 128, 129, 207, 0, 0, 0, 1, 0, 0, 0, 0, 145, 129, 217, 37, 98, 101, 118, 121, - 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, - 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, - 2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, - 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 146, 128, 129, 207, 0, 0, 0, 1, 255, 255, 255, 255, 145, 129, 217, 37, 98, 101, + 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, + 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, + 147, 147, 1, 2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, + 84, 117, 112, 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, + 33 ], buf ); @@ -899,12 +900,13 @@ mod tests { assert_eq!( vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, - 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, - 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, - 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, - 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, + 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, + 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, + 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, + 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, + 100, 33 ], serialized_scene ); diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index ecf66512715e9..62038b37ed90f 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -798,8 +798,8 @@ mod test { let translation = vec3(1.0, 0.0, 0.0); // These will be overwritten. - let mut child = Entity::from_raw(0); - let mut grandchild = Entity::from_raw(1); + let mut child = Entity::from_raw_u32(0).unwrap(); + let mut grandchild = Entity::from_raw_u32(1).unwrap(); let parent = app .world_mut() .spawn(Transform::from_translation(translation)) @@ -849,7 +849,7 @@ mod test { ); fn setup_world(world: &mut World) -> (Entity, Entity) { - let mut grandchild = Entity::from_raw(0); + let mut grandchild = Entity::from_raw_u32(0).unwrap(); let child = world .spawn(Transform::IDENTITY) .with_children(|builder| { diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1c1c60ea8fd90..9e6906b1f7f69 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -1039,7 +1039,7 @@ mod tests { let (mut world, ..) = setup_ui_test_world(); - let root_node_entity = Entity::from_raw(1); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); struct TestSystemParam { root_node_entity: Entity, diff --git a/crates/bevy_ui/src/layout/ui_surface.rs b/crates/bevy_ui/src/layout/ui_surface.rs index a4af6737a79dc..2df6afa947dac 100644 --- a/crates/bevy_ui/src/layout/ui_surface.rs +++ b/crates/bevy_ui/src/layout/ui_surface.rs @@ -318,7 +318,7 @@ mod tests { #[test] fn test_upsert() { let mut ui_surface = UiSurface::default(); - let root_node_entity = Entity::from_raw(1); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); let node = Node::default(); // standard upsert @@ -350,7 +350,7 @@ mod tests { #[test] fn test_remove_entities() { let mut ui_surface = UiSurface::default(); - let root_node_entity = Entity::from_raw(1); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); let node = Node::default(); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); @@ -366,7 +366,7 @@ mod tests { #[test] fn test_try_update_measure() { let mut ui_surface = UiSurface::default(); - let root_node_entity = Entity::from_raw(1); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); let node = Node::default(); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); @@ -381,8 +381,8 @@ mod tests { #[test] fn test_update_children() { let mut ui_surface = UiSurface::default(); - let root_node_entity = Entity::from_raw(1); - let child_entity = Entity::from_raw(2); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); + let child_entity = Entity::from_raw_u32(2).unwrap(); let node = Node::default(); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); @@ -402,8 +402,8 @@ mod tests { #[test] fn test_set_camera_children() { let mut ui_surface = UiSurface::default(); - let root_node_entity = Entity::from_raw(1); - let child_entity = Entity::from_raw(2); + let root_node_entity = Entity::from_raw_u32(1).unwrap(); + let child_entity = Entity::from_raw_u32(2).unwrap(); let node = Node::default(); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); diff --git a/examples/ecs/error_handling.rs b/examples/ecs/error_handling.rs index b13a018530fc0..d326d7d4aae81 100644 --- a/examples/ecs/error_handling.rs +++ b/examples/ecs/error_handling.rs @@ -169,7 +169,7 @@ fn failing_system(world: &mut World) -> Result { fn failing_commands(mut commands: Commands) { commands // This entity doesn't exist! - .entity(Entity::from_raw(12345678)) + .entity(Entity::from_raw_u32(12345678).unwrap()) // Normally, this failed command would panic, // but since we've set the global error handler to `warn` // it will log a warning instead. diff --git a/release-content/migration-guides/make_entity_index_non_max.md b/release-content/migration-guides/make_entity_index_non_max.md new file mode 100644 index 0000000000000..d3ba963854254 --- /dev/null +++ b/release-content/migration-guides/make_entity_index_non_max.md @@ -0,0 +1,25 @@ +--- +title: Manual Entity Creation +pull_requests: [18704] +--- + +`Entity` no longer stores its index as a plain `u32` but as the new `EntityRow`, which wraps a `NonMaxU32`. +Previously, `Entity::index` could be `u32::MAX`, but that is no longer a valid index. +As a result, `Entity::from_raw` now takes `EntityRow` as a parameter instead of `u32`. `EntityRow` can be constructed via `EntityRow::new`, which takes a `NonMaxU32`. +This also means that the `Ord` implementation of `Entity` has changed: the index order is reversed, so 'newer' entities come before 'older' entities. +If you don't want to add [nonmax](https://docs.rs/nonmax/latest/nonmax/) as a dependency, use `Entity::from_raw_u32` which is identical to the previous `Entity::from_raw`, except that it now returns `Option` where the result is `None` if `u32::MAX` is passed. + +Bevy made this change because it puts a niche in the `EntityRow` type which makes `Option` half the size of `Option`. +This is used internally to open up performance improvements to the ECS. + +Although you probably shouldn't be making entities manually, it is sometimes useful to do so for tests. +To migrate tests, use: + +```diff +- let entity = Entity::from_raw(1); ++ let entity = Entity::from_raw_u32(1).unwrap(); +``` + +If you are creating entities manually in production, don't do that! +Use `Entities::alloc` instead. +But if you must create one manually, either reuse a `EntityRow` you know to be valid by using `Entity::from_raw` and `Entity::row`, or handle the error case of `None` returning from `Entity::from_raw_u32(my_index)`.