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 managers ~= 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 @property public static Scene[] scenes() { 193 return _scenes; 194 } 195 196 @property public static Scene primary_scene() { 197 return _scenes.front; 198 } 199 200 /// sets the current scenes 201 static void load_scenes(Scene[] new_scenes) { 202 foreach (scene; _scenes) { 203 // end old scenes 204 scene.end(); 205 scene = null; 206 } 207 // clear scenes list 208 _scenes = []; 209 210 _scenes ~= new_scenes; 211 // begin new scenes 212 foreach (scene; _scenes) { 213 scene.begin(); 214 } 215 } 216 217 /// releases all resources and cleans up 218 public void destroy() { 219 debug { 220 debugger.destroy(); 221 } 222 content.destroy(); 223 load_scenes([]); // end scenes 224 foreach (manager; managers) { 225 manager.destroy(); 226 } 227 if (!Core.headless) { 228 window.destroy(); 229 } 230 } 231 } 232 233 @("core-basic") 234 unittest { 235 import re.util.test : TestGame; 236 import std.string : format; 237 import std.math : approxEqual; 238 239 class Game : TestGame { 240 override void initialize() { 241 // nothing much 242 } 243 } 244 245 auto game = new Game(); 246 game.run(); 247 248 // ensure time has passed 249 auto target_time = Core.frame_limit / Core.target_fps; 250 assert(approxEqual(Time.total_time, target_time), 251 format("time did not pass (expected: %s, actual: %s)", target_time, Time.total_time)); 252 253 game.destroy(); // clean up 254 255 assert(game.scenes.length == 0, "scenes were not removed after Game cleanup"); 256 }