bevy/crates/bevy_ecs/hecs/src/entities.rs
Carter Anderson 70ad6671db
ecs: use generational entity ids and other optimizations (#504)
ecs: use generational entity ids and other optimizations
2020-09-17 17:16:38 -07:00

377 lines
13 KiB
Rust

use alloc::{boxed::Box, vec::Vec};
use core::{
convert::TryFrom,
fmt, mem,
sync::atomic::{AtomicU32, Ordering},
};
#[cfg(feature = "std")]
use std::error::Error;
/// Lightweight unique ID of an entity
///
/// Obtained from `World::spawn`. Can be stored to refer to an entity in the future.
#[derive(Clone, Copy, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entity {
pub(crate) generation: u32,
pub(crate) id: u32,
}
impl Entity {
/// Creates a new entity reference with a generation of 0
pub fn new(id: u32) -> Entity {
Entity { id, generation: 0 }
}
/// 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.
pub fn to_bits(self) -> u64 {
u64::from(self.generation) << 32 | u64::from(self.id)
}
/// Reconstruct an `Entity` previously destructured with `to_bits`
///
/// Only useful when applied to results from `to_bits` in the same instance of an application.
pub fn from_bits(bits: u64) -> Self {
Self {
generation: (bits >> 32) as u32,
id: bits as u32,
}
}
/// Extract a transiently unique identifier
///
/// No two simultaneously-live entities share the same ID, but dead entities' IDs may collide
/// with both live and dead entities. Useful for compactly representing entities within a
/// specific snapshot of the world, such as when serializing.
pub fn id(self) -> u32 {
self.id
}
}
impl fmt::Debug for Entity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}v{}", self.id, self.generation)
}
}
#[derive(Default)]
pub(crate) struct Entities {
pub meta: Vec<EntityMeta>,
// Reserved entities outside the range of `meta`, having implicit generation 0, archetype 0, and
// undefined index. Calling `flush` converts these to real entities, which can have a fully
// defined location.
pending: AtomicU32,
// Unused entity IDs below `meta.len()`
free: Vec<u32>,
free_cursor: AtomicU32,
// Reserved IDs within `meta.len()` with implicit archetype 0 and undefined index. Should be
// consumed and used to initialize locations to produce real entities after calling `flush`.
reserved: Box<[AtomicU32]>,
reserved_cursor: AtomicU32,
}
impl Entities {
/// Reserve an entity ID concurrently
///
/// Storage for entity generation and location is lazily allocated by calling `flush`. Locations
/// can be determined by the return value of `flush` and by iterating through the `reserved`
/// accessors, and should all be written immediately after flushing.
pub fn reserve_entity(&self) -> Entity {
loop {
let index = self.free_cursor.load(Ordering::Relaxed);
match index.checked_sub(1) {
// The freelist is empty, so increment `pending` to arrange for a new entity with a
// predictable ID to be allocated on the next `flush` call
None => {
let n = self.pending.fetch_add(1, Ordering::Relaxed);
return Entity {
generation: 0,
id: u32::try_from(self.meta.len())
.ok()
.and_then(|x| x.checked_add(n))
.expect("too many entities"),
};
}
// The freelist has entities in it, so move the last entry to the reserved list, to
// be consumed by the caller as part of a higher-level flush.
Some(next) => {
// We don't care about memory ordering here so long as we get our slot.
if self
.free_cursor
.compare_exchange_weak(index, next, Ordering::Relaxed, Ordering::Relaxed)
.is_err()
{
// Another thread already consumed this slot, start over.
continue;
}
let id = self.free[next as usize];
let reservation = self.reserved_cursor.fetch_add(1, Ordering::Relaxed);
self.reserved[reservation as usize].store(id, Ordering::Relaxed);
return Entity {
generation: self.meta[id as usize].generation,
id,
};
}
}
}
}
/// Allocate an entity ID directly
///
/// Location should be written immediately.
pub fn alloc(&mut self) -> Entity {
debug_assert_eq!(
self.pending.load(Ordering::Relaxed),
0,
"allocator must be flushed before potentially growing"
);
let index = self.free_cursor.load(Ordering::Relaxed);
match index.checked_sub(1) {
None => {
self.grow(0);
let cursor = self.free_cursor.fetch_sub(1, Ordering::Relaxed);
let id = self.free[(cursor - 1) as usize];
Entity {
generation: self.meta[id as usize].generation,
id,
}
}
Some(next) => {
// Not racey due to &mut self
self.free_cursor.store(next, Ordering::Relaxed);
let id = self.free[next as usize];
Entity {
generation: self.meta[id as usize].generation,
id,
}
}
}
}
/// Destroy an entity, allowing it to be reused
///
/// Must not be called on reserved entities prior to `flush`.
pub fn free(&mut self, entity: Entity) -> Result<Location, NoSuchEntity> {
let meta = &mut self.meta[entity.id as usize];
if meta.generation != entity.generation {
return Err(NoSuchEntity);
}
meta.generation += 1;
let loc = mem::replace(
&mut meta.location,
Location {
archetype: 0,
// Guard against bugs in reservation handling
index: usize::max_value(),
},
);
let index = self.free_cursor.fetch_add(1, Ordering::Relaxed); // Not racey due to &mut self
self.free[index as usize] = entity.id;
debug_assert!(
loc.index != usize::max_value(),
"free called on reserved entity without flush"
);
Ok(loc)
}
/// Ensure `n` at least allocations can succeed without reallocating
pub fn reserve(&mut self, additional: u32) {
debug_assert_eq!(
self.pending.load(Ordering::Relaxed),
0,
"allocator must be flushed before potentially growing"
);
let free = self.free_cursor.load(Ordering::Relaxed);
if additional > free {
self.grow(additional - free);
}
}
pub fn contains(&self, entity: Entity) -> bool {
if entity.id >= self.meta.len() as u32 {
return true;
}
self.meta[entity.id as usize].generation == entity.generation
}
pub fn clear(&mut self) {
// Not racey due to &mut self
self.free_cursor
.store(self.meta.len() as u32, Ordering::Relaxed);
for (i, x) in self.free.iter_mut().enumerate() {
*x = i as u32;
}
self.pending.store(0, Ordering::Relaxed);
self.reserved_cursor.store(0, Ordering::Relaxed);
}
/// Access the location storage of an entity
///
/// Must not be called on pending entities.
pub fn get_mut(&mut self, entity: Entity) -> Result<&mut Location, NoSuchEntity> {
let meta = &mut self.meta[entity.id as usize];
if meta.generation == entity.generation {
Ok(&mut meta.location)
} else {
Err(NoSuchEntity)
}
}
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities
pub fn get(&self, entity: Entity) -> Result<Location, NoSuchEntity> {
if self.meta.len() <= entity.id as usize {
return Ok(Location {
archetype: 0,
index: usize::max_value(),
});
}
let meta = &self.meta[entity.id as usize];
if meta.generation != entity.generation {
return Err(NoSuchEntity);
}
if meta.location.archetype == 0 {
return Ok(Location {
archetype: 0,
index: usize::max_value(),
});
}
Ok(meta.location)
}
/// Allocate space for and enumerate pending entities
#[allow(clippy::reversed_empty_ranges)]
pub fn flush(&mut self) -> impl Iterator<Item = u32> {
let pending = self.pending.load(Ordering::Relaxed); // Not racey due to &mut self
if pending != 0 {
let first = self.meta.len() as u32;
self.grow(0);
first..(first + pending)
} else {
0..0
}
}
// The following three methods allow iteration over `reserved` simultaneous to location
// writes. This is a lazy hack, but we only use it in `World::flush` so the complexity and unsafety
// involved in producing an `impl Iterator<Item=(u32, &mut Location)>` isn't a clear win.
pub fn reserved_len(&self) -> u32 {
self.reserved_cursor.load(Ordering::Relaxed)
}
pub fn reserved(&self, i: u32) -> u32 {
debug_assert!(i < self.reserved_len());
self.reserved[i as usize].load(Ordering::Relaxed)
}
pub fn clear_reserved(&mut self) {
self.reserved_cursor.store(0, Ordering::Relaxed);
}
/// Expand storage and mark all but the first `pending` of the new slots as free
fn grow(&mut self, increment: u32) {
let pending = self.pending.swap(0, Ordering::Relaxed);
let new_len = (self.meta.len() + pending as usize + increment as usize)
.max(self.meta.len() * 2)
.max(1024);
let mut new_meta = Vec::with_capacity(new_len);
new_meta.extend_from_slice(&self.meta);
new_meta.resize(
new_len,
EntityMeta {
generation: 0,
location: Location {
archetype: 0,
index: usize::max_value(), // dummy value, to be filled in
},
},
);
let free_cursor = self.free_cursor.load(Ordering::Relaxed); // Not racey due to &mut self
let mut new_free = Vec::with_capacity(new_len);
new_free.extend_from_slice(&self.free[0..free_cursor as usize]);
// Add freshly allocated trailing free slots
new_free.extend(((self.meta.len() as u32 + pending)..new_len as u32).rev());
debug_assert!(new_free.len() <= new_len);
self.free_cursor
.store(new_free.len() as u32, Ordering::Relaxed); // Not racey due to &mut self
// Zero-fill
new_free.resize(new_len, 0);
self.meta = new_meta;
self.free = new_free;
let mut new_reserved = Vec::with_capacity(new_len);
// Not racey due to &mut self
let reserved_cursor = self.reserved_cursor.load(Ordering::Relaxed);
for x in &self.reserved[..reserved_cursor as usize] {
new_reserved.push(AtomicU32::new(x.load(Ordering::Relaxed)));
}
new_reserved.resize_with(new_len, || AtomicU32::new(0));
self.reserved = new_reserved.into();
}
pub fn get_reserver(&self) -> EntityReserver {
// SAFE: reservers use atomics for anything write-related
let entities: &'static Entities = unsafe { mem::transmute(self) };
EntityReserver { entities }
}
}
/// Reserves entities in a way that is usable in multi-threaded contexts.
pub struct EntityReserver {
entities: &'static Entities,
}
impl EntityReserver {
/// Reserves an entity
pub fn reserve_entity(&self) -> Entity {
self.entities.reserve_entity()
}
}
#[derive(Copy, Clone)]
pub(crate) struct EntityMeta {
pub generation: u32,
pub location: Location,
}
/// A location of an entity in an archetype
#[derive(Copy, Clone)]
pub struct Location {
/// The archetype index
pub archetype: u32,
/// The index of the entity in the archetype
pub index: usize,
}
/// Error indicating that no entity with a particular ID exists
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct NoSuchEntity;
impl fmt::Display for NoSuchEntity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("no such entity")
}
}
#[cfg(feature = "std")]
impl Error for NoSuchEntity {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entity_bits_roundtrip() {
let e = Entity {
generation: 0xDEADBEEF,
id: 0xBAADF00D,
};
assert_eq!(Entity::from_bits(e.to_bits()), e);
}
}