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 }