Skip to content

Commit 44dd620

Browse files
authored
Merge pull request #158 from OpenBrickProtocolFoundation/add-loading-screen
Add loading screen
2 parents 32b515d + e9f91c7 commit 44dd620

File tree

10 files changed

+467
-39
lines changed

10 files changed

+467
-39
lines changed

src/application.cpp

Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
#include "helper/message_box.hpp"
55
#include "helper/sleep.hpp"
66
#include "input/input.hpp"
7+
#include "manager/music_manager.hpp"
8+
#include "scenes/loading_screen/loading_screen.hpp"
79
#include "scenes/scene.hpp"
10+
#include "ui/layout.hpp"
811

912
#include <chrono>
13+
#include <fmt/chrono.h>
14+
#include <future>
1015
#include <memory>
1116
#include <ranges>
1217
#include <stdexcept>
@@ -35,9 +40,6 @@ Application::Application(std::shared_ptr<Window>&& window, const std::vector<std
3540
m_window{ std::move(window) },
3641
m_renderer{ *m_window, m_command_line_arguments.target_fps.has_value() ? Renderer::VSync::Disabled
3742
: Renderer::VSync::Enabled },
38-
m_music_manager{ this, num_audio_channels },
39-
m_input_manager{ std::make_shared<input::InputManager>(m_window) },
40-
m_settings_manager{ this },
4143
m_target_framerate{ m_command_line_arguments.target_fps } {
4244
initialize();
4345
} catch (const helper::GeneralError& general_error) {
@@ -156,7 +158,7 @@ void Application::handle_event(const SDL_Event& event) {
156158

157159
// this global event handlers (atm only one) are special cases, they receive all inputs if they are not handled by the scenes explicitly
158160

159-
if (m_music_manager.handle_event(m_input_manager, event)) {
161+
if (m_music_manager->handle_event(m_input_manager, event)) {
160162
return;
161163
}
162164
}
@@ -249,32 +251,121 @@ void Application::render() const {
249251

250252
void Application::initialize() {
251253

252-
load_resources();
253-
push_scene(scenes::create_scene(*this, SceneId::MainMenu, ui::FullScreenLayout{ *m_window }));
254+
auto loading_screen = scenes::LoadingScreen{ this };
255+
256+
const auto start_time = SDL_GetTicks64();
257+
258+
const std::future<void> load_everything = std::async(std::launch::async, [this] {
259+
this->m_music_manager = std::make_unique<MusicManager>(this, num_audio_channels);
260+
261+
this->m_input_manager = std::make_shared<input::InputManager>(this->m_window);
262+
263+
this->m_settings_manager = std::make_unique<SettingsManager>(this);
264+
265+
this->m_font_manager = std::make_unique<FontManager>();
266+
267+
this->load_resources();
254268

255269
#ifdef DEBUG_BUILD
256-
m_fps_text = std::make_unique<ui::Label>(
257-
this, "FPS: ?", font_manager().get(FontId::Default), Color::white(),
258-
std::pair<double, double>{ 0.95, 0.95 },
259-
ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center },
260-
ui::RelativeLayout{ window(), 0.0, 0.0, 0.1, 0.05 }, false
261-
);
270+
m_fps_text = std::make_unique<ui::Label>(
271+
this, "FPS: ?", font_manager().get(FontId::Default), Color::white(),
272+
std::pair<double, double>{ 0.95, 0.95 },
273+
ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center },
274+
ui::RelativeLayout{ window(), 0.0, 0.0, 0.1, 0.05 }, false
275+
);
262276
#endif
263277

264278
#if defined(_HAVE_DISCORD_SDK)
265-
if (m_settings_manager.settings().discord) {
266-
auto discord_instance = DiscordInstance::initialize();
267-
if (not discord_instance.has_value()) {
268-
spdlog::warn(
269-
"Error initializing the discord instance, it might not be running: {}", discord_instance.error()
270-
);
271-
} else {
272-
m_discord_instance = std::move(discord_instance.value());
273-
m_discord_instance->after_setup();
279+
if (m_settings_manager->settings().discord) {
280+
auto discord_instance = DiscordInstance::initialize();
281+
if (not discord_instance.has_value()) {
282+
spdlog::warn(
283+
"Error initializing the discord instance, it might not be running: {}", discord_instance.error()
284+
);
285+
} else {
286+
m_discord_instance = std::move(discord_instance.value());
287+
m_discord_instance->after_setup();
288+
}
274289
}
275-
}
276290

277291
#endif
292+
});
293+
294+
295+
using namespace std::chrono_literals;
296+
297+
const auto sleep_time = m_target_framerate.has_value() ? std::chrono::duration_cast<std::chrono::nanoseconds>(1s)
298+
/ m_target_framerate.value()
299+
: 0s;
300+
auto start_execution_time = std::chrono::steady_clock::now();
301+
302+
303+
bool finished_loading = false;
304+
305+
// this is a duplicate of below in some cases, but it's just for the loading screen and can't be factored out easily
306+
// this also only uses a subset of all things, the real event loop uses, so that nothing breaks while doing multithreading
307+
// the only things usable are: (since NOT accessed (writing) via the loading thread and already initialized):
308+
// - m_command_line_arguments
309+
// - m_window
310+
// - m_renderer
311+
// - m_target_framerate
312+
313+
while ((not finished_loading) and m_is_running
314+
#if defined(__CONSOLE__)
315+
and console::inMainLoop()
316+
#endif
317+
) {
318+
319+
// we can't use the normal event loop, so we have to do it manually
320+
SDL_Event event;
321+
while (SDL_PollEvent(&event) != 0) {
322+
if (event.type == SDL_QUIT) {
323+
m_is_running = false;
324+
}
325+
}
326+
327+
loading_screen.update();
328+
// this service_provider only guarantees the renderer + the window to be accessible without race conditions
329+
loading_screen.render(*this);
330+
331+
// present and wait (depending if vsync is on or not, this has to be done manually)
332+
m_renderer.present();
333+
334+
if (m_target_framerate.has_value()) {
335+
336+
const auto now = std::chrono::steady_clock::now();
337+
const auto runtime = (now - start_execution_time);
338+
if (runtime < sleep_time) {
339+
//TODO(totto): use SDL_DelayNS in sdl >= 3.0
340+
helper::sleep_nanoseconds(sleep_time - runtime);
341+
start_execution_time = std::chrono::steady_clock::now();
342+
} else {
343+
start_execution_time = now;
344+
}
345+
}
346+
// end waiting
347+
348+
// wait until is faster, since it just compares two time_points instead of getting now() and than adding the wait-for argument
349+
finished_loading =
350+
load_everything.wait_until(std::chrono::system_clock::time_point::min()) == std::future_status::ready;
351+
}
352+
353+
354+
const auto duration = std::chrono::milliseconds(SDL_GetTicks64() - start_time);
355+
356+
// we can reach this via SDL_QUIT or (not console::inMainLoop())
357+
if (not finished_loading) {
358+
359+
spdlog::debug("Aborted loading after {}", duration);
360+
361+
// just exit immediately, without cleaning up, since than we would have to cancel the loading thread somehow, which is way rto complicated, let the OS clean up our mess we create her xD
362+
std::exit(0);
363+
}
364+
365+
366+
spdlog::debug("Took {} to load", duration);
367+
368+
push_scene(scenes::create_scene(*this, SceneId::MainMenu, ui::FullScreenLayout{ *m_window }));
278369
}
279370

280371
void Application::load_resources() {
@@ -292,7 +383,7 @@ void Application::load_resources() {
292383
};
293384
for (const auto& [font_id, path] : fonts) {
294385
const auto font_path = utils::get_assets_folder() / "fonts" / path;
295-
m_font_manager.load(font_id, font_path, fonts_size);
386+
m_font_manager->load(font_id, font_path, fonts_size);
296387
}
297388
}
298389

src/application.hpp

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@ struct Application final : public EventListener, public ServiceProvider {
2121
private:
2222
static constexpr auto num_audio_channels = u8{ 2 };
2323

24+
bool m_is_running{ true };
2425
CommandLineArguments m_command_line_arguments;
2526
std::shared_ptr<Window> m_window;
2627
Renderer m_renderer;
27-
bool m_is_running{ true };
28-
MusicManager m_music_manager;
29-
std::shared_ptr<input::InputManager> m_input_manager;
30-
SettingsManager m_settings_manager;
31-
FontManager m_font_manager;
3228
helper::optional<u32> m_target_framerate;
3329

30+
// these fields are initalized asynchronously in a separate thread
31+
std::unique_ptr<MusicManager> m_music_manager;
32+
std::shared_ptr<input::InputManager> m_input_manager;
33+
std::unique_ptr<SettingsManager> m_settings_manager;
34+
std::unique_ptr<FontManager> m_font_manager;
35+
3436

3537
#ifdef DEBUG_BUILD
3638
std::unique_ptr<ui::Label> m_fps_text{ nullptr };
@@ -74,29 +76,35 @@ struct Application final : public EventListener, public ServiceProvider {
7476
}
7577

7678
FontManager& font_manager() override {
77-
return m_font_manager;
79+
return *m_font_manager;
7880
}
79-
const FontManager& font_manager() const override {
80-
return m_font_manager;
81+
82+
[[nodiscard]] const FontManager& font_manager() const override {
83+
return *m_font_manager;
8184
}
8285

8386
CommandLineArguments& command_line_arguments() override {
8487
return m_command_line_arguments;
8588
}
86-
const CommandLineArguments& command_line_arguments() const override {
89+
90+
[[nodiscard]] const CommandLineArguments& command_line_arguments() const override {
8791
return m_command_line_arguments;
8892
}
93+
8994
SettingsManager& settings_manager() override {
90-
return m_settings_manager;
95+
return *m_settings_manager;
9196
}
92-
const SettingsManager& settings_manager() const override {
93-
return m_settings_manager;
97+
98+
[[nodiscard]] const SettingsManager& settings_manager() const override {
99+
return *m_settings_manager;
94100
}
101+
95102
MusicManager& music_manager() override {
96-
return m_music_manager;
103+
return *m_music_manager;
97104
}
98-
const MusicManager& music_manager() const override {
99-
return m_music_manager;
105+
106+
[[nodiscard]] const MusicManager& music_manager() const override {
107+
return *m_music_manager;
100108
}
101109

102110
[[nodiscard]] const Renderer& renderer() const override {

src/helper/clock_source.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace {
88
[[nodiscard]] double elapsed_time() {
9-
return static_cast<double>(SDL_GetTicks()) / 1000.0;
9+
return static_cast<double>(SDL_GetTicks64()) / 1000.0;
1010
}
1111

1212
} // namespace
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#include "loading_screen.hpp"
2+
#include "game/graphic_helpers.hpp"
3+
#include "game/tetromino_type.hpp"
4+
#include "graphics/point.hpp"
5+
#include "graphics/rect.hpp"
6+
#include "graphics/renderer.hpp"
7+
#include "graphics/window.hpp"
8+
#include "helper/platform.hpp"
9+
#include "manager/service_provider.hpp"
10+
#include "scenes/logo/logo.hpp"
11+
#include "ui/layout.hpp"
12+
13+
#include <numbers>
14+
15+
scenes::LoadingScreen::LoadingScreen(ServiceProvider* service_provider)
16+
: m_segments{
17+
{ Mino{ Mino::GridPoint{ 0, 0 }, helper::TetrominoType::J }, 1.0 },
18+
{ Mino{ Mino::GridPoint{ 1, 0 }, helper::TetrominoType::L }, 1.0 },
19+
{ Mino{ Mino::GridPoint{ 2, 0 }, helper::TetrominoType::I }, 1.0 },
20+
{ Mino{ Mino::GridPoint{ 2, 1 }, helper::TetrominoType::O }, 1.0 },
21+
{ Mino{ Mino::GridPoint{ 2, 2 }, helper::TetrominoType::S }, 1.0 },
22+
{ Mino{ Mino::GridPoint{ 1, 2 }, helper::TetrominoType::T }, 1.0 },
23+
{ Mino{ Mino::GridPoint{ 0, 2 }, helper::TetrominoType::I }, 1.0 },
24+
{ Mino{ Mino::GridPoint{ 0, 1 }, helper::TetrominoType::Z }, 1.0 },
25+
},m_logo{logo::get_logo(service_provider)} {
26+
27+
const auto [total_x_tiles, total_y_tiles] = utils::get_orientation() == utils::Orientation::Landscape
28+
? std::pair<u32, u32>{ 17, 9 }
29+
: std::pair<u32, u32>{ 9, 17 };
30+
31+
constexpr auto loading_segments_size = 3;
32+
33+
const auto& window = service_provider->window();
34+
35+
const auto layout = window.size();
36+
37+
const u32 tile_size_x = layout.x / total_x_tiles;
38+
const u32 tile_size_y = layout.y / total_y_tiles;
39+
40+
m_tile_size = std::min(tile_size_y, tile_size_x);
41+
42+
const shapes::UPoint grid_start_offset = { (total_x_tiles - loading_segments_size) / 2,
43+
(total_y_tiles - loading_segments_size) / 2 };
44+
45+
m_start_offset = grid_start_offset * m_tile_size;
46+
47+
constexpr const auto logo_width_percentage = 0.8;
48+
49+
constexpr const auto start_x = (1.0 - logo_width_percentage) / 2.0;
50+
51+
const auto window_ratio = static_cast<double>(layout.x) / static_cast<double>(layout.y);
52+
53+
const auto logo_ratio = static_cast<double>(logo::height) / static_cast<double>(logo::width) * window_ratio;
54+
55+
const auto logo_height_percentage = logo_width_percentage * logo_ratio;
56+
57+
m_logo_rect = ui::RelativeLayout(window, start_x, 0.05, logo_width_percentage, logo_height_percentage).get_rect();
58+
}
59+
60+
namespace {
61+
[[nodiscard]] double elapsed_time() {
62+
return static_cast<double>(SDL_GetTicks64()) / 1000.0;
63+
}
64+
} // namespace
65+
66+
67+
void scenes::LoadingScreen::update() {
68+
69+
constexpr const auto speed = std::numbers::pi_v<double> * 1.0;
70+
constexpr const auto amplitude = 1.1;
71+
constexpr const auto scale_offset = 1.3;
72+
73+
const auto length = m_segments.size();
74+
const auto length_d = static_cast<double>(length);
75+
76+
const auto time = elapsed_time();
77+
78+
for (size_t i = 0; i < length; ++i) {
79+
80+
auto& segment = m_segments.at(i);
81+
82+
auto& scale = std::get<1>(segment);
83+
84+
const auto offset = std::numbers::pi_v<double> * 2.0 * static_cast<double>(length - i - 1) / length_d;
85+
86+
scale = std::min(amplitude * std::sin(time * speed + offset) + scale_offset, 1.0);
87+
}
88+
//
89+
}
90+
91+
void scenes::LoadingScreen::render(const ServiceProvider& service_provider) const {
92+
93+
service_provider.renderer().draw_rect_filled(service_provider.window().screen_rect(), Color::black());
94+
95+
service_provider.renderer().draw_texture(m_logo, m_logo_rect);
96+
97+
constexpr const auto scale_threshold = 0.25;
98+
99+
for (const auto& [mino, scale] : m_segments) {
100+
if (scale >= scale_threshold) {
101+
const auto original_scale =
102+
static_cast<double>(m_tile_size) / static_cast<double>(grid::original_tile_size);
103+
104+
105+
const auto tile_size = static_cast<u32>(static_cast<double>(m_tile_size) * scale);
106+
107+
helper::graphics::render_mino(
108+
mino, service_provider, MinoTransparency::Solid, original_scale,
109+
[this, tile_size](const Mino::GridPoint& point) -> auto {
110+
return this->to_screen_coords(point, tile_size);
111+
},
112+
{ tile_size, tile_size }
113+
);
114+
}
115+
116+
//TODO(Totto): render text here, but than we need to load the fonts before this, not in the loading thread (not that they take that long)
117+
}
118+
}
119+
120+
121+
[[nodiscard]] shapes::UPoint scenes::LoadingScreen::to_screen_coords(const Mino::GridPoint& point, u32 tile_size)
122+
const {
123+
const auto start_edge = m_start_offset + point.cast<u32>() * m_tile_size;
124+
const auto inner_offset = m_tile_size - tile_size / 2;
125+
return start_edge + shapes::UPoint{ inner_offset, inner_offset };
126+
}

0 commit comments

Comments
 (0)