1 /* 2 * BSD 3-Clause License 3 * 4 * Copyright (c) 2016, Mango-Engine Team 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions are met: 9 * 10 * * Redistributions of source code must retain the above copyright notice, this 11 * list of conditions and the following disclaimer. 12 * 13 * * Redistributions in binary form must reproduce the above copyright notice, 14 * this list of conditions and the following disclaimer in the documentation 15 * and/or other materials provided with the distribution. 16 * 17 * * Neither the name of the copyright holder nor the names of its 18 * contributors may be used to endorse or promote products derived from 19 * this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 module mango_engine.event.core; 33 34 import mango_engine.game; 35 import mango_engine.util; 36 37 import std.conv; 38 import std.container.dlist; 39 40 /// Represents a callable event with properties. 41 abstract class Event { 42 43 } 44 45 /// Event that is fired each tick. 46 class TickEvent : Event { 47 /// The current tick the GameManager is on. 48 immutable ulong currentTick; 49 50 this(in ulong currentTick) @safe nothrow pure { 51 this.currentTick = currentTick; 52 } 53 } 54 55 /// Event that is fired when the GameManager.run() method is called. 56 class GameManagerStartEvent : Event { 57 58 } 59 60 /// Event that is fired when the engine begins to clean up. 61 class EngineCleanupEvent : Event { 62 63 } 64 65 /// Alias for a delegate used for an event hook. 66 alias HookDelegate = void delegate(Event evt) @system; 67 68 /// Represents a hook that is called for a specific event. 69 struct EventHook { 70 /// The function delegate to be ran. 71 immutable HookDelegate hook; 72 /// If the delegate can be ran in a separate worker thread. 73 immutable bool runAsync = true; 74 } 75 76 /// Handles all Event related activities. 77 class EventManager { 78 __gshared { 79 private ThreadPool pool; 80 private GameManager game; 81 82 private DList!Event eventQueue; 83 } 84 85 private shared SyncLock hookLock; 86 private shared EventHook[][string] hooks; 87 88 this(GameManager game) @trusted { 89 import core.cpuid : coresPerCPU; 90 91 this.game = game; 92 this.game.logger.logInfo("This CPU has " ~ to!string(coresPerCPU()) ~ " cores avaliable. Assigning one worker thread to each."); 93 this.pool = new ThreadPool(coresPerCPU()); 94 this.eventQueue = DList!Event(); 95 96 this.hookLock = new SyncLock(); 97 98 this.registerEventHook(EngineCleanupEvent.classinfo.name, EventHook(&this.stop, false)); 99 } 100 101 102 private void stop(Event evt) @trusted { 103 this.pool.stopImmediate(); 104 } 105 106 /++ 107 Adds an event to the firing queue. The event 108 will be fired on the next EventManager update 109 pass. 110 111 Params: 112 event = The Event to be fired. 113 +/ 114 void fireEvent(Event event) @trusted { 115 this.eventQueue.insertBack(event); 116 } 117 118 /++ 119 Registers an EventHook for a specific Event. 120 This hook will be called when the event is fired. 121 122 Params: 123 eventType = The event's full class name. 124 This is given by [EventClass].classinfo.name 125 126 hook = The EventHook to be called when 127 the Event is fired. 128 +/ 129 void registerEventHook(in string eventType, EventHook hook) @trusted { 130 synchronized(this.hookLock) { 131 this.hooks[eventType] ~= cast(shared) hook; 132 } 133 } 134 135 /// Update function called by GameManager 136 void update(TickEvent te, size_t limit = 25) @trusted { 137 if(limit == 0) { 138 limit = size_t.max; 139 } 140 141 bool a = false; 142 143 if(te is null) 144 goto handleEvents; 145 146 if(te.classinfo.name in this.hooks) { 147 foreach(EventHook hook; this.hooks[te.classinfo.name]) { 148 // TODO: work around issue #8 149 hook.hook(cast(Event) te); 150 /*if(hook.runAsync) { // Check if we can run this in a worker 151 pool.submitWork(() { 152 hook.hook(cast(Event) te); 153 }, te.classinfo.name); 154 } else { 155 hook.hook(cast(Event) te); 156 }*/ 157 } 158 } 159 160 handleEvents: 161 if(this.eventQueue.empty) return; 162 163 while(!this.eventQueue.empty && (limit-- > 0)) { 164 synchronized(this.hookLock) { 165 Event event = this.eventQueue.front; 166 this.eventQueue.removeFront(); 167 168 if(event.classinfo.name in this.hooks) { 169 foreach(EventHook hook; this.hooks[event.classinfo.name]) { 170 if(hook.runAsync) { // Check if we can run this in a worker 171 pool.submitWork(() { 172 /*debug { 173 import std.stdio; 174 writeln("Executing: ", event.classinfo.name); 175 }*/ 176 hook.hook(cast(Event) event); 177 }, event.classinfo.name); 178 } else { 179 hook.hook(cast(Event) event); 180 } 181 } 182 } 183 } 184 } 185 } 186 }