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 }