1 module mango_engine.input; 2 3 import mango_engine.mango; 4 import mango_engine.game; 5 import mango_engine.event.core; 6 7 import std.concurrency; 8 import std.container.dlist; 9 import std.exception; 10 11 import derelict.glfw3.glfw3; 12 13 immutable size_t INPUT_TYPE_KEY; 14 immutable size_t INPUT_TYPE_MOUSE; 15 16 /// Represents data for a given input type. 17 abstract class InputData { 18 19 } 20 21 class KeyInputData : InputData { 22 immutable size_t key; 23 immutable size_t state; 24 25 this(in size_t key, in size_t state) @safe nothrow { 26 this.key = key; 27 this.state = state; 28 } 29 } 30 31 /++ 32 This struct is message-passed to the InputManager thread, 33 where it is then handled in it's corresponding hook. 34 +/ 35 package struct InputEventMessage { 36 /// The type of InputEvent 37 shared size_t type; 38 /// The input data 39 shared InputData data; 40 } 41 42 /// The InputHook delegate, which is called when input is received. 43 public alias InputHook = void delegate(size_t type, InputData data) @system; 44 45 enum ThreadSignal { 46 SIGNAL_STOP 47 } 48 49 /++ 50 Handles input and processes them in 51 InputHooks. 52 +/ 53 class InputManager { 54 private shared GameManager _game; 55 private shared Tid _threadTid; 56 private shared bool _running = false; 57 58 private shared InputHook[] hooks; 59 private DList!InputEventMessage events; 60 61 @property GameManager game() @trusted nothrow { return cast(GameManager) _game; } 62 @property bool running() @safe nothrow { return _running; } 63 64 this(GameManager game) @system { 65 this._game = cast(shared) game; 66 67 this.events = DList!InputEventMessage(); 68 this._threadTid = cast(shared) spawn(&spawnInputThread, cast(shared) this); 69 70 this.game.eventManager.registerEventHook(EngineCleanupEvent.classinfo.name, 71 EventHook((Event e) { 72 this.stopInputThread(); 73 }, false) 74 ); 75 } 76 77 void registerInputHook(InputHook hook) @safe { 78 synchronized(this) { 79 this.hooks ~= cast(shared) hook; 80 } 81 } 82 83 package void run() @system { 84 enforce(!this.running, new Exception("The InputManager Thread is already running!")); 85 86 this._running = true; 87 88 GLOBAL_LOGGER.logDebug("Input Thread started."); 89 90 while(this.running) { 91 import std.datetime; 92 93 receiveTimeout(1.msecs, 94 (ThreadSignal signal) { 95 switch(signal) { 96 case ThreadSignal.SIGNAL_STOP: 97 this._running = false; 98 return; 99 default: 100 GLOBAL_LOGGER.logDebug("Unknown signal"); 101 break; 102 } 103 }, 104 &handleInputEventMessage, 105 ); 106 107 if(!this.events.empty) { 108 InputEventMessage msg = this.events.front; 109 this.events.removeFront(); 110 111 handleInputEventMessage(msg); 112 } 113 } 114 115 GLOBAL_LOGGER.logDebug("Input Thread exiting."); 116 } 117 118 private void handleInputEventMessage(InputEventMessage m) @trusted { 119 synchronized(this) { 120 foreach(hook; this.hooks) { 121 hook(cast(size_t) m.type, cast(InputData) m.data); 122 } 123 } 124 } 125 126 void sendInputEventMessage(size_t type, InputData data) @system nothrow { 127 this.events.insertBack(InputEventMessage(type, cast(shared) data)); 128 //send((cast(Tid) this._threadTid), new InputEventMessage(type, cast(shared) data)); 129 } 130 131 /// Sends a THREAD_SIGNAL_STOP signal to the Thread. 132 void stopInputThread() @system { 133 prioritySend((cast(Tid) this._threadTid), ThreadSignal.SIGNAL_STOP); 134 } 135 } 136 137 private void spawnInputThread(shared InputManager manager) @system { 138 import core.thread : Thread; 139 140 Thread.getThis().name = "InputManager"; 141 142 try { 143 (cast(InputManager) manager).run(); 144 } catch(OwnerTerminated e) { 145 GLOBAL_LOGGER.logDebug("Input Thread crashed (Main Thread terminated)."); 146 } catch(Exception e) { 147 GLOBAL_LOGGER.logError("Input Thread has crashed!"); 148 GLOBAL_LOGGER.logException("Exception in Input Thread! ", e); 149 } 150 }