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 }