Make entity::index non max (#18704)

# Objective

There are two problems this aims to solve. 

First, `Entity::index` is currently a `u32`. That means there are
`u32::MAX + 1` possible entities. Not only is that awkward, but it also
make `Entity` allocation more difficult. I discovered this while working
on remote entity reservation, but even on main, `Entities` doesn't
handle the `u32::MAX + 1` entity very well. It can not be batch reserved
because that iterator uses exclusive ranges, which has a maximum upper
bound of `u32::MAX - 1`. In other words, having `u32::MAX` as a valid
index can be thought of as a bug right now. We either need to make that
invalid (this PR), which makes Entity allocation cleaner and makes
remote reservation easier (because the length only needs to be u32
instead of u64, which, in atomics is a big deal), or we need to take
another pass at `Entities` to make it handle the `u32::MAX` index
properly.

Second, `TableRow`, `ArchetypeRow` and `EntityIndex` (a type alias for
u32) all have `u32` as the underlying type. That means using these as
the index type in a `SparseSet` uses 64 bits for the sparse list because
it stores `Option<IndexType>`. By using `NonMaxU32` here, we cut the
memory of that list in half. To my knowledge, `EntityIndex` is the only
thing that would really benefit from this niche. `TableRow` and
`ArchetypeRow` I think are not stored in an `Option` in bulk. But if
they ever are, this would help. Additionally this ensures
`TableRow::INVALID` and `ArchetypeRow::INVALID` never conflict with an
actual row, which in a nice bonus.

As a related note, if we do components as entities where `ComponentId`
becomes `Entity`, the the `SparseSet<ComponentId>` will see a similar
memory improvement too.

## Solution

Create a new type `EntityRow` that wraps `NonMaxU32`, similar to
`TableRow` and `ArchetypeRow`.
Change `Entity::index` to this type.

## Downsides

`NonMax` is implemented as a `NonZero` with a binary inversion. That
means accessing and storing the value takes one more instruction. I
don't think that's a big deal, but it's worth mentioning.

As a consequence, `to_bits` uses `transmute` to skip the inversion which
keeps it a nop. But that also means that ordering has now flipped. In
other words, higher indices are considered less than lower indices. I
don't think that's a problem, but it's also worth mentioning.

## Alternatives

We could keep the index as a u32 type and just document that `u32::MAX`
is invalid, modifying `Entities` to ensure it never gets handed out.
(But that's not enforced by the type system.) We could still take
advantage of the niche here in `ComponentSparseSet`. We'd just need some
unsafe manual conversions, which is probably fine, but opens up the
possibility for correctness problems later.

We could change `Entities` to fully support the `u32::MAX` index. (But
that makes `Entities` more complex and potentially slightly slower.)

## Testing

- CI
- A few tests were changed because they depend on different ordering and
`to_bits` values.

## Future Work

- It might be worth removing the niche on `Entity::generation` since
there is now a different niche.
- We could move `Entity::generation` into it's own type too for clarity.
- We should change `ComponentSparseSet` to take advantage of the new
niche. (This PR doesn't change that yet.)
- Consider removing or updating `Identifier`. This is only used for
`Entity`, so it might be worth combining since `Entity` is now more
unique.

---------

Co-authored-by: atlv <email@atlasdostal.com>
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
Eagster 2025-05-07 14:20:30 -04:00 committed by GitHub
parent 9e2bd8ac18
commit 0b4858726c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 447 additions and 194 deletions

View File

@ -5,7 +5,7 @@
), ),
}, },
entities: { entities: {
4294967296: ( 4294967297: (
components: { components: {
"bevy_ecs::name::Name": "joe", "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)), "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: { components: {
"scene::ComponentA": ( "scene::ComponentA": (
x: 3.0, x: 3.0,

View File

@ -32,6 +32,7 @@ bevy_platform = { path = "../crates/bevy_platform", default-features = false, fe
glam = "0.29" glam = "0.29"
rand = "0.8" rand = "0.8"
rand_chacha = "0.3" 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 # 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. # because the benches do not actually open any windows.

View File

@ -17,9 +17,10 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity {
let generation = 1.0 + -(1.0 - x).log2() * 2.0; let generation = 1.0 + -(1.0 - x).log2() * 2.0;
// this is not reliable, but we're internal so a hack is ok // 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); 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); assert_eq!(e.generation(), generation as u32);
e e
} }

View File

@ -1,9 +1,10 @@
use core::hint::black_box; use core::hint::black_box;
use nonmax::NonMaxU32;
use bevy_ecs::{ use bevy_ecs::{
bundle::{Bundle, NoBundleEffect}, bundle::{Bundle, NoBundleEffect},
component::Component, component::Component,
entity::Entity, entity::{Entity, EntityRow},
system::{Query, SystemState}, system::{Query, SystemState},
world::World, world::World,
}; };
@ -53,7 +54,9 @@ pub fn world_entity(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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)); black_box(world.entity(entity));
} }
}); });
@ -74,7 +77,9 @@ pub fn world_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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::<Table>(entity).is_some()); assert!(world.get::<Table>(entity).is_some());
} }
}); });
@ -84,7 +89,9 @@ pub fn world_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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::<Sparse>(entity).is_some()); assert!(world.get::<Sparse>(entity).is_some());
} }
}); });
@ -106,7 +113,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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()); assert!(query.get(&world, entity).is_ok());
} }
}); });
@ -131,7 +140,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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()); assert!(query.get(&world, entity).is_ok());
} }
}); });
@ -142,7 +153,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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()); assert!(query.get(&world, entity).is_ok());
} }
}); });
@ -169,7 +182,10 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| { bencher.iter(|| {
for i in 0..entity_count { 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()); assert!(query.get(&world, entity).is_ok());
} }
}); });

View File

@ -228,7 +228,7 @@ impl EntityMapper for SceneEntityMapper<'_> {
// this new entity reference is specifically designed to never represent any living entity // this new entity reference is specifically designed to never represent any living entity
let new = Entity::from_raw_and_generation( 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), IdentifierMask::inc_masked_high_by(self.dead_start.generation, self.generations),
); );
@ -331,6 +331,7 @@ impl<'m> SceneEntityMapper<'m> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper}, entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
world::World, world::World,
@ -338,14 +339,11 @@ mod tests {
#[test] #[test]
fn entity_mapper() { fn entity_mapper() {
const FIRST_IDX: u32 = 1;
const SECOND_IDX: u32 = 2;
let mut map = EntityHashMap::default(); let mut map = EntityHashMap::default();
let mut world = World::new(); let mut world = World::new();
let mut mapper = SceneEntityMapper::new(&mut map, &mut world); 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); let dead_ref = mapper.get_mapped(mapped_ent);
assert_eq!( assert_eq!(
@ -354,7 +352,7 @@ mod tests {
"should persist the allocated mapping from the previous line" "should persist the allocated mapping from the previous line"
); );
assert_eq!( assert_eq!(
mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(), mapper.get_mapped(Entity::from_raw_u32(2).unwrap()).index(),
dead_ref.index(), dead_ref.index(),
"should re-use the same index for further dead refs" "should re-use the same index for further dead refs"
); );
@ -372,7 +370,7 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| { 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 // Next allocated entity should be a further generation on the same index

View File

@ -45,6 +45,7 @@ use bevy_reflect::Reflect;
use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
pub use clone_entities::*; pub use clone_entities::*;
use derive_more::derive::Display;
pub use entity_set::*; pub use entity_set::*;
pub use map_entities::*; pub use map_entities::*;
@ -67,6 +68,7 @@ pub mod unique_array;
pub mod unique_slice; pub mod unique_slice;
pub mod unique_vec; pub mod unique_vec;
use nonmax::NonMaxU32;
pub use unique_array::{UniqueEntityArray, UniqueEntityEquivalentArray}; pub use unique_array::{UniqueEntityArray, UniqueEntityEquivalentArray};
pub use unique_slice::{UniqueEntityEquivalentSlice, UniqueEntitySlice}; pub use unique_slice::{UniqueEntityEquivalentSlice, UniqueEntitySlice};
pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec}; pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec};
@ -103,6 +105,86 @@ use bevy_platform::sync::atomic::AtomicIsize as AtomicIdCursor;
#[cfg(not(target_has_atomic = "64"))] #[cfg(not(target_has_atomic = "64"))]
type IdCursor = isize; 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::<NonMaxU32, u32>(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<Self> {
match NonZero::<u32>::new(bits) {
// SAFETY: NonMax and NonZero are repr transparent.
Some(underlying) => Some(Self(unsafe {
mem::transmute::<NonZero<u32>, 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). /// Lightweight identifier of an [entity](crate::entity).
/// ///
/// The identifier is implemented using a [generational index]: a combination of an index and a generation. /// 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) // Do not reorder the fields here. The ordering is explicitly used by repr(C)
// to make this struct equivalent to a u64. // to make this struct equivalent to a u64.
#[cfg(target_endian = "little")] #[cfg(target_endian = "little")]
index: u32, row: EntityRow,
generation: NonZero<u32>, generation: NonZero<u32>,
#[cfg(target_endian = "big")] #[cfg(target_endian = "big")]
index: u32, row: EntityRow,
} }
// By not short-circuiting in comparisons, we get better codegen. // By not short-circuiting in comparisons, we get better codegen.
@ -244,13 +326,16 @@ impl Hash for Entity {
} }
impl 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`. /// Ensure that the generation value is never greater than `0x7FFF_FFFF`.
#[inline(always)] #[inline(always)]
pub(crate) const fn from_raw_and_generation(index: u32, generation: NonZero<u32>) -> Entity { pub(crate) const fn from_raw_and_generation(
row: EntityRow,
generation: NonZero<u32>,
) -> Entity {
debug_assert!(generation.get() <= HIGH_MASK); 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, /// 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 /// # Note
/// ///
@ -302,8 +387,19 @@ impl Entity {
/// `Entity` lines up between instances, but instead insert a secondary identifier as /// `Entity` lines up between instances, but instead insert a secondary identifier as
/// a component. /// a component.
#[inline(always)] #[inline(always)]
pub const fn from_raw(index: u32) -> Entity { pub const fn from_raw(row: EntityRow) -> Entity {
Self::from_raw_and_generation(index, NonZero::<u32>::MIN) Self::from_raw_and_generation(row, NonZero::<u32>::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<Entity> {
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. /// 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. /// No particular structure is guaranteed for the returned bits.
#[inline(always)] #[inline(always)]
pub const fn to_bits(self) -> u64 { 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`]. /// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`].
@ -346,29 +442,38 @@ impl Entity {
let kind = id.kind() as u8; let kind = id.kind() as u8;
if kind == (IdKind::Entity as u8) { if kind == (IdKind::Entity as u8) {
if let Some(row) = EntityRow::try_from_bits(id.low()) {
return Ok(Self { return Ok(Self {
index: id.low(), row,
generation: id.high(), generation: id.high(),
}); });
} }
} }
}
Err(IdentifierError::InvalidEntityId(bits)) Err(IdentifierError::InvalidEntityId(bits))
} }
/// Return a transiently unique identifier. /// 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 /// with both live and dead entities. Useful for compactly representing entities within a
/// specific snapshot of the world, such as when serializing. /// specific snapshot of the world, such as when serializing.
#[inline] #[inline]
pub const fn index(self) -> u32 { pub const fn row(self) -> EntityRow {
self.index self.row
} }
/// Returns the generation of this Entity's index. The generation is incremented each time an /// Equivalent to `self.row().index()`. See [`Self::row`] for details.
/// entity with a given index is despawned. This serves as a "count" of the number of times a #[inline]
/// given index has been reused (index, generation) pairs uniquely identify a given Entity. pub const fn index(self) -> u32 {
self.row.index()
}
/// 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] #[inline]
pub const fn generation(self) -> u32 { pub const fn generation(self) -> u32 {
// Mask so not to expose any flags // Mask so not to expose any flags
@ -471,12 +576,12 @@ impl fmt::Display for Entity {
impl SparseSetIndex for Entity { impl SparseSetIndex for Entity {
#[inline] #[inline]
fn sparse_set_index(&self) -> usize { fn sparse_set_index(&self) -> usize {
self.index() as usize self.row().sparse_set_index()
} }
#[inline] #[inline]
fn get_sparse_set_index(value: usize) -> Self { 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], meta: &'a [EntityMeta],
// Reserved indices formerly in the freelist to hand out. // 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 Entity indices to hand out, outside the range of meta.len().
new_indices: core::ops::Range<u32>, new_indices: core::ops::Range<u32>,
@ -498,10 +603,16 @@ impl<'a> Iterator for ReserveEntitiesIterator<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.freelist_indices self.freelist_indices
.next() .next()
.map(|&index| { .map(|&row| {
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)
})
.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<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
@ -568,7 +679,7 @@ pub struct Entities {
/// [`reserve_entity`]: Entities::reserve_entity /// [`reserve_entity`]: Entities::reserve_entity
/// [`reserve_entities`]: Entities::reserve_entities /// [`reserve_entities`]: Entities::reserve_entities
/// [`flush`]: Entities::flush /// [`flush`]: Entities::flush
pending: Vec<u32>, pending: Vec<EntityRow>,
free_cursor: AtomicIdCursor, free_cursor: AtomicIdCursor,
} }
@ -644,17 +755,21 @@ impl Entities {
let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed); let n = self.free_cursor.fetch_sub(1, Ordering::Relaxed);
if n > 0 { if n > 0 {
// Allocate from the freelist. // Allocate from the freelist.
let index = self.pending[(n - 1) as usize]; let row = self.pending[(n - 1) as usize];
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 { } else {
// Grab a new ID, outside the range of `meta.len()`. `flush()` must // Grab a new ID, outside the range of `meta.len()`. `flush()` must
// eventually be called to make it valid. // eventually be called to make it valid.
// //
// As `self.free_cursor` goes more and more negative, we return IDs farther // As `self.free_cursor` goes more and more negative, we return IDs farther
// and farther beyond `meta.len()`. // and farther beyond `meta.len()`.
Entity::from_raw( let raw = self.meta.len() as IdCursor - n;
u32::try_from(self.meta.len() as IdCursor - n).expect("too many entities"), 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. /// Allocate an entity ID directly.
pub fn alloc(&mut self) -> Entity { pub fn alloc(&mut self) -> Entity {
self.verify_flushed(); 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; let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor; *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 { } 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); 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::<u32>::MIN { if meta.generation == NonZero::<u32>::MIN {
warn!( warn!(
"Entity({}) generation wrapped on Entities::free, aliasing may occur", "Entity({}) generation wrapped on Entities::free, aliasing may occur",
entity.index entity.row()
); );
} }
let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location); 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; let new_free_cursor = self.pending.len() as IdCursor;
*self.free_cursor.get_mut() = new_free_cursor; *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 // This will return false for entities which have been freed, even if
// not reallocated since the generation is incremented in `free` // not reallocated since the generation is incremented in `free`
pub fn contains(&self, entity: Entity) -> bool { 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()) .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: This method may return [`Entities`](Entity) which are currently free
/// Note that [`contains`](Entities::contains) will correctly return false for freed /// Note that [`contains`](Entities::contains) will correctly return false for freed
/// entities, since it checks the generation /// entities, since it checks the generation
pub fn resolve_from_id(&self, index: u32) -> Option<Entity> { pub fn resolve_from_id(&self, row: EntityRow) -> Option<Entity> {
let idu = index as usize; let idu = row.index() as usize;
if let Some(&EntityMeta { generation, .. }) = self.meta.get(idu) { 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 { } else {
// `id` is outside of the meta list - check whether it is reserved but not yet flushed. // `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); let free_cursor = self.free_cursor.load(Ordering::Relaxed);
// If this entity was manually created, then free_cursor might be positive // If this entity was manually created, then free_cursor might be positive
// Returning None handles that case correctly // Returning None handles that case correctly
let num_pending = usize::try_from(-free_cursor).ok()?; 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; let new_meta_len = old_meta_len + -current_free_cursor as usize;
self.meta.resize(new_meta_len, EntityMeta::EMPTY); self.meta.resize(new_meta_len, EntityMeta::EMPTY);
for (index, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) { 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( init(
Entity::from_raw_and_generation(index as u32, meta.generation), Entity::from_raw_and_generation(row, meta.generation),
&mut meta.location, &mut meta.location,
); );
} }
@ -849,10 +969,10 @@ impl Entities {
0 0
}; };
for index in self.pending.drain(new_free_cursor..) { for row in self.pending.drain(new_free_cursor..) {
let meta = &mut self.meta[index as usize]; let meta = &mut self.meta[row.index() as usize];
init( init(
Entity::from_raw_and_generation(index, meta.generation), Entity::from_raw_and_generation(row, meta.generation),
&mut meta.location, &mut meta.location,
); );
} }
@ -1064,9 +1184,14 @@ mod tests {
#[test] #[test]
fn entity_bits_roundtrip() { 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 // Generation cannot be greater than 0x7FFF_FFFF else it will be an invalid Entity id
let e = let e = Entity::from_raw_and_generation(
Entity::from_raw_and_generation(0xDEADBEEF, NonZero::<u32>::new(0x5AADF00D).unwrap()); EntityRow::new(NonMaxU32::new(0xDEADBEEF).unwrap()),
NonZero::<u32>::new(0x5AADF00D).unwrap(),
);
assert_eq!(Entity::from_bits(e.to_bits()), e); assert_eq!(Entity::from_bits(e.to_bits()), e);
} }
@ -1099,18 +1224,18 @@ mod tests {
#[test] #[test]
fn entity_const() { 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!(42, C1.index());
assert_eq!(1, C1.generation()); assert_eq!(1, C1.generation());
const C2: Entity = Entity::from_bits(0x0000_00ff_0000_00cc); 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()); 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); 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); assert_eq!(0x00dd_00ff, C4);
} }
@ -1146,65 +1271,139 @@ mod tests {
)] )]
fn entity_comparison() { fn entity_comparison() {
assert_eq!( assert_eq!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()), Entity::from_raw_and_generation(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
),
Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
)
); );
assert_ne!( assert_ne!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(789).unwrap()), Entity::from_raw_and_generation(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(789).unwrap()
),
Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
)
); );
assert_ne!( assert_ne!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()), Entity::from_raw_and_generation(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(789).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
),
Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(789).unwrap()
)
); );
assert_ne!( assert_ne!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()), Entity::from_raw_and_generation(
Entity::from_raw_and_generation(456, NonZero::<u32>::new(123).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
),
Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(456).unwrap()),
NonZero::<u32>::new(123).unwrap()
)
); );
// ordering is by generation then by index // ordering is by generation then by index
assert!( assert!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) Entity::from_raw_and_generation(
>= Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
) >= Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) Entity::from_raw_and_generation(
<= Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
) <= Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
)
); );
assert!( assert!(
!(Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) !(Entity::from_raw_and_generation(
< Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap())) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
) < Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
))
); );
assert!( assert!(
!(Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap()) !(Entity::from_raw_and_generation(
> Entity::from_raw_and_generation(123, NonZero::<u32>::new(456).unwrap())) EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
) > Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(123).unwrap()),
NonZero::<u32>::new(456).unwrap()
))
); );
assert!( assert!(
Entity::from_raw_and_generation(9, NonZero::<u32>::new(1).unwrap()) Entity::from_raw_and_generation(
< Entity::from_raw_and_generation(1, NonZero::<u32>::new(9).unwrap()) EntityRow::new(NonMaxU32::new(9).unwrap()),
NonZero::<u32>::new(1).unwrap()
) < Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(9).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(1, NonZero::<u32>::new(9).unwrap()) Entity::from_raw_and_generation(
> Entity::from_raw_and_generation(9, NonZero::<u32>::new(1).unwrap()) EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(9).unwrap()
) > Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(9).unwrap()),
NonZero::<u32>::new(1).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(1, NonZero::<u32>::new(1).unwrap()) Entity::from_raw_and_generation(
< Entity::from_raw_and_generation(2, NonZero::<u32>::new(1).unwrap()) EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(1).unwrap()
) > Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(2).unwrap()),
NonZero::<u32>::new(1).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(1, NonZero::<u32>::new(1).unwrap()) Entity::from_raw_and_generation(
<= Entity::from_raw_and_generation(2, NonZero::<u32>::new(1).unwrap()) EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(1).unwrap()
) >= Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(2).unwrap()),
NonZero::<u32>::new(1).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(2, NonZero::<u32>::new(2).unwrap()) Entity::from_raw_and_generation(
> Entity::from_raw_and_generation(1, NonZero::<u32>::new(2).unwrap()) EntityRow::new(NonMaxU32::new(2).unwrap()),
NonZero::<u32>::new(2).unwrap()
) < Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(2).unwrap()
)
); );
assert!( assert!(
Entity::from_raw_and_generation(2, NonZero::<u32>::new(2).unwrap()) Entity::from_raw_and_generation(
>= Entity::from_raw_and_generation(1, NonZero::<u32>::new(2).unwrap()) EntityRow::new(NonMaxU32::new(2).unwrap()),
NonZero::<u32>::new(2).unwrap()
) <= Entity::from_raw_and_generation(
EntityRow::new(NonMaxU32::new(1).unwrap()),
NonZero::<u32>::new(2).unwrap()
)
); );
} }
@ -1216,12 +1415,16 @@ mod tests {
let hash = EntityHash; let hash = EntityHash;
let first_id = 0xC0FFEE << 8; 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 { for i in 1..=255 {
let id = first_id + i; let id = first_id + i;
let hash = hash.hash_one(Entity::from_raw(id)); let hash = hash.hash_one(Entity::from_raw(EntityRow::new(
assert_eq!(hash.wrapping_sub(first_hash) as u32, i); NonMaxU32::new(id).unwrap(),
)));
assert_eq!(first_hash.wrapping_sub(hash) as u32, i);
} }
} }
@ -1232,20 +1435,24 @@ mod tests {
let hash = EntityHash; let hash = EntityHash;
let first_id = 0xC0FFEE; 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 { for bit in 0..u32::BITS {
let id = first_id ^ (1 << bit); 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); assert_ne!(hash, first_hash);
} }
} }
#[test] #[test]
fn entity_debug() { fn entity_debug() {
let entity = Entity::from_raw(42); let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));
let string = format!("{:?}", entity); let string = format!("{:?}", entity);
assert_eq!(string, "42v1#4294967338"); assert_eq!(string, "42v1#8589934549");
let entity = Entity::PLACEHOLDER; let entity = Entity::PLACEHOLDER;
let string = format!("{:?}", entity); let string = format!("{:?}", entity);
@ -1254,7 +1461,7 @@ mod tests {
#[test] #[test]
fn entity_display() { fn entity_display() {
let entity = Entity::from_raw(42); let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));
let string = format!("{}", entity); let string = format!("{}", entity);
assert_eq!(string, "42v1"); assert_eq!(string, "42v1");

View File

@ -484,10 +484,9 @@ mod tests {
results.lock().unwrap().push((e, i)); results.lock().unwrap().push((e, i));
}); });
results.lock().unwrap().sort(); results.lock().unwrap().sort();
assert_eq!( let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)];
&*results.lock().unwrap(), expected.sort();
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] assert_eq!(&*results.lock().unwrap(), &expected);
);
} }
#[test] #[test]
@ -505,10 +504,9 @@ mod tests {
.par_iter(&world) .par_iter(&world)
.for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i))); .for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i)));
results.lock().unwrap().sort(); results.lock().unwrap().sort();
assert_eq!( let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)];
&*results.lock().unwrap(), expected.sort();
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)] assert_eq!(&*results.lock().unwrap(), &expected);
);
} }
#[test] #[test]
@ -1538,8 +1536,8 @@ mod tests {
let mut world_a = World::new(); let mut world_a = World::new();
let world_b = World::new(); let world_b = World::new();
let mut query = world_a.query::<&A>(); let mut query = world_a.query::<&A>();
let _ = query.get(&world_a, Entity::from_raw(0)); let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap());
let _ = query.get(&world_b, Entity::from_raw(0)); let _ = query.get(&world_b, Entity::from_raw_u32(0).unwrap());
} }
#[test] #[test]
@ -1780,7 +1778,7 @@ mod tests {
fn try_insert_batch() { fn try_insert_batch() {
let mut world = World::default(); let mut world = World::default();
let e0 = world.spawn(A(0)).id(); 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)))]; let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];
@ -1804,7 +1802,7 @@ mod tests {
fn try_insert_batch_if_new() { fn try_insert_batch_if_new() {
let mut world = World::default(); let mut world = World::default();
let e0 = world.spawn(A(0)).id(); 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)))]; let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];

View File

@ -1075,7 +1075,7 @@ mod tests {
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2")); world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
world.spawn(A).flush(); world.spawn(A).flush();
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0); assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
// Our A entity plus our two observers // Our A entity plus our two observers
assert_eq!(world.entities().len(), 3); assert_eq!(world.entities().len(), 3);
} }

View File

@ -993,7 +993,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// ///
/// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// 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); /// 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<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// ///
/// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// 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); /// 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<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// ///
/// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// 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(); /// 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); /// 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<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// ///
/// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// 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(); /// 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); /// 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<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// ///
/// # assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// # 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(); /// # 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); /// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
@ -1884,7 +1884,7 @@ mod tests {
let world_2 = World::new(); let world_2 = World::new();
let mut query_state = world_1.query::<Entity>(); let mut query_state = world_1.query::<Entity>();
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] #[test]

View File

@ -709,7 +709,7 @@ mod tests {
let collection = rel_target.collection(); let collection = rel_target.collection();
// Insertions should maintain ordering // Insertions should maintain ordering
assert!(collection.iter().eq(&[b, c, d])); assert!(collection.iter().eq(&[d, c, b]));
world.entity_mut(c).despawn(); world.entity_mut(c).despawn();
@ -717,7 +717,7 @@ mod tests {
let collection = rel_target.collection(); let collection = rel_target.collection();
// Removals should maintain ordering // Removals should maintain ordering
assert!(collection.iter().eq(&[b, d])); assert!(collection.iter().eq(&[d, b]));
} }
#[test] #[test]

View File

@ -659,10 +659,11 @@ mod tests {
use super::SparseSets; use super::SparseSets;
use crate::{ use crate::{
component::{Component, ComponentDescriptor, ComponentId, ComponentInfo}, component::{Component, ComponentDescriptor, ComponentId, ComponentInfo},
entity::Entity, entity::{Entity, EntityRow},
storage::SparseSet, storage::SparseSet,
}; };
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use nonmax::NonMaxU32;
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
struct Foo(usize); struct Foo(usize);
@ -670,11 +671,11 @@ mod tests {
#[test] #[test]
fn sparse_set() { fn sparse_set() {
let mut set = SparseSet::<Entity, Foo>::default(); let mut set = SparseSet::<Entity, Foo>::default();
let e0 = Entity::from_raw(0); let e0 = Entity::from_raw(EntityRow::new(NonMaxU32::new(0).unwrap()));
let e1 = Entity::from_raw(1); let e1 = Entity::from_raw(EntityRow::new(NonMaxU32::new(1).unwrap()));
let e2 = Entity::from_raw(2); let e2 = Entity::from_raw(EntityRow::new(NonMaxU32::new(2).unwrap()));
let e3 = Entity::from_raw(3); let e3 = Entity::from_raw(EntityRow::new(NonMaxU32::new(3).unwrap()));
let e4 = Entity::from_raw(4); let e4 = Entity::from_raw(EntityRow::new(NonMaxU32::new(4).unwrap()));
set.insert(e1, Foo(1)); set.insert(e1, Foo(1));
set.insert(e2, Foo(2)); set.insert(e2, Foo(2));

View File

@ -823,11 +823,12 @@ mod tests {
use crate::{ use crate::{
change_detection::MaybeLocation, change_detection::MaybeLocation,
component::{Component, ComponentIds, Components, ComponentsRegistrator, Tick}, component::{Component, ComponentIds, Components, ComponentsRegistrator, Tick},
entity::Entity, entity::{Entity, EntityRow},
ptr::OwningPtr, ptr::OwningPtr,
storage::{TableBuilder, TableId, TableRow, Tables}, storage::{TableBuilder, TableId, TableRow, Tables},
}; };
use alloc::vec::Vec; use alloc::vec::Vec;
use nonmax::NonMaxU32;
#[derive(Component)] #[derive(Component)]
struct W<T>(T); struct W<T>(T);
@ -856,7 +857,9 @@ mod tests {
let mut table = TableBuilder::with_capacity(0, columns.len()) let mut table = TableBuilder::with_capacity(0, columns.len())
.add_column(components.get_info(component_id).unwrap()) .add_column(components.get_info(component_id).unwrap())
.build(); .build();
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>(); let entities = (0..200)
.map(|index| Entity::from_raw(EntityRow::new(NonMaxU32::new(index).unwrap())))
.collect::<Vec<_>>();
for entity in &entities { for entity in &entities {
// SAFETY: we allocate and immediately set data afterwards // SAFETY: we allocate and immediately set data afterwards
unsafe { unsafe {

View File

@ -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)]); /// 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!( /// assert_eq!(
/// match query.get_many([wrong_entity]).unwrap_err() { /// match query.get_many([wrong_entity]).unwrap_err() {
@ -1465,7 +1465,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// ///
/// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); /// 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!( /// assert_eq!(
/// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() { /// match query.get_many_unique(UniqueEntityArray::from([wrong_entity])).unwrap_err() {
@ -1610,7 +1610,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// let entities: [Entity; 3] = entities.try_into().unwrap(); /// let entities: [Entity; 3] = entities.try_into().unwrap();
/// ///
/// world.spawn(A(73)); /// 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(); /// let invalid_entity = world.spawn_empty().id();
/// ///
/// ///
@ -1684,7 +1684,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap(); /// let entity_set: UniqueEntityArray<3> = entity_set.try_into().unwrap();
/// ///
/// world.spawn(A(73)); /// 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(); /// let invalid_entity = world.spawn_empty().id();
/// ///
/// ///

View File

@ -1490,13 +1490,14 @@ mod tests {
"Deserialized value does not match original" "Deserialized value does not match original"
); );
} }
use super::*; use super::*;
#[test] #[test]
fn serialization_tests() { fn serialization_tests() {
test_serialize_deserialize(BrpQueryRow { test_serialize_deserialize(BrpQueryRow {
components: Default::default(), components: Default::default(),
entity: Entity::from_raw(0), entity: Entity::from_raw_u32(0).unwrap(),
has: Default::default(), has: Default::default(),
}); });
test_serialize_deserialize(BrpListWatchingResponse::default()); test_serialize_deserialize(BrpListWatchingResponse::default());
@ -1510,7 +1511,7 @@ mod tests {
..Default::default() ..Default::default()
}); });
test_serialize_deserialize(BrpListParams { test_serialize_deserialize(BrpListParams {
entity: Entity::from_raw(0), entity: Entity::from_raw_u32(0).unwrap(),
}); });
} }
} }

View File

@ -509,10 +509,10 @@ mod tests {
let mut entities = builder.build().entities.into_iter(); let mut entities = builder.build().entities.into_iter();
// Assert entities are ordered // 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_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] #[test]
@ -539,7 +539,7 @@ mod tests {
assert_eq!(scene.entities.len(), 2); assert_eq!(scene.entities.len(), 2);
let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity]; let mut scene_entities = vec![scene.entities[0].entity, scene.entities[1].entity];
scene_entities.sort(); scene_entities.sort();
assert_eq!(scene_entities, [entity_a_b, entity_a]); assert_eq!(scene_entities, [entity_a, entity_a_b]);
} }
#[test] #[test]
@ -621,9 +621,9 @@ mod tests {
.build(); .build();
assert_eq!(scene.entities.len(), 3); assert_eq!(scene.entities.len(), 3);
assert!(scene.entities[0].components[0].represents::<ComponentA>()); assert!(scene.entities[2].components[0].represents::<ComponentA>());
assert!(scene.entities[1].components[0].represents::<ComponentA>()); assert!(scene.entities[1].components[0].represents::<ComponentA>());
assert_eq!(scene.entities[2].components.len(), 0); assert_eq!(scene.entities[0].components.len(), 0);
} }
#[test] #[test]

View File

@ -912,7 +912,7 @@ mod tests {
app.update(); app.update();
let world = app.world_mut(); let world = app.world_mut();
let spawned_root = world.entity(spawned).get::<Children>().unwrap()[0]; let spawned_root = world.entity(spawned).get::<Children>().unwrap()[1];
let children = world.entity(spawned_root).get::<Children>().unwrap(); let children = world.entity(spawned_root).get::<Children>().unwrap();
assert_eq!(children.len(), 3); assert_eq!(children.len(), 3);
assert!(world.entity(children[0]).get::<ComponentF>().is_some()); assert!(world.entity(children[0]).get::<ComponentF>().is_some());

View File

@ -638,24 +638,24 @@ mod tests {
), ),
}, },
entities: { entities: {
4294967296: ( 8589934589: (
components: {
"bevy_scene::serde::tests::Foo": (123),
},
),
4294967297: (
components: {
"bevy_scene::serde::tests::Bar": (345),
"bevy_scene::serde::tests::Foo": (123),
},
),
4294967298: (
components: { components: {
"bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Bar": (345),
"bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Baz": (789),
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
}, },
), ),
8589934590: (
components: {
"bevy_scene::serde::tests::Bar": (345),
"bevy_scene::serde::tests::Foo": (123),
},
),
8589934591: (
components: {
"bevy_scene::serde::tests::Foo": (123),
},
),
}, },
)"#; )"#;
let output = scene let output = scene
@ -675,18 +675,18 @@ mod tests {
), ),
}, },
entities: { entities: {
4294967296: ( 8589934591: (
components: { components: {
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
}, },
), ),
4294967297: ( 8589934590: (
components: { components: {
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
"bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Bar": (345),
}, },
), ),
4294967298: ( 8589934589: (
components: { components: {
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
"bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Bar": (345),
@ -815,7 +815,7 @@ mod tests {
assert_eq!( assert_eq!(
vec![ 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, 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, 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 108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33
@ -856,11 +856,12 @@ mod tests {
assert_eq!( assert_eq!(
vec![ vec![
146, 128, 129, 207, 0, 0, 0, 1, 0, 0, 0, 0, 145, 129, 217, 37, 98, 101, 118, 121, 146, 128, 129, 207, 0, 0, 0, 1, 255, 255, 255, 255, 145, 129, 217, 37, 98, 101,
95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 118, 121, 95, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116,
116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 101, 115, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116,
2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 147, 147, 1, 2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165,
108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 84, 117, 112, 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100,
33
], ],
buf buf
); );
@ -899,12 +900,13 @@ mod tests {
assert_eq!( assert_eq!(
vec![ 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, 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, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101,
58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58,
67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0,
0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1,
12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 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 serialized_scene
); );

View File

@ -798,8 +798,8 @@ mod test {
let translation = vec3(1.0, 0.0, 0.0); let translation = vec3(1.0, 0.0, 0.0);
// These will be overwritten. // These will be overwritten.
let mut child = Entity::from_raw(0); let mut child = Entity::from_raw_u32(0).unwrap();
let mut grandchild = Entity::from_raw(1); let mut grandchild = Entity::from_raw_u32(1).unwrap();
let parent = app let parent = app
.world_mut() .world_mut()
.spawn(Transform::from_translation(translation)) .spawn(Transform::from_translation(translation))
@ -849,7 +849,7 @@ mod test {
); );
fn setup_world(world: &mut World) -> (Entity, Entity) { 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 let child = world
.spawn(Transform::IDENTITY) .spawn(Transform::IDENTITY)
.with_children(|builder| { .with_children(|builder| {

View File

@ -1039,7 +1039,7 @@ mod tests {
let (mut world, ..) = setup_ui_test_world(); 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 { struct TestSystemParam {
root_node_entity: Entity, root_node_entity: Entity,

View File

@ -318,7 +318,7 @@ mod tests {
#[test] #[test]
fn test_upsert() { fn test_upsert() {
let mut ui_surface = UiSurface::default(); 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(); let node = Node::default();
// standard upsert // standard upsert
@ -350,7 +350,7 @@ mod tests {
#[test] #[test]
fn test_remove_entities() { fn test_remove_entities() {
let mut ui_surface = UiSurface::default(); 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(); let node = Node::default();
ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
@ -366,7 +366,7 @@ mod tests {
#[test] #[test]
fn test_try_update_measure() { fn test_try_update_measure() {
let mut ui_surface = UiSurface::default(); 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(); let node = Node::default();
ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
@ -381,8 +381,8 @@ mod tests {
#[test] #[test]
fn test_update_children() { fn test_update_children() {
let mut ui_surface = UiSurface::default(); 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 child_entity = Entity::from_raw(2); let child_entity = Entity::from_raw_u32(2).unwrap();
let node = Node::default(); let node = Node::default();
ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);
@ -402,8 +402,8 @@ mod tests {
#[test] #[test]
fn test_set_camera_children() { fn test_set_camera_children() {
let mut ui_surface = UiSurface::default(); 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 child_entity = Entity::from_raw(2); let child_entity = Entity::from_raw_u32(2).unwrap();
let node = Node::default(); let node = Node::default();
ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None); ui_surface.upsert_node(&LayoutContext::TEST_CONTEXT, root_node_entity, &node, None);

View File

@ -169,7 +169,7 @@ fn failing_system(world: &mut World) -> Result {
fn failing_commands(mut commands: Commands) { fn failing_commands(mut commands: Commands) {
commands commands
// This entity doesn't exist! // This entity doesn't exist!
.entity(Entity::from_raw(12345678)) .entity(Entity::from_raw_u32(12345678).unwrap())
// Normally, this failed command would panic, // Normally, this failed command would panic,
// but since we've set the global error handler to `warn` // but since we've set the global error handler to `warn`
// it will log a warning instead. // it will log a warning instead.

View File

@ -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<EntityRow>` half the size of `Option<u32>`.
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)`.