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.exception; 36 import mango_engine.util; 37 import mango_engine.audio; 38 import mango_engine.logging; 39 import mango_engine.event.core; 40 import mango_engine.graphics.window; 41 import mango_engine.graphics.renderer; 42 import mango_engine.graphics.scene; 43 44 import std.exception : enforce; 45 import std.datetime; 46 47 class GameManager { 48 private shared Window _window; 49 private shared Renderer _renderer; 50 private shared Scene _scene; 51 52 private shared SyncLock loadedScenesLock; 53 private shared SyncLock sceneLock; 54 55 private shared Scene[string] loadedScenes; 56 57 private shared EventManager _eventManager; 58 private shared AudioManager _audioManager; 59 private shared Logger _logger; 60 61 @property Window window() @trusted nothrow { return cast(Window) _window; } 62 @property Renderer renderer() @trusted nothrow { return cast(Renderer) _renderer; } 63 @property Scene scene() @trusted nothrow { return cast(Scene) _scene; } 64 65 66 @property EventManager eventManager() @trusted nothrow { return cast(EventManager) _eventManager; } 67 @property AudioManager audioManager() @trusted nothrow { return cast(AudioManager) _audioManager; } 68 @property Logger logger() @trusted nothrow { return cast(Logger) _logger; } 69 70 private shared bool running = false; 71 72 this(Window window, GraphicsBackendType backend) @trusted { 73 this._window = cast(shared) window; 74 initLogger(); 75 76 this._eventManager = cast(shared) new EventManager(this); 77 this._audioManager = cast(shared) new AudioManager(this); 78 if(window !is null) 79 this._renderer = cast(shared) Renderer.rendererFactory(this, backend); 80 81 loadedScenesLock = new SyncLock(); 82 sceneLock = new SyncLock(); 83 84 window.setGame(this); 85 } 86 87 private void initLogger() @trusted { 88 // TODO: make changable 89 this._logger = cast(shared) new ConsoleLogger("Game"); 90 } 91 92 void run() @system { 93 import core.thread : Thread; 94 95 enforce(!running, new Exception("Game is already running!")); 96 97 running = true; 98 99 ulong ticks = 0; 100 101 size_t fps = 300; // TODO: allow configuration 102 long time = 1000 / fps; 103 StopWatch sw = StopWatch(); 104 105 logger.logDebug("Starting main loop..."); 106 107 while(running) { 108 sw.reset(); 109 sw.start(); 110 111 TickEvent te = new TickEvent(ticks); 112 113 eventManager.fireEvent(te); 114 eventManager.update(); 115 116 sw.stop(); 117 if(sw.peek.msecs < time) { 118 Thread.sleep((time - sw.peek.msecs).msecs); 119 } else { 120 version(mango_warnOvertime) { 121 logger.logWarn("Can't keep up! (" ~ to!string(sw.peek.msecs) ~ " > " ~ to!string(time) ~ ")"); 122 } 123 } 124 ticks++; 125 } 126 127 logger.logDebug("Cleaning up..."); 128 129 eventManager.fireEvent(new EngineCleanupEvent()); 130 eventManager.update(); 131 } 132 133 void stop() @safe { 134 synchronized(this) { 135 running = false; 136 } 137 } 138 139 void loadScene(Scene scene) @trusted { 140 string sceneName = scene.name; 141 enforce(!(sceneName in loadedScenes), new InvalidArgumentException("Scene \"" ~ sceneName ~ "\" is already loaded!")); 142 143 synchronized(loadedScenesLock) { 144 loadedScenes[sceneName] = cast(shared) scene; 145 } 146 } 147 148 void unloadScene(Scene scene) @trusted { 149 string sceneName = scene.name; 150 enforce(sceneName in loadedScenes, new InvalidArgumentException("Scene \"" ~ sceneName ~ "\" is not loaded!")); 151 enforce(this._scene.name != sceneName, new InvalidArgumentException("Can't unload the current scene being rendered!")); 152 153 synchronized(loadedScenesLock) { 154 loadedScenes.remove(sceneName); 155 } 156 } 157 158 void setCurrentScene(Scene scene) @trusted { 159 enforce(scene.name in loadedScenes, new InvalidArgumentException("Scene is not loaded!")); 160 161 synchronized(sceneLock) { 162 _scene = cast(shared) scene; 163 renderer.setScene(scene); 164 } 165 } 166 }