1 module re.core; 2 3 import re.input; 4 import re.content; 5 import re.time; 6 import re.gfx.window; 7 import re.ng.scene; 8 import re.ng.diag; 9 import re.ng.manager; 10 import re.gfx.render_ext; 11 import re.math; 12 import re.util.logger; 13 import re.util.tweens.tween_manager; 14 import std.array; 15 import std.typecons; 16 import jar; 17 static import raylib; 18 19 /** 20 Core class 21 */ 22 abstract class Core { 23 /// logger utility 24 public static Logger log; 25 26 /// game window 27 public static Window window; 28 29 /// content manager 30 public static ContentManager content; 31 32 /// the current scenes 33 private static Scene[] _scenes; 34 35 /// type registration container 36 public static Jar jar; 37 38 /// global managers 39 public static Manager[] managers; 40 41 /// whether to draw debug information 42 public static bool debug_render; 43 44 /// debugger utility 45 debug public static Debugger debugger; 46 47 /// whether the game is running 48 public static bool running; 49 50 version (unittest) { 51 /// the frame limit (used for testing) 52 public static int frame_limit = 60; 53 } 54 55 /// target frames per second 56 public static int target_fps = 60; 57 58 /// whether graphics should be disabled 59 public static bool headless = false; 60 61 /// whether to pause when unfocused 62 public static bool pause_on_focus_lost = true; 63 64 /// whether to exit when escape pressed 65 public static bool exit_on_escape_pressed = true; 66 67 /// whether to automatically scale things to compensate for hidpi 68 public static bool auto_compensate_hidpi = true; 69 70 /// the default render resolution for all scenes 71 public static Vector2 default_resolution; 72 73 /// the default texture filtering mode for render targets 74 public static raylib.TextureFilter default_filter_mode 75 = raylib.TextureFilter.TEXTURE_FILTER_POINT; 76 77 /// sets up a game core 78 this(int width, int height, string title) { 79 log = new Logger(Logger.Verbosity.Information); 80 log.sinks ~= new Logger.ConsoleSink(); 81 82 version (unittest) { 83 } else { 84 log.info("initializing rengfx core"); 85 } 86 87 default_resolution = Vector2(width, height); 88 if (!Core.headless) { 89 window = new Window(width, height); 90 window.initialize(); 91 window.set_title(title); 92 if (auto_compensate_hidpi) { 93 // resize window according to dpi scale 94 window.resize(cast(int)(window.width * window.scale_dpi), cast(int)(window.height * window.scale_dpi)); 95 } 96 } 97 98 // disable default exit key 99 raylib.SetExitKey(raylib.KeyboardKey.KEY_NULL); 100 101 content = new ContentManager(); 102 103 jar = new Jar(); 104 105 add_manager(new TweenManager()); 106 107 debug { 108 debugger = new Debugger(); 109 } 110 111 version (unittest) { 112 } else { 113 log.info("initializing game"); 114 } 115 116 initialize(); 117 } 118 119 @property public static int fps() { 120 return raylib.GetFPS(); 121 } 122 123 /// sets up the game 124 abstract void initialize(); 125 126 /// starts the game 127 public void run() { 128 running = true; 129 // start the game loop 130 while (running) { 131 if (!headless) { 132 running = !raylib.WindowShouldClose(); 133 } 134 135 update(); 136 draw(); 137 138 version (unittest) { 139 if (Time.frame_count >= frame_limit) { 140 running = false; 141 } 142 } 143 } 144 } 145 146 /// gracefully exits the game 147 public static void exit() { 148 running = false; 149 version (unittest) { 150 } else { 151 log.info("gracefully exiting"); 152 } 153 } 154 155 protected void update() { 156 if (pause_on_focus_lost && raylib.IsWindowMinimized()) { 157 return; // pause 158 } 159 if (exit_on_escape_pressed && raylib.IsKeyPressed(raylib.KeyboardKey.KEY_ESCAPE)) { 160 exit(); 161 } 162 version (unittest) { 163 Time.update(1f / target_fps); // 60 fps 164 } else { 165 Time.update(raylib.GetFrameTime()); 166 } 167 foreach (manager; managers) { 168 manager.update(); 169 } 170 Input.update(); 171 foreach (scene; _scenes) { 172 scene.update(); 173 } 174 debug { 175 debugger.update(); 176 } 177 } 178 179 protected void draw() { 180 if (Core.headless) 181 return; 182 if (raylib.IsWindowMinimized()) { 183 return; // suppress draw 184 } 185 raylib.BeginDrawing(); 186 foreach (scene; _scenes) { 187 // render scene 188 scene.render(); 189 // post-render 190 scene.post_render(); 191 // composite screen render to window 192 // TODO: support better compositing 193 RenderExt.draw_render_target(scene.render_target, Rectangle(0, 0, 194 window.width, window.height), scene.composite_mode.color); 195 } 196 debug { 197 debugger.render(); 198 } 199 raylib.EndDrawing(); 200 } 201 202 public static T get_scene(T)() { 203 import std.algorithm.searching : find; 204 205 // find a scene matching the type 206 auto matches = _scenes.find!(x => (cast(T) x) !is null); 207 assert(matches.length > 0, "no matching scene was found"); 208 return cast(T) matches.front; 209 } 210 211 public static Nullable!T get_manager(T)() { 212 import std.algorithm.searching : find; 213 214 // find a manager matching the type 215 auto matches = managers.find!(x => (cast(T) x) !is null); 216 if (matches.length > 0) { 217 return Nullable!T(cast(T) matches.front); 218 } 219 return Nullable!T.init; 220 } 221 222 /// adds a global manager 223 public T add_manager(T)(T manager) { 224 managers ~= manager; 225 manager.setup(); 226 return manager; 227 } 228 229 @property public static Scene[] scenes() { 230 return _scenes; 231 } 232 233 @property public static Scene primary_scene() { 234 return _scenes.front; 235 } 236 237 /// sets the current scenes 238 static void load_scenes(Scene[] new_scenes) { 239 foreach (scene; _scenes) { 240 // end old scenes 241 scene.end(); 242 scene = null; 243 } 244 // clear scenes list 245 _scenes = []; 246 247 _scenes ~= new_scenes; 248 // begin new scenes 249 foreach (scene; _scenes) { 250 scene.begin(); 251 } 252 } 253 254 /// releases all resources and cleans up 255 public void destroy() { 256 debug { 257 debugger.destroy(); 258 } 259 content.destroy(); 260 load_scenes([]); // end scenes 261 foreach (manager; managers) { 262 manager.destroy(); 263 } 264 if (!Core.headless) { 265 window.destroy(); 266 } 267 } 268 } 269 270 @("core-basic") 271 unittest { 272 import re.util.test : TestGame; 273 import std.string : format; 274 import std.math : isClose; 275 276 class Game : TestGame { 277 override void initialize() { 278 // nothing much 279 } 280 } 281 282 auto game = new Game(); 283 game.run(); 284 285 // ensure time has passed 286 auto target_time = Core.frame_limit / Core.target_fps; 287 assert(isClose(Time.total_time, target_time), 288 format("time did not pass (expected: %s, actual: %s)", target_time, Time.total_time)); 289 290 game.destroy(); // clean up 291 292 assert(game.scenes.length == 0, "scenes were not removed after Game cleanup"); 293 }