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 }