
# Objective Minimize the number of dependencies low in the tree. ## Solution * Remove the dependency on rustc-hash in bevy_ecs (not used) and bevy_macro_utils (only used in one spot). * Deduplicate the dependency on `sha1_smol` with the existing blake3 dependency already being used for bevy_asset. * Remove the unused `ron` dependency on `bevy_app` * Make the `serde` dependency for `bevy_ecs` optional. It's only used for serializing Entity. * Change the `wgpu` dependency to `wgpu-types`, and make it optional for `bevy_color`. * Remove the unused `thread-local` dependency on `bevy_render`. * Make multiple dependencies for `bevy_tasks` optional and enabled only when running with the `multi-threaded` feature. Preferably they'd be disabled all the time on wasm, but I couldn't find a clean way to do this. --- ## Changelog TODO ## Migration Guide TODO
1173 lines
44 KiB
Rust
1173 lines
44 KiB
Rust
//! Entity handling types.
|
|
//!
|
|
//! An **entity** exclusively owns zero or more [component] instances, all of different types, and can dynamically acquire or lose them over its lifetime.
|
|
//!
|
|
//! **empty entity**: Entity with zero components.
|
|
//! **pending entity**: Entity reserved, but not flushed yet (see [`Entities::flush`] docs for reference).
|
|
//! **reserved entity**: same as **pending entity**.
|
|
//! **invalid entity**: **pending entity** flushed with invalid (see [`Entities::flush_as_invalid`] docs for reference).
|
|
//!
|
|
//! See [`Entity`] to learn more.
|
|
//!
|
|
//! [component]: crate::component::Component
|
|
//!
|
|
//! # Usage
|
|
//!
|
|
//! Operations involving entities and their components are performed either from a system by submitting commands,
|
|
//! or from the outside (or from an exclusive system) by directly using [`World`] methods:
|
|
//!
|
|
//! |Operation|Command|Method|
|
|
//! |:---:|:---:|:---:|
|
|
//! |Spawn an entity with components|[`Commands::spawn`]|[`World::spawn`]|
|
|
//! |Spawn an entity without components|[`Commands::spawn_empty`]|[`World::spawn_empty`]|
|
|
//! |Despawn an entity|[`EntityCommands::despawn`]|[`World::despawn`]|
|
|
//! |Insert a component, bundle, or tuple of components and bundles to an entity|[`EntityCommands::insert`]|[`EntityWorldMut::insert`]|
|
|
//! |Remove a component, bundle, or tuple of components and bundles from an entity|[`EntityCommands::remove`]|[`EntityWorldMut::remove`]|
|
|
//!
|
|
//! [`World`]: crate::world::World
|
|
//! [`Commands::spawn`]: crate::system::Commands::spawn
|
|
//! [`Commands::spawn_empty`]: crate::system::Commands::spawn_empty
|
|
//! [`EntityCommands::despawn`]: crate::system::EntityCommands::despawn
|
|
//! [`EntityCommands::insert`]: crate::system::EntityCommands::insert
|
|
//! [`EntityCommands::remove`]: crate::system::EntityCommands::remove
|
|
//! [`World::spawn`]: crate::world::World::spawn
|
|
//! [`World::spawn_empty`]: crate::world::World::spawn_empty
|
|
//! [`World::despawn`]: crate::world::World::despawn
|
|
//! [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert
|
|
//! [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
|
|
mod map_entities;
|
|
#[cfg(feature = "bevy_reflect")]
|
|
use bevy_reflect::Reflect;
|
|
#[cfg(all(feature = "bevy_reflect", feature = "serde"))]
|
|
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
|
pub use map_entities::*;
|
|
|
|
mod hash;
|
|
pub use hash::*;
|
|
|
|
use bevy_utils::tracing::warn;
|
|
|
|
use crate::{
|
|
archetype::{ArchetypeId, ArchetypeRow},
|
|
identifier::{
|
|
error::IdentifierError,
|
|
kinds::IdKind,
|
|
masks::{IdentifierMask, HIGH_MASK},
|
|
Identifier,
|
|
},
|
|
storage::{SparseSetIndex, TableId, TableRow},
|
|
};
|
|
#[cfg(feature = "serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{fmt, hash::Hash, mem, num::NonZeroU32, sync::atomic::Ordering};
|
|
|
|
#[cfg(target_has_atomic = "64")]
|
|
use std::sync::atomic::AtomicI64 as AtomicIdCursor;
|
|
#[cfg(target_has_atomic = "64")]
|
|
type IdCursor = i64;
|
|
|
|
/// Most modern platforms support 64-bit atomics, but some less-common platforms
|
|
/// do not. This fallback allows compilation using a 32-bit cursor instead, with
|
|
/// the caveat that some conversions may fail (and panic) at runtime.
|
|
#[cfg(not(target_has_atomic = "64"))]
|
|
use std::sync::atomic::AtomicIsize as AtomicIdCursor;
|
|
#[cfg(not(target_has_atomic = "64"))]
|
|
type IdCursor = isize;
|
|
|
|
/// Lightweight identifier of an [entity](crate::entity).
|
|
///
|
|
/// The identifier is implemented using a [generational index]: a combination of an index and a generation.
|
|
/// This allows fast insertion after data removal in an array while minimizing loss of spatial locality.
|
|
///
|
|
/// These identifiers are only valid on the [`World`] it's sourced from. Attempting to use an `Entity` to
|
|
/// fetch entity components or metadata from a different world will either fail or return unexpected results.
|
|
///
|
|
/// [generational index]: https://lucassardois.medium.com/generational-indices-guide-8e3c5f7fd594
|
|
///
|
|
/// # Stability warning
|
|
/// For all intents and purposes, `Entity` should be treated as an opaque identifier. The internal bit
|
|
/// representation is liable to change from release to release as are the behaviors or performance
|
|
/// characteristics of any of its trait implementations (i.e. `Ord`, `Hash`, etc.). This means that changes in
|
|
/// `Entity`'s representation, though made readable through various functions on the type, are not considered
|
|
/// breaking changes under [SemVer].
|
|
///
|
|
/// In particular, directly serializing with `Serialize` and `Deserialize` make zero guarantee of long
|
|
/// term wire format compatibility. Changes in behavior will cause serialized `Entity` values persisted
|
|
/// to long term storage (i.e. disk, databases, etc.) will fail to deserialize upon being updated.
|
|
///
|
|
/// # Usage
|
|
///
|
|
/// This data type is returned by iterating a `Query` that has `Entity` as part of its query fetch type parameter ([learn more]).
|
|
/// It can also be obtained by calling [`EntityCommands::id`] or [`EntityWorldMut::id`].
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # #[derive(Component)]
|
|
/// # struct SomeComponent;
|
|
/// fn setup(mut commands: Commands) {
|
|
/// // Calling `spawn` returns `EntityCommands`.
|
|
/// let entity = commands.spawn(SomeComponent).id();
|
|
/// }
|
|
///
|
|
/// fn exclusive_system(world: &mut World) {
|
|
/// // Calling `spawn` returns `EntityWorldMut`.
|
|
/// let entity = world.spawn(SomeComponent).id();
|
|
/// }
|
|
/// #
|
|
/// # bevy_ecs::system::assert_is_system(setup);
|
|
/// # bevy_ecs::system::assert_is_system(exclusive_system);
|
|
/// ```
|
|
///
|
|
/// It can be used to refer to a specific entity to apply [`EntityCommands`], or to call [`Query::get`] (or similar methods) to access its components.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// #
|
|
/// # #[derive(Component)]
|
|
/// # struct Expired;
|
|
/// #
|
|
/// fn dispose_expired_food(mut commands: Commands, query: Query<Entity, With<Expired>>) {
|
|
/// for food_entity in &query {
|
|
/// commands.entity(food_entity).despawn();
|
|
/// }
|
|
/// }
|
|
/// #
|
|
/// # bevy_ecs::system::assert_is_system(dispose_expired_food);
|
|
/// ```
|
|
///
|
|
/// [learn more]: crate::system::Query#entity-id-access
|
|
/// [`EntityCommands::id`]: crate::system::EntityCommands::id
|
|
/// [`EntityWorldMut::id`]: crate::world::EntityWorldMut::id
|
|
/// [`EntityCommands`]: crate::system::EntityCommands
|
|
/// [`Query::get`]: crate::system::Query::get
|
|
/// [`World`]: crate::world::World
|
|
/// [SemVer]: https://semver.org/
|
|
#[derive(Clone, Copy, Debug)]
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
|
#[cfg_attr(feature = "bevy_reflect", reflect_value(Hash, PartialEq))]
|
|
#[cfg_attr(
|
|
all(feature = "bevy_reflect", feature = "serde"),
|
|
reflect_value(Serialize, Deserialize)
|
|
)]
|
|
// Alignment repr necessary to allow LLVM to better output
|
|
// optimised codegen for `to_bits`, `PartialEq` and `Ord`.
|
|
#[repr(C, align(8))]
|
|
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,
|
|
generation: NonZeroU32,
|
|
#[cfg(target_endian = "big")]
|
|
index: u32,
|
|
}
|
|
|
|
// By not short-circuiting in comparisons, we get better codegen.
|
|
// See <https://github.com/rust-lang/rust/issues/117800>
|
|
impl PartialEq for Entity {
|
|
#[inline]
|
|
fn eq(&self, other: &Entity) -> bool {
|
|
// By using `to_bits`, the codegen can be optimised out even
|
|
// further potentially. Relies on the correct alignment/field
|
|
// order of `Entity`.
|
|
self.to_bits() == other.to_bits()
|
|
}
|
|
}
|
|
|
|
impl Eq for Entity {}
|
|
|
|
// The derive macro codegen output is not optimal and can't be optimised as well
|
|
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
|
|
// on comparing against the bit representation of `Entity` instead of comparing
|
|
// the fields. The result is then LLVM is able to optimise the codegen for Entity
|
|
// far beyond what the derive macro can.
|
|
// See <https://github.com/rust-lang/rust/issues/106107>
|
|
impl PartialOrd for Entity {
|
|
#[inline]
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
// Make use of our `Ord` impl to ensure optimal codegen output
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
// The derive macro codegen output is not optimal and can't be optimised as well
|
|
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
|
|
// on comparing against the bit representation of `Entity` instead of comparing
|
|
// the fields. The result is then LLVM is able to optimise the codegen for Entity
|
|
// far beyond what the derive macro can.
|
|
// See <https://github.com/rust-lang/rust/issues/106107>
|
|
impl Ord for Entity {
|
|
#[inline]
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
// This will result in better codegen for ordering comparisons, plus
|
|
// avoids pitfalls with regards to macro codegen relying on property
|
|
// position when we want to compare against the bit representation.
|
|
self.to_bits().cmp(&other.to_bits())
|
|
}
|
|
}
|
|
|
|
impl Hash for Entity {
|
|
#[inline]
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
self.to_bits().hash(state);
|
|
}
|
|
}
|
|
|
|
pub(crate) enum AllocAtWithoutReplacement {
|
|
Exists(EntityLocation),
|
|
DidNotExist,
|
|
ExistsWithWrongGeneration,
|
|
}
|
|
|
|
impl Entity {
|
|
/// Construct an [`Entity`] from a raw `index` 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: NonZeroU32) -> Entity {
|
|
debug_assert!(generation.get() <= HIGH_MASK);
|
|
|
|
Self { index, generation }
|
|
}
|
|
|
|
/// An entity ID with a placeholder value. This may or may not correspond to an actual entity,
|
|
/// and should be overwritten by a new value before being used.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// Initializing a collection (e.g. `array` or `Vec`) with a known size:
|
|
///
|
|
/// ```no_run
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// // Create a new array of size 10 filled with invalid entity ids.
|
|
/// let mut entities: [Entity; 10] = [Entity::PLACEHOLDER; 10];
|
|
///
|
|
/// // ... replace the entities with valid ones.
|
|
/// ```
|
|
///
|
|
/// Deriving [`Reflect`] for a component that has an `Entity` field:
|
|
///
|
|
/// ```no_run
|
|
/// # use bevy_ecs::{prelude::*, component::*};
|
|
/// # use bevy_reflect::Reflect;
|
|
/// #[derive(Reflect, Component)]
|
|
/// #[reflect(Component)]
|
|
/// pub struct MyStruct {
|
|
/// pub entity: Entity,
|
|
/// }
|
|
///
|
|
/// impl FromWorld for MyStruct {
|
|
/// fn from_world(_world: &mut World) -> Self {
|
|
/// Self {
|
|
/// entity: Entity::PLACEHOLDER,
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
pub const PLACEHOLDER: Self = Self::from_raw(u32::MAX);
|
|
|
|
/// Creates a new entity ID with the specified `index` and a generation of 1.
|
|
///
|
|
/// # Note
|
|
///
|
|
/// Spawning a specific `entity` value is __rarely the right choice__. Most apps should favor
|
|
/// [`Commands::spawn`](crate::system::Commands::spawn). This method should generally
|
|
/// only be used for sharing entities across apps, and only when they have a scheme
|
|
/// worked out to share an index space (which doesn't happen by default).
|
|
///
|
|
/// In general, one should not try to synchronize the ECS by attempting to ensure that
|
|
/// `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, NonZeroU32::MIN)
|
|
}
|
|
|
|
/// Convert to a form convenient for passing outside of rust.
|
|
///
|
|
/// Only useful for identifying entities within the same instance of an application. Do not use
|
|
/// for serialization between runs.
|
|
///
|
|
/// 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())
|
|
}
|
|
|
|
/// Reconstruct an `Entity` previously destructured with [`Entity::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 `u64` values that did not come from [`Entity::to_bits`].
|
|
#[inline]
|
|
pub const fn from_bits(bits: u64) -> Self {
|
|
// Construct an Identifier initially to extract the kind from.
|
|
let id = Self::try_from_bits(bits);
|
|
|
|
match id {
|
|
Ok(entity) => entity,
|
|
Err(_) => panic!("Attempted to initialise invalid bits as an entity"),
|
|
}
|
|
}
|
|
|
|
/// Reconstruct an `Entity` previously destructured with [`Entity::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 [`Entity::from_bits`].
|
|
#[inline(always)]
|
|
pub const fn try_from_bits(bits: u64) -> Result<Self, IdentifierError> {
|
|
if let Ok(id) = Identifier::try_from_bits(bits) {
|
|
let kind = id.kind() as u8;
|
|
|
|
if kind == (IdKind::Entity as u8) {
|
|
return Ok(Self {
|
|
index: id.low(),
|
|
generation: id.high(),
|
|
});
|
|
}
|
|
}
|
|
|
|
Err(IdentifierError::InvalidEntityId(bits))
|
|
}
|
|
|
|
/// Return a transiently unique identifier.
|
|
///
|
|
/// No two simultaneously-live entities share the same index, 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 index(self) -> u32 {
|
|
self.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.
|
|
#[inline]
|
|
pub const fn generation(self) -> u32 {
|
|
// Mask so not to expose any flags
|
|
IdentifierMask::extract_value_from_high(self.generation.get())
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Identifier> for Entity {
|
|
type Error = IdentifierError;
|
|
|
|
#[inline]
|
|
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
|
|
Self::try_from_bits(value.to_bits())
|
|
}
|
|
}
|
|
|
|
impl From<Entity> for Identifier {
|
|
#[inline]
|
|
fn from(value: Entity) -> Self {
|
|
Identifier::from_bits(value.to_bits())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl Serialize for Entity {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_u64(self.to_bits())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> Deserialize<'de> for Entity {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
let id: u64 = serde::de::Deserialize::deserialize(deserializer)?;
|
|
Entity::try_from_bits(id).map_err(D::Error::custom)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Entity {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(
|
|
f,
|
|
"{}v{}|{}",
|
|
self.index(),
|
|
self.generation(),
|
|
self.to_bits()
|
|
)
|
|
}
|
|
}
|
|
|
|
impl SparseSetIndex for Entity {
|
|
#[inline]
|
|
fn sparse_set_index(&self) -> usize {
|
|
self.index() as usize
|
|
}
|
|
|
|
#[inline]
|
|
fn get_sparse_set_index(value: usize) -> Self {
|
|
Entity::from_raw(value as u32)
|
|
}
|
|
}
|
|
|
|
/// An [`Iterator`] returning a sequence of [`Entity`] values from
|
|
pub struct ReserveEntitiesIterator<'a> {
|
|
// Metas, so we can recover the current generation for anything in the freelist.
|
|
meta: &'a [EntityMeta],
|
|
|
|
// Reserved indices formerly in the freelist to hand out.
|
|
index_iter: std::slice::Iter<'a, u32>,
|
|
|
|
// New Entity indices to hand out, outside the range of meta.len().
|
|
index_range: std::ops::Range<u32>,
|
|
}
|
|
|
|
impl<'a> Iterator for ReserveEntitiesIterator<'a> {
|
|
type Item = Entity;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.index_iter
|
|
.next()
|
|
.map(|&index| {
|
|
Entity::from_raw_and_generation(index, self.meta[index as usize].generation)
|
|
})
|
|
.or_else(|| self.index_range.next().map(Entity::from_raw))
|
|
}
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
let len = self.index_iter.len() + self.index_range.len();
|
|
(len, Some(len))
|
|
}
|
|
}
|
|
|
|
impl<'a> ExactSizeIterator for ReserveEntitiesIterator<'a> {}
|
|
impl<'a> core::iter::FusedIterator for ReserveEntitiesIterator<'a> {}
|
|
|
|
/// A [`World`]'s internal metadata store on all of its entities.
|
|
///
|
|
/// Contains metadata on:
|
|
/// - The generation of every entity.
|
|
/// - The alive/dead status of a particular entity. (i.e. "has entity 3 been despawned?")
|
|
/// - The location of the entity's components in memory (via [`EntityLocation`])
|
|
///
|
|
/// [`World`]: crate::world::World
|
|
#[derive(Debug)]
|
|
pub struct Entities {
|
|
meta: Vec<EntityMeta>,
|
|
|
|
/// The `pending` and `free_cursor` fields describe three sets of Entity IDs
|
|
/// that have been freed or are in the process of being allocated:
|
|
///
|
|
/// - The `freelist` IDs, previously freed by `free()`. These IDs are available to any of
|
|
/// [`alloc`], [`reserve_entity`] or [`reserve_entities`]. Allocation will always prefer
|
|
/// these over brand new IDs.
|
|
///
|
|
/// - The `reserved` list of IDs that were once in the freelist, but got reserved by
|
|
/// [`reserve_entities`] or [`reserve_entity`]. They are now waiting for [`flush`] to make them
|
|
/// fully allocated.
|
|
///
|
|
/// - The count of new IDs that do not yet exist in `self.meta`, but which we have handed out
|
|
/// and reserved. [`flush`] will allocate room for them in `self.meta`.
|
|
///
|
|
/// The contents of `pending` look like this:
|
|
///
|
|
/// ```txt
|
|
/// ----------------------------
|
|
/// | freelist | reserved |
|
|
/// ----------------------------
|
|
/// ^ ^
|
|
/// free_cursor pending.len()
|
|
/// ```
|
|
///
|
|
/// As IDs are allocated, `free_cursor` is atomically decremented, moving
|
|
/// items from the freelist into the reserved list by sliding over the boundary.
|
|
///
|
|
/// Once the freelist runs out, `free_cursor` starts going negative.
|
|
/// The more negative it is, the more IDs have been reserved starting exactly at
|
|
/// the end of `meta.len()`.
|
|
///
|
|
/// This formulation allows us to reserve any number of IDs first from the freelist
|
|
/// and then from the new IDs, using only a single atomic subtract.
|
|
///
|
|
/// Once [`flush`] is done, `free_cursor` will equal `pending.len()`.
|
|
///
|
|
/// [`alloc`]: Entities::alloc
|
|
/// [`reserve_entity`]: Entities::reserve_entity
|
|
/// [`reserve_entities`]: Entities::reserve_entities
|
|
/// [`flush`]: Entities::flush
|
|
pending: Vec<u32>,
|
|
free_cursor: AtomicIdCursor,
|
|
/// Stores the number of free entities for [`len`](Entities::len)
|
|
len: u32,
|
|
}
|
|
|
|
impl Entities {
|
|
pub(crate) const fn new() -> Self {
|
|
Entities {
|
|
meta: Vec::new(),
|
|
pending: Vec::new(),
|
|
free_cursor: AtomicIdCursor::new(0),
|
|
len: 0,
|
|
}
|
|
}
|
|
|
|
/// Reserve entity IDs concurrently.
|
|
///
|
|
/// Storage for entity generation and location is lazily allocated by calling [`flush`](Entities::flush).
|
|
#[allow(clippy::unnecessary_fallible_conversions)] // Because `IdCursor::try_from` may fail on 32-bit platforms.
|
|
pub fn reserve_entities(&self, count: u32) -> ReserveEntitiesIterator {
|
|
// Use one atomic subtract to grab a range of new IDs. The range might be
|
|
// entirely nonnegative, meaning all IDs come from the freelist, or entirely
|
|
// negative, meaning they are all new IDs to allocate, or a mix of both.
|
|
let range_end = self
|
|
.free_cursor
|
|
// Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics
|
|
// and use AtomicIsize instead (see note on `IdCursor`).
|
|
.fetch_sub(IdCursor::try_from(count).unwrap(), Ordering::Relaxed);
|
|
let range_start = range_end - IdCursor::try_from(count).unwrap();
|
|
|
|
let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize;
|
|
|
|
let (new_id_start, new_id_end) = if range_start >= 0 {
|
|
// We satisfied all requests from the freelist.
|
|
(0, 0)
|
|
} else {
|
|
// We need to allocate some new Entity IDs outside of the range of self.meta.
|
|
//
|
|
// `range_start` covers some negative territory, e.g. `-3..6`.
|
|
// Since the nonnegative values `0..6` are handled by the freelist, that
|
|
// means we need to handle the negative range here.
|
|
//
|
|
// In this example, we truncate the end to 0, leaving us with `-3..0`.
|
|
// Then we negate these values to indicate how far beyond the end of `meta.end()`
|
|
// to go, yielding `meta.len()+0 .. meta.len()+3`.
|
|
let base = self.meta.len() as IdCursor;
|
|
|
|
let new_id_end = u32::try_from(base - range_start).expect("too many entities");
|
|
|
|
// `new_id_end` is in range, so no need to check `start`.
|
|
let new_id_start = (base - range_end.min(0)) as u32;
|
|
|
|
(new_id_start, new_id_end)
|
|
};
|
|
|
|
ReserveEntitiesIterator {
|
|
meta: &self.meta[..],
|
|
index_iter: self.pending[freelist_range].iter(),
|
|
index_range: new_id_start..new_id_end,
|
|
}
|
|
}
|
|
|
|
/// Reserve one entity ID concurrently.
|
|
///
|
|
/// Equivalent to `self.reserve_entities(1).next().unwrap()`, but more efficient.
|
|
pub fn reserve_entity(&self) -> Entity {
|
|
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)
|
|
} 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"),
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Check that we do not have pending work requiring `flush()` to be called.
|
|
fn verify_flushed(&mut self) {
|
|
debug_assert!(
|
|
!self.needs_flush(),
|
|
"flush() needs to be called before this operation is legal"
|
|
);
|
|
}
|
|
|
|
/// Allocate an entity ID directly.
|
|
pub fn alloc(&mut self) -> Entity {
|
|
self.verify_flushed();
|
|
self.len += 1;
|
|
if let Some(index) = 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)
|
|
} else {
|
|
let index = u32::try_from(self.meta.len()).expect("too many entities");
|
|
self.meta.push(EntityMeta::EMPTY);
|
|
Entity::from_raw(index)
|
|
}
|
|
}
|
|
|
|
/// Allocate a specific entity ID, overwriting its generation.
|
|
///
|
|
/// Returns the location of the entity currently using the given ID, if any. Location should be
|
|
/// written immediately.
|
|
pub fn alloc_at(&mut self, entity: Entity) -> Option<EntityLocation> {
|
|
self.verify_flushed();
|
|
|
|
let loc = if entity.index() as usize >= self.meta.len() {
|
|
self.pending
|
|
.extend((self.meta.len() as u32)..entity.index());
|
|
let new_free_cursor = self.pending.len() as IdCursor;
|
|
*self.free_cursor.get_mut() = new_free_cursor;
|
|
self.meta
|
|
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
|
|
self.len += 1;
|
|
None
|
|
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
|
|
self.pending.swap_remove(index);
|
|
let new_free_cursor = self.pending.len() as IdCursor;
|
|
*self.free_cursor.get_mut() = new_free_cursor;
|
|
self.len += 1;
|
|
None
|
|
} else {
|
|
Some(mem::replace(
|
|
&mut self.meta[entity.index() as usize].location,
|
|
EntityMeta::EMPTY.location,
|
|
))
|
|
};
|
|
|
|
self.meta[entity.index() as usize].generation = entity.generation;
|
|
|
|
loc
|
|
}
|
|
|
|
/// Allocate a specific entity ID, overwriting its generation.
|
|
///
|
|
/// Returns the location of the entity currently using the given ID, if any.
|
|
pub(crate) fn alloc_at_without_replacement(
|
|
&mut self,
|
|
entity: Entity,
|
|
) -> AllocAtWithoutReplacement {
|
|
self.verify_flushed();
|
|
|
|
let result = if entity.index() as usize >= self.meta.len() {
|
|
self.pending
|
|
.extend((self.meta.len() as u32)..entity.index());
|
|
let new_free_cursor = self.pending.len() as IdCursor;
|
|
*self.free_cursor.get_mut() = new_free_cursor;
|
|
self.meta
|
|
.resize(entity.index() as usize + 1, EntityMeta::EMPTY);
|
|
self.len += 1;
|
|
AllocAtWithoutReplacement::DidNotExist
|
|
} else if let Some(index) = self.pending.iter().position(|item| *item == entity.index()) {
|
|
self.pending.swap_remove(index);
|
|
let new_free_cursor = self.pending.len() as IdCursor;
|
|
*self.free_cursor.get_mut() = new_free_cursor;
|
|
self.len += 1;
|
|
AllocAtWithoutReplacement::DidNotExist
|
|
} else {
|
|
let current_meta = &self.meta[entity.index() as usize];
|
|
if current_meta.location.archetype_id == ArchetypeId::INVALID {
|
|
AllocAtWithoutReplacement::DidNotExist
|
|
} else if current_meta.generation == entity.generation {
|
|
AllocAtWithoutReplacement::Exists(current_meta.location)
|
|
} else {
|
|
return AllocAtWithoutReplacement::ExistsWithWrongGeneration;
|
|
}
|
|
};
|
|
|
|
self.meta[entity.index() as usize].generation = entity.generation;
|
|
result
|
|
}
|
|
|
|
/// Destroy an entity, allowing it to be reused.
|
|
///
|
|
/// Must not be called while reserved entities are awaiting `flush()`.
|
|
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> {
|
|
self.verify_flushed();
|
|
|
|
let meta = &mut self.meta[entity.index() as usize];
|
|
if meta.generation != entity.generation {
|
|
return None;
|
|
}
|
|
|
|
meta.generation = IdentifierMask::inc_masked_high_by(meta.generation, 1);
|
|
|
|
if meta.generation == NonZeroU32::MIN {
|
|
warn!(
|
|
"Entity({}) generation wrapped on Entities::free, aliasing may occur",
|
|
entity.index
|
|
);
|
|
}
|
|
|
|
let loc = mem::replace(&mut meta.location, EntityMeta::EMPTY.location);
|
|
|
|
self.pending.push(entity.index());
|
|
|
|
let new_free_cursor = self.pending.len() as IdCursor;
|
|
*self.free_cursor.get_mut() = new_free_cursor;
|
|
self.len -= 1;
|
|
Some(loc)
|
|
}
|
|
|
|
/// Ensure at least `n` allocations can succeed without reallocating.
|
|
#[allow(clippy::unnecessary_fallible_conversions)] // Because `IdCursor::try_from` may fail on 32-bit platforms.
|
|
pub fn reserve(&mut self, additional: u32) {
|
|
self.verify_flushed();
|
|
|
|
let freelist_size = *self.free_cursor.get_mut();
|
|
// Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics
|
|
// and use AtomicIsize instead (see note on `IdCursor`).
|
|
let shortfall = IdCursor::try_from(additional).unwrap() - freelist_size;
|
|
if shortfall > 0 {
|
|
self.meta.reserve(shortfall as usize);
|
|
}
|
|
}
|
|
|
|
/// Returns true if the [`Entities`] contains [`entity`](Entity).
|
|
// 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())
|
|
.map_or(false, |e| e.generation() == entity.generation())
|
|
}
|
|
|
|
/// Clears all [`Entity`] from the World.
|
|
pub fn clear(&mut self) {
|
|
self.meta.clear();
|
|
self.pending.clear();
|
|
*self.free_cursor.get_mut() = 0;
|
|
self.len = 0;
|
|
}
|
|
|
|
/// Returns the location of an [`Entity`].
|
|
/// Note: for pending entities, returns `Some(EntityLocation::INVALID)`.
|
|
#[inline]
|
|
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
|
|
if let Some(meta) = self.meta.get(entity.index() as usize) {
|
|
if meta.generation != entity.generation
|
|
|| meta.location.archetype_id == ArchetypeId::INVALID
|
|
{
|
|
return None;
|
|
}
|
|
Some(meta.location)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Updates the location of an [`Entity`]. This must be called when moving the components of
|
|
/// the entity around in storage.
|
|
///
|
|
/// # Safety
|
|
/// - `index` must be a valid entity index.
|
|
/// - `location` must be valid for the entity at `index` or immediately made valid afterwards
|
|
/// before handing control to unknown code.
|
|
#[inline]
|
|
pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) {
|
|
// SAFETY: Caller guarantees that `index` a valid entity index
|
|
let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
|
|
meta.location = location;
|
|
}
|
|
|
|
/// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this
|
|
/// `index` will count `generation` starting from the prior `generation` + the specified
|
|
/// value + 1.
|
|
///
|
|
/// Does nothing if no entity with this `index` has been allocated yet.
|
|
pub(crate) fn reserve_generations(&mut self, index: u32, generations: u32) -> bool {
|
|
if (index as usize) >= self.meta.len() {
|
|
return false;
|
|
}
|
|
|
|
let meta = &mut self.meta[index as usize];
|
|
if meta.location.archetype_id == ArchetypeId::INVALID {
|
|
meta.generation = IdentifierMask::inc_masked_high_by(meta.generation, generations);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Get the [`Entity`] with a given id, if it exists in this [`Entities`] collection
|
|
/// Returns `None` if this [`Entity`] is outside of the range of currently reserved 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<Entity> {
|
|
let idu = index as usize;
|
|
if let Some(&EntityMeta { generation, .. }) = self.meta.get(idu) {
|
|
Some(Entity::from_raw_and_generation(index, 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))
|
|
}
|
|
}
|
|
|
|
fn needs_flush(&mut self) -> bool {
|
|
*self.free_cursor.get_mut() != self.pending.len() as IdCursor
|
|
}
|
|
|
|
/// Allocates space for entities previously reserved with [`reserve_entity`](Entities::reserve_entity) or
|
|
/// [`reserve_entities`](Entities::reserve_entities), then initializes each one using the supplied function.
|
|
///
|
|
/// # Safety
|
|
/// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`]
|
|
/// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`]
|
|
/// has not been assigned to an [`Archetype`][crate::archetype::Archetype].
|
|
///
|
|
/// Note: freshly-allocated entities (ones which don't come from the pending list) are guaranteed
|
|
/// to be initialized with the invalid archetype.
|
|
pub unsafe fn flush(&mut self, mut init: impl FnMut(Entity, &mut EntityLocation)) {
|
|
let free_cursor = self.free_cursor.get_mut();
|
|
let current_free_cursor = *free_cursor;
|
|
|
|
let new_free_cursor = if current_free_cursor >= 0 {
|
|
current_free_cursor as usize
|
|
} else {
|
|
let old_meta_len = self.meta.len();
|
|
let new_meta_len = old_meta_len + -current_free_cursor as usize;
|
|
self.meta.resize(new_meta_len, EntityMeta::EMPTY);
|
|
self.len += -current_free_cursor as u32;
|
|
for (index, meta) in self.meta.iter_mut().enumerate().skip(old_meta_len) {
|
|
init(
|
|
Entity::from_raw_and_generation(index as u32, meta.generation),
|
|
&mut meta.location,
|
|
);
|
|
}
|
|
|
|
*free_cursor = 0;
|
|
0
|
|
};
|
|
|
|
self.len += (self.pending.len() - new_free_cursor) as u32;
|
|
for index in self.pending.drain(new_free_cursor..) {
|
|
let meta = &mut self.meta[index as usize];
|
|
init(
|
|
Entity::from_raw_and_generation(index, meta.generation),
|
|
&mut meta.location,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return `None`
|
|
/// unless they are later populated with a valid archetype.
|
|
pub fn flush_as_invalid(&mut self) {
|
|
// SAFETY: as per `flush` safety docs, the archetype id can be set to [`ArchetypeId::INVALID`] if
|
|
// the [`Entity`] has not been assigned to an [`Archetype`][crate::archetype::Archetype], which is the case here
|
|
unsafe {
|
|
self.flush(|_entity, location| {
|
|
location.archetype_id = ArchetypeId::INVALID;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
///
|
|
/// This function is safe if and only if the world this Entities is on has no entities.
|
|
pub unsafe fn flush_and_reserve_invalid_assuming_no_entities(&mut self, count: usize) {
|
|
let free_cursor = self.free_cursor.get_mut();
|
|
*free_cursor = 0;
|
|
self.meta.reserve(count);
|
|
// SAFETY: The EntityMeta struct only contains integers, and it is valid to have all bytes set to u8::MAX
|
|
unsafe {
|
|
self.meta.as_mut_ptr().write_bytes(u8::MAX, count);
|
|
}
|
|
// SAFETY: We have reserved `count` elements above and we have initialized values from index 0 to `count`.
|
|
unsafe {
|
|
self.meta.set_len(count);
|
|
}
|
|
|
|
self.len = count as u32;
|
|
}
|
|
|
|
/// The count of all entities in the [`World`] that have ever been allocated
|
|
/// including the entities that are currently freed.
|
|
///
|
|
/// This does not include entities that have been reserved but have never been
|
|
/// allocated yet.
|
|
///
|
|
/// [`World`]: crate::world::World
|
|
#[inline]
|
|
pub fn total_count(&self) -> usize {
|
|
self.meta.len()
|
|
}
|
|
|
|
/// The count of currently allocated entities.
|
|
#[inline]
|
|
pub fn len(&self) -> u32 {
|
|
self.len
|
|
}
|
|
|
|
/// Checks if any entity is currently active.
|
|
#[inline]
|
|
pub fn is_empty(&self) -> bool {
|
|
self.len == 0
|
|
}
|
|
}
|
|
|
|
// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill
|
|
// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`].
|
|
// Safety:
|
|
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
|
|
/// Metadata for an [`Entity`].
|
|
#[derive(Copy, Clone, Debug)]
|
|
#[repr(C)]
|
|
struct EntityMeta {
|
|
/// The current generation of the [`Entity`].
|
|
pub generation: NonZeroU32,
|
|
/// The current location of the [`Entity`]
|
|
pub location: EntityLocation,
|
|
}
|
|
|
|
impl EntityMeta {
|
|
/// meta for **pending entity**
|
|
const EMPTY: EntityMeta = EntityMeta {
|
|
generation: NonZeroU32::MIN,
|
|
location: EntityLocation::INVALID,
|
|
};
|
|
}
|
|
|
|
// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill
|
|
// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`].
|
|
// SAFETY:
|
|
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
|
|
/// A location of an entity in an archetype.
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
#[repr(C)]
|
|
pub struct EntityLocation {
|
|
/// The ID of the [`Archetype`] the [`Entity`] belongs to.
|
|
///
|
|
/// [`Archetype`]: crate::archetype::Archetype
|
|
pub archetype_id: ArchetypeId,
|
|
|
|
/// The index of the [`Entity`] within its [`Archetype`].
|
|
///
|
|
/// [`Archetype`]: crate::archetype::Archetype
|
|
pub archetype_row: ArchetypeRow,
|
|
|
|
/// The ID of the [`Table`] the [`Entity`] belongs to.
|
|
///
|
|
/// [`Table`]: crate::storage::Table
|
|
pub table_id: TableId,
|
|
|
|
/// The index of the [`Entity`] within its [`Table`].
|
|
///
|
|
/// [`Table`]: crate::storage::Table
|
|
pub table_row: TableRow,
|
|
}
|
|
|
|
impl EntityLocation {
|
|
/// location for **pending entity** and **invalid entity**
|
|
const INVALID: EntityLocation = EntityLocation {
|
|
archetype_id: ArchetypeId::INVALID,
|
|
archetype_row: ArchetypeRow::INVALID,
|
|
table_id: TableId::INVALID,
|
|
table_row: TableRow::INVALID,
|
|
};
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn entity_niche_optimization() {
|
|
assert_eq!(
|
|
std::mem::size_of::<Entity>(),
|
|
std::mem::size_of::<Option<Entity>>()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn entity_bits_roundtrip() {
|
|
// Generation cannot be greater than 0x7FFF_FFFF else it will be an invalid Entity id
|
|
let e = Entity::from_raw_and_generation(0xDEADBEEF, NonZeroU32::new(0x5AADF00D).unwrap());
|
|
assert_eq!(Entity::from_bits(e.to_bits()), e);
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_entity_len() {
|
|
let mut e = Entities::new();
|
|
e.reserve_entity();
|
|
// SAFETY: entity_location is left invalid
|
|
unsafe { e.flush(|_, _| {}) };
|
|
assert_eq!(e.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn get_reserved_and_invalid() {
|
|
let mut entities = Entities::new();
|
|
let e = entities.reserve_entity();
|
|
assert!(entities.contains(e));
|
|
assert!(entities.get(e).is_none());
|
|
|
|
// SAFETY: entity_location is left invalid
|
|
unsafe {
|
|
entities.flush(|_entity, _location| {
|
|
// do nothing ... leaving entity location invalid
|
|
});
|
|
};
|
|
|
|
assert!(entities.contains(e));
|
|
assert!(entities.get(e).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn entity_const() {
|
|
const C1: Entity = Entity::from_raw(42);
|
|
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_00ff, C2.generation());
|
|
|
|
const C3: u32 = Entity::from_raw(33).index();
|
|
assert_eq!(33, C3);
|
|
|
|
const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation();
|
|
assert_eq!(0x00dd_00ff, C4);
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_generations() {
|
|
let mut entities = Entities::new();
|
|
let entity = entities.alloc();
|
|
entities.free(entity);
|
|
|
|
assert!(entities.reserve_generations(entity.index(), 1));
|
|
}
|
|
|
|
#[test]
|
|
fn reserve_generations_and_alloc() {
|
|
const GENERATIONS: u32 = 10;
|
|
|
|
let mut entities = Entities::new();
|
|
let entity = entities.alloc();
|
|
entities.free(entity);
|
|
|
|
assert!(entities.reserve_generations(entity.index(), GENERATIONS));
|
|
|
|
// The very next entity allocated should be a further generation on the same index
|
|
let next_entity = entities.alloc();
|
|
assert_eq!(next_entity.index(), entity.index());
|
|
assert!(next_entity.generation() > entity.generation() + GENERATIONS);
|
|
}
|
|
|
|
#[test]
|
|
fn entity_comparison() {
|
|
// This is intentionally testing `lt` and `ge` as separate functions.
|
|
#![allow(clippy::nonminimal_bool)]
|
|
|
|
assert_eq!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()),
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
);
|
|
assert_ne!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(789).unwrap()),
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
);
|
|
assert_ne!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()),
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(789).unwrap())
|
|
);
|
|
assert_ne!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()),
|
|
Entity::from_raw_and_generation(456, NonZeroU32::new(123).unwrap())
|
|
);
|
|
|
|
// ordering is by generation then by index
|
|
|
|
assert!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
>= Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
);
|
|
assert!(
|
|
Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
<= Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
);
|
|
assert!(
|
|
!(Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
< Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()))
|
|
);
|
|
assert!(
|
|
!(Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap())
|
|
> Entity::from_raw_and_generation(123, NonZeroU32::new(456).unwrap()))
|
|
);
|
|
|
|
assert!(
|
|
Entity::from_raw_and_generation(9, NonZeroU32::new(1).unwrap())
|
|
< Entity::from_raw_and_generation(1, NonZeroU32::new(9).unwrap())
|
|
);
|
|
assert!(
|
|
Entity::from_raw_and_generation(1, NonZeroU32::new(9).unwrap())
|
|
> Entity::from_raw_and_generation(9, NonZeroU32::new(1).unwrap())
|
|
);
|
|
|
|
assert!(
|
|
Entity::from_raw_and_generation(1, NonZeroU32::new(1).unwrap())
|
|
< Entity::from_raw_and_generation(2, NonZeroU32::new(1).unwrap())
|
|
);
|
|
assert!(
|
|
Entity::from_raw_and_generation(1, NonZeroU32::new(1).unwrap())
|
|
<= Entity::from_raw_and_generation(2, NonZeroU32::new(1).unwrap())
|
|
);
|
|
assert!(
|
|
Entity::from_raw_and_generation(2, NonZeroU32::new(2).unwrap())
|
|
> Entity::from_raw_and_generation(1, NonZeroU32::new(2).unwrap())
|
|
);
|
|
assert!(
|
|
Entity::from_raw_and_generation(2, NonZeroU32::new(2).unwrap())
|
|
>= Entity::from_raw_and_generation(1, NonZeroU32::new(2).unwrap())
|
|
);
|
|
}
|
|
|
|
// Feel free to change this test if needed, but it seemed like an important
|
|
// part of the best-case performance changes in PR#9903.
|
|
#[test]
|
|
fn entity_hash_keeps_similar_ids_together() {
|
|
use std::hash::BuildHasher;
|
|
let hash = EntityHash;
|
|
|
|
let first_id = 0xC0FFEE << 8;
|
|
let first_hash = hash.hash_one(Entity::from_raw(first_id));
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn entity_hash_id_bitflip_affects_high_7_bits() {
|
|
use std::hash::BuildHasher;
|
|
|
|
let hash = EntityHash;
|
|
|
|
let first_id = 0xC0FFEE;
|
|
let first_hash = hash.hash_one(Entity::from_raw(first_id)) >> 57;
|
|
|
|
for bit in 0..u32::BITS {
|
|
let id = first_id ^ (1 << bit);
|
|
let hash = hash.hash_one(Entity::from_raw(id)) >> 57;
|
|
assert_ne!(hash, first_hash);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn entity_display() {
|
|
let entity = Entity::from_raw(42);
|
|
let string = format!("{}", entity);
|
|
let bits = entity.to_bits().to_string();
|
|
assert!(string.contains("42"));
|
|
assert!(string.contains("v1"));
|
|
assert!(string.contains(&bits));
|
|
}
|
|
}
|