Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
runners.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <chrono>
8#include <optional>
9#include <thread>
10
11namespace helios::app {
12
13/// @brief Configuration for fixed-timestep runners.
15 static constexpr auto kDefaultInterval =
16 std::chrono::nanoseconds{16'666'667}; // ~60 FPS default
17
18 /// Time to wait between the start of consecutive updates.
19 std::chrono::nanoseconds update_interval = kDefaultInterval;
20
21 /**
22 * @brief Creates a config targeting the given frames per second.
23 * @param fps Target frames per second (must be > 0)
24 * @return FixedRunnerConfig with the corresponding update interval
25 */
26 [[nodiscard]] static constexpr auto FromFPS(size_t fps) noexcept
28 constexpr size_t kNanosecsInSec = 1'000'000'000;
29 return {.update_interval = std::chrono::nanoseconds{kNanosecsInSec / fps}};
30 }
31
32 /**
33 * @brief Creates a config targeting the given update frequency in Hz.
34 * @param hz Target frequency in Hz (must be > 0)
35 * @return FixedRunnerConfig with the corresponding update interval
36 */
37 [[nodiscard]] static constexpr auto FromHz(double hz) noexcept
39 auto nanosec = std::chrono::duration_cast<std::chrono::nanoseconds>(
40 std::chrono::duration<double>{1.0 / hz});
41 return {.update_interval = nanosec};
42 }
43
44 /**
45 * @brief Creates a config with the given update interval.
46 * @tparam Rep Duration representation type
47 * @tparam Period Duration period type
48 * @param interval Update interval as a `std::chrono::duration`
49 * @return FixedRunnerConfig with the given interval
50 */
51 template <typename Rep, typename Period>
52 [[nodiscard]] static constexpr auto FromInterval(
53 std::chrono::duration<Rep, Period> interval) noexcept
55 auto nanosec =
56 std::chrono::duration_cast<std::chrono::nanoseconds>(interval);
57 return {.update_interval = nanosec};
58 }
59};
60
61/**
62 * @brief Runs the application until an `AppExit` message is received.
63 * @param app Application to update
64 * @return Exit code from the first `AppExit` message
65 */
67 std::optional<ExitCode> exit_code;
68 while (exit_code = app.ShouldExit(), !exit_code.has_value()) {
69 app.Update();
70 }
71 return *exit_code;
72}
73
74/**
75 * @brief Runs the application with a fixed timestep.
76 * @details Uses `sleep_until` against an advancing absolute target time point
77 * so that scheduler wake-up overshoot on one frame is automatically absorbed by
78 * a shorter sleep on the next, preventing drift accumulation.
79 * @param app Application to update
80 * @param config Fixed-runner configuration
81 * @return Exit code from the first `AppExit` message
82 */
83inline ExitCode RunFixed(App& app, const FixedRunnerConfig& config = {}) {
84 auto next_tick = std::chrono::steady_clock::now();
85
86 std::optional<ExitCode> exit_code;
87 while (exit_code = app.ShouldExit(), !exit_code.has_value()) {
88 next_tick += config.update_interval;
89
90 app.Update();
91
92 std::this_thread::sleep_until(next_tick);
93 // Prevent unbounded catch-up after a long stall (e.g. debugger pause)
94 next_tick = std::max(next_tick, std::chrono::steady_clock::now());
95 }
96 return *exit_code;
97}
98
99/**
100 * @brief Runs a single application frame.
101 * @param app Application to update
102 * @return Exit code from an `AppExit` message, or `ExitCode::kSuccess`
103 */
105 app.Update();
106 return app.ShouldExit().value_or(ExitCode::kSuccess);
107}
108
109/**
110 * @brief Runs the sub-app update loop until `SubApp::ShouldExit` is true.
111 * @details Intended for async sub-apps via `SubApp::SetRunner`.
112 * @param sub_app Sub-app to update
113 * @param executor Async executor used to build and run schedules
114 */
115inline void RunDefaultSubApp(SubApp& sub_app, async::Executor& executor) {
116 while (!sub_app.ShouldExit()) {
117 sub_app.Update(executor);
118 }
119}
120
121/**
122 * @brief Runs the sub-app with a fixed timestep.
123 * @details Intended for async sub-apps via `SubApp::SetRunner`.
124 * Uses `sleep_until` against an advancing absolute target time point so that
125 * scheduler wake-up overshoot on one frame is automatically absorbed by a
126 * shorter sleep on the next, preventing drift accumulation.
127 * @param sub_app Sub-app to update
128 * @param executor Async executor used to build and run schedules
129 * @param config Fixed-runner configuration
130 */
131inline void RunFixedSubApp(SubApp& sub_app, async::Executor& executor,
132 const FixedRunnerConfig& config = {}) {
133 auto next_tick = std::chrono::steady_clock::now();
134
135 while (!sub_app.ShouldExit()) {
136 next_tick += config.update_interval;
137
138 sub_app.Update(executor);
139
140 std::this_thread::sleep_until(next_tick);
141 // Prevent unbounded catch-up after a long stall (e.g. debugger pause)
142 next_tick = std::max(next_tick, std::chrono::steady_clock::now());
143 }
144}
145
146/**
147 * @brief Runs a single sub-app update stage.
148 * @details Intended for `SubApp::SetRunner` or blocking per-frame updates.
149 * @param sub_app Sub-app to update
150 * @param executor Async executor used to build and run schedules
151 */
152inline void RunOnceSubApp(SubApp& sub_app, async::Executor& executor) {
153 sub_app.Update(executor);
154}
155
156} // namespace helios::app
Application class.
A sub-application with its own ECS world and scheduler.
Definition sub_app.hpp:144
void Update(async::Executor &executor)
Runs the configured update stage.
Definition sub_app.hpp:680
bool ShouldExit() const noexcept
Returns whether this sub-app should stop its update loop.
Definition sub_app.cpp:60
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:32
ExitCode RunDefault(App &app)
Runs the application until an AppExit message is received.
Definition runners.hpp:66
ExitCode RunOnce(App &app)
Runs a single application frame.
Definition runners.hpp:104
ExitCode RunFixed(App &app, const FixedRunnerConfig &config={})
Runs the application with a fixed timestep.
Definition runners.hpp:83
ExitCode
Application exit codes.
@ kSuccess
Successful execution.
void RunFixedSubApp(SubApp &sub_app, async::Executor &executor, const FixedRunnerConfig &config={})
Runs the sub-app with a fixed timestep.
Definition runners.hpp:131
void RunDefaultSubApp(SubApp &sub_app, async::Executor &executor)
Runs the sub-app update loop until SubApp::ShouldExit is true.
Definition runners.hpp:115
void RunOnceSubApp(SubApp &sub_app, async::Executor &executor)
Runs a single sub-app update stage.
Definition runners.hpp:152
Configuration for fixed-timestep runners.
Definition runners.hpp:14
static constexpr auto FromFPS(size_t fps) noexcept -> FixedRunnerConfig
Creates a config targeting the given frames per second.
Definition runners.hpp:26
std::chrono::nanoseconds update_interval
Time to wait between the start of consecutive updates.
Definition runners.hpp:19
static constexpr auto FromHz(double hz) noexcept -> FixedRunnerConfig
Creates a config targeting the given update frequency in Hz.
Definition runners.hpp:37
static constexpr auto FromInterval(std::chrono::duration< Rep, Period > interval) noexcept -> FixedRunnerConfig
Creates a config with the given update interval.
Definition runners.hpp:52
static constexpr auto kDefaultInterval
Definition runners.hpp:15
Update schedule.
Definition schedules.hpp:93