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 }