1 /************************************************************************************************************************ 2 Copyright: Copyright © 2018-2023, Dawid Masiukiewicz, Michał Masiukiewicz 3 License: BSD 3-clause, see LICENSE file in project root folder. 4 */ 5 module bubel.ecs.id_manager; 6 7 import bubel.ecs.entity; 8 import bubel.ecs.std; 9 import bubel.ecs.vector; 10 11 import bubel.ecs.atomic; 12 import core.stdc.string : memcpy; 13 14 /************************************************************************************************************************ 15 IDManager is responsible for assignment and removing IDs. IDs are unique throughtout a whole duration of the program. 16 */ 17 struct IDManager 18 { 19 /************************************************************************************************************************ 20 Get new ID. 21 */ 22 EntityID getNewID() nothrow @nogc 23 { 24 int current = m_stack_top.atomicOp!"-="(1) + 1; 25 if (current < 0) 26 { 27 uint add_id = m_last_id.atomicOp!"+="(1) - 1; 28 29 if (add_id >= m_ids_array.length) 30 { 31 uint local_id = add_id - cast(uint) m_ids_array.length; 32 uint block_id = local_id >> 16; 33 if (block_id >= m_blocks_count) 34 { 35 add_mutex.lock(); 36 if (block_id >= m_blocks_count) 37 { 38 m_blocks[m_blocks_count].alloc(); 39 m_blocks_count++; 40 } 41 add_mutex.unlock(); 42 } 43 } 44 45 EntityID id; 46 id.id = add_id; 47 id.counter = 0; 48 return id; 49 } 50 51 uint index = m_free_stack[current]; 52 EntityID id; 53 id.id = index; 54 id.counter = m_ids_array[index].counter; 55 return id; 56 } 57 58 /************************************************************************************************************************ 59 Release ID. 60 */ 61 void releaseID(EntityID id) nothrow @nogc 62 { 63 optimize(); 64 Data* data = &m_ids_array[id.id]; 65 if (data.counter != id.counter) 66 return; 67 data.counter++; 68 data.entity = null; 69 70 m_stack_top.atomicOp!"+="(1); 71 m_free_stack[m_stack_top] = id.id; 72 } 73 74 /************************************************************************************************************************ 75 Update pointer to entity. The purpose of this function is to ensure that pointer to entity is always correct. 76 */ 77 void update(ref Entity entity) nothrow @nogc 78 { 79 if (entity.id.id >= cast(uint) m_ids_array.length) 80 { 81 uint local_id = entity.id.id - cast(uint) m_ids_array.length; 82 uint block_id = local_id >> 16; 83 local_id -= block_id << 16; 84 m_blocks[block_id].data[local_id].entity = &entity; 85 } 86 else //if (entity.id.counter == m_ids_array[entity.id.id].counter) 87 m_ids_array[entity.id.id].entity = &entity; 88 } 89 90 /************************************************************************************************************************ 91 Returns pointer to entity. 92 */ 93 export Entity* getEntityPointer(EntityID id) nothrow @nogc 94 { 95 if (id.id >= m_ids_array.length) 96 { 97 uint local_id = id.id - cast(uint) m_ids_array.length; 98 uint block_id = local_id >> 16; 99 assert(block_id < m_blocks_count); 100 if (block_id >= m_blocks_count) 101 return null; 102 local_id -= block_id << 16; 103 if (m_blocks[block_id].data[local_id].counter != id.counter) 104 return null; 105 return m_blocks[block_id].data[local_id].entity; 106 } 107 108 Data* data = &m_ids_array[id.id]; 109 if (data.counter != id.counter) 110 return null; 111 else 112 return data.entity; 113 } 114 115 /************************************************************************************************************************ 116 Check if entity with specified ID exist. 117 */ 118 export bool isExist(EntityID id) nothrow @nogc 119 { 120 if (id.id >= m_ids_array.length) 121 return false; 122 Data* data = &m_ids_array[id.id]; 123 if (data.entity is null) 124 return false; 125 return data.counter == id.counter; 126 } 127 128 /************************************************************************************************************************ 129 Initialize manager. 130 */ 131 void initialize() nothrow @nogc 132 { 133 m_ids_array = Mallocator.makeArray!Data(65536); 134 m_free_stack = Mallocator.makeArray!uint(65536); 135 m_blocks = Mallocator.makeArray!Block(64); 136 foreach (ref block; m_blocks) 137 block = Block(); 138 m_blocks_count = 1; 139 m_blocks[0].alloc(); 140 141 add_mutex = Mallocator.make!Mutex(); 142 add_mutex.initialize(); 143 144 getNewID(); 145 optimize(); 146 } 147 148 /************************************************************************************************************************ 149 Free manager memory. 150 */ 151 void deinitialize() @trusted @nogc nothrow 152 { 153 if (m_ids_array) 154 Mallocator.dispose(m_ids_array); 155 if (m_free_stack) 156 Mallocator.dispose(m_free_stack); 157 if (m_blocks) 158 { 159 foreach (ref block; m_blocks) 160 { 161 if (block.data) 162 block.free(); 163 } 164 Mallocator.dispose(m_blocks); 165 } 166 if (add_mutex) 167 { 168 add_mutex.destroy(); 169 Mallocator.dispose(add_mutex); //cast(void*)add_mutex); //workaround for compiler bug 170 add_mutex = null; 171 } 172 } 173 174 /************************************************************************************************************************ 175 Optimize memory. Must be called if any ID was added and some ID will be removed. 176 */ 177 void optimize() nothrow @nogc 178 { 179 if (m_stack_top < -1) 180 m_stack_top = -1; 181 if (m_last_id > m_ids_array.length) 182 { 183 uint begin = cast(uint) m_ids_array.length; 184 185 m_ids_array = Mallocator.resizeArray(m_ids_array, begin + (m_blocks_count << 16)); 186 m_free_stack = Mallocator.resizeArray(m_free_stack, m_ids_array.length); 187 188 foreach (block; m_blocks[0 .. m_blocks_count - 1]) 189 { 190 memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof, 191 block.data.ptr, 65536 * Data.sizeof); 192 begin += 65536; 193 } 194 memcpy(cast(void*) m_ids_array.ptr + begin * Data.sizeof, 195 m_blocks[m_blocks_count - 1].data.ptr, (m_last_id - begin) * Data.sizeof); 196 foreach (ref block; m_blocks[1 .. m_blocks_count]) 197 block.free(); 198 m_blocks_count = 1; 199 } 200 } 201 202 private static struct Block 203 { 204 void alloc() nothrow @nogc 205 { 206 assert(data is null); 207 data = Mallocator.makeArray!Data(65536); 208 } 209 210 void free() nothrow @nogc 211 { 212 assert(data !is null); 213 Mallocator.dispose(data); 214 data = null; 215 } 216 217 Data[] data = null; //65536 218 } 219 220 private static struct Data 221 { 222 uint counter = 0; 223 //uint next_id = uint.max; 224 Entity* entity = null; 225 } 226 227 private: 228 Mutex* add_mutex; 229 230 Data[] m_ids_array = null; 231 uint m_blocks_count = 0; 232 Block[] m_blocks; 233 uint[] m_free_stack = null; 234 235 align(64) shared uint m_last_id = 0; 236 align(64) shared int m_stack_top = -1; 237 }