use super::storage::{ComponentStorage, ErasedStorage, StorageError};
use super::{Component, Entity};
use crate::runtime::{ScheduleError, Scheduler};
use core::any::TypeId;
use core::fmt;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WorldError {
EntityNotAlive,
EntityIndexExhausted,
AllocationFailed,
}
impl fmt::Display for WorldError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::EntityNotAlive => f.write_str("entity is not alive in this world"),
Self::EntityIndexExhausted => f.write_str("entity index space is exhausted"),
Self::AllocationFailed => f.write_str("failed to reserve component storage"),
}
}
}
impl std::error::Error for WorldError {}
#[derive(Debug)]
struct EntitySlot {
generation: u32,
alive: bool,
retired: bool,
}
#[derive(Default)]
pub struct World {
slots: Vec<EntitySlot>,
free_indices: Vec<u32>,
alive_count: usize,
storages: HashMap<TypeId, Box<dyn ErasedStorage>>,
}
impl fmt::Debug for World {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("World")
.field("slots", &self.slots)
.field("free_indices", &self.free_indices)
.field("alive_count", &self.alive_count)
.field("storage_count", &self.storages.len())
.finish()
}
}
impl World {
#[must_use]
pub fn new() -> Self {
Self {
slots: Vec::new(),
free_indices: Vec::new(),
alive_count: 0,
storages: HashMap::new(),
}
}
pub fn spawn(&mut self) -> Result<Entity, WorldError> {
if let Some(index) = self.free_indices.pop() {
let slot = &mut self.slots[index as usize];
debug_assert!(!slot.alive && !slot.retired);
slot.alive = true;
self.alive_count += 1;
return Ok(Entity::from_parts(index, slot.generation));
}
let index =
u32::try_from(self.slots.len()).map_err(|_| WorldError::EntityIndexExhausted)?;
self.slots.push(EntitySlot {
generation: 0,
alive: true,
retired: false,
});
self.alive_count += 1;
Ok(Entity::from_parts(index, 0))
}
pub fn despawn(&mut self, entity: Entity) -> Result<(), WorldError> {
let Some(slot) = self.slots.get_mut(entity.index() as usize) else {
return Err(WorldError::EntityNotAlive);
};
if !slot.alive || slot.generation != entity.generation() {
return Err(WorldError::EntityNotAlive);
}
for storage in self.storages.values_mut() {
storage.remove_entity(entity);
}
slot.alive = false;
self.alive_count -= 1;
if let Some(next_generation) = slot.generation.checked_add(1) {
slot.generation = next_generation;
self.free_indices.push(entity.index());
} else {
slot.retired = true;
}
Ok(())
}
pub fn insert<T: Component>(
&mut self,
entity: Entity,
component: T,
) -> Result<Option<T>, WorldError> {
if !self.contains(entity) {
return Err(WorldError::EntityNotAlive);
}
let storage = self
.storages
.entry(TypeId::of::<T>())
.or_insert_with(|| Box::new(ComponentStorage::<T>::new()));
let typed = storage
.as_any_mut()
.downcast_mut::<ComponentStorage<T>>()
.expect("TypeId and component storage type must agree");
typed
.insert(entity, component)
.map_err(|error| match error {
StorageError::AllocationFailed => WorldError::AllocationFailed,
})
}
#[must_use]
pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> {
if !self.contains(entity) {
return None;
}
self.storages
.get(&TypeId::of::<T>())?
.as_ref()
.as_any()
.downcast_ref::<ComponentStorage<T>>()?
.get(entity)
}
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<&mut T> {
if !self.contains(entity) {
return None;
}
self.storages
.get_mut(&TypeId::of::<T>())?
.as_any_mut()
.downcast_mut::<ComponentStorage<T>>()?
.get_mut(entity)
}
pub fn remove<T: Component>(&mut self, entity: Entity) -> Option<T> {
if !self.contains(entity) {
return None;
}
self.storages
.get_mut(&TypeId::of::<T>())?
.as_any_mut()
.downcast_mut::<ComponentStorage<T>>()?
.remove(entity)
}
#[must_use]
pub fn has<T: Component>(&self, entity: Entity) -> bool {
self.get::<T>(entity).is_some()
}
#[must_use]
pub fn contains(&self, entity: Entity) -> bool {
self.slots
.get(entity.index() as usize)
.is_some_and(|slot| slot.alive && slot.generation == entity.generation())
}
#[must_use]
pub const fn len(&self) -> usize {
self.alive_count
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.alive_count == 0
}
pub fn run_system<T, E, F>(
&mut self,
scheduler: &Scheduler,
system: F,
) -> Result<Result<T, E>, ScheduleError>
where
F: FnOnce(&mut World) -> Result<T, E>,
{
scheduler.schedule(|| system(self))
}
}
#[cfg(test)]
mod tests {
use super::{World, WorldError};
use crate::runtime::Scheduler;
fn assert_world_consistent(world: &World) {
assert_eq!(
world.alive_count,
world.slots.iter().filter(|slot| slot.alive).count()
);
let mut seen = std::collections::HashSet::new();
for index in &world.free_indices {
assert!(seen.insert(*index), "duplicate free index: {index}");
let slot = &world.slots[*index as usize];
assert!(!slot.alive);
assert!(!slot.retired);
}
for (index, slot) in world.slots.iter().enumerate() {
if slot.alive || slot.retired {
assert!(!seen.contains(&(index as u32)));
} else {
assert!(seen.contains(&(index as u32)));
}
}
}
#[test]
fn spawn_and_despawn_track_liveness_and_counts() {
let mut world = World::new();
assert!(world.is_empty());
let entity = world.spawn().unwrap();
assert!(world.contains(entity));
assert_eq!(world.len(), 1);
assert!(!world.is_empty());
world.despawn(entity).unwrap();
assert!(!world.contains(entity));
assert!(world.is_empty());
assert_eq!(world.despawn(entity), Err(WorldError::EntityNotAlive));
}
#[test]
fn reused_indices_advance_generation_and_reject_stale_handles() {
let mut world = World::new();
let stale = world.spawn().unwrap();
world.despawn(stale).unwrap();
let current = world.spawn().unwrap();
assert_eq!(current.index(), stale.index());
assert_eq!(current.generation(), stale.generation() + 1);
assert!(!world.contains(stale));
assert!(world.contains(current));
assert_eq!(world.despawn(stale), Err(WorldError::EntityNotAlive));
}
#[test]
fn generation_overflow_retires_slot_instead_of_wrapping() {
let mut world = World::new();
let entity = world.spawn().unwrap();
let slot = &mut world.slots[entity.index() as usize];
slot.generation = u32::MAX;
let max_generation = super::Entity::from_parts(entity.index(), u32::MAX);
world.despawn(max_generation).unwrap();
let next = world.spawn().unwrap();
assert_ne!(next.index(), max_generation.index());
assert!(!world.contains(max_generation));
}
#[test]
fn components_insert_replace_mutate_remove_and_cleanup_on_despawn() {
let mut world = World::new();
let entity = world.spawn().unwrap();
assert_eq!(world.insert(entity, 10_u32), Ok(None));
assert_eq!(world.get::<u32>(entity), Some(&10));
assert!(world.has::<u32>(entity));
assert_eq!(world.insert(entity, 20_u32), Ok(Some(10)));
*world.get_mut::<u32>(entity).unwrap() += 2;
assert_eq!(world.get::<u32>(entity), Some(&22));
assert_eq!(world.remove::<u32>(entity), Some(22));
assert!(!world.has::<u32>(entity));
world.insert(entity, String::from("cleanup")).unwrap();
world.despawn(entity).unwrap();
assert_eq!(world.get::<String>(entity), None);
}
#[test]
fn component_access_rejects_dead_and_stale_entities() {
let mut world = World::new();
let stale = world.spawn().unwrap();
world.insert(stale, 7_u32).unwrap();
world.despawn(stale).unwrap();
assert_eq!(world.get::<u32>(stale), None);
assert_eq!(world.insert(stale, 9_u32), Err(WorldError::EntityNotAlive));
let current = world.spawn().unwrap();
assert_eq!(current.index(), stale.index());
assert_eq!(world.get::<u32>(current), None);
assert!(!world.has::<u32>(current));
}
#[test]
fn runtime_system_can_mutate_world_synchronously() {
let scheduler = Scheduler::new();
let mut world = World::new();
let result = world
.run_system(&scheduler, |world| {
let entity = world.spawn().map_err(|_| "spawn failed")?;
Ok::<_, &'static str>(entity)
})
.unwrap();
let entity = result.unwrap();
assert!(world.contains(entity));
assert_eq!(world.len(), 1);
}
#[test]
fn runtime_system_error_is_preserved_separately() {
let scheduler = Scheduler::new();
let mut world = World::new();
let result = world.run_system(&scheduler, |_| Err::<(), _>("system failed"));
assert_eq!(result, Ok(Err("system failed")));
assert!(world.is_empty());
}
#[test]
fn entity_slot_and_free_list_invariants_hold_across_repeated_reuse() {
let mut world = World::new();
let mut live = Vec::new();
assert_world_consistent(&world);
for step in 0..512_u32 {
let entity = world.spawn().unwrap();
assert!(world.contains(entity));
live.push(entity);
assert_world_consistent(&world);
if step % 3 == 2 {
let entity = live.remove(0);
world.despawn(entity).unwrap();
assert!(!world.contains(entity));
assert_world_consistent(&world);
}
}
while let Some(entity) = live.pop() {
world.despawn(entity).unwrap();
assert_world_consistent(&world);
}
assert!(world.is_empty());
assert_eq!(world.len(), 0);
}
}