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.events;
6 
7 import bubel.ecs.block_allocator;
8 import bubel.ecs.entity;
9 import bubel.ecs.manager;
10 import bubel.ecs.std;
11 import bubel.ecs.traits : becsID;
12 
13 import std.algorithm.comparison : max;
14 
15 package struct EventManager
16 {
17 
18     void initialize(EntityManager* m) nothrow @nogc
19     {
20         allocator = BlockAllocator(events_block_size, events_blocks_in_allocation);
21         event_block_alloc_mutex = Mallocator.make!Mutex;
22         event_block_alloc_mutex.initialize();
23         manager = m;
24     }
25 
26     void destroy() nothrow @nogc
27     {
28         if (event_block_alloc_mutex)
29         {
30             event_block_alloc_mutex.destroy();
31             Mallocator.dispose(event_block_alloc_mutex);
32             event_block_alloc_mutex = null;
33         }
34     }
35 
36     export void sendEvent(Ev)(EntityID id, Ev event, uint thread_id = 0) nothrow @nogc
37     {
38         uint block_id = current_index + thread_id;
39 
40         EventData* data = &events[becsID!Ev];
41         EventBlock* block = data.blocks[block_id];
42         //EntityManager.EventInfo* info = &manager.events[Ev.event_id];
43         //event.entity_id = id;
44 
45         if (block is null)
46         {
47             event_block_alloc_mutex.lock();
48             block = cast(EventBlock*) allocator.getBlock();
49             event_block_alloc_mutex.unlock();
50 
51             *block = EventBlock();
52             data.first_blocks[block_id] = block;
53             data.blocks[block_id] = block;
54         }
55 
56         if (block.count >= data.max_events)
57         {
58             event_block_alloc_mutex.lock();
59             EventBlock* new_block = cast(EventBlock*) allocator.getBlock();
60             event_block_alloc_mutex.unlock();
61 
62             *new_block = EventBlock();
63             block.next = new_block;
64             block = new_block;
65             data.blocks[block_id] = block;
66         }
67 
68         uint size = Ev.sizeof + EntityID.sizeof;
69         void* ptr = cast(void*) block + data.data_offset + block.count * size;
70         *cast(EntityID*)ptr = id;
71         *cast(Ev*)(ptr + EntityID.sizeof) = event;
72         //Ev* event_array = cast(Ev*)(cast(void*) block + data.data_offset);
73         //event_array[block.count] = event;
74         block.count++;
75     }
76 
77     void swapCurrent() nothrow @nogc
78     {
79         uint threads_count = cast(uint) manager.threads.length;
80         if (current_index == 0)
81             current_index = threads_count;
82         else
83             current_index = 0;
84 
85         foreach (ref event; events)
86         {
87             foreach (ref first_block; event.first_blocks[current_index
88                     .. current_index + threads_count])
89             {
90                 EventBlock* block = first_block;
91                 while (block)
92                 {
93                     EventBlock* to_dispose = block;
94                     block = block.next;
95                     allocator.freeBlock(to_dispose);
96                 }
97                 first_block = null;
98             }
99             foreach (ref block; event.blocks[current_index .. current_index + threads_count])
100             {
101                 block = null;
102             }
103         }
104     }
105 
106     void clearEvents() nothrow @nogc
107     {
108         //uint threads_count = cast(uint)manager.threads.length;
109         foreach (ref event; events)
110         {
111             foreach (ref first_block; event.first_blocks)
112             {
113                 EventBlock* block = first_block;
114                 while (block)
115                 {
116                     EventBlock* to_dispose = block;
117                     block = block.next;
118                     allocator.freeBlock(to_dispose);
119                 }
120                 first_block = null;
121             }
122             foreach (ref block; event.blocks)
123             {
124                 block = null;
125             }
126         }
127     }
128 
129     void allocateData(uint threads_count) nothrow @nogc
130     {
131         disposeData();
132         events = Mallocator.makeArray!EventData(manager.events.length);
133         foreach (i, ref event; events)
134         {
135             event.blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2);
136             event.first_blocks = Mallocator.makeArray!(EventBlock*)(threads_count * 2);
137             event.data_offset = EventBlock.sizeof; //manager.events[i].
138             manager.alignNum(event.data_offset, manager.events[i].alignment);
139 
140             uint size = manager.events[i].size + EntityID.sizeof;
141             event.max_events = cast(ushort)(
142                     (events_block_size - event.data_offset) / size);
143         }
144     }
145 
146     private void disposeData() nothrow @nogc
147     {
148         clearEvents();
149         if (events)
150         {
151             foreach (ref event; events)
152             {
153                 Mallocator.dispose(event.blocks);
154                 Mallocator.dispose(event.first_blocks);
155             }
156             Mallocator.dispose(events);
157         }
158         allocator.freeMemory();
159     }
160 
161     ~this() nothrow @nogc
162     {
163         disposeData();
164     }
165 
166     ///Single page size. Must be power of two.
167     enum events_block_size = 1 << 14;
168     ///Number of pages in block.
169     enum events_blocks_in_allocation = 128;
170 
171     struct EventBlock
172     {
173         EventBlock* next;
174         ushort count = 0;
175     }
176 
177     struct EventData
178     {
179         ushort data_offset;
180         ushort max_events;
181         EventBlock*[] blocks;
182         EventBlock*[] first_blocks;
183 
184         //EventBlock*[] current_blocks;
185     }
186 
187     uint current_index = 0;
188     EventData[] events;
189     Mutex* event_block_alloc_mutex;
190 
191     BlockAllocator allocator;
192     EntityManager* manager;
193 }