Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
runners.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
10#include <helios/core/timer.hpp>
11
12#include <chrono>
13#include <cstdint>
14#include <exception>
15#include <utility>
16
17namespace helios::app {
18
19/**
20 * @brief Checks if a shutdown event has been received.
21 * @param app Application to check
22 * @return Pair of (should_shutdown, exit_code)
23 */
24[[nodiscard]] inline auto CheckShutdownEvent(App& app) noexcept -> std::pair<bool, ecs::ShutdownExitCode> {
25 const auto& world = app.GetMainWorld();
26
27 if (!world.HasEvent<ecs::ShutdownEvent>()) {
28 return {false, ecs::ShutdownExitCode::Success};
29 }
30
31 const auto reader = world.ReadEvents<ecs::ShutdownEvent>();
32 if (reader.Empty()) {
33 return {false, ecs::ShutdownExitCode::Success};
34 }
35
36 // Return the first shutdown event's exit code
37 const auto events = reader.Read();
38 return {true, events.front().exit_code};
39}
40
41/**
42 * @brief Converts shutdown exit code to app exit code.
43 */
44[[nodiscard]] constexpr AppExitCode ToAppExitCode(ecs::ShutdownExitCode code) noexcept {
45 switch (code) {
48 default:
50 }
51}
52
53/**
54 * @brief Configuration for the default runner.
55 */
57 bool update_time_resource = true; ///< Whether to automatically update the Time resource
58};
59
60/**
61 * @brief Default runner that runs until a ShutdownEvent is received.
62 * @details This runner:
63 * - Updates the Time resource each frame (if configured)
64 * - Checks for ShutdownEvent to gracefully exit
65 * - Handles exceptions and returns appropriate exit codes
66 *
67 * @param app Reference to the application
68 * @param config Runner configuration
69 * @return Exit code based on shutdown event or exception
70 *
71 * @example
72 * @code
73 * App app;
74 * app.SetRunner([](App& app) {
75 * return DefaultRunner(app);
76 * });
77 * app.Run();
78 * @endcode
79 */
81 try {
82 while (app.IsRunning()) {
83 if (config.update_time_resource) [[likely]] {
84 app.TickTime();
85 }
86
87 app.Update();
88
89 // Check for shutdown event
90 const auto [should_shutdown, exit_code] = CheckShutdownEvent(app);
91 if (should_shutdown) [[unlikely]] {
92 return ToAppExitCode(exit_code);
93 }
94 }
95 } catch (const std::exception& exception) {
96 HELIOS_CRITICAL("Application encountered an unhandled exception and will exit: {}!", exception.what());
98 }
99
101}
102
103/**
104 * @brief Configuration for the frame-limited runner.
105 */
107 uint64_t max_frames = 1; ///< Maximum number of frames to run
108 bool update_time_resource = true; ///< Whether to automatically update the Time resource
109};
110
111/**
112 * @brief Runner that executes for a fixed number of frames.
113 * @details This runner:
114 * - Runs for exactly the specified number of frames
115 * - Updates the Time resource each frame (if configured)
116 * - Respects ShutdownEvent for early termination
117 *
118 * @param app Reference to the application
119 * @param config Runner configuration with max frame count
120 * @return Success if all frames completed, otherwise based on shutdown event
121 *
122 * @example
123 * @code
124 * App app;
125 * app.SetRunner([](App& app) {
126 * return FrameLimitedRunner(app, {.max_frames = 100});
127 * });
128 * app.Run();
129 * @endcode
130 */
132 try {
133 uint64_t frame = 0;
134 while (app.IsRunning() && frame < config.max_frames) {
135 if (config.update_time_resource) [[likely]] {
136 app.TickTime();
137 }
138
139 app.Update();
140 ++frame;
141
142 // Check for shutdown event
143 const auto [should_shutdown, exit_code] = CheckShutdownEvent(app);
144 if (should_shutdown) [[unlikely]] {
145 return ToAppExitCode(exit_code);
146 }
147 }
148 } catch (const std::exception& exception) {
149 HELIOS_CRITICAL("Application encountered an unhandled exception and will exit: {}!", exception.what());
151 }
152
154}
155
156/**
157 * @brief Configuration for the timed runner.
158 */
160 float duration_seconds = 1.0F; ///< Duration to run in seconds
161 bool update_time_resource = true; ///< Whether to automatically update the Time resource
162};
163
164/**
165 * @brief Runner that executes for a specified duration.
166 * @details This runner:
167 * - Runs for the specified duration in seconds
168 * - Updates the Time resource each frame (if configured)
169 * - Respects ShutdownEvent for early termination
170 *
171 * @param app Reference to the application
172 * @param config Runner configuration with duration
173 * @return Success if duration completed, otherwise based on shutdown event
174 *
175 * @example
176 * @code
177 * App app;
178 * app.SetRunner([](App& app) {
179 * return TimedRunner(app, {.duration_seconds = 5.0F});
180 * });
181 * app.Run();
182 * @endcode
183 */
185 try {
186 const auto duration_seconds = static_cast<double>(config.duration_seconds);
187
188 Timer timer;
189 while (app.IsRunning()) {
190 if (timer.ElapsedSec() >= duration_seconds) {
191 break;
192 }
193
194 if (config.update_time_resource) [[likely]] {
195 app.TickTime();
196 }
197
198 app.Update();
199
200 // Check for shutdown event
201 const auto [should_shutdown, exit_code] = CheckShutdownEvent(app);
202 if (should_shutdown) [[unlikely]] {
203 return ToAppExitCode(exit_code);
204 }
205 }
206 } catch (const std::exception& exception) {
207 HELIOS_CRITICAL("Application encountered an unhandled exception and will exit: {}!", exception.what());
209 }
210
212}
213
214/**
215 * @brief Configuration for the fixed timestep runner.
216 */
218 float fixed_delta_seconds = 1.0F / 60.0F; ///< Fixed timestep in seconds (default: 60 FPS)
219 uint32_t max_substeps = 10; ///< Maximum physics substeps per frame to prevent spiral of death
220 bool update_time_resource = true; ///< Whether to automatically update the Time resource
221};
222
223/**
224 * @brief Runner that uses fixed timestep for deterministic updates.
225 * @details This runner:
226 * - Uses a fixed delta time for each Update() call
227 * - Accumulates real time and catches up with multiple substeps if needed
228 * - Limits substeps to prevent spiral of death
229 * - Respects ShutdownEvent for termination
230 *
231 * @param app Reference to the application
232 * @param config Runner configuration with fixed timestep settings
233 * @return Exit code based on shutdown event or exception
234 *
235 * @example
236 * @code
237 * App app;
238 * app.SetRunner([](App& app) {
239 * return FixedTimestepRunner(app, {.fixed_delta_seconds = 1.0F / 120.0F});
240 * });
241 * app.Run();
242 * @endcode
243 */
245 try {
246 using Duration = std::chrono::duration<float>;
247
248 const Duration fixed_delta{config.fixed_delta_seconds};
249 Duration accumulator{0.0F};
250
251 Timer frame_timer;
252 while (app.IsRunning()) {
253 // Calculate frame time using Timer
254 const auto frame_time = frame_timer.ElapsedDuration<Duration>();
255 frame_timer.Reset();
256
257 accumulator += frame_time;
258
259 // Limit the number of substeps to prevent spiral of death
260 uint32_t substeps = 0;
261 while (accumulator >= fixed_delta && substeps < config.max_substeps) {
262 if (config.update_time_resource) [[likely]] {
263 app.TickTime();
264 }
265
266 app.Update();
267 accumulator -= fixed_delta;
268 ++substeps;
269
270 // Check for shutdown event
271 const auto [should_shutdown, exit_code] = CheckShutdownEvent(app);
272 if (should_shutdown) [[unlikely]] {
273 return ToAppExitCode(exit_code);
274 }
275 }
276
277 // If we hit max substeps, clamp accumulator to prevent runaway
278 if (substeps >= config.max_substeps && accumulator >= fixed_delta) {
279 HELIOS_WARN("Fixed timestep runner hit max substeps ({}), clamping accumulator", config.max_substeps);
280 accumulator = Duration{0.0F};
281 }
282 }
283 } catch (const std::exception& exception) {
284 HELIOS_CRITICAL("Application encountered an unhandled exception and will exit: {}!", exception.what());
286 }
287
289}
290
291/**
292 * @brief Configuration for the once runner.
293 */
295 bool update_time_resource = true; ///< Whether to update the Time resource
296};
297
298/**
299 * @brief Runner that executes exactly one frame.
300 * @details Useful for testing or single-shot operations.
301 *
302 * @param app Reference to the application
303 * @param config Runner configuration
304 * @return Success after single frame
305 *
306 * @example
307 * @code
308 * App app;
309 * app.SetRunner([](App& app) {
310 * return OnceRunner(app);
311 * });
312 * app.Run();
313 * @endcode
314 */
315inline AppExitCode OnceRunner(App& app, OnceRunnerConfig config = {}) {
316 try {
317 if (app.IsRunning()) {
318 if (config.update_time_resource) [[likely]] {
319 app.TickTime();
320 }
321
322 app.Update();
323 }
324 } catch (const std::exception& exception) {
325 HELIOS_CRITICAL("Application encountered an unhandled exception and will exit: {}!", exception.what());
327 }
328
330}
331
332} // namespace helios::app
High–resolution timer with configurable clock and rich elapsed API.
Definition timer.hpp:64
constexpr double ElapsedSec() const
Elapsed time in seconds as double.
Definition timer.hpp:111
constexpr Units ElapsedDuration() const
Get elapsed time as a std::chrono::duration.
Definition timer.hpp:145
constexpr void Reset() noexcept(noexcept(Clock::now()))
Reset the timer start point to current time.
Definition timer.hpp:84
Application class.
Definition app.hpp:97
bool IsRunning() const noexcept
Checks if the app is currently running.
Definition app.hpp:430
void TickTime() noexcept
Updates the Time resource for the current frame.
Definition app.cpp:206
void Update()
Updates the application and its subsystems.
Definition app.cpp:84
#define HELIOS_WARN(...)
Definition logger.hpp:687
#define HELIOS_CRITICAL(...)
Definition logger.hpp:691
AppExitCode FrameLimitedRunner(App &app, FrameLimitedRunnerConfig config)
Definition runners.hpp:131
AppExitCode OnceRunner(App &app, OnceRunnerConfig config={})
Definition runners.hpp:315
AppExitCode TimedRunner(App &app, TimedRunnerConfig config)
Definition runners.hpp:184
AppExitCode
Application exit codes.
Definition app.hpp:87
@ Success
Successful execution.
@ Failure
General failure.
AppExitCode DefaultRunner(App &app, DefaultRunnerConfig config={})
Definition runners.hpp:80
auto CheckShutdownEvent(App &app) noexcept -> std::pair< bool, ecs::ShutdownExitCode >
Checks if a shutdown event has been received.
Definition runners.hpp:24
constexpr AppExitCode ToAppExitCode(ecs::ShutdownExitCode code) noexcept
Converts shutdown exit code to app exit code.
Definition runners.hpp:44
AppExitCode FixedTimestepRunner(App &app, FixedTimestepRunnerConfig config)
Definition runners.hpp:244
ShutdownExitCode
Exit code for application shutdown.
Configuration for the default runner.
Definition runners.hpp:56
bool update_time_resource
Whether to automatically update the Time resource.
Definition runners.hpp:57
Configuration for the fixed timestep runner.
Definition runners.hpp:217
float fixed_delta_seconds
Fixed timestep in seconds (default: 60 FPS)
Definition runners.hpp:218
bool update_time_resource
Whether to automatically update the Time resource.
Definition runners.hpp:220
uint32_t max_substeps
Maximum physics substeps per frame to prevent spiral of death.
Definition runners.hpp:219
Configuration for the frame-limited runner.
Definition runners.hpp:106
uint64_t max_frames
Maximum number of frames to run.
Definition runners.hpp:107
bool update_time_resource
Whether to automatically update the Time resource.
Definition runners.hpp:108
Configuration for the once runner.
Definition runners.hpp:294
bool update_time_resource
Whether to update the Time resource.
Definition runners.hpp:295
Configuration for the timed runner.
Definition runners.hpp:159
float duration_seconds
Duration to run in seconds.
Definition runners.hpp:160
bool update_time_resource
Whether to automatically update the Time resource.
Definition runners.hpp:161
ShutdownExitCode exit_code
Exit code for the shutdown.