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 }