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