bubel-ecs/source/ecs/id_manager.d
Mergul d3f7593afc -BlockAllocator is no longer template
-Multithreaded IDManager.getNewID()
 *use implementation with free IDs stack (instead of classic pool)
-support for multiple UpdatePasses. Passes are added by name, and must be called between begin() end() functions.
-removed mutex from addEntity()
-commit() function added. Used to commit all changes made while update() call. Called automatically by begin() end() functions.
2018-10-25 11:46:08 +02:00

246 lines
7.5 KiB
D

module ecs.id_manager;
import std.experimental.allocator;
import std.experimental.allocator.mallocator : AlignedMallocator, Mallocator;
import ecs.entity;
import ecs.vector;
import core.atomic;
import core.stdc.string : memcpy;
import core.sync.mutex;
/************************************************************************************************************************
*IDManager is responsible for assignment and removing IDs. IDs are unique throughtout a whole duration of the program.
*/
struct IDManager
{
/************************************************************************************************************************
*Get new ID.
*/
pragma(inline, false) EntityID getNewID()
{
//uint current = m_next_id;
//uint next;// = m_ids_array[m_next_id].next_id;
begin:
//if (current == uint.max)//> m_last_id)
int current = m_stack_top.atomicOp!"-="(1) + 1;
if(current < 0)
{
uint add_id = m_last_id.atomicOp!"+="(1) - 1;
if (add_id >= m_ids_array.length)
{
uint local_id = add_id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
if (block_id >= m_blocks_count)
{
add_mutex.lock();
if(block_id >= m_blocks_count)
{
m_blocks[m_blocks_count].alloc();
m_blocks_count++;
}
add_mutex.unlock();
}
}
EntityID id;
id.id = add_id;
id.counter = 0;
return id;
}
//current += 1;
uint index = m_free_stack[current];
EntityID id;
id.id = index;
id.counter = m_ids_array[index].counter;
//current = m_ids_array[index].next_id;
return id;
/*next = m_ids_array[current].next_id;
if(cas(&m_next_id,current,next))
{
EntityID id;
id.id = current;
id.counter = m_ids_array[current].counter;
m_next_id = m_ids_array[current].next_id;
return id;
}
else
{
current = next;
goto begin;
}*/
}
/************************************************************************************************************************
*Release ID.
*/
void releaseID(EntityID id)
{
Data* data = &m_ids_array[id.id];
if (data.counter != id.counter)
return;
data.counter++;
//data.next_id = m_next_id;
data.entity = null;
///m_next_id = id.id;
m_stack_top.atomicOp!"+="(1);
m_free_stack[m_stack_top] = id.id;
}
/************************************************************************************************************************
*Update pointer to entity. The purpose of this function is to ensure that pointer to entity is always correct.
*/
void update(ref Entity entity)
{
if (entity.id.id >= cast(uint) m_ids_array.length)
{
uint local_id = entity.id.id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
local_id -= block_id << 16;
m_blocks[block_id].data[local_id].entity = &entity;
}
else //if (entity.id.counter == m_ids_array[entity.id.id].counter)
m_ids_array[entity.id.id].entity = &entity;
}
/************************************************************************************************************************
*Returns pointer to entity.
*/
export Entity* getEntityPointer(EntityID id)
{
if (id.id >= m_ids_array.length)
{
uint local_id = id.id - cast(uint) m_ids_array.length;
uint block_id = local_id >> 16;
assert(block_id < m_blocks_count);
if (block_id >= m_blocks_count)
return null;
local_id -= block_id << 16;
if (m_blocks[block_id].data[local_id].counter != id.counter)
return null;
return m_blocks[block_id].data[local_id].entity;
}
Data* data = &m_ids_array[id.id];
if (data.counter != id.counter)
return null;
else
return data.entity;
}
/************************************************************************************************************************
*Check if entity with specified ID exist.
*/
export bool isExist(EntityID id)
{
Data* data = &m_ids_array[id.id];
return data.counter == id.counter;
}
void initialize()
{
m_ids_array = Mallocator.instance.makeArray!Data(65536);
m_free_stack = Mallocator.instance.makeArray!uint(65536);
m_blocks = Mallocator.instance.makeArray!Block(64);
m_blocks_count = 1;
m_blocks[0].alloc();
add_mutex = Mallocator.instance.make!Mutex();
}
void optimize()
{
if(m_stack_top < -1)m_stack_top = -1;
if(m_last_id > m_ids_array.length)
{
uint begin = cast(uint)m_ids_array.length;
Data[] new_array = Mallocator.instance.makeArray!Data(begin + (m_blocks_count << 16));
memcpy(new_array.ptr, m_ids_array.ptr, m_ids_array.length * Data.sizeof);
Mallocator.instance.dispose(m_ids_array);
m_ids_array = new_array;
uint[] new_stack = Mallocator.instance.makeArray!uint(m_ids_array.length);
memcpy(new_stack.ptr,m_free_stack.ptr,m_free_stack.length * uint.sizeof);
Mallocator.instance.dispose(m_free_stack);
m_free_stack = new_stack;
foreach(block;m_blocks[0..m_blocks_count-1])
{
memcpy(cast(void*)m_ids_array.ptr + begin * Data.sizeof, block.data.ptr, 65536 * Data.sizeof);
begin += 65536;
}
memcpy(cast(void*)m_ids_array.ptr + begin * Data.sizeof, m_blocks[m_blocks_count-1].data.ptr, (m_last_id - begin) * Data.sizeof);
foreach(block;m_blocks[1..m_blocks_count])block.free();
m_blocks_count = 1;
}
}
private static struct Block
{
void alloc()
{
assert(data is null);
data = Mallocator.instance.makeArray!Data(65536);
}
void free()
{
assert(data !is null);
Mallocator.instance.dispose(data);
}
Data[] data; //65536
}
private static struct Data
{
uint counter = 0;
//uint next_id = uint.max;
Entity* entity = null;
}
private:
Mutex add_mutex;
//shared uint m_next_id = 0;
//shared uint m_last_id = 0;
Data[] m_ids_array = null;
uint m_blocks_count = 0;
Block[] m_blocks;
//shared int m_stack_top = -1;
uint[] m_free_stack = null;
align(64) shared uint m_last_id = 0;
align(64) shared int m_stack_top = -1;
}
unittest
{
IDManager manager;
EntityID id1 = manager.getNewID();
EntityID id2 = manager.getNewID();
EntityID id3 = manager.getNewID();
assert(id1 == EntityID(0, 1));
assert(id2 == EntityID(1, 1));
assert(id3 == EntityID(2, 1));
manager.releaseID(id2);
manager.releaseID(id1);
id2 = manager.getNewID();
id1 = manager.getNewID();
assert(id1 == EntityID(1, 2));
assert(id2 == EntityID(0, 2));
assert(id3 == EntityID(2, 1));
assert(manager.isExist(id3));
assert(!manager.isExist(EntityID(0, 1)));
}