module ecs.id_manager; import ecs.entity; import ecs.std; import ecs.vector; import ecs.atomic; import core.stdc.string : memcpy; /************************************************************************************************************************ *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() nothrow @nogc { //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) nothrow @nogc { 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) nothrow @nogc { 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) nothrow @nogc { 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) nothrow @nogc { if(id.id >= m_ids_array.length)return false; Data* data = &m_ids_array[id.id]; if(data.entity is null)return false; return data.counter == id.counter; } void initialize() nothrow @nogc { m_ids_array = Mallocator.makeArray!Data(65536); m_free_stack = Mallocator.makeArray!uint(65536); m_blocks = Mallocator.makeArray!Block(64); foreach(ref block;m_blocks)block = Block(); m_blocks_count = 1; m_blocks[0].alloc(); add_mutex = Mallocator.make!Mutex(); add_mutex.initialize(); getNewID(); optimize(); } void deinitialize() @trusted @nogc nothrow { if(m_ids_array)Mallocator.dispose(m_ids_array); if(m_free_stack)Mallocator.dispose(m_free_stack); if(m_blocks) { foreach(ref block;m_blocks) { if(block.data)block.free(); } Mallocator.dispose(m_blocks); } if(add_mutex) { add_mutex.destroy(); Mallocator.dispose(add_mutex);//cast(void*)add_mutex); //workaround for compiler bug add_mutex = null; } } void optimize() nothrow @nogc { 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.makeArray!Data(begin + (m_blocks_count << 16)); memcpy(new_array.ptr, m_ids_array.ptr, m_ids_array.length * Data.sizeof); Mallocator.dispose(m_ids_array); m_ids_array = new_array; uint[] new_stack = Mallocator.makeArray!uint(m_ids_array.length); memcpy(new_stack.ptr,m_free_stack.ptr,m_free_stack.length * uint.sizeof); Mallocator.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(ref block;m_blocks[1..m_blocks_count])block.free(); m_blocks_count = 1; } } private static struct Block { void alloc() nothrow @nogc { assert(data is null); data = Mallocator.makeArray!Data(65536); } void free() nothrow @nogc { assert(data !is null); Mallocator.dispose(data); data = null; } Data[] data = null; //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; manager.initialize(); EntityID id1 = manager.getNewID(); EntityID id2 = manager.getNewID(); EntityID id3 = manager.getNewID(); assert(id1 == EntityID(1, 0)); assert(id2 == EntityID(2, 0)); assert(id3 == EntityID(3, 0)); manager.optimize(); manager.releaseID(id2); manager.releaseID(id1); id2 = manager.getNewID(); id1 = manager.getNewID(); Entity e; e.id = id3; manager.update(e); assert(id1 == EntityID(2, 1)); assert(id2 == EntityID(1, 1)); assert(id3 == EntityID(3, 0)); assert(manager.isExist(id3)); assert(!manager.isExist(EntityID(1, 0))); assert(!manager.isExist(EntityID(0, 0))); manager.deinitialize(); }