1 module evael.core.Game;
2 
3 import std.array : split;
4 import std.conv;
5 import std.typecons;
6 
7 public import decs;
8 
9 import dnogc.DynamicArray;
10 
11 import evael.core.GameState;
12 
13 import evael.graphics.GraphicsDevice;
14 import evael.graphics.Font;
15 import evael.graphics.gui.GuiManager;
16 
17 import evael.audio.AudioDevice;
18 
19 import evael.system.AssetLoader;
20 import evael.system.InputHandler;
21 import evael.system.Input;
22 import evael.system.I18n;
23 import evael.system.Window;
24 import evael.system.WindowSettings;
25 import evael.system.GLContextSettings;
26 import evael.system.Cursor;
27 
28 import evael.utils.Math;
29 import evael.utils.Singleton;
30 import evael.utils.Size;
31 import evael.utils.Config;
32 import evael.utils.Functions;
33 
34 import evael.Init;
35 
36 /**
37  * Game
38  */
39 class Game
40 {
41 	private Window         m_window;
42 	private GraphicsDevice m_graphicsDevice;
43 	private GuiManager     m_guiManager;
44 	private AssetLoader    m_assetLoader;
45 	private AudioDevice    m_audioDevice;
46 	private EntityManager  m_entityManager;
47 	private I18n           m_i18n;
48 	private InputHandler   m_inputHandler;
49 
50 	/// Current game state.
51 	private GameState m_currentGameState;
52 
53 	/// Tasks of other thread that will be processed by main thread.
54 	private alias OnEvent = void delegate();
55 	private DynamicArray!OnEvent m_events;
56 
57 	private alias Task = Tuple!(long, OnEvent);
58 	private DynamicArray!Task m_scheduledTasks;
59 
60 	/// Indicates if the game is still running.
61 	private bool m_running;
62 
63 	/// Values for fixed timestep game loop.
64 	private float m_tickrate;       // How many times world will be updated per second.
65 	private float m_deltaTime;      // Time that elapsed since the last tick in ms.
66 	private int   m_maxFrameSkip;
67 
68 	private GameState[string] m_gameStates;
69 
70 	/**
71 	 * Game constructor.
72 	 * Params:
73 	 *      settings : window settings
74 	 *      contextSettings : gl context settings
75 	 */
76 	public this(in WindowSettings settings, in GLContextSettings contextSettings = GLContextSettings())
77 	{
78 		loadExternalLibraries();
79 			   
80 		Config.load("./config.ini");
81 
82 		this.m_window = new Window(settings, contextSettings);
83 		this.m_window.setWindowCloseCallback(bindDelegate(&this.onWindowClose));
84 		this.m_window.setWindowSizeCallback(bindDelegate(&this.onWindowResize));
85 		this.m_window.setScrollCallback(bindDelegate(&this.onMouseWheel));
86 		this.m_window.setKeyCallback(bindDelegate(&this.onKey));
87 		this.m_window.setCharCallback(bindDelegate(&this.onText));
88 
89 		this.m_inputHandler = new InputHandler(this);
90 
91 		this.m_window.setCursorPosCallback(bindDelegate(&this.m_inputHandler.onMouseMove));
92 		this.m_window.setMouseButtonCallback(bindDelegate(&this.m_inputHandler.onMouseClick));
93 
94 		this.m_graphicsDevice = GraphicsDevice.getInstance();
95 		this.m_assetLoader = AssetLoader.getInstance();
96 		this.m_graphicsDevice.defaultFont = this.m_assetLoader.load!(Font)("Roboto-Regular.ttf", this.m_graphicsDevice.nvgContext);
97 		this.m_graphicsDevice.resolution = settings.resolution;
98 		this.m_guiManager = new GuiManager(this.m_graphicsDevice);
99 		this.m_audioDevice = new AudioDevice();
100 		this.m_entityManager = new EntityManager();
101 
102 		this.m_tickrate = 64.0f;
103 		this.m_deltaTime = 1000.0f / this.m_tickrate;
104 		this.m_maxFrameSkip = 5;
105 
106 		this.m_i18n = new I18n();
107 		this.m_i18n.setLocale("fr");
108 
109 		this.m_running = true;
110 	}
111 
112 	/**
113 	 * Game destructor.
114 	 */
115 	public void dispose()
116 	{
117 		this.m_running = false;
118 
119 		this.m_window.dispose();
120 		this.m_events.dispose();
121 		this.m_currentGameState.dispose();
122 		this.m_audioDevice.dispose();
123 		this.m_graphicsDevice.dispose();
124 		this.m_guiManager.dispose();
125 
126 		unloadExternalLibraries();        
127 	}
128 
129 	/**
130 	 * Deault game loop.
131 	 */
132 	public void run()
133 	{
134 		float nextGameTick = getCurrentTime();
135 
136         int loops = 0;
137 
138 		while (this.m_running)
139 		{	
140             immutable currentTick = getCurrentTime();
141             loops = 0;
142 
143             while (currentTick > nextGameTick && loops < this.m_maxFrameSkip)
144             {
145                 this.fixedUpdate();
146 
147                 nextGameTick += this.m_deltaTime;
148                 loops++;               
149             }
150 
151             immutable interpolation = cast(float)(currentTick + this.m_deltaTime - nextGameTick) / cast(float) this.m_deltaTime;
152 
153 			this.update(interpolation);    
154             this.pollEvents();
155 		}
156 	}
157 
158 	/**
159 	 * Processes game logic at fixed time rate, defined by m_tickrate.
160 	 */
161 	public void fixedUpdate()
162 	{
163 		synchronized(this)
164 		{
165 			for (; this.m_events.length ;)
166 			{
167 				this.m_events[0]();
168 				this.m_events.remove(0);
169 			}
170 		}
171 
172 		this.m_currentGameState.fixedUpdate();        
173 	}
174 
175 	/**
176 	 * Processes game rendering.
177 	 * Params:
178 	 *      interpolation : 
179 	 */
180 	public void update(in float interpolation)
181 	{
182 		this.m_inputHandler.update();        
183 		this.m_currentGameState.update(interpolation);
184 	}
185 
186 	/**
187 	 * Defines current game state.
188 	 * Params:
189 	 *		gameState : new gamestate to set
190 	 */
191 	public void setGameState(T)() nothrow
192 	{
193 		T* gameState = cast(T*) (T.stringof in this.m_gameStates);
194 		
195 		if (gameState is null) 
196 		{
197 			T gs = new T();
198 			(cast(GameState) gs).setParent(this);
199 			this.m_gameStates[T.stringof] = gs;
200 			this.m_currentGameState = gs;
201 		}
202 		else this.m_currentGameState = *gameState;
203 	}
204 
205 	/**
206 	 * Sets window mouse cursor.
207 	 * Params:
208 	 *      cursor : new cursor toset
209 	 */
210 	@nogc
211 	public void setCursor(in Cursor cursor) nothrow
212 	{
213 		this.m_window.setCursor(cursor);
214 	}
215 
216 	/**
217 	 * Polls window events.
218 	 */
219 	@nogc
220 	public void pollEvents() nothrow
221 	{
222 		this.m_window.pollEvents();
223 	}
224 
225 	/**
226 	 * Adds event that will be processed by the main thread.
227 	 * Params:
228 	 *      event : event delegate
229 	 */
230 	@nogc
231 	public void addEvent(OnEvent event)
232 	{
233 		synchronized(this)
234 		{
235 			this.m_events ~= event;
236 		}
237 	}
238 
239 	/**
240 	 * Adds scheduled event that will be processed by the main thread.
241 	 * Params:
242 	 *      time : time in seconds before executing the task
243 	 *      task : task delegate
244 	 */
245 	@nogc
246 	public void addScheduledTask(in float time, OnEvent task) nothrow
247 	{
248 		auto taskTuple = tuple(cast(long) (1000 * time), task);
249 
250 		// Before adding a new tuple, we search for a free task slot
251 		foreach (i, ref t; this.m_scheduledTasks)
252 		{
253 			// Free slot check
254 			if (t[1] is null)
255 			{
256 				this.m_scheduledTasks[i] = taskTuple;
257 				return;
258 			}
259 		}
260 
261 		this.m_scheduledTasks ~= taskTuple;
262 	}
263 
264 	extern(C) nothrow
265 	{
266 		/**
267 		 * Event called on mouse wheel action.
268 		 * Params:
269 		 *      window : window
270 		 *      xoffset : xoffset
271 		 *      yoffset : yoffset
272 		 */
273 		public void onMouseWheel(GLFWwindow* window, double xoffset, double yoffset)
274 		{
275 			try
276 			{
277 				this.m_currentGameState.onMouseWheel(cast(int)xoffset);
278 			}
279 			catch(Exception e)
280 			{
281 			}
282 		}
283 
284 		/**
285 		 * Event called on character input.
286 		 * Params:
287 		 *      window : window
288 		 *      codepoint : codepoint
289 		 */
290 		public void onText(GLFWwindow* window, uint codepoint)
291 		{
292 			try
293 			{
294 				this.m_currentGameState.onText(codepoint);
295 			}
296 			catch(Exception e)
297 			{
298 			}
299 		}
300 
301 		/**
302 		 * Event called on key action.
303 		 * Params:
304 		 *      window : window
305 		 *      key : key
306 		 *      scancode : scancode
307 		 *      action : action (key press or release)
308 		 *      mods : mods
309 		 */
310 		public void onKey(GLFWwindow* window, int key, int scancode, int action, int mods)
311 		{
312 			if(action == GLFW_RELEASE)
313 			{
314 				try
315 				{
316 					this.m_currentGameState.onKey(key);
317 				}
318 				catch(Exception e)
319 				{
320 				}
321 			}
322 		}
323 
324 		/**
325 		 * Event called on window resize action.
326 		 * Params:
327 		 *      window : window
328 		 *      width : new width
329 		 *      height : new height
330 		 */
331 		public void onWindowResize(GLFWwindow* window, int width, int height)
332 		{
333 			this.m_graphicsDevice.resolution = Size!int(width, height);
334 		}
335 
336 		/**
337 		 * Event called on window close action.
338 		 * Params:
339 		 *      window : window
340 		 */
341 		public void onWindowClose(GLFWwindow* window)
342 		{
343 			this.m_running = false;
344 		}
345 	}
346 
347 	/**
348 	 * Properties.
349 	 */
350 	@nogc @safe
351 	@property pure nothrow
352 	{
353 		public GraphicsDevice graphicsDevice()
354 		{
355 			return this.m_graphicsDevice;
356 		}
357 
358 		public GuiManager guiManager()
359 		{
360 			return this.m_guiManager;
361 		}
362 
363 		public AssetLoader assetLoader()
364 		{
365 			return this.m_assetLoader;
366 		}
367 
368 		public EntityManager entityManager()
369 		{
370 			return this.m_entityManager;
371 		}
372 
373 		public GameState currentGameState()
374 		{
375 			return this.m_currentGameState;
376 		}
377 
378 		public I18n i18n()
379 		{
380 			return this.m_i18n;
381 		}
382 
383 		public InputHandler inputHandler()
384 		{
385 			return this.m_inputHandler;
386 		}
387 
388 		public bool running() const
389 		{
390 			return this.m_running;
391 		}
392 
393 		public float deltaTime() const
394 		{
395 			return this.m_deltaTime;
396 		}
397 
398 		public void tickrate(in float value)
399 		{
400 			this.m_tickrate = value;
401 			this.m_deltaTime = 1000.0f / this.m_tickrate;
402 		}
403 
404 		public int maxFrameSkip() const
405 		{
406 			return this.m_maxFrameSkip;
407 		}
408 
409 		public void maxFrameSkip(in int value)
410 		{
411 			this.m_maxFrameSkip = value;
412 		}
413 
414 		public Window window()
415 		{
416 			return this.m_window;
417 		}
418 	}
419 }