Last active
September 16, 2018 15:38
-
-
Save techgeek1/5bbb66e8c5fe67b7380925c401bd2b47 to your computer and use it in GitHub Desktop.
Attempt two at an ecs api. This time encoding as much data in the type system as possible via associated types
This file contains hidden or 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
#![feature(const_fn, const_type_id)] | |
#[macro_use] | |
mod macros { | |
macro_rules! foreach_permutation { | |
($action:ident!(), $type:ident) => { | |
$action!($type); | |
}; | |
($action:ident!(), $type_lead:ident, $($type:ident),*) => { | |
$action!($type_lead, $($type),*); | |
foreach_permutation!($action!(), $($type),*); | |
}; | |
} | |
} | |
mod slice { | |
use std::slice; | |
pub fn empty<'a, T: 'a>() -> &'a [T] { | |
unsafe { slice::from_raw_parts(0x1 as *const T, 0) } | |
} | |
pub fn empty_mut<'a, T: 'a>() -> &'a mut [T] { | |
unsafe { slice::from_raw_parts_mut(0x1 as *mut T, 0) } | |
} | |
} | |
mod component { | |
use slice; | |
use id::ComponentId; | |
pub trait Component : Sized + 'static { | |
const ID: ComponentId = ComponentId::of::<Self>(); | |
} | |
pub trait ComponentData<'a> : Sized { | |
type IterData; | |
type IterItem; | |
type IterDataMut; | |
type IterItemMut; | |
fn empty() -> Self::IterData; | |
fn empty_mut() -> Self::IterDataMut; | |
} | |
macro_rules! impl_component_data { | |
($($type:ident),*) => { | |
impl<'data, $($type),*> ComponentData<'data> for ($($type),*) | |
where $($type: 'data + Component),* | |
{ | |
type IterData = ($(&'data [$type]),*); | |
type IterItem = ($(&'data $type),*); | |
type IterDataMut = ($(&'data mut [$type]),*); | |
type IterItemMut = ($(&'data mut $type),*); | |
fn empty() -> Self::IterData { | |
($(slice::empty::<$type>()),*) | |
} | |
fn empty_mut() -> Self::IterDataMut { | |
($(slice::empty_mut::<$type>()),*) | |
} | |
} | |
} | |
} | |
foreach_permutation!( | |
impl_component_data!(), | |
A, B, C, D, E, F, G, H, I, J, K, L, M//, | |
//N, O, P, Q, R, S, T, U, V, W, X, Y, Z | |
); | |
pub mod id { | |
use std::any::TypeId; | |
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | |
pub struct ComponentId{ | |
id: TypeId | |
} | |
impl ComponentId { | |
pub const fn of<T: ?Sized + 'static>() -> ComponentId { | |
ComponentId { | |
id: TypeId::of::<T>() | |
} | |
} | |
} | |
} | |
pub mod iter { | |
use component::*; | |
pub struct ReadIter<'data, T: ComponentData<'data>> { | |
data: T::IterData, | |
index: usize | |
} | |
impl<'data, T> ReadIter<'data, T> | |
where T: ComponentData<'data> | |
{ | |
pub fn new(data: T::IterData) -> Self { | |
ReadIter { | |
data: data, | |
index: 0 | |
} | |
} | |
} | |
impl<'data, T> Iterator for ReadIter<'data, T> | |
where T: ComponentData<'data> | |
{ | |
type Item = T::IterItem; | |
fn next(&mut self) -> Option<Self::Item> { | |
unimplemented!() | |
} | |
} | |
pub struct WriteIter<'data, T: ComponentData<'data>> { | |
data: T::IterDataMut, | |
index: usize | |
} | |
impl<'data, T> WriteIter<'data, T> | |
where T: ComponentData<'data> | |
{ | |
pub fn new(data: T::IterDataMut) -> Self { | |
WriteIter { | |
data: data, | |
index: 0 | |
} | |
} | |
} | |
impl<'data, T> Iterator for WriteIter<'data, T> | |
where T: ComponentData<'data> | |
{ | |
type Item = T::IterItemMut; | |
fn next(&mut self) -> Option<Self::Item> { | |
unimplemented!() | |
} | |
} | |
} | |
} | |
mod storage { | |
use component::*; | |
use component::id::ComponentId; | |
use std::collections::HashMap; | |
use std::marker::PhantomData; | |
trait Array { } | |
struct ComponentArray<T> { marker: PhantomData<T> } | |
impl<T> Array for ComponentArray<T> {} | |
// Context for locating components in storage and getting their reference | |
pub struct ComponentStorage { | |
components: HashMap<ComponentId, Box<Array>> | |
} | |
impl ComponentStorage { | |
pub fn new() -> Self { | |
ComponentStorage { | |
components: HashMap::new() | |
} | |
} | |
pub fn add<T: Component>(&mut self) { | |
self.components.insert(T::ID, Box::new(ComponentArray::<T> { marker: PhantomData })); | |
} | |
pub fn find<'a, T: Component>(&self) -> Option<&'a [T]> { | |
if let Some(storage) = self.components.get(&T::ID) { | |
None | |
} | |
else { | |
None | |
} | |
} | |
pub fn find_mut<'a, T: Component>(&self) -> Option<&'a mut [T]> { | |
None | |
} | |
} | |
} | |
mod system { | |
use std::marker::PhantomData; | |
use storage::ComponentStorage; | |
use component::ComponentData; | |
use component::iter::{ReadIter, WriteIter}; | |
pub struct SystemContext<'ctx, TReads, TWrites> { | |
storage: &'ctx ComponentStorage, | |
read_marker: PhantomData<TReads>, | |
write_marker: PhantomData<TWrites> | |
} | |
impl<'ctx, 'a, TReads, TWrites> SystemContext<'ctx, TReads, TWrites> | |
where TReads: ComponentData<'a>, | |
TWrites: ComponentData<'a> | |
{ | |
pub fn new(storage: &'ctx ComponentStorage) -> Self { | |
SystemContext { | |
storage: storage, | |
read_marker: PhantomData, | |
write_marker: PhantomData | |
} | |
} | |
pub fn reads(&self) -> ReadIter<'a, TReads> { | |
// TODO: - Figure out a way to query all the necessary components, return a 0 length iterator on failure | |
// - Construct the iterator | |
// - Verify lifetimes | |
ReadIter::new(TReads::empty()) | |
} | |
pub fn writes(&self) -> WriteIter<'a, TWrites> { | |
// TODO: - Figure out a way to query all the necessary components, return a 0 length iterator on failure | |
// - Construct the iterator | |
// - Verify lifetimes | |
// - Enforce mutability constraints, preferrably at compile time | |
WriteIter::new(TWrites::empty_mut()) | |
} | |
} | |
pub trait System<'a> { | |
type Reads: ComponentData<'a>; | |
type Writes: ComponentData<'a>; | |
fn run<'ctx>(&self, context: &SystemContext<'ctx, Self::Reads, Self::Writes>); | |
fn create_context<'ctx>(&self, storage: &'ctx ComponentStorage) -> SystemContext<'ctx, Self::Reads, Self::Writes> { | |
SystemContext::new(storage) | |
} | |
} | |
} | |
mod test_components { | |
use component::Component; | |
#[derive(Debug)] | |
pub struct Position { | |
pub x: f32, | |
pub y: f32, | |
pub z: f32 | |
} | |
impl Component for Position {} | |
#[derive(Debug)] | |
pub struct Rotation { | |
pub x: f32, | |
pub y: f32, | |
pub z: f32, | |
pub w: f32 | |
} | |
impl Component for Rotation {} | |
#[derive(Debug)] | |
pub struct Velocity { | |
pub x: f32, | |
pub y: f32, | |
pub z: f32 | |
} | |
impl Component for Velocity {} | |
#[derive(Debug)] | |
pub struct AngularVelocity { | |
pub x: f32, | |
pub y: f32, | |
pub z: f32 | |
} | |
impl Component for AngularVelocity {} | |
} | |
use storage::*; | |
use component::*; | |
use system::*; | |
use test_components::*; | |
struct Sys; | |
impl<'a> System<'a> for Sys { | |
type Reads = (Velocity, AngularVelocity); | |
type Writes = (Position, Rotation); | |
fn run<'ctx>(&self, context: &SystemContext<'ctx, Self::Reads, Self::Writes>) { | |
// Standard read | |
for (vel, ang_vel) in context.reads() { | |
println!("{:?}", vel); | |
} | |
// Standard write | |
for (pos, rot) in context.writes() { | |
pos.x += 1.0; | |
} | |
// Read only over writes | |
for (pos, rot) in context.writes() { | |
println!("{:?}", pos); | |
} | |
// INVALID: Mutation of reads | |
for (vel, ang_vel) in context.reads() { | |
vel.x += 1.0; | |
} | |
} | |
} | |
fn main() { | |
let mut storage = ComponentStorage::new(); | |
storage.add::<Position>(); | |
storage.add::<Velocity>(); | |
let sys = Sys {}; | |
let context = sys.create_context(&storage); | |
sys.run(&context); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment