Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
scheduler.cpp
Go to the documentation of this file.
1#include <pch.hpp>
2
4
5#include <helios/app/app.hpp>
6#include <helios/app/details/profile.hpp>
8#include <helios/assert.hpp>
10#include <helios/ecs/world.hpp>
11
12#include <cstddef>
13#include <thread>
14
15namespace helios::app {
16
18 HELIOS_APP_PROFILE_SCOPE_N("helios::app::Scheduler::Build");
19
20 auto& main = app.GetMainSubApp();
21 auto& executor = app.GetExecutor();
22
23 main.BuildScheduler(executor);
24
25 startup_graph_.Clear();
26 blocking_update_graph_.Clear();
27 shutdown_graph_.Clear();
28 sub_app_states_.clear();
29
30 if (app.sub_apps_.empty()) {
31 return;
32 }
33
34 sub_app_states_.reserve(app.sub_apps_.size());
35
36 for (auto&& [index, sub_app] : app.sub_apps_) {
37 sub_app.SetOwnerApp(app);
38 sub_app_states_.push_back(SubAppFrameState{
39 .sub_app = sub_app,
40 .mode = ClassifySubApp(sub_app),
41 });
42
43 startup_graph_.EmplaceTask(
44 [&sub_app, &executor]() { sub_app.RunStartupUnchecked(executor); });
45
46 shutdown_graph_.EmplaceTask([&sub_app, &executor]() {
47 sub_app.WaitUntilFullyIdle();
48 sub_app.RunShutdownUnchecked(executor);
49 });
50 }
51
52 for (auto&& [index, sub_app] : app.sub_apps_) {
53 if (ClassifySubApp(sub_app) != SubAppMode::kBlocking) {
54 continue;
55 }
56
57 blocking_update_graph_.EmplaceTask(
58 [&sub_app, &executor]() { sub_app.RunUpdatePass(executor); });
59 }
60}
61
63 HELIOS_APP_PROFILE_SCOPE_N("helios::app::Scheduler::RunStartup");
64
65 auto& main = app.GetMainSubApp();
66 auto& executor = app.GetExecutor();
67
68 RunMainStartup(main, executor);
69
70 if (!app.sub_apps_.empty()) {
71 executor.Run(startup_graph_).Wait();
72 StartAsyncUpdateLoops(app, executor);
73 }
74}
75
77 HELIOS_APP_PROFILE_SCOPE_N("helios::app::Scheduler::RunFrame");
78
79 auto& executor = app.GetExecutor();
80 auto& main = app.GetMainSubApp();
81
82 for (SubAppFrameState& state : sub_app_states_) {
83 state.fresh_extract_this_frame = false;
84 }
85
86 RunUpdateStage(main, executor);
87 RunExtractStage(main, executor);
88 LaunchSubAppUpdates(app, executor);
90}
91
93 HELIOS_APP_PROFILE_SCOPE_N("helios::app::Scheduler::Shutdown");
94
95 StopAsyncUpdateLoops();
96
97 auto& main = app.GetMainSubApp();
98 auto& executor = app.GetExecutor();
99
100 for (SubAppFrameState& state : sub_app_states_) {
101 if (state.mode == SubAppMode::kAsync) {
102 continue;
103 }
104
105 state.sub_app.get().WaitUntilFullyIdle();
106 }
107
108 if (!app.sub_apps_.empty()) {
109 executor.Run(shutdown_graph_).Wait();
110 }
111
112 RunMainShutdown(main, executor);
113}
114
116 HELIOS_APP_PROFILE_SCOPE_N("helios::app::Scheduler::WaitForSubApps");
117
118 if (blocking_update_future_.has_value()) {
119 blocking_update_future_->Wait();
120 blocking_update_future_.reset();
121 }
122}
123
124void Scheduler::RunMainStartup(SubApp& main, async::Executor& executor) {
125 main.BuildScheduler(executor);
127}
128
129void Scheduler::RunUpdateStage(SubApp& main, async::Executor& executor) {
130 main.BuildScheduler(executor);
131 auto& world = main.GetWorld();
132 main.GetScheduler().RunStage(kUpdateStage, world);
133 world.Update();
134}
135
136void Scheduler::RunMainShutdown(SubApp& main, async::Executor& executor) {
137 main.BuildScheduler(executor);
138 main.GetScheduler().RunStage(kShutdownStage, main.GetWorld());
139}
140
141void Scheduler::RunExtractStage(SubApp& main, async::Executor& executor) {
142 main.BuildScheduler(executor);
143
144 auto& world = main.GetWorld();
145 main.GetScheduler().RunStage(kExtractStage, world);
146
147 const auto& main_world = main.GetWorld();
148 for (SubAppFrameState& state : sub_app_states_) {
149 ExtractSubApp(state, main_world);
150 }
151}
152
153void Scheduler::LaunchSubAppUpdates(App& app, async::Executor& executor) {
154 if (app.sub_apps_.empty()) {
155 return;
156 }
157
158 if (blocking_update_graph_.TaskCount() > 0) {
159 blocking_update_future_ = executor.Run(blocking_update_graph_);
160 }
161
162 for (SubAppFrameState& state : sub_app_states_) {
163 if (state.mode != SubAppMode::kOverlapping ||
164 !state.fresh_extract_this_frame) {
165 continue;
166 }
167
168 SubApp& sub_app = state.sub_app.get();
169 executor.SilentAsync(
170 [&sub_app, &executor]() { sub_app.RunUpdatePass(executor); }); // +
171 }
172}
173
174void Scheduler::StartAsyncUpdateLoops(App& app, async::Executor& executor) {
175 for (auto&& [index, sub_app] : app.sub_apps_) {
176 if (!sub_app.IsAsync()) {
177 continue;
178 }
179
180 sub_app.ResetAsyncLoopStop();
181 async_loops_running_.fetch_add(1, std::memory_order_acq_rel);
182
183 executor.SilentAsync([&sub_app, &executor, this]() { // +
184 sub_app.RunUpdatePass(executor);
185 async_loops_running_.fetch_sub(1, std::memory_order_acq_rel);
186 });
187 }
188}
189
190void Scheduler::StopAsyncUpdateLoops() {
191 for (SubAppFrameState& state : sub_app_states_) {
192 if (state.mode != SubAppMode::kAsync) {
193 continue;
194 }
195
196 state.sub_app.get().RequestAsyncLoopStop();
197 }
198
199 while (async_loops_running_.load(std::memory_order_acquire) > 0) {
200 std::this_thread::yield();
201 }
202}
203
204void Scheduler::ExtractSubApp(SubAppFrameState& state,
205 const ecs::World& main_world) {
206 SubApp& sub_app = state.sub_app.get();
207 state.fresh_extract_this_frame = false;
208
209 switch (state.mode) {
210 case SubAppMode::kBlocking:
211 sub_app.WaitUntilFullyIdle();
212 sub_app.Extract(main_world);
213 state.fresh_extract_this_frame = true;
214 state.consecutive_extract_skips = 0;
215 return;
216 case SubAppMode::kAsync:
217 sub_app.Extract(main_world, true);
218 return;
219 case SubAppMode::kOverlapping:
220 break;
221 }
222
223 if (sub_app.IsUpdating()) {
224 const size_t max_skips = sub_app.MaxExtractionSkips();
225 if (max_skips == 0 || state.consecutive_extract_skips < max_skips) {
226 ++state.consecutive_extract_skips;
227 return;
228 }
229
230 sub_app.WaitUntilFullyIdle();
231 }
232
233 sub_app.Extract(main_world);
234 state.fresh_extract_this_frame = true;
235 state.consecutive_extract_skips = 0;
236}
237
238auto Scheduler::ClassifySubApp(const SubApp& sub_app) noexcept
239 -> Scheduler::SubAppMode {
240 if (sub_app.IsAsync()) {
241 return SubAppMode::kAsync;
242 }
243
244 if (sub_app.AllowsOverlappingUpdates()) {
245 return SubAppMode::kOverlapping;
246 }
247
248 return SubAppMode::kBlocking;
249}
250
251} // namespace helios::app
Application class.
void RunStartup(App &app)
Runs startup on the main sub-app, then startup on all sub-apps in parallel, then starts async sub-app...
Definition scheduler.cpp:62
void RunFrame(App &app)
Runs one full application frame.
Definition scheduler.cpp:76
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 Build(App &app)
Builds schedulers and precomputes sub-app task graphs.
Definition scheduler.cpp:17
void WaitForSubApps()
Waits until blocking sub-apps finish their frame update.
A sub-application with its own ECS world and scheduler.
Definition sub_app.hpp:144
ecs::Scheduler & GetScheduler() noexcept
Gets this sub-app's ECS schedule collection.
Definition sub_app.hpp:569
void BuildScheduler(async::Executor &executor)
Builds all ECS schedules when any are dirty.
Definition sub_app.hpp:716
ecs::World & GetWorld() noexcept
Gets this sub-app's ECS world.
Definition sub_app.hpp:556
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:32
void RunStage(StageTypeIndex stage, World &world)
constexpr ExtractStage kExtractStage
Builtin stage for extraction.
Definition schedules.hpp:34
constexpr ShutdownStage kShutdownStage
Builtin stage for shutdown.
Definition schedules.hpp:42
constexpr StartupStage kStartupStage
Builtin stage for startup.
Definition schedules.hpp:18
constexpr UpdateStage kUpdateStage
Builtin stage for per-frame updates.
Definition schedules.hpp:26