galaxy 1.0.0
Real-Time C++23 Game Programming Framework. Built on data-driven design principles and agile software engineering.
Loading...
Searching...
No Matches
Application.cpp
Go to the documentation of this file.
1
7
8#include <format>
9
10#include <BS_thread_pool.hpp>
11#include <entt/signal/dispatcher.hpp>
12#include <glad/glad.h>
13#include <mimalloc.h>
14#include <SDL3/SDL.h>
15#include <sol/sol.hpp>
16
25#include "galaxy/lua/Lua.hpp"
29#include "galaxy/time/Time.hpp"
30
31#include "Application.hpp"
32
33using namespace std::chrono_literals;
34
35namespace galaxy
36{
37 App::App(const std::string& config_file)
38 {
39 SDL_SetMemoryFunctions(&mi_malloc, &mi_calloc, &mi_realloc, &mi_free);
40
43 setup_config(config_file);
45 setup_fs();
48 // setup_nuklear();
49 // setup_loader();
50 setup_meta();
53
54 // Load game assets.
55 // core::entt::locator<core::Loader>::ref().load_all();
56
57 m_update = [&](entt::dispatcher& dispatcher, Window& window, World& world) {
58 // nui.begin_input();
59 window.process_events(dispatcher);
60 // nui.end_input();
61 world.update();
62 };
63
64 m_render = [&](entt::dispatcher& dispatcher, Window& window, World& world) {
65 // graphics::renderer::begin();
66 world.render();
67 // graphics::renderer::end();
68 };
69 }
70
72 {
73 entt::locator<VirtualFileSystem>::reset();
74 entt::locator<Config>::reset();
75 entt::locator<BS::light_thread_pool>::reset();
76
77 GALAXY_LOG(GALAXY_INFO, "Application closed.");
78 entt::locator<Log>::reset();
79
80 SDL_Quit();
81 }
82
83 /*void App::load()
84 {
85 const auto path = Settings::root_dir() / Settings::asset_pack();
86
87 auto& sm = entt::locator<scene::SceneManager>::value();
88 sm.load_app(path.string());
89 }*/
90
91 void App::run()
92 {
93 // https://stackoverflow.com/a/59446610
94 // We dont need 't' or 'alpha/render' sections.
95
96 auto& dispatcher = entt::locator<entt::dispatcher>::value();
97 auto& window = entt::locator<Window>::value();
98 auto& world = entt::locator<World>::value();
99
100 // The expression dt/1s simply converts the double-based chrono seconds
101 // into a double so it can participate in the physics computation.
102 constexpr const auto dt = std::chrono::duration<long long, std::ratio<1, 60>> {1};
103 time::dt(dt / 1.0s);
104
105 using clock = std::chrono::steady_clock;
106 using duration = decltype(clock::duration {} + dt);
107 using time_point = std::chrono::time_point<clock, duration>;
108
109 duration accum = 0s;
110 time_point prev = clock::now();
111 time_point now = clock::now();
112
113 auto updates = 0u;
114 auto frames = 0u;
115 duration perf = 0s;
116
117 while (window.is_open())
118 {
119 now = clock::now();
120 auto elapsed = now - prev;
121
122 // 250ms is the limit put in place on the frame time to cope with the spiral of death.
123 // It doesn't have to be 250ms exactly but it should be sufficiently high enough to deal with spikes in load.
124 if (elapsed > 250ms)
125 {
126 elapsed = 250ms;
127 }
128
129 prev = now;
130 accum += elapsed;
131
132 while (accum >= dt)
133 {
134 perf += dt;
135 accum -= dt;
136
137 m_update(dispatcher, window, world);
138 updates++;
139 }
140
141 m_render(dispatcher, window, world);
142 frames++;
143
144 if (perf >= 1s)
145 {
146 window.append_title(std::format(" | UPS: {0}, FPS: {1}", updates, frames));
147
148 frames = 0;
149 updates = 0;
150 perf = 0s;
151 }
152 }
153 }
154
156 {
157 m_update = std::move(update);
158 }
159
161 {
162 m_render = std::move(render);
163 }
164
166 {
167 platform::configure_terminal();
168 if (!std::filesystem::exists(Settings::log_dir()))
169 {
170 std::filesystem::create_directory(Settings::log_dir());
171 }
172 entt::locator<Log>::emplace();
173
174 const auto path = std::format("{0}{1}{2}", Settings::log_dir(), std::format("{0:%d-%m-%Y-[%H-%M]}", time::now()), ".log");
176
178 GALAXY_LOG(GALAXY_INFO, "App started.");
179 }
180
182 {
183 // Configure threadpool.
184 // Use half of available cores minus 2 for audio and main thread.
185 const auto system_cores = std::thread::hardware_concurrency();
186 if (system_cores < 4)
187 {
188 GALAXY_LOG(GALAXY_WARN, "Total cores are less than 4, this is not optimal.");
189 }
190
191 const auto cores = static_cast<int>(std::floor(system_cores / 2.0) - 2);
192 entt::locator<BS::light_thread_pool>::emplace(cores);
193 }
194
195 void App::setup_config(std::string_view config_file)
196 {
197 auto& config = entt::locator<Config>::emplace(config_file);
200 }
201
203 {
205
206 platform::set_metadata(SDL_PROP_APP_METADATA_NAME_STRING, Settings::title().c_str());
207 platform::set_metadata(SDL_PROP_APP_METADATA_VERSION_STRING, Settings::version().c_str());
208 platform::set_metadata(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, Settings::identifier().c_str());
209 platform::set_metadata(SDL_PROP_APP_METADATA_CREATOR_STRING, Settings::creator().c_str());
210 platform::set_metadata(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, Settings::copyright().c_str());
211 platform::set_metadata(SDL_PROP_APP_METADATA_URL_STRING, Settings::website().c_str());
212 platform::set_metadata(SDL_PROP_APP_METADATA_TYPE_STRING, "game");
213
214 platform::set_hint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "1");
215 platform::set_hint(SDL_HINT_ASSERT, "break");
216 platform::set_hint(SDL_HINT_AUDIO_CATEGORY, "playback");
217 platform::set_hint(SDL_HINT_AUDIO_CHANNELS, "2");
218 platform::set_hint(SDL_HINT_AUDIO_INCLUDE_MONITORS, "0");
219 platform::set_hint(SDL_HINT_AUTO_UPDATE_JOYSTICKS, "1");
220 platform::set_hint(SDL_HINT_AUTO_UPDATE_SENSORS, "1");
221 platform::set_hint(SDL_HINT_LOGGING, "warn");
222 platform::set_hint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
223 platform::set_hint(SDL_HINT_EVENT_LOGGING, "0");
224 platform::set_hint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "opengl");
225 platform::set_hint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
226 platform::set_hint(SDL_HINT_GPU_DRIVER, "vulkan");
227 platform::set_hint(SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS, "1");
228 platform::set_hint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
229 platform::set_hint(SDL_HINT_JOYSTICK_DIRECTINPUT, "1");
230 platform::set_hint(SDL_HINT_TV_REMOTE_AS_JOYSTICK, "1");
231 platform::set_hint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
232 platform::set_hint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
233 platform::set_hint(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0");
234 platform::set_hint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
235 platform::set_hint(SDL_HINT_PEN_MOUSE_EVENTS, "1");
236 platform::set_hint(SDL_HINT_PEN_TOUCH_EVENTS, "1");
237 platform::set_hint(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "1");
238 platform::set_hint(SDL_HINT_RENDER_DRIVER, "opengl");
239 platform::set_hint(SDL_HINT_RENDER_GPU_DEBUG, "0");
240 platform::set_hint(SDL_HINT_RENDER_GPU_LOW_POWER, "0");
241 platform::set_hint(SDL_HINT_TOUCH_MOUSE_EVENTS, "1");
242 platform::set_hint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "0");
243 platform::set_hint(SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL, "0");
244 platform::set_hint(SDL_HINT_XINPUT_ENABLED, "1");
245 platform::set_hint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "0");
246 platform::set_hint(SDL_HINT_VIDEO_DUMMY_SAVE_FRAMES, "0");
247 platform::set_hint(SDL_HINT_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE, "1");
248 platform::set_hint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
249 platform::set_hint(SDL_HINT_VIDEO_OFFSCREEN_SAVE_FRAMES, "0");
250 platform::set_hint(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, "0");
251 platform::set_hint(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, "1");
252 platform::set_hint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1");
253 platform::set_hint(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, "0");
254 platform::set_hint(SDL_HINT_WINDOWS_GAMEINPUT, "1");
255 platform::set_hint(SDL_HINT_WINDOWS_RAW_KEYBOARD, "0");
256 // platform::set_hint(SDL_HINT_IME_IMPLEMENTED_UI, "0");
257
258 const auto vsync = Settings::vsync() ? "1" : "0";
259 platform::set_hint(SDL_HINT_RENDER_VSYNC, vsync);
260
261 const auto audio_freq = std::to_string(Settings::audio_freq());
262 platform::set_hint(SDL_HINT_AUDIO_FREQUENCY, audio_freq.c_str());
263
264 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_EVENTS | SDL_INIT_HAPTIC | SDL_INIT_SENSOR))
265 {
266 GALAXY_LOG(GALAXY_FATAL, SDL_GetError());
267 }
268 }
269
271 {
272 entt::locator<VirtualFileSystem>::emplace();
273 }
274
276 {
277 auto& window = entt::locator<Window>::emplace();
278
279 window.set_icon(Settings::window_icon());
280 window.show();
281
282 auto& sampler = entt::locator<Sampler>::emplace();
283
284 // Need to create our default texture sampler object.
285 if (Settings::mipmap())
286 {
287 switch (Settings::texture_filter())
288 {
290 sampler.set(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
291 sampler.set(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
292 break;
294 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
295 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
296 break;
298 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
299 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
300 break;
301 }
302 }
303 else
304 {
306 {
307 sampler.set(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
308 sampler.set(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
309 }
310 else
311 {
312 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
313 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
314 }
315 }
316
317 auto ansio = std::clamp(Settings::ansiotrophy(), 1, 16);
318 if (ansio == 3)
319 {
320 ansio = 4;
321 }
322 else if (ansio > 4 && ansio < 8)
323 {
324 ansio = 8;
325 }
326 else
327 {
328 ansio = 16;
329 }
330 sampler.setf(GL_TEXTURE_MAX_ANISOTROPY, static_cast<float>(ansio));
331
332 sampler.set(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
333 sampler.set(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
334 sampler.set(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
335 sampler.set(GL_TEXTURE_LOD_BIAS, GL_NONE);
336 }
337
339 {
340 entt::locator<entt::dispatcher>::emplace();
341 }
342
343 // void App::setup_nuklear()
344 //{
345 // auto& nui = ServiceLocator<ui::NuklearUI>::make();
346 // }
347
348 // void App::setup_loader()
349 //{
350 // // entt::locator<Loader>::make();
351 // }
352
354 {
355 auto& sf = entt::locator<SystemFactory>::emplace();
356 // auto& ef = entt::locator<meta::EntityFactory>::emplace();
357
358 /*ef.register_component<components::Tag>("Tag");
359
360 em.register_component<components::Animated>("Animated");
361 em.register_component<components::Circle>("Circle");
362 em.register_component<components::Ellipse>("Ellipse");
363 em.register_component<components::Point>("Point");
364 em.register_component<components::Polygon>("Polygon");
365 em.register_component<components::Polyline>("Polyline");
366 em.register_component<components::RigidBody>("RigidBody");
367 em.register_component<components::Script>("Script");
368 em.register_component<components::Sprite>("Sprite");
369 em.register_component<components::Text>("Text");
370 em.register_component<components::TileMap>("TileMap");
371 em.register_component<components::Transform>("Transform");
372 em.register_component<flags::DenySerialization>("DenySerialization");
373 em.register_component<flags::Disabled>("Disabled");
374
375 em.register_dependencies<components::Animated, components::Sprite>();
376 em.register_dependencies<components::Circle, components::Transform>();
377 em.register_dependencies<components::Ellipse, components::Transform>();
378 em.register_dependencies<components::Point, components::Transform>();
379 em.register_dependencies<components::Polygon, components::Transform>();
380 em.register_dependencies<components::Polyline, components::Transform>();
381 em.register_dependencies<components::RigidBody, components::Transform>();
382 em.register_dependencies<components::Sprite, components::Transform>();
383 em.register_dependencies<components::Text, components::Transform>();
384 */
385 }
386
388 {
389 // entt::locator<media::SoundEngine>::make(listener_count);
390 // entt::locator<media::MusicEngine>::make(listener_count);
391 // entt::locator<media::VoiceEngine>::make(listener_count);
392 // entt::locator<resource::SoundCache>::make();
393 // entt::locator<resource::MusicCache>::make();
394 // entt::locator<resource::VoiceCache>::make();
395 // entt::locator<resource::VideoCache>::make();
396 // entt::locator<resource::Animations>::make();
397 // entt::locator<resource::Shaders>::make();
398 // entt::locator<resource::Fonts>::make();
399 // entt::locator<resource::Textures>::make();
400 // entt::locator<resource::Prefabs>::make();
401 // entt::locator<resource::Scripts>::make();
402 entt::locator<World>::emplace();
403 }
404
406 {
407 auto& lua = entt::locator<sol::state>::emplace();
408 lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::coroutine, sol::lib::string, sol::lib::os, sol::lib::math, sol::lib::table, sol::lib::io, sol::lib::utf8);
409
410 //
411 // Add external libraries to Lua.
412 // Inject all configured galaxy into Lua.
413 // Add engine services to lua.
414 //
415 Lua::inject();
416 }
417} // namespace galaxy
#define GALAXY_ADD_SINK(sink,...)
Definition Log.hpp:27
#define GALAXY_INFO
Log.hpp galaxy.
Definition Log.hpp:22
#define GALAXY_LOG(level, msg,...)
Definition Log.hpp:28
#define GALAXY_WARN
Definition Log.hpp:23
#define GALAXY_FATAL
Definition Log.hpp:25
std::move_only_function< void(entt::dispatcher &, Window &, World &)> LoopFunc
Defines a callback for update() or render() loops in app.run().
void setup_config(std::string_view config_file)
void run()
Loads the default appdata file.
App(const std::string &config_file="config.json")
Default constructor.
void setup_logging()
void set_render_func(LoopFunc &&render)
Use a custom rendering step in game loop.
void setup_async()
void setup_events()
void setup_rendering()
void setup_meta()
void set_update_func(LoopFunc &&update)
Use a custom update step in game loop.
void setup_services()
void setup_scripting()
void setup_platform()
LoopFunc m_render
Render step in gameloop.
~App()
Destructor.
LoopFunc m_update
Update step in gameloop.
Logs a message to the console.
static void inject() noexcept
Inject everything into Lua.
Definition Lua.cpp:12
RAII Window. Handles events, input & display.
Definition Window.hpp:33
Scene, Entity and global game management.
Definition World.hpp:20
void seed_random() noexcept
Seed the cstdlib rng algos.
Definition Platform.cpp:20
void set_metadata(const char *type, const char *value) noexcept
Sets metadata.
Definition Platform.cpp:25
void set_hint(const char *hint, const char *value) noexcept
Sets SDL hints.
Definition Platform.cpp:30
double dt() noexcept
Get galaxy delta time.
Definition Time.cpp:26
auto now() noexcept -> std::chrono::local_time< std::chrono::system_clock::duration >
Current local time.
Definition Time.cpp:16
Animated.cpp galaxy.
Definition Animated.cpp:16
@ NEAREST
Nearest-neighbour.
static auto window_icon() noexcept -> const std::string &
Window icon file in vfs.
Definition Settings.cpp:139
static auto log_dir() noexcept -> const std::string &
Current root directory of application, unless it has been changed.
Definition Settings.cpp:244
static auto ansiotrophy() noexcept -> int
Ansiotropic filtering level.
Definition Settings.cpp:194
static auto copyright() noexcept -> const std::string &
Copyright message.
Definition Settings.cpp:234
static auto vsync() noexcept -> bool
Vsync control.
Definition Settings.cpp:154
static auto mipmap() noexcept -> bool
Mipmapping.
Definition Settings.cpp:199
static auto set_settings_from_config() -> void
Set all our settings using the provided config file.
Definition Settings.cpp:76
static auto identifier() noexcept -> const std::string &
Game identifier i.e. com.galaxy.app.
Definition Settings.cpp:224
static auto texture_filter() noexcept -> GLTextureFilter
Texture filtering type.
Definition Settings.cpp:204
static auto title() noexcept -> const std::string &
Game title.
Definition Settings.cpp:214
static auto version() noexcept -> const std::string &
Game semver.
Definition Settings.cpp:219
static auto audio_freq() noexcept -> int
Set audio frequency to use with SDL.
Definition Settings.cpp:189
static auto website() noexcept -> const std::string &
Website URL.
Definition Settings.cpp:239
static auto set_config_to_default() -> void
Restore all config settings to default.
Definition Settings.cpp:23
static auto creator() noexcept -> const std::string &
Owner.
Definition Settings.cpp:229