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