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#define BS_THREAD_POOL_NATIVE_EXTENSIONS
9
10#include <filesystem>
11#include <format>
12#include <chrono>
13
14#include <BS_thread_pool.hpp>
15#include <entt/locator/locator.hpp>
16// #include <entt/signal/dispatcher.hpp>
17// #include <glad/glad.h>
18#include <mimalloc.h>
19#include <SDL3/SDL.h>
20// #include <sol/sol.hpp>
21
23// #include "galaxy/core/Window.hpp"
26// #include "galaxy/graphics/gl/Sampler.hpp"
27// #include "galaxy/input/Keyboard.hpp"
28// #include "galaxy/input/Mouse.hpp"
32// #include "galaxy/lua/Lua.hpp"
33// #include "galaxy/meta/SystemFactory.hpp"
35// #include "galaxy/scene/SceneManager.hpp"
36#include "galaxy/time/Time.hpp"
37
38#include "Application.hpp"
39
40using namespace std::chrono_literals;
41
42namespace galaxy
43{
44 App::App(const std::string& config_file)
45 {
46 SDL_SetMemoryFunctions(&mi_malloc, &mi_calloc, &mi_realloc, &mi_free);
47
50 setup_config(config_file);
52 setup_fs();
53 // setup_rendering();
54 // setup_events();
55 // setup_input();
56 // setup_nuklear();
57 // setup_loader();
58 // setup_meta();
59 // setup_services();
60 // setup_scripting();
61
62 // Load game assets.
63 // core::entt::locator<core::Loader>::ref().load_all();
64 }
65
67 {
68 entt::locator<BS::priority_thread_pool>::value().wait();
69
70 entt::locator<VirtualFileSystem>::reset();
71 entt::locator<Config>::reset();
72 entt::locator<Log>::reset();
73 entt::locator<BS::priority_thread_pool>::reset();
74
75 SDL_Quit();
76 }
77
78 /*void App::load()
79 {
80 const auto path = Settings::root_dir() / Settings::asset_pack();
81
82 auto& sm = entt::locator<scene::SceneManager>::value();
83 sm.load_app(path.string());
84 }*/
85
86 void App::run()
87 {
88 // https://stackoverflow.com/a/59446610
89 // We dont need 't' or 'alpha/render' sections.
90
91 // auto& window = entt::locator<Window>::value();
92 // auto& scenes = entt::locator<SceneManager>::value();
93
94 // The expression dt/1s simply converts the double-based chrono seconds
95 // into a double so it can participate in the physics computation.
96 constexpr const auto dt = std::chrono::duration<long long, std::ratio<1, 60>> {1};
97 time::dt(dt / 1.0s);
98
99 using clock = std::chrono::steady_clock;
100 using duration = decltype(clock::duration {} + dt);
101 using time_point = std::chrono::time_point<clock, duration>;
102
103 duration accum = 0s;
104 time_point prev = clock::now();
105 time_point now = clock::now();
106
107 auto updates = 0u;
108 auto frames = 0u;
109 duration perf = 0s;
110
111 if (!m_update)
112 {
113 m_update = [&](App* app) {
114 while (SDL_PollEvent(&m_events))
115 {
116 switch (m_events.type)
117 {
118 case SDL_EVENT_QUIT:
119 // window.close();
120 break;
121
122 case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
123 // if (m_events.window.windowID == SDL_GetWindowID(window.handle()))
124 {
125 // window.close();
126 }
127 break;
128
129 default:
130 // scenes.on_event(m_events);
131 break;
132 }
133 }
134
135 // scenes.update();
136 };
137 }
138
139 if (!m_render)
140 {
141 m_render = [&](App* app) {
142 // scenes.render();
143 // window.swap();
144 };
145 }
146
147 while (false) // window.is_open()
148 {
149 now = clock::now();
150 auto elapsed = now - prev;
151
152 // 250ms is the limit put in place on the frame time to cope with the spiral of death.
153 // It doesn't have to be 250ms exactly but it should be sufficiently high enough to deal with spikes in load.
154 if (elapsed > 250ms)
155 {
156 elapsed = 250ms;
157 }
158
159 prev = now;
160 accum += elapsed;
161
162 while (accum >= dt)
163 {
164 perf += dt;
165 accum -= dt;
166
167 m_update(this);
168
169 updates++;
170 }
171
172 m_render(this);
173
174 frames++;
175
176 if (perf >= 1s)
177 {
178 // window.append_title(std::format(" | UPS: {0}, FPS: {1}", updates, frames));
179
180 frames = 0;
181 updates = 0;
182 perf = 0s;
183 }
184 }
185 }
186
188 {
189 m_update = std::move(update);
190 }
191
193 {
194 m_render = std::move(render);
195 }
196
197 SDL_Event& App::events() noexcept
198 {
199 return m_events;
200 }
201
203 {
204 // Configure threadpool.
205
206 // Calc threads.
207 // We optimize for 6: 1 for audio, 1 for main, 4 for tasks.
208 auto system_cores = std::thread::hardware_concurrency();
209 if (system_cores < 6)
210 {
211 system_cores = std::thread::hardware_concurrency();
212 }
213
214 // Check for highest available priority.
215 BS::set_os_process_priority(BS::os_process_priority::high);
216 entt::locator<BS::priority_thread_pool>::emplace(system_cores, [](const std::size_t idx) {
217 BS::this_thread::set_os_thread_priority(BS::os_thread_priority::highest);
218 });
219 }
220
222 {
223 platform::configure_terminal();
224 if (!std::filesystem::exists(Settings::logs_dir()))
225 {
226 std::filesystem::create_directory(Settings::logs_dir());
227 }
228 entt::locator<Log>::emplace();
229
230 const auto path = std::format("{0}{1}{2}", Settings::logs_dir(), std::format("{0:%d-%m-%Y-[%H-%M]}", time::now()), ".log");
232
234 GALAXY_LOG(GALAXY_INFO, "App started.");
235 }
236
237 void App::setup_config(std::string_view config_file)
238 {
239 auto& config = entt::locator<Config>::emplace(config_file);
242 }
243
245 {
247
248 platform::set_metadata(SDL_PROP_APP_METADATA_NAME_STRING, Settings::title().c_str());
249 platform::set_metadata(SDL_PROP_APP_METADATA_VERSION_STRING, Settings::version().c_str());
250 platform::set_metadata(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, Settings::identifier().c_str());
251 platform::set_metadata(SDL_PROP_APP_METADATA_CREATOR_STRING, Settings::creator().c_str());
252 platform::set_metadata(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, Settings::copyright().c_str());
253 platform::set_metadata(SDL_PROP_APP_METADATA_URL_STRING, Settings::website().c_str());
254 platform::set_metadata(SDL_PROP_APP_METADATA_TYPE_STRING, "game");
255
256 // https://wiki.libsdl.org/SDL3/CategoryHints
257 platform::set_hint(SDL_HINT_ALLOW_ALT_TAB_WHILE_GRABBED, "1");
258 platform::set_hint(SDL_HINT_ASSERT, "break");
259 platform::set_hint(SDL_HINT_AUDIO_CATEGORY, "playback");
260 platform::set_hint(SDL_HINT_AUDIO_CHANNELS, "2");
261 platform::set_hint(SDL_HINT_AUDIO_INCLUDE_MONITORS, "0");
262 platform::set_hint(SDL_HINT_AUTO_UPDATE_JOYSTICKS, "1");
263 platform::set_hint(SDL_HINT_AUTO_UPDATE_SENSORS, "1");
264 platform::set_hint(SDL_HINT_LOGGING, "warn");
265 platform::set_hint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
266 platform::set_hint(SDL_HINT_EVENT_LOGGING, "0");
267 platform::set_hint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "opengl");
268 platform::set_hint(SDL_HINT_GAMECONTROLLER_SENSOR_FUSION, "1");
269 platform::set_hint(SDL_HINT_GPU_DRIVER, "vulkan");
270 platform::set_hint(SDL_HINT_HIDAPI_ENUMERATE_ONLY_CONTROLLERS, "1");
271 platform::set_hint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
272 platform::set_hint(SDL_HINT_JOYSTICK_DIRECTINPUT, "1");
273 platform::set_hint(SDL_HINT_TV_REMOTE_AS_JOYSTICK, "1");
274 platform::set_hint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
275 platform::set_hint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
276 platform::set_hint(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0");
277 platform::set_hint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
278 platform::set_hint(SDL_HINT_MOUSE_DPI_SCALE_CURSORS, "1");
279 platform::set_hint(SDL_HINT_PEN_MOUSE_EVENTS, "1");
280 platform::set_hint(SDL_HINT_PEN_TOUCH_EVENTS, "1");
281 platform::set_hint(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "1");
282 platform::set_hint(SDL_HINT_RENDER_DRIVER, "opengl");
283 platform::set_hint(SDL_HINT_RENDER_GPU_DEBUG, "0");
284 platform::set_hint(SDL_HINT_RENDER_GPU_LOW_POWER, "0");
285 platform::set_hint(SDL_HINT_TOUCH_MOUSE_EVENTS, "1");
286 platform::set_hint(SDL_HINT_TRACKPAD_IS_TOUCH_ONLY, "0");
287 platform::set_hint(SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL, "0");
288 platform::set_hint(SDL_HINT_XINPUT_ENABLED, "1");
289 platform::set_hint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "0");
290 platform::set_hint(SDL_HINT_VIDEO_DUMMY_SAVE_FRAMES, "0");
291 platform::set_hint(SDL_HINT_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE, "1");
292 platform::set_hint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
293 platform::set_hint(SDL_HINT_VIDEO_OFFSCREEN_SAVE_FRAMES, "0");
294 platform::set_hint(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, "0");
295 platform::set_hint(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, "1");
296 platform::set_hint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1");
297 platform::set_hint(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, "0");
298 platform::set_hint(SDL_HINT_WINDOWS_GAMEINPUT, "1");
299 platform::set_hint(SDL_HINT_WINDOWS_RAW_KEYBOARD, "0");
300 platform::set_hint(SDL_HINT_IME_IMPLEMENTED_UI, "0");
301
302 const auto vsync = Settings::vsync() ? "1" : "0";
303 platform::set_hint(SDL_HINT_RENDER_VSYNC, vsync);
304
305 const auto audio_freq = std::to_string(Settings::audio_freq());
306 platform::set_hint(SDL_HINT_AUDIO_FREQUENCY, audio_freq.c_str());
307
308 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_EVENTS | SDL_INIT_HAPTIC | SDL_INIT_SENSOR))
309 {
310 GALAXY_LOG(GALAXY_FATAL, SDL_GetError());
311 }
312 }
313
315 {
316 entt::locator<VirtualFileSystem>::emplace();
317 }
318
319 /*
320 void App::setup_rendering()
321 {
322 auto& window = entt::locator<Window>::emplace();
323
324 window.set_icon(Settings::window_icon());
325 window.show();
326
327 auto& sampler = entt::locator<Sampler>::emplace();
328
329 // Need to create our default texture sampler object.
330 if (Settings::mipmap())
331 {
332 switch (Settings::texture_filter())
333 {
334 case GLTextureFilter::NEAREST:
335 sampler.set(GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
336 sampler.set(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
337 break;
338 case GLTextureFilter::BILINEAR:
339 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
340 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
341 break;
342 case GLTextureFilter::TRILINEAR:
343 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
344 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
345 break;
346 }
347 }
348 else
349 {
350 if (Settings::texture_filter() == GLTextureFilter::NEAREST)
351 {
352 sampler.set(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
353 sampler.set(GL_TEXTURE_MAG_FILTER, GL_NEAREST);
354 }
355 else
356 {
357 sampler.set(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
358 sampler.set(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
359 }
360 }
361
362 auto ansio = std::clamp(Settings::ansiotrophy(), 1, 16);
363 if (ansio == 3)
364 {
365 ansio = 4;
366 }
367 else if (ansio > 4 && ansio < 8)
368 {
369 ansio = 8;
370 }
371 else
372 {
373 ansio = 16;
374 }
375 sampler.setf(GL_TEXTURE_MAX_ANISOTROPY, static_cast<float>(ansio));
376
377 sampler.set(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
378 sampler.set(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
379 sampler.set(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
380 sampler.set(GL_TEXTURE_LOD_BIAS, GL_NONE);
381 }
382
383 void App::setup_events()
384 {
385 SDL_zero(m_events);
386 }
387
388 void App::setup_input()
389 {
390 auto& keyboard = entt::locator<Keyboard>::emplace();
391 auto& mouse = entt::locator<Mouse>::emplace();
392
393 mouse.set_cursor_custom(Settings::cursor_icon(), Settings::cursor_hotspot());
394 if (Settings::cursor_show())
395 {
396 mouse.show_cursor();
397 }
398 else
399 {
400 mouse.hide_cursor();
401 }
402 }
403
404 // void App::setup_nuklear()
405 //{
406 // auto& nui = ServiceLocator<ui::NuklearUI>::make();
407 // }
408
409 // void App::setup_loader()
410 //{
411 // // entt::locator<Loader>::make();
412 // }
413
414 void App::setup_meta()
415 {
416 auto& sf = entt::locator<SystemFactory>::emplace();
417 // auto& ef = entt::locator<meta::EntityFactory>::emplace();
418
419 /*ef.register_component<components::Tag>("Tag");
420
421 em.register_component<components::Animated>("Animated");
422 em.register_component<components::Circle>("Circle");
423 em.register_component<components::Ellipse>("Ellipse");
424 em.register_component<components::Point>("Point");
425 em.register_component<components::Polygon>("Polygon");
426 em.register_component<components::Polyline>("Polyline");
427 em.register_component<components::RigidBody>("RigidBody");
428 em.register_component<components::Script>("Script");
429 em.register_component<components::Sprite>("Sprite");
430 em.register_component<components::Text>("Text");
431 em.register_component<components::TileMap>("TileMap");
432 em.register_component<components::Transform>("Transform");
433 em.register_component<flags::DenySerialization>("DenySerialization");
434 em.register_component<flags::Disabled>("Disabled");
435
436 em.register_dependencies<components::Animated, components::Sprite>();
437 em.register_dependencies<components::Circle, components::Transform>();
438 em.register_dependencies<components::Ellipse, components::Transform>();
439 em.register_dependencies<components::Point, components::Transform>();
440 em.register_dependencies<components::Polygon, components::Transform>();
441 em.register_dependencies<components::Polyline, components::Transform>();
442 em.register_dependencies<components::RigidBody, components::Transform>();
443 em.register_dependencies<components::Sprite, components::Transform>();
444 em.register_dependencies<components::Text, components::Transform>();
445 }
446
447 void App::setup_services()
448 {
449 // entt::locator<media::SoundEngine>::make(listener_count);
450 // entt::locator<media::MusicEngine>::make(listener_count);
451 // entt::locator<media::VoiceEngine>::make(listener_count);
452 // entt::locator<resource::SoundCache>::make();
453 // entt::locator<resource::MusicCache>::make();
454 // entt::locator<resource::VoiceCache>::make();
455 // entt::locator<resource::VideoCache>::make();
456 // entt::locator<resource::Animations>::make();
457 // entt::locator<resource::Shaders>::make();
458 // entt::locator<resource::Fonts>::make();
459 // entt::locator<resource::Textures>::make();
460 // entt::locator<resource::Prefabs>::make();
461 // entt::locator<resource::Scripts>::make();
462 entt::locator<SceneManager>::emplace();
463 }
464
465 void App::setup_scripting()
466 {
467 auto& lua = entt::locator<sol::state>::emplace();
468 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);
469
470 //
471 // Add external libraries to Lua.
472 // Inject all configured galaxy into Lua.
473 // Add engine services to lua.
474 //
475 Lua::inject();
476 }*/
477} // namespace galaxy
#define GALAXY_ADD_SINK(sink,...)
Definition Log.hpp:28
#define GALAXY_INFO
Log.hpp galaxy.
Definition Log.hpp:23
#define GALAXY_LOG(level, msg,...)
Definition Log.hpp:29
#define GALAXY_FATAL
Definition Log.hpp:26
void setup_config(std::string_view config_file)
void run()
Loads the default appdata file.
SDL_Event & events() noexcept
Get event data.
App(const std::string &config_file="config.json")
Default constructor.
SDL_Event m_events
Core event data.
void setup_logging()
void set_render_func(LoopFunc &&render)
Use a custom rendering step in game loop.
void setup_async()
std::move_only_function< void(App *app)> LoopFunc
Defines a callback for update() or render() loops in app.run().
void set_update_func(LoopFunc &&update)
Use a custom update step in game loop.
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.
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:34
auto now() noexcept -> std::chrono::local_time< std::chrono::system_clock::duration >
Current local time.
Definition Time.cpp:18
Application.hpp galaxy.
static auto logs_dir() noexcept -> const std::string &
Current root directory of application, unless it has been changed.
Definition Settings.cpp:171
static auto copyright() noexcept -> const std::string &
Copyright message.
Definition Settings.cpp:161
static auto vsync() noexcept -> bool
Vsync control.
Definition Settings.cpp:131
static auto set_settings_from_config() -> void
Set all our settings using the provided config file.
Definition Settings.cpp:77
static auto identifier() noexcept -> const std::string &
Game identifier i.e. com.galaxy.app.
Definition Settings.cpp:151
static auto title() noexcept -> const std::string &
Game title.
Definition Settings.cpp:141
static auto version() noexcept -> const std::string &
Game semver.
Definition Settings.cpp:146
static auto audio_freq() noexcept -> int
Set audio frequency to use with SDL.
Definition Settings.cpp:136
static auto website() noexcept -> const std::string &
Website URL.
Definition Settings.cpp:166
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:156