Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
app.cpp
Go to the documentation of this file.
2
13
14#include <atomic>
15#include <cstddef>
16#include <future>
17#include <ranges>
18#include <utility>
19
20namespace helios::app {
21
22App::App() : runner_(DefaultRunnerWrapper) {}
23
24App::App(size_t worker_thread_count) : executor_(worker_thread_count), runner_(DefaultRunnerWrapper) {}
25
27 // Ensure we're not running (should already be false if properly shut down)
28 is_running_.store(false, std::memory_order_release);
29
30 // Wait for any pending overlapping sub-app updates
32
33 // Wait for all pending executor tasks to complete
34 executor_.WaitForAll();
35}
36
37void App::Clear() {
38 HELIOS_ASSERT(!IsRunning(), "Failed to clear app: Cannot clear app while it is running!");
39
40 WaitForOverlappingUpdates(); // Ensure all async work is done
41 sub_app_overlapping_futures_.clear();
42 main_sub_app_.Clear();
43 sub_apps_.clear();
44 sub_app_index_map_.clear();
45
46 is_initialized_ = false;
47}
48
50 HELIOS_ASSERT(!IsRunning(), "Failed to run: App is already running!");
51
52 HELIOS_INFO("Starting application...");
53
54 // Register built-in resources and events if not already registered
55 RegisterBuiltins();
56
57 BuildModules();
58 FinishModules();
59 Initialize();
60
61 // Run the application using the provided runner function
62 is_running_.store(true, std::memory_order_release);
63 const AppExitCode exit_code = runner_(*this);
64 is_running_.store(false, std::memory_order_release);
65
66 HELIOS_INFO("Cleaning up application...");
67 CleanUp();
68
69 executor_.WaitForAll();
70
71 DestroyModules();
72
73 // Reset state
74 is_initialized_ = false;
75 sub_app_overlapping_futures_.clear();
76 main_sub_app_.Clear();
77 sub_apps_.clear();
78 sub_app_index_map_.clear();
79
80 HELIOS_INFO("Application exiting with code: {}", std::to_underlying(exit_code));
81 return exit_code;
82}
83
85 // Clean up completed per-sub-app overlapping futures using cached ready flags
86 for (auto& [index, futures] : sub_app_overlapping_futures_) {
87 std::erase_if(futures, [](details::TrackedFuture& tracked) { return tracked.IsReady(); });
88 }
89
90 // Update main sub-app (synchronous)
91 main_sub_app_.Update(executor_);
92
93 // Process non-overlapping sub-apps
94 auto non_overlapping_future = executor_.Run(update_graph_);
95
96 // Launch overlapping sub-apps asynchronously (non-blocking)
97 for (size_t i = 0; i < sub_apps_.size(); ++i) {
98 auto& sub_app = sub_apps_[i];
99 if (!sub_app.AllowsOverlappingUpdates()) {
100 continue;
101 }
102
103 // Capture by reference is safe: App lifetime exceeds sub-app updates
104 auto future = executor_.Async([this, &sub_app]() {
105 sub_app.Extract(main_sub_app_.GetWorld());
106 sub_app.Update(executor_);
107 });
108
109 // Store as TrackedFuture for optimized ready-state tracking
110 sub_app_overlapping_futures_[i].push_back(details::TrackedFuture{.future = future.share(), .ready = false});
111 }
112
113 non_overlapping_future.Wait();
114}
115
117 HELIOS_ASSERT(!IsInitialized(), "Failed to initialize: App is already initialized!");
118 HELIOS_ASSERT(!IsRunning(), "Failed to initialize: Cannot initialize while app is running!");
119
120 async::TaskGraph initialization_graph;
121 initialization_graph.EmplaceTask([this]() {
122 main_sub_app_.BuildScheduler();
123 main_sub_app_.ExecuteStage<helios::app::StartUpStage>(executor_);
124 main_sub_app_.GetWorld().Update();
125 });
126
127 initialization_graph.ForEach(sub_apps_, [this](const SubApp& sub_app_ref) mutable {
128 // Need non-const access to modify sub_app
129 auto& sub_app = const_cast<SubApp&>(sub_app_ref);
130
131 sub_app.BuildScheduler();
132 sub_app.ExecuteStage<helios::app::StartUpStage>(executor_);
133 sub_app.GetWorld().Update();
134 });
135
136 auto future = executor_.Run(initialization_graph);
137
138 update_graph_.ForEach(sub_apps_, [this](const SubApp& sub_app_ref) mutable {
139 if (sub_app_ref.AllowsOverlappingUpdates()) {
140 return;
141 }
142
143 // Need non-const access to modify sub_app
144 auto& sub_app = const_cast<SubApp&>(sub_app_ref);
145 sub_app.Extract(main_sub_app_.GetWorld());
146 sub_app.Update(executor_);
147 });
148
149 future.Wait();
150
151 is_initialized_ = true;
152}
153
154void App::CleanUp() {
155 HELIOS_ASSERT(IsInitialized(), "Failed to clean up: App is not initialized!");
156 HELIOS_ASSERT(!IsRunning(), "Failed to clean up: Cannot clean up while app is running!");
157
158 // Wait for all overlapping sub-app updates to complete
160
161 async::TaskGraph cleanup_graph;
162 cleanup_graph.EmplaceTask([this]() { main_sub_app_.ExecuteStage<helios::app::CleanUpStage>(executor_); });
163 cleanup_graph.ForEach(sub_apps_, [this](const SubApp& sub_app_ref) mutable {
164 // Need non-const access to modify sub_app
165 auto& sub_app = const_cast<SubApp&>(sub_app_ref);
166 sub_app.ExecuteStage<helios::app::CleanUpStage>(executor_);
167 });
168
169 executor_.Run(cleanup_graph).Wait();
170}
171
173 for (auto& [index, futures] : sub_app_overlapping_futures_) {
174 for (auto& tracked : futures) {
175 tracked.Wait();
176 }
177 futures.clear();
178 }
179}
180
182 // Find the sub-app index by comparing addresses
183 auto sub_app_index = static_cast<size_t>(-1);
184 for (size_t i = 0; i < sub_apps_.size(); ++i) {
185 if (&sub_apps_[i] == &sub_app) {
186 sub_app_index = i;
187 break;
188 }
189 }
190
191 if (sub_app_index == static_cast<size_t>(-1)) [[unlikely]] {
192 return; // Sub-app not found
193 }
194
195 const auto futures_it = sub_app_overlapping_futures_.find(sub_app_index);
196 if (futures_it == sub_app_overlapping_futures_.end()) {
197 return; // No overlapping futures for this sub-app
198 }
199
200 for (auto& tracked : futures_it->second) {
201 tracked.Wait();
202 }
203 futures_it->second.clear();
204}
205
206void App::TickTime() noexcept {
207 if (main_sub_app_.GetWorld().HasResource<Time>()) [[likely]] {
208 main_sub_app_.GetWorld().WriteResource<Time>().Tick();
209 }
210}
211
212void App::BuildModules() {
213 HELIOS_ASSERT(!IsRunning(), "Failed to build modules: Cannot build modules while app is running!");
214
215 // Build static modules
216 for (auto& module : modules_) {
217 if (module) [[likely]] {
218 module->Build(*this);
219 }
220 }
221
222 // Build dynamic modules
223 for (auto& dyn_module : dynamic_modules_) {
224 if (dyn_module.Loaded()) [[likely]] {
225 dyn_module.GetModule().Build(*this);
226 }
227 }
228}
229
230void App::FinishModules() {
231 HELIOS_ASSERT(!IsRunning(), "Failed to finish modules: Cannot finish modules while app is running!");
232
233 // Poll until all modules are ready
234 bool all_ready = false;
235 while (!all_ready) {
236 all_ready = true;
237
238 // Check static modules
239 for (const auto& module : modules_) {
240 if (module && !module->IsReady(*this)) {
241 all_ready = false;
242 break;
243 }
244 }
245
246 // Check dynamic modules
247 if (all_ready) {
248 for (const auto& dyn_module : dynamic_modules_) {
249 if (dyn_module.Loaded() && !dyn_module.GetModule().IsReady(*this)) {
250 all_ready = false;
251 break;
252 }
253 }
254 }
255
256 // If not all ready, yield to allow async operations to complete
257 if (!all_ready) {
258 std::this_thread::yield();
259 }
260 }
261
262 // All modules are ready, call Finish on each
263 for (auto& module : modules_) {
264 if (module) [[likely]] {
265 module->Finish(*this);
266 }
267 }
268
269 for (auto& dyn_module : dynamic_modules_) {
270 if (dyn_module.Loaded()) [[likely]] {
271 dyn_module.GetModule().Finish(*this);
272 }
273 }
274}
275
276void App::DestroyModules() {
277 HELIOS_ASSERT(!IsRunning(), "Failed to destroy modules: Cannot destroy modules while app is running!");
278
279 // Destroy dynamic modules first (reverse order of addition for proper cleanup)
280 for (auto& dyn_module : dynamic_modules_ | std::views::reverse) {
281 if (dyn_module.Loaded()) [[likely]] {
282 dyn_module.GetModule().Destroy(*this);
283 }
284 }
285 dynamic_modules_.clear();
286 dynamic_module_index_map_.clear();
287
288 // Destroy static modules (reverse order of addition for proper cleanup)
289 for (auto& module : modules_ | std::views::reverse) {
290 if (module) [[likely]] {
291 module->Destroy(*this);
292 }
293 }
294 modules_.clear();
295 module_index_map_.clear();
296}
297
298void App::RegisterBuiltins() {
299 // Register Time resource if not already present
300 if (!HasResource<Time>()) {
301 EmplaceResource<Time>();
302 }
303
304 // Register ShutdownEvent if not already present
305 if (!HasEvent<ecs::ShutdownEvent>()) {
306 AddEvent<ecs::ShutdownEvent>();
307 }
308}
309
310AppExitCode App::DefaultRunnerWrapper(App& app) {
311 // Delegate to the DefaultRunner implementation
312 return helios::app::DefaultRunner(app);
313}
314
315} // namespace helios::app
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
bool IsRunning() const noexcept
Checks if the app is currently running.
Definition app.hpp:430
AppExitCode Run()
Runs the application with the given arguments.
Definition app.cpp:49
void Initialize()
Initializes the application and its subsystems.
Definition app.cpp:116
bool IsInitialized() const noexcept
Checks if the app has been initialized.
Definition app.hpp:424
void WaitForOverlappingUpdates()
Waits for all overlapping sub-app updates to complete.
Definition app.cpp:172
void Clear()
Clears the application state, removing all data.
Definition app.cpp:37
void TickTime() noexcept
Updates the Time resource for the current frame.
Definition app.cpp:206
~App()
Destructor that ensures all async work is completed before destruction.
Definition app.cpp:26
void Update()
Updates the application and its subsystems.
Definition app.cpp:84
A sub-application with its own ECS world, systems, and resources.
Definition sub_app.hpp:167
void Clear()
Clears the sub-app, removing all data.
Definition sub_app.hpp:512
const ecs::World & GetWorld() const noexcept
Gets const reference to this sub-app's world.
Definition sub_app.hpp:449
void Update(async::Executor &executor)
Updates this sub-app by executing all scheduled systems.
Definition sub_app.hpp:521
void BuildScheduler()
Builds execution graphs for all schedules.
Definition sub_app.hpp:575
void Extract(const ecs::World &main_world)
Extracts data from the main world into this sub-app.
Definition sub_app.hpp:567
bool AllowsOverlappingUpdates() const noexcept
Checks if this sub-app allows overlapping updates.
Definition sub_app.hpp:426
void ExecuteStage(async::Executor &executor)
Executes all schedules in a specific stage.
Definition sub_app.hpp:589
void WaitForAll()
Blocks until all submitted tasks complete.
Definition executor.hpp:298
auto Run(TaskGraph &graph) -> Future< void >
Runs a task graph once.
Definition executor.hpp:57
auto Async(C &&callable) -> std::future< std::invoke_result_t< C > >
Creates an asynchronous task that runs the given callable.
Definition executor.hpp:225
Represents a task dependency graph that can be executed by an Executor.
Task ForEach(const R &range, C &&callable)
Creates a parallel for-each task over the given range.
Task EmplaceTask(C &&callable)
Creates a static task with the given callable.
bool HasResource() const
Checks if a resource exists.
Definition world.hpp:534
T & WriteResource()
Gets reference to a resource.
Definition world.hpp:933
void Update()
Flushes buffer of pending operations.
Definition world.hpp:627
#define HELIOS_INFO(...)
Definition logger.hpp:685
AppExitCode
Application exit codes.
Definition app.hpp:87
AppExitCode DefaultRunner(App &app, DefaultRunnerConfig config={})
Definition runners.hpp:80
STL namespace.
CleanUpStage - cleanup/shutdown phase.
Definition schedules.hpp:64
CleanUp schedule - main cleanup.
StartUpStage - application initialization phase.
Definition schedules.hpp:26
Tracked future with ready flag to reduce syscall churn.
Definition app.hpp:45
bool IsReady() noexcept
Checks if the future is ready, caching the result.
Definition app.hpp:64
std::shared_future< void > future
The underlying future.
Definition app.hpp:46