componentile 0.5.0

Kind of like a minimal, tile-based ECS
Documentation
/// Exports a macro for defining the `World`, the struct which holds all component arrays.
/// The `World` has an external interface presenting `get`, `put`, and `delete` methods.
///
/// Component arrays are 4-dimensional, with 2 dimensions of space and an additional dimension per
/// spatial dimension, which serves to break the space into evenly-sized 8x8 chunks for improving
/// cache locality during iteration.  This should be an invisible detail when using these
/// structures.
///
/// Components which are just a single bool just use a bitset.

////////////////////////////////////////////////////////////////////////////////////////////////////
// constants and helpers shared between the storages ///////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

const NUM_CELLS_PER_CHUNK_SIDE: usize = 8;
const NUM_CHUNKS_PER_WORLD_SIDE: usize = 128;

#[inline(always)]
fn translate_coordinates(x: usize, y: usize) -> (usize, usize, usize, usize) {
    let y_chunk = y / NUM_CELLS_PER_CHUNK_SIDE;
    let y_cell = y % NUM_CELLS_PER_CHUNK_SIDE;
    let x_chunk = x / NUM_CELLS_PER_CHUNK_SIDE;
    let x_cell = x % NUM_CELLS_PER_CHUNK_SIDE;

    (y_chunk, y_cell, x_chunk, x_cell)
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// component slice of booleans /////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

pub struct WorldComponentBitSlice(
    [[[u8; NUM_CELLS_PER_CHUNK_SIDE]; NUM_CHUNKS_PER_WORLD_SIDE]; NUM_CHUNKS_PER_WORLD_SIDE],
);

impl WorldComponentBitSlice {
    #[inline(always)]
    pub fn get(&self, x: usize, y: usize) -> bool {
        let (y_chunk, y_cell, x_chunk, x_cell) = translate_coordinates(x, y);

        let chunk = self.0[y_chunk][x_chunk];
        let row = chunk[y_cell];
        let i = x_cell;
        let val = (row & (0b10000000 >> i)) != 0;

        val
    }

    pub fn put(&mut self, val: bool, x: usize, y: usize) {
        let (y_chunk, y_cell, x_chunk, x_cell) = translate_coordinates(x, y);
        let row = self.0[y_chunk][x_chunk][y_cell];

        let inversion_mask = 0b11111111 ^ (0b10000000 >> x_cell);
        let row_with_zeroed_x_cell = row & inversion_mask;

        let modifier = (val as u8) >> x_cell;

        let new_row = row_with_zeroed_x_cell | modifier;

        self.0[y_chunk][x_chunk][y_cell] = new_row;
    }

    pub fn is_byte_set(&self, x: usize, y: usize) -> bool {
        let (y_chunk, y_cell, x_chunk, _) = translate_coordinates(x, y);
        self.0[y_chunk][x_chunk][y_cell] != 0
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// all other component slice types /////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

type Chunk<T> = [[T; NUM_CELLS_PER_CHUNK_SIDE]; NUM_CELLS_PER_CHUNK_SIDE];

pub struct WorldComponentSlice<T: Copy> {
    values: [[Chunk<T>; NUM_CHUNKS_PER_WORLD_SIDE]; NUM_CHUNKS_PER_WORLD_SIDE],
}

impl<T: Copy> WorldComponentSlice<T> {
    pub fn get(&self, x: usize, y: usize) -> T {
        let (y_chunk, y_cell, x_chunk, x_cell) = translate_coordinates(x, y);
        self.values[y_chunk][x_chunk][y_cell][x_cell]
    }

    pub fn put(&mut self, val: T, x: usize, y: usize) {
        let (y_chunk, y_cell, x_chunk, x_cell) = translate_coordinates(x, y);
        self.values[y_chunk][x_chunk][y_cell][x_cell] = val;
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// iterator implementations ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

const NUM_CELLS_PER_WORLD_SIDE: usize = NUM_CELLS_PER_CHUNK_SIDE * NUM_CHUNKS_PER_WORLD_SIDE;
const NUM_CELLS_IN_WORLD: usize = NUM_CELLS_PER_WORLD_SIDE * NUM_CELLS_PER_WORLD_SIDE;

/// an iterator with automatic bounds-checking and chunkwise iteration.
/// Also includes a method `skip_8` which integrates nicely with the bitsets to allow skipping over
/// a byte at a time.  This can and must be called manually.
pub struct ComponentIterator {
    index: usize,
}

impl ComponentIterator {
    pub fn skip_8(&mut self) {
        self.index += 8;
    }
}

impl Iterator for ComponentIterator {
    type Item = (usize, usize);

    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= NUM_CELLS_IN_WORLD {
            None
        } else {
            let x = self.index % NUM_CELLS_PER_WORLD_SIDE;
            let y = self.index / NUM_CELLS_PER_WORLD_SIDE;
            self.index += 1;
            Some((x, y))
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// macro definitions ///////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

#[macro_export]
macro_rules! define_world {
    ($($x:ident: $y:ident,)*) => {
        pub struct World {
            $($x: wrap_type!($y),)*
        }

        impl World {
            pub fn new() -> Box<World> {
                unsafe {
                    let layout = std::alloc::Layout::new::<World>();
                    let ptr = std::alloc::alloc_zeroed(layout) as *mut World;
                    Box::from_raw(ptr)
                }
            }
        }
    };

    ($($x:ident: $y:tt,)*) => {
        pub struct World {
            $($x: wrap_type!($y),)*
        }

        impl World {
            pub fn new() -> Box<World> {
                unsafe {
                    let layout = std::alloc::Layout::new::<World>();
                    let ptr = std::alloc::alloc_zeroed(layout) as *mut World;
                    Box::from_raw(ptr)
                }
            }
        }
    }
}

#[macro_export]
macro_rules! wrap_type {
    (bool) => {
        WorldComponentBitSlice
    };

    ($t:ident) => {WorldComponentSlice<$t>};

    ($t:tt) => {WorldComponentSlice<$t>};
}