361 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			361 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use parking_lot::{Mutex, RwLock};
 | 
						|
use std::fmt::Display;
 | 
						|
use std::num::Wrapping;
 | 
						|
use std::sync::Arc;
 | 
						|
 | 
						|
pub(crate) type EntityIndex = u32;
 | 
						|
pub(crate) type EntityVersion = Wrapping<u32>;
 | 
						|
 | 
						|
/// A handle to an entity.
 | 
						|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 | 
						|
pub struct Entity {
 | 
						|
    index: EntityIndex,
 | 
						|
    version: EntityVersion,
 | 
						|
}
 | 
						|
 | 
						|
impl Entity {
 | 
						|
    pub(crate) fn new(index: EntityIndex, version: EntityVersion) -> Entity {
 | 
						|
        Entity { index, version }
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn index(self) -> EntityIndex { self.index }
 | 
						|
}
 | 
						|
 | 
						|
impl Display for Entity {
 | 
						|
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
 | 
						|
        write!(f, "{}#{}", self.index, self.version)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 | 
						|
pub(crate) struct EntityLocation {
 | 
						|
    archetype_index: usize,
 | 
						|
    set_index: usize,
 | 
						|
    chunk_index: usize,
 | 
						|
    component_index: usize,
 | 
						|
}
 | 
						|
 | 
						|
impl EntityLocation {
 | 
						|
    pub(crate) fn new(
 | 
						|
        archetype_index: usize,
 | 
						|
        set_index: usize,
 | 
						|
        chunk_index: usize,
 | 
						|
        component_index: usize,
 | 
						|
    ) -> Self {
 | 
						|
        EntityLocation {
 | 
						|
            archetype_index,
 | 
						|
            set_index,
 | 
						|
            chunk_index,
 | 
						|
            component_index,
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn archetype(&self) -> usize { self.archetype_index }
 | 
						|
 | 
						|
    pub(crate) fn set(&self) -> usize { self.set_index }
 | 
						|
 | 
						|
    pub(crate) fn chunk(&self) -> usize { self.chunk_index }
 | 
						|
 | 
						|
    pub(crate) fn component(&self) -> usize { self.component_index }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Debug)]
 | 
						|
pub(crate) struct BlockAllocator {
 | 
						|
    allocated: usize,
 | 
						|
    free: Vec<EntityBlock>,
 | 
						|
}
 | 
						|
 | 
						|
impl BlockAllocator {
 | 
						|
    const BLOCK_SIZE: usize = 1024;
 | 
						|
 | 
						|
    pub(crate) fn new() -> Self {
 | 
						|
        BlockAllocator {
 | 
						|
            allocated: 0,
 | 
						|
            free: Vec::new(),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn allocate(&mut self) -> EntityBlock {
 | 
						|
        if let Some(block) = self.free.pop() {
 | 
						|
            block
 | 
						|
        } else {
 | 
						|
            let block = EntityBlock::new(self.allocated as EntityIndex, BlockAllocator::BLOCK_SIZE);
 | 
						|
            self.allocated += BlockAllocator::BLOCK_SIZE;
 | 
						|
            block
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn free(&mut self, block: EntityBlock) { self.free.push(block); }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Debug)]
 | 
						|
pub(crate) struct EntityBlock {
 | 
						|
    start: EntityIndex,
 | 
						|
    len: usize,
 | 
						|
    versions: Vec<EntityVersion>,
 | 
						|
    free: Vec<EntityIndex>,
 | 
						|
    locations: Vec<EntityLocation>,
 | 
						|
}
 | 
						|
 | 
						|
impl EntityBlock {
 | 
						|
    pub fn new(start: EntityIndex, len: usize) -> EntityBlock {
 | 
						|
        EntityBlock {
 | 
						|
            start,
 | 
						|
            len,
 | 
						|
            versions: Vec::with_capacity(len),
 | 
						|
            free: Vec::new(),
 | 
						|
            locations: std::iter::repeat(EntityLocation::new(0, 0, 0, 0))
 | 
						|
                .take(len)
 | 
						|
                .collect(),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    fn index(&self, index: EntityIndex) -> usize { (index - self.start) as usize }
 | 
						|
 | 
						|
    pub fn in_range(&self, index: EntityIndex) -> bool {
 | 
						|
        index >= self.start && index < (self.start + self.len as u32)
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn is_alive(&self, entity: Entity) -> Option<bool> {
 | 
						|
        if entity.index >= self.start {
 | 
						|
            let i = self.index(entity.index);
 | 
						|
            self.versions.get(i).map(|v| *v == entity.version)
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn allocate(&mut self) -> Option<Entity> {
 | 
						|
        if let Some(index) = self.free.pop() {
 | 
						|
            let i = self.index(index);
 | 
						|
            Some(Entity::new(index, self.versions[i]))
 | 
						|
        } else if self.versions.len() < self.len {
 | 
						|
            let index = self.start + self.versions.len() as EntityIndex;
 | 
						|
            self.versions.push(Wrapping(1));
 | 
						|
            Some(Entity::new(index, Wrapping(1)))
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> {
 | 
						|
        if let Some(true) = self.is_alive(entity) {
 | 
						|
            let i = self.index(entity.index);
 | 
						|
            self.versions[i] += Wrapping(1);
 | 
						|
            self.free.push(entity.index);
 | 
						|
            self.get_location(entity.index)
 | 
						|
        } else {
 | 
						|
            None
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn set_location(&mut self, entity: EntityIndex, location: EntityLocation) {
 | 
						|
        assert!(entity >= self.start);
 | 
						|
        let index = (entity - self.start) as usize;
 | 
						|
        *self.locations.get_mut(index).unwrap() = location;
 | 
						|
    }
 | 
						|
 | 
						|
    pub fn get_location(&self, entity: EntityIndex) -> Option<EntityLocation> {
 | 
						|
        if entity < self.start {
 | 
						|
            return None;
 | 
						|
        }
 | 
						|
 | 
						|
        let index = (entity - self.start) as usize;
 | 
						|
        self.locations.get(index).copied()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/// Manages the allocation and deletion of `Entity` IDs within a world.
 | 
						|
#[derive(Debug, Clone)]
 | 
						|
pub struct EntityAllocator {
 | 
						|
    allocator: Arc<Mutex<BlockAllocator>>,
 | 
						|
    blocks: Arc<RwLock<Vec<EntityBlock>>>,
 | 
						|
}
 | 
						|
 | 
						|
impl EntityAllocator {
 | 
						|
    pub(crate) fn new(allocator: Arc<Mutex<BlockAllocator>>) -> Self {
 | 
						|
        EntityAllocator {
 | 
						|
            allocator,
 | 
						|
            blocks: Arc::new(RwLock::new(Vec::new())),
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /// Determines if the given `Entity` is considered alive.
 | 
						|
    pub fn is_alive(&self, entity: Entity) -> bool {
 | 
						|
        self.blocks
 | 
						|
            .read()
 | 
						|
            .iter()
 | 
						|
            .filter_map(|b| b.is_alive(entity))
 | 
						|
            .nth(0)
 | 
						|
            .unwrap_or(false)
 | 
						|
    }
 | 
						|
 | 
						|
    /// Allocates a new unused `Entity` ID.
 | 
						|
    pub fn create_entity(&self) -> Entity {
 | 
						|
        let mut blocks = self.blocks.write();
 | 
						|
 | 
						|
        if let Some(entity) = blocks.iter_mut().rev().filter_map(|b| b.allocate()).nth(0) {
 | 
						|
            entity
 | 
						|
        } else {
 | 
						|
            let mut block = self.allocator.lock().allocate();
 | 
						|
            let entity = block.allocate().unwrap();
 | 
						|
            blocks.push(block);
 | 
						|
            entity
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn delete_entity(&self, entity: Entity) -> Option<EntityLocation> {
 | 
						|
        self.blocks.write().iter_mut().find_map(|b| b.free(entity))
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn set_location(&self, entity: EntityIndex, location: EntityLocation) {
 | 
						|
        self.blocks
 | 
						|
            .write()
 | 
						|
            .iter_mut()
 | 
						|
            .rev()
 | 
						|
            .find(|b| b.in_range(entity))
 | 
						|
            .unwrap()
 | 
						|
            .set_location(entity, location);
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn get_location(&self, entity: EntityIndex) -> Option<EntityLocation> {
 | 
						|
        self.blocks
 | 
						|
            .read()
 | 
						|
            .iter()
 | 
						|
            .find(|b| b.in_range(entity))
 | 
						|
            .and_then(|b| b.get_location(entity))
 | 
						|
    }
 | 
						|
 | 
						|
    pub(crate) fn merge(&self, other: EntityAllocator) {
 | 
						|
        assert!(Arc::ptr_eq(&self.allocator, &other.allocator));
 | 
						|
        self.blocks.write().append(&mut other.blocks.write());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl Drop for EntityAllocator {
 | 
						|
    fn drop(&mut self) {
 | 
						|
        for block in self.blocks.write().drain(..) {
 | 
						|
            self.allocator.lock().free(block);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[cfg(test)]
 | 
						|
mod tests {
 | 
						|
    use crate::entity::*;
 | 
						|
    use std::collections::HashSet;
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn create_entity() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        allocator.create_entity();
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn create_entity_many() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
 | 
						|
        for _ in 0..512 {
 | 
						|
            allocator.create_entity();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn create_entity_many_blocks() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
 | 
						|
        for _ in 0..3000 {
 | 
						|
            allocator.create_entity();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn create_entity_recreate() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
 | 
						|
        for _ in 0..3 {
 | 
						|
            let entities: Vec<Entity> = (0..512).map(|_| allocator.create_entity()).collect();
 | 
						|
            for e in entities {
 | 
						|
                allocator.delete_entity(e);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn is_alive_allocated() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = allocator.create_entity();
 | 
						|
 | 
						|
        assert_eq!(true, allocator.is_alive(entity));
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn is_alive_unallocated() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = Entity::new(10 as EntityIndex, Wrapping(10));
 | 
						|
 | 
						|
        assert_eq!(false, allocator.is_alive(entity));
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn is_alive_killed() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = allocator.create_entity();
 | 
						|
        allocator.delete_entity(entity);
 | 
						|
 | 
						|
        assert_eq!(false, allocator.is_alive(entity));
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn delete_entity_was_alive() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = allocator.create_entity();
 | 
						|
 | 
						|
        assert_eq!(true, allocator.delete_entity(entity).is_some());
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn delete_entity_was_dead() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = allocator.create_entity();
 | 
						|
        allocator.delete_entity(entity);
 | 
						|
 | 
						|
        assert_eq!(None, allocator.delete_entity(entity));
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn delete_entity_was_unallocated() {
 | 
						|
        let allocator = EntityAllocator::new(Arc::from(Mutex::new(BlockAllocator::new())));
 | 
						|
        let entity = Entity::new(10 as EntityIndex, Wrapping(10));
 | 
						|
 | 
						|
        assert_eq!(None, allocator.delete_entity(entity));
 | 
						|
    }
 | 
						|
 | 
						|
    #[test]
 | 
						|
    fn multiple_allocators_unique_ids() {
 | 
						|
        let blocks = Arc::from(Mutex::new(BlockAllocator::new()));
 | 
						|
        let allocator_a = EntityAllocator::new(blocks.clone());
 | 
						|
        let allocator_b = EntityAllocator::new(blocks.clone());
 | 
						|
 | 
						|
        let mut entities_a = HashSet::<Entity>::default();
 | 
						|
        let mut entities_b = HashSet::<Entity>::default();
 | 
						|
 | 
						|
        for _ in 0..5 {
 | 
						|
            entities_a.extend((0..1500).map(|_| allocator_a.create_entity()));
 | 
						|
            entities_b.extend((0..1500).map(|_| allocator_b.create_entity()));
 | 
						|
        }
 | 
						|
 | 
						|
        assert_eq!(true, entities_a.is_disjoint(&entities_b));
 | 
						|
 | 
						|
        for e in entities_a {
 | 
						|
            assert_eq!(true, allocator_a.is_alive(e));
 | 
						|
            assert_eq!(false, allocator_b.is_alive(e));
 | 
						|
        }
 | 
						|
 | 
						|
        for e in entities_b {
 | 
						|
            assert_eq!(false, allocator_a.is_alive(e));
 | 
						|
            assert_eq!(true, allocator_b.is_alive(e));
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |