Last active
September 5, 2015 15:09
-
-
Save amaranth/6b85616352272d9c397e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TODO: Add systems, figure out parallelization | |
extern crate fixedbitset; | |
use self::fixedbitset::FixedBitSet; | |
use std::collections::HashMap; | |
use std::{mem, ptr}; | |
pub const MAX_COMPONENTS: usize = 256; | |
const CHUNK_SIZE: usize = 8192; | |
// Methods of this are generic on T instead of the struct itself so we can | |
// store them in a Vec<Pool> without trying to box a trait object and later | |
// downcast | |
pub struct Pool { | |
element_size: usize, | |
elements_per_chunk: usize, | |
chunks: Vec<Box<[u8; CHUNK_SIZE]>>, | |
} | |
impl Pool { | |
pub fn new<T>() -> Pool { | |
Pool { | |
element_size: mem::size_of::<T>(), | |
elements_per_chunk: CHUNK_SIZE / mem::size_of::<T>(), | |
chunks: Vec::new(), | |
} | |
} | |
pub fn with_capacity<T>(capacity: usize) -> Pool { | |
let mut pool = Pool::new::<T>(); | |
pool.reserve(capacity); | |
pool | |
} | |
pub fn capacity(&self) -> usize { | |
self.chunks.len() * self.elements_per_chunk | |
} | |
pub fn reserve(&mut self, additional: usize) { | |
let mut elements = self.chunks.len() * self.elements_per_chunk; | |
let new_elements = elements + additional; | |
while elements < new_elements { | |
self.chunks.push(Box::new([0; CHUNK_SIZE])); | |
elements += self.elements_per_chunk; | |
} | |
} | |
pub unsafe fn get<T>(&self, index: usize) -> &T { | |
assert!(index < self.capacity()); | |
let outer = index / self.elements_per_chunk; | |
let inner = (index % self.elements_per_chunk) * self.element_size; | |
let inner_end = inner + self.element_size; | |
let raw = self.chunks[outer][inner..(inner + inner_end)].as_ptr(); | |
mem::transmute(raw) | |
} | |
pub unsafe fn get_mut<T>(&mut self, index: usize) -> &mut T { | |
assert!(index < self.capacity()); | |
let outer = index / self.elements_per_chunk; | |
let inner = (index % self.elements_per_chunk) * self.element_size; | |
let inner_end = inner + self.element_size; | |
let raw = self.chunks[outer][inner..(inner + inner_end)].as_mut_ptr(); | |
mem::transmute(raw) | |
} | |
pub unsafe fn put<T>(&mut self, index: usize, value: T) { | |
assert!(index < self.capacity()); | |
let outer = index / self.elements_per_chunk; | |
let inner = (index % self.elements_per_chunk) * self.element_size; | |
let inner_end = inner + self.element_size; | |
let raw = self.chunks[outer][inner..(inner + inner_end)].as_mut_ptr(); | |
ptr::copy_nonoverlapping(&value, mem::transmute(raw), 1); | |
} | |
pub unsafe fn clear<T>(&mut self, index: usize) { | |
let dummy: T = unsafe { mem::zeroed() }; | |
self.put(index, dummy); | |
} | |
} | |
#[derive(PartialEq, Eq)] | |
pub struct Entity(u32, u32); | |
impl Entity { | |
fn new(id: u32, version: u32) -> Entity { | |
Entity(id, version) | |
} | |
fn id(&self) -> u32 { | |
self.0 | |
} | |
fn version(&self) -> u32 { | |
self.1 | |
} | |
} | |
pub trait Component : Copy + Default { | |
/// The id for this component type, used for storage. **Must** be a unique | |
/// value and less than MAX_COMPONENTS. | |
fn id() -> u16; | |
} | |
pub struct EntityManager { | |
entity_counter: u32, | |
entity_versions: Vec<u32>, | |
entity_freelist: Vec<u32>, | |
components: Vec<Option<Pool>>, | |
components_mask: Vec<FixedBitSet>, | |
} | |
impl EntityManager { | |
pub fn new() -> EntityManager { | |
EntityManager { | |
entity_counter: 0, | |
entity_versions: Vec::new(), | |
entity_freelist: Vec::new(), | |
components: Vec::new(), | |
components_mask: Vec::new(), | |
} | |
} | |
pub fn register_component<T: Component>(&mut self) -> Result<(), &str> { | |
let index = T::id() as usize; | |
if index >= MAX_COMPONENTS { | |
Err("tried to register component with id larger than MAX_COMPONENTS") | |
} else if self.components[index].is_some() { | |
Err("tried to register component that is already registered") | |
} else { | |
self.components[index] = Some(Pool::with_capacity::<Data<T>>(self.entity_counter as usize)); | |
Ok(()) | |
} | |
} | |
pub fn unregister_component<T: Component>(&mut self) { | |
let index = T::id() as usize; | |
if index < MAX_COMPONENTS { | |
self.components[index] = None; | |
} | |
} | |
pub fn create(&mut self) -> Entity { | |
if self.entity_freelist.is_empty() { | |
let index = self.entity_counter as usize; | |
self.entity_versions[index] = 0; | |
for entry in self.components.iter_mut() { | |
if let Some(ref mut pool) = *entry { | |
pool.reserve(1); | |
} | |
} | |
self.components_mask[index] = FixedBitSet::with_capacity(MAX_COMPONENTS); | |
let entity = Entity::new(self.entity_counter, 0); | |
self.entity_counter += 1; | |
entity | |
} else { | |
let id = self.entity_freelist.pop().unwrap(); | |
Entity::new(id, self.entity_versions[id as usize]) | |
} | |
} | |
pub fn destroy(&mut self, entity: Entity) -> Result<(), &str> { | |
// These checks are from validate_entity but the damn borrow checker | |
if entity.id() as usize >= self.entity_versions.len() { | |
Err("entity outside known range") | |
} else if entity.version() != self.entity_versions[entity.id() as usize] { | |
Err("tried to access destroyed entity") | |
} else { | |
let index = entity.id() as usize; | |
self.entity_versions[index] += 1; | |
self.entity_freelist.push(entity.id()); | |
self.components_mask[index].clear(); | |
Ok(()) | |
} | |
} | |
pub fn add_component<T: Component>(&mut self, entity: &Entity) -> Result<&mut Data<T>, &str> { | |
// These checks are from validate_entity but the damn borrow checker | |
if entity.id() as usize >= self.entity_versions.len() { | |
Err("entity outside known range") | |
} else if entity.version() != self.entity_versions[entity.id() as usize] { | |
Err("tried to access destroyed entity") | |
} else { | |
let entity_index = entity.id() as usize; | |
let component_index = T::id() as usize; | |
if self.components_mask[entity_index].contains(component_index) { | |
Err("tried to add component to entity twice") | |
} else { | |
if let Some(ref mut pool) = self.components[component_index] { | |
let data = Data::<T>::new(); | |
self.components_mask[entity_index].set(component_index, true); | |
unsafe { | |
pool.put::<Data<T>>(entity_index, data); | |
Ok(pool.get_mut::<Data<T>>(entity_index)) | |
} | |
} else { | |
Err("tried to add component to entity that is not registered") | |
} | |
} | |
} | |
} | |
pub fn remove_component<T: Component>(&mut self, entity: &Entity) -> Result<(), &str> { | |
// These checks are from validate_entity but the damn borrow checker | |
if entity.id() as usize >= self.entity_versions.len() { | |
Err("entity outside known range") | |
} else if entity.version() != self.entity_versions[entity.id() as usize] { | |
Err("tried to access destroyed entity") | |
} else { | |
let entity_index = entity.id() as usize; | |
let component_index = T::id() as usize; | |
if self.components[component_index].is_none() { | |
Err("tried to remove component from entity that is not registered") | |
} else { | |
self.components_mask[entity_index].set(component_index, false); | |
Ok(()) | |
} | |
} | |
} | |
pub fn get_component<T: Component>(&self, entity: &Entity) -> Result<Option<&Data<T>>, &str> { | |
try!(self.validate_entity(entity)); | |
let entity_index = entity.id() as usize; | |
let component_index = T::id() as usize; | |
if let Some(ref pool) = self.components[component_index] { | |
if !self.components_mask[entity_index].contains(component_index) { | |
Ok(None) | |
} else { | |
unsafe { | |
Ok(Some(pool.get::<Data<T>>(entity_index))) | |
} | |
} | |
} else { | |
Err("tried to add component to entity that is not registered") | |
} | |
} | |
pub fn get_mut_component<T: Component>(&mut self, entity: &Entity) -> Result<Option<&mut Data<T>>, &str> { | |
// These checks are from validate_entity but the damn borrow checker | |
if entity.id() as usize >= self.entity_versions.len() { | |
Err("entity outside known range") | |
} else if entity.version() != self.entity_versions[entity.id() as usize] { | |
Err("tried to access destroyed entity") | |
} else { | |
let entity_index = entity.id() as usize; | |
let component_index = T::id() as usize; | |
if let Some(ref mut pool) = self.components[component_index] { | |
if !self.components_mask[entity_index].contains(component_index) { | |
Ok(None) | |
} else { | |
unsafe { | |
Ok(Some(pool.get_mut::<Data<T>>(entity_index))) | |
} | |
} | |
} else { | |
Err("tried to add component to entity that is not registered") | |
} | |
} | |
} | |
fn validate_entity(&self, entity: &Entity) -> Result<(), &str> { | |
if entity.id() as usize >= self.entity_versions.len() { | |
Err("entity outside known range") | |
} else if entity.version() != self.entity_versions[entity.id() as usize] { | |
Err("attempted to access destroyed entity") | |
} else { | |
Ok(()) | |
} | |
} | |
} | |
pub struct Data<T: Component> { | |
versions: [T; 3], | |
index: usize, | |
} | |
impl <T: Component> Data<T> { | |
pub fn new() -> Data<T> { | |
Data { | |
versions: [Default::default(); 3], | |
index: 0, | |
} | |
} | |
pub fn previous(&self) -> &T { | |
&self.versions[self.index % 3] | |
} | |
pub fn current(&self) -> &T { | |
&self.versions[(self.index + 1) % 3] | |
} | |
// TODO: Maybe this should be multiple things, use system priority for merging them | |
pub fn mutable(&mut self) -> &mut T { | |
&mut self.versions[(self.index + 2) % 3] | |
} | |
fn swap(&mut self) { | |
// After updating the index previous will be mutable, current will be | |
// previous, and mutable will be current. We want mutable to start | |
// with the contents of current so we copy from "mutable" to | |
// "previous" before updating the index. | |
self.versions[self.index % 3] = self.versions[(self.index + 2) % 3]; | |
self.index += 1; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let mut manager = EntityManager::new(); | |
manager.register_component::<TestComponent>(); | |
let entity = manager.create(); | |
{ | |
let mut data = manager.add_component::<TestComponent>(&entity); | |
} | |
manager.remove_component::<TestComponent>(&entity); | |
manager.unregister_component::<TestComponent>(); | |
manager.destroy_entity(entity); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment