Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
application.cpp
Go to the documentation of this file.
1#include <pch.hpp>
2
4
5#include <helios/app/details/profile.hpp>
7#include <helios/assert.hpp>
9
10#include <algorithm>
11#include <atomic>
12#include <chrono>
13#include <string>
14
15#if defined(HELIOS_APP_ENABLE_PROFILE) && \
16 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
17#include <format>
18#endif
19
20namespace helios::app {
21
22namespace {
23
24constexpr auto kPluginReadyPollMinSleep = std::chrono::microseconds(50);
25constexpr auto kPluginReadyPollMaxSleep = std::chrono::microseconds(8000);
26
27} // namespace
28
30 : main_sub_app_(std::string(SubAppNameOf(kMainSubApp))),
31 runner_(RunDefault) {
32 RegisterBuiltinSchedules(main_sub_app_.GetScheduler());
33}
34
35App::App(size_t worker_thread_count)
36 : executor_(worker_thread_count),
37 main_sub_app_(std::string(SubAppNameOf(kMainSubApp))),
38 runner_(RunDefault) {
39 RegisterBuiltinSchedules(main_sub_app_.GetScheduler());
40}
41
42void App::Clear() {
43 HELIOS_ASSERT(!IsRunning(), "Cannot clear app while it is running!");
44
45 is_initialized_ = false;
46 is_running_.store(false, std::memory_order_relaxed);
47 plugins_.clear();
48 dynamic_plugins_.clear();
49 scheduler_.Clear();
50 main_sub_app_.Clear();
51 sub_apps_.clear();
52
53 RegisterBuiltinSchedules(main_sub_app_.GetScheduler());
54}
55
57 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::Initialize");
58
59 HELIOS_ASSERT(!IsInitialized(), "App is already initialized!");
60 HELIOS_ASSERT(!IsRunning(), "Cannot initialize while app is running!");
61
62 BuildPlugins();
63 WaitForPluginsReady();
64 FinishPlugins();
65
66 RegisterMessages();
67
68 scheduler_.Build(*this);
69 scheduler_.RunStartup(*this);
70
71 is_initialized_ = true;
72}
73
75 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::Run");
76
77 HELIOS_ASSERT(!IsRunning(), "App is already running!");
78
79 log::Info("Starting application...");
80
81 Initialize();
82
83 is_running_.store(true, std::memory_order_release);
84 const ExitCode exit_code = runner_(*this);
85 is_running_.store(false, std::memory_order_release);
86
87 CleanUp();
88
89 return exit_code;
90}
91
92auto App::ShouldExit() const noexcept -> std::optional<ExitCode> {
93 const auto& world = GetWorld();
94 if (!world.HasMessage<AppExit>()) [[unlikely]] {
95 return std::nullopt;
96 }
97
98 const auto reader = world.ReadMessages<AppExit>();
99 if (reader.Empty()) {
100 return std::nullopt;
101 }
102
103 const bool any_failure = std::ranges::any_of(
104 reader, [](auto wrapper) { return wrapper->code == ExitCode::kFailure; });
105
106 return any_failure ? ExitCode::kFailure : ExitCode::kSuccess;
107}
108
109void App::CleanUp() {
110 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::Clear");
111
112 if (!IsInitialized()) {
113 return;
114 }
115
116 scheduler_.WaitForSubApps();
117 scheduler_.Shutdown(*this);
118 DestroyPlugins();
119
120 is_initialized_ = false;
121 is_running_.store(false, std::memory_order_relaxed);
122}
123
124void App::RegisterMessages() {
125 main_sub_app_.AddMessages<AppExit>();
126 for (auto&& [_, sub_app] : sub_apps_) {
127 sub_app.AddMessages<AppExit>();
128 }
129}
130
131void App::BuildPlugins() {
132 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::BuildPlugins");
133
134#if defined(HELIOS_APP_ENABLE_PROFILE) && \
135 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
136 std::string zone_name = "name::Build"; // Plugin name + "::Poll"
137#endif
138
139 for (auto&& [index, storage] : plugins_) {
140 HELIOS_APP_PROFILE_SCOPE();
141#if defined(HELIOS_APP_ENABLE_PROFILE) && \
142 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
143 zone_name.clear();
144 std::format_to(std::back_inserter(zone_name),
145 "helios::app::Plugin::Build{{name: {}}}", storage.name);
146#endif
147 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
148
149 storage.plugin->Build(*this);
150 }
151
152 for (auto&& [index, dynamic_plugin] : dynamic_plugins_) {
153 if (dynamic_plugin.Loaded()) [[likely]] {
154 HELIOS_APP_PROFILE_SCOPE();
155#if defined(HELIOS_APP_ENABLE_PROFILE) && \
156 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
157 zone_name.clear();
158 std::format_to(std::back_inserter(zone_name),
159 "helios::app::Plugin::Build{{name: {}}}",
160 dynamic_plugin.GetPluginName());
161#endif
162 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
163 HELIOS_APP_PROFILE_ZONE_TEXT("dynamic");
164
165 dynamic_plugin.GetPlugin().Build(*this);
166 }
167 }
168}
169
170void App::PollPlugins() {
171 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::PollPlugins");
172
173#if defined(HELIOS_APP_ENABLE_PROFILE) && \
174 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
175 std::string zone_name = "name::Poll"; // Plugin name + "::Poll"
176#endif
177
178 for (auto&& [index, storage] : plugins_) {
179 HELIOS_APP_PROFILE_SCOPE();
180#if defined(HELIOS_APP_ENABLE_PROFILE) && \
181 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
182 zone_name.clear();
183 std::format_to(std::back_inserter(zone_name),
184 "helios::app::Plugin::Poll{{name: {}}}", storage.name);
185#endif
186 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
187
188 storage.plugin->Poll(*this);
189 }
190
191 for (auto&& [index, dynamic_plugin] : dynamic_plugins_) {
192 if (dynamic_plugin.Loaded()) [[likely]] {
193 HELIOS_APP_PROFILE_SCOPE();
194#if defined(HELIOS_APP_ENABLE_PROFILE) && \
195 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
196 zone_name.clear();
197 std::format_to(std::back_inserter(zone_name),
198 "helios::app::Plugin::Poll{{name: {}}}",
199 dynamic_plugin.GetPluginName());
200#endif
201 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
202 HELIOS_APP_PROFILE_ZONE_TEXT("dynamic");
203
204 dynamic_plugin.GetPlugin().Poll(*this);
205 }
206 }
207}
208
209void App::WaitForPluginsReady() {
210 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::WaitForPluginsReady");
211
212 if (PluginsReady()) {
213 return;
214 }
215
216 auto sleep_duration = kPluginReadyPollMinSleep;
217
218 while (!PluginsReady()) {
219 PollPlugins();
220
221 {
222 std::unique_lock lock(plugins_ready_mutex_);
223 plugins_ready_cv_.wait_for(lock, sleep_duration,
224 [this] { return PluginsReady(); });
225 }
226
227 sleep_duration = std::min(sleep_duration * 2, kPluginReadyPollMaxSleep);
228 }
229}
230
231bool App::PluginsReady() const {
232 for (const auto& [index, storage] : plugins_) {
233 if (!storage.plugin->IsReady(*this)) {
234 return false;
235 }
236 }
237
238 return std::ranges::all_of(dynamic_plugins_, [this](const auto& pair) {
239 return !pair.second.Loaded() || pair.second.GetPlugin().IsReady(*this);
240 });
241}
242
243void App::FinishPlugins() {
244 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::FinishPlugins");
245
246#if defined(HELIOS_APP_ENABLE_PROFILE) && \
247 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
248 std::string zone_name = "name::Poll"; // Plugin name + "::Poll"
249#endif
250
251 for (auto&& [index, storage] : plugins_) {
252 HELIOS_APP_PROFILE_SCOPE();
253#if defined(HELIOS_APP_ENABLE_PROFILE) && \
254 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
255 zone_name.clear();
256 std::format_to(std::back_inserter(zone_name),
257 "helios::app::Plugin::Finish{{name: {}}}", storage.name);
258#endif
259 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
260
261 storage.plugin->Finish(*this);
262 }
263
264 for (auto&& [index, dynamic_plugin] : dynamic_plugins_) {
265 if (dynamic_plugin.Loaded()) [[likely]] {
266 HELIOS_APP_PROFILE_SCOPE();
267#if defined(HELIOS_APP_ENABLE_PROFILE) && \
268 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
269 zone_name.clear();
270 std::format_to(std::back_inserter(zone_name),
271 "helios::app::Plugin::Finish{{name: {}}}",
272 dynamic_plugin.GetPluginName());
273#endif
274 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
275 HELIOS_APP_PROFILE_ZONE_TEXT("dynamic");
276
277 dynamic_plugin.GetPlugin().Finish(*this);
278 }
279 }
280}
281
282void App::DestroyPlugins() {
283 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::DestroyPlugins");
284
285#if defined(HELIOS_APP_ENABLE_PROFILE) && \
286 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
287 std::string zone_name = "name::Poll"; // Plugin name + "::Poll"
288#endif
289
290 for (auto&& [index, storage] : plugins_) {
291 HELIOS_APP_PROFILE_SCOPE();
292#if defined(HELIOS_APP_ENABLE_PROFILE) && \
293 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
294 zone_name.clear();
295 std::format_to(std::back_inserter(zone_name),
296 "helios::app::Plugin::Destroy{{name: {}}}", storage.name);
297#endif
298 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
299
300 storage.plugin->Destroy(*this);
301 }
302
303 for (auto&& [index, dynamic_plugin] : dynamic_plugins_) {
304 if (dynamic_plugin.Loaded()) [[likely]] {
305 HELIOS_APP_PROFILE_SCOPE();
306#if defined(HELIOS_APP_ENABLE_PROFILE) && \
307 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
308 zone_name.clear();
309 std::format_to(std::back_inserter(zone_name),
310 "helios::app::Plugin::Destroy{{name: {}}}",
311 dynamic_plugin.GetPluginName());
312#endif
313 HELIOS_APP_PROFILE_ZONE_NAME(zone_name);
314 HELIOS_APP_PROFILE_ZONE_TEXT("dynamic");
315
316 dynamic_plugin.GetPlugin().Destroy(*this);
317 }
318 }
319}
320
321} // namespace helios::app
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
auto ShouldExit() const noexcept -> std::optional< ExitCode >
Returns an exit code if an AppExit message was written this frame.
bool IsRunning() const noexcept
Checks if the app is currently running.
ecs::World & GetWorld() noexcept
Gets the main sub-app's ECS world.
void Initialize()
Initializes the application and its subsystems.
bool IsInitialized() const noexcept
Checks if the app has been initialized.
void Clear()
Clears the application state, removing all data.
ExitCode Run()
Runs the application with the given arguments.
void Shutdown(App &app)
Stops async loops, waits for in-flight updates, shuts down sub-apps, then the main sub-app.
Definition scheduler.cpp:92
void WaitForSubApps()
Waits until blocking sub-apps finish their frame update.
auto AddMessages(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Registers multiple message types on this sub-app's world.
Definition sub_app.hpp:762
ExitCode RunDefault(App &app)
Runs the application until an AppExit message is received.
Definition runners.hpp:66
constexpr std::string_view SubAppNameOf(const T &sub_app={}) noexcept
Returns the name of the sub-app.
Definition sub_app.hpp:85
ExitCode
Application exit codes.
@ kFailure
General failure.
@ kSuccess
Successful execution.
void RegisterBuiltinSchedules(ecs::Scheduler &scheduler)
Registers default application schedules on an ECS scheduler.
void Info(std::string_view message) noexcept
Logs an info message with the default logger.
Definition logger.hpp:483
STL namespace.
Message that requests the application to exit.