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