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 }