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.game;
33 
34 import mango_engine.mango;
35 import mango_engine.logging;
36 import mango_engine.resource;
37 import mango_engine.input;
38 import mango_engine.event.core;
39 import mango_engine.graphics.window;
40 import mango_engine.graphics.renderer;
41 
42 import std.exception;
43 import std.datetime;
44 
45 import core.thread;
46 
47 /// Used to create a GameManager instance. DO NOT SHARE ACROSS THREADS.
48 class GameManagerFactory {
49     /// The Backend that will be used by the GameManager
50     immutable BackendType backendType;
51 
52     private Window window;
53     private Renderer renderer;
54 
55     /++
56         Internal Constructor used by the Backend's Initializer
57         class.
58     +/
59     this(BackendType type) @safe nothrow {
60         this.backendType = backendType;
61     }
62 
63     /++
64         Sets the Window that this GameManager will render
65         to.
66 
67         Params:
68                 window =    The Window which will be rendered to.
69     +/
70     void setWindow(Window window) @safe nothrow {
71         this.window = window;
72     }
73 
74     /++
75         Sets the Renderer that this GameManager will
76         use to render scenes. This is already set by
77         the Backend's Initalizer, there is no need
78         to reset it.
79 
80         Params:
81                 renderer =  The Renderer which will render
82                             scenes for the GameManager.
83     +/
84     void setRenderer(Renderer renderer) @safe nothrow {
85         this.renderer = renderer;
86     }
87 
88     /++
89         Gets the Window the GameManager will use
90         for rendering. If it was not set, will return
91         null.
92 
93         Returns: The Window the GameManager will use for
94                  rendering.
95     +/
96     Window getWindow() @safe nothrow { 
97         return window;
98     }
99 
100     /++
101         Gets the Renderer the GameManager will
102         use to render.
103 
104         Returns: The Renderer the GameMAnager will
105                  use to render scenes.
106     +/
107     Renderer getRenderer() @safe nothrow {
108         return renderer;
109     }
110 
111     /++
112         Build the GameManager using all the values
113         set by the set_ methods. 
114         
115         Make sure you have set all values before building, 
116         this is checked in debug mode using assert()
117         but in release you could end up with a nasty suprise!
118 
119         Returns: A new GameManager instance.
120     +/
121     GameManager build() @safe 
122     in {
123         assert(window !is null, "Window is null: please set all values before building!");
124         assert(renderer !is null, "Renderer is null: please set all values before building!");
125     } body {
126         return new GameManager(
127             new ConsoleLogger("Game"),
128             window,
129             renderer,
130             backendType
131         );
132     }
133 }
134 
135 /// Main class that handles the Game.
136 class GameManager {
137     /// The Backend the GameManager is using for graphics output.
138     immutable BackendType backendType;
139 
140     private shared Window _window;
141     private shared Renderer _renderer;
142     
143     private shared EventManager _eventManager;
144     private shared InputManager _inputManager;
145     private shared ResourceManager _resourceManager;
146     private shared Logger _logger;
147 
148     /// Returns: The Window this GameManager is rendering to.
149     @property Window window() @trusted nothrow { return cast(Window) _window; }
150     /// Returns: The Renderer this GameManager is using to render.
151     @property Renderer renderer() @trusted nothrow { return cast(Renderer) _renderer; }
152     /// Returns: The EventManager this GameManager is using to handle events.
153     @property EventManager eventManager() @trusted nothrow { return cast(EventManager) _eventManager; }
154     /// Returns: The InputManager which handles input for the Game.
155     @property InputManager inputManager() @trusted nothrow { return cast(InputManager) _inputManager; }
156     /// Returns: The ResourceManager used to load and manage resources from the disk (such as textures).
157     @property ResourceManager resourceManager() @trusted nothrow { return cast(ResourceManager) _resourceManager; }
158     /// Returns: The Logger this GameManager is using for Logging.
159     @property Logger logger() @trusted nothrow { return cast(Logger) _logger; }
160 
161     private shared bool running = false;
162 
163     /// Internal constructor used by GameManagerFactory
164     package this(Logger logger, Window window, Renderer renderer, BackendType type) @trusted {
165         this.backendType = type;
166 
167 		this._logger = cast(shared) logger;
168         this._renderer = cast(shared) renderer;
169         this._window = cast(shared) window;
170         
171         this._eventManager = cast(shared) new EventManager(this);
172         this._inputManager = cast(shared) new InputManager(this);
173         this._resourceManager = cast(shared) new ResourceManager(this);
174 
175         window.gamemanager_notify(this); // Tell Window that we have been created
176     }
177 
178     /++
179         Main run method. This will block
180         until the Game has finished running.
181     +/
182     void run() @safe {
183         try {
184             _run();
185         } catch(Exception e) {
186             logger.logError("The main Game thread has crashed!");
187             logger.logException("Exception in Game thread! ", e);
188             
189         }
190     }
191 
192     private void _run() @trusted {
193         enforce(!this.running, new Exception("Game is already running!"));
194 
195         this.running = true;
196 
197         ulong ticks = 0;
198 
199         size_t fps = 144; // TODO: allow configuration
200         long time = 1000 / fps;
201         StopWatch sw = StopWatch();
202 
203         this.eventManager.fireEvent(new GameManagerStartEvent());
204 
205         this.logger.logDebug("Entering main loop.");
206 
207         do {
208 			sw.reset();
209             sw.start();
210 
211             TickEvent te = new TickEvent(ticks);
212 
213             this.eventManager.update(te);
214 
215             if(!renderer.running) {
216                 this.logger.logError("It appears the renderer thread has crashed! Exiting...");
217                 this.running = false;
218                 break;
219             }
220 
221             sw.stop();
222             if(sw.peek.msecs < time) {
223                 Thread.sleep((time - sw.peek.msecs).msecs);
224             } else {
225                 version(mango_warnOvertime) {
226                     this.logger.logWarn("Can't keep up! (" ~ to!string(sw.peek.msecs) ~ " > " ~ to!string(time) ~ ")");
227                 }
228             }
229             ticks++;
230         } while(this.running);
231         
232         this.logger.logDebug("Cleaning up...");
233 
234         if(this.renderer.running) {
235             this.renderer.stop();
236         }
237         
238         this.eventManager.fireEvent(new EngineCleanupEvent());
239         this.eventManager.update(null, 0);
240     }
241 
242     /++
243         Tell the GameManager to stop running and quit.
244         The GameManager will then cleanup resources and exit
245         the run() method.
246     +/
247     void stop() @safe nothrow {
248         this.running = false;
249     }
250 
251     /// Returns: If the GameManager is currently running.
252     bool isRunning() @safe nothrow {
253         return this.running;
254     }
255 }