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 log.info("initializing rengfx core"); 83 84 default_resolution = Vector2(width, height); 85 if (!Core.headless) { 86 window = new Window(width, height); 87 window.initialize(); 88 window.set_title(title); 89 if (auto_compensate_hidpi) { 90 // resize window according to dpi scale 91 window.resize(cast(int)(window.width * window.scale_dpi), cast(int)(window.height * window.scale_dpi)); 92 } 93 } 94 95 // disable default exit key 96 raylib.SetExitKey(raylib.KeyboardKey.KEY_NULL); 97 98 content = new ContentManager(); 99 100 jar = new Jar(); 101 102 add_manager(new TweenManager()); 103 104 debug { 105 debugger = new Debugger(); 106 } 107 108 log.info("initializing game"); 109 110 initialize(); 111 } 112 113 @property public static int fps() { 114 return raylib.GetFPS(); 115 } 116 117 /// sets up the game 118 abstract void initialize(); 119 120 /// starts the game 121 public void run() { 122 running = true; 123 // start the game loop 124 while (running) { 125 if (!headless) { 126 running = !raylib.WindowShouldClose(); 127 } 128 129 update(); 130 draw(); 131 132 version (unittest) { 133 if (Time.frame_count >= frame_limit) { 134 running = false; 135 } 136 } 137 } 138 } 139 140 /// gracefully exits the game 141 public static void exit() { 142 running = false; 143 log.info("gracefully exiting"); 144 } 145 146 protected void update() { 147 if (pause_on_focus_lost && raylib.IsWindowMinimized()) { 148 return; // pause 149 } 150 if (exit_on_escape_pressed && raylib.IsKeyPressed(raylib.KeyboardKey.KEY_ESCAPE)) { 151 exit(); 152 } 153 version (unittest) { 154 Time.update(1f / target_fps); // 60 fps 155 } else { 156 Time.update(raylib.GetFrameTime()); 157 } 158 foreach (manager; managers) { 159 manager.update(); 160 } 161 Input.update(); 162 foreach (scene; _scenes) { 163 scene.update(); 164 } 165 debug { 166 debugger.update(); 167 } 168 } 169 170 protected void draw() { 171 if (Core.headless) 172 return; 173 if (raylib.IsWindowMinimized()) { 174 return; // suppress draw 175 } 176 raylib.BeginDrawing(); 177 foreach (scene; _scenes) { 178 // render scene 179 scene.render(); 180 // post-render 181 scene.post_render(); 182 // composite screen render to window 183 // TODO: support better compositing 184 RenderExt.draw_render_target(scene.render_target, Rectangle(0, 0, 185 window.width, window.height), scene.composite_mode.color); 186 } 187 debug { 188 debugger.render(); 189 } 190 raylib.EndDrawing(); 191 } 192 193 public static T get_scene(T)() { 194 import std.algorithm.searching : find; 195 196 // find a scene matching the type 197 auto matches = _scenes.find!(x => (cast(T) x) !is null); 198 assert(matches.length > 0, "no matching scene was found"); 199 return cast(T) matches.front; 200 } 201 202 public static Nullable!T get_manager(T)() { 203 import std.algorithm.searching : find; 204 205 // find a manager matching the type 206 auto matches = managers.find!(x => (cast(T) x) !is null); 207 if (matches.length > 0) { 208 return Nullable!T(cast(T) matches.front); 209 } 210 return Nullable!T.init; 211 } 212 213 /// adds a global manager 214 public T add_manager(T)(T manager) { 215 managers ~= manager; 216 manager.setup(); 217 return manager; 218 } 219 220 @property public static Scene[] scenes() { 221 return _scenes; 222 } 223 224 @property public static Scene primary_scene() { 225 return _scenes.front; 226 } 227 228 /// sets the current scenes 229 static void load_scenes(Scene[] new_scenes) { 230 foreach (scene; _scenes) { 231 // end old scenes 232 scene.end(); 233 scene = null; 234 } 235 // clear scenes list 236 _scenes = []; 237 238 _scenes ~= new_scenes; 239 // begin new scenes 240 foreach (scene; _scenes) { 241 scene.begin(); 242 } 243 } 244 245 /// releases all resources and cleans up 246 public void destroy() { 247 debug { 248 debugger.destroy(); 249 } 250 content.destroy(); 251 load_scenes([]); // end scenes 252 foreach (manager; managers) { 253 manager.destroy(); 254 } 255 if (!Core.headless) { 256 window.destroy(); 257 } 258 } 259 } 260 261 @("core-basic") 262 unittest { 263 import re.util.test : TestGame; 264 import std..string : format; 265 import std.math : approxEqual; 266 267 class Game : TestGame { 268 override void initialize() { 269 // nothing much 270 } 271 } 272 273 auto game = new Game(); 274 game.run(); 275 276 // ensure time has passed 277 auto target_time = Core.frame_limit / Core.target_fps; 278 assert(approxEqual(Time.total_time, target_time), 279 format("time did not pass (expected: %s, actual: %s)", target_time, Time.total_time)); 280 281 game.destroy(); // clean up 282 283 assert(game.scenes.length == 0, "scenes were not removed after Game cleanup"); 284 }