Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
scheduler.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
4#include <helios/ecs/details/profile.hpp>
7
8#include <cstddef>
9#include <functional>
10#include <memory>
11#include <optional>
12#include <unordered_map>
13#include <utility>
14#include <vector>
15
16#if defined(HELIOS_ECS_ENABLE_PROFILE) && \
17 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
18#include <format>
19#endif
20
21namespace helios::async {
22
23class Executor;
24
25}
26
27namespace helios::ecs {
28
29class Scheduler;
30class Executor;
31
32/// @brief Handle returned when configuring stage ordering.
34public:
35 StageOrdering(Scheduler& scheduler, size_t hash) noexcept
36 : scheduler_(scheduler), hash_(hash) {}
37
38 StageOrdering(Scheduler& scheduler, StageTypeIndex index) noexcept
39 : StageOrdering(scheduler, index.Hash()) {}
40
41 template <StageTrait T>
42 explicit StageOrdering(const T& stage, Scheduler& scheduler) noexcept
43 : StageOrdering(scheduler, StageTypeIndex::From(stage)) {}
44
45 StageOrdering(StageOrdering&&) noexcept = default;
46 StageOrdering(const StageOrdering&) = delete;
47 ~StageOrdering() noexcept = default;
48
49 StageOrdering& operator=(const StageOrdering&) = delete;
50 StageOrdering& operator=(StageOrdering&&) noexcept = default;
51
52 auto After(this auto&& self, StageTypeIndex index)
53 -> decltype(std::forward<decltype(self)>(self));
54
55 template <StageTrait T>
56 auto After(this auto&& self, const T& stage = {})
57 -> decltype(std::forward<decltype(self)>(self)) {
58 return std::forward<decltype(self)>(self).After(
60 }
61
62 auto Before(this auto&& self, StageTypeIndex index)
63 -> decltype(std::forward<decltype(self)>(self));
64
65 template <StageTrait T>
66 auto Before(this auto&& self, const T& stage = {})
67 -> decltype(std::forward<decltype(self)>(self)) {
68 return std::forward<decltype(self)>(self).Before(
70 }
71
72 Scheduler& Done() noexcept { return scheduler_.get(); }
73
74private:
75 std::reference_wrapper<Scheduler> scheduler_;
76 size_t hash_ = 0;
77
78 friend class Scheduler;
79};
80
81/// @brief Handle returned when adding a schedule to the scheduler for
82/// configuring ordering.
84public:
85 ScheduleOrdering(Scheduler& scheduler, size_t hash) noexcept
86 : scheduler_(scheduler), hash_(hash) {}
87
88 ScheduleOrdering(Scheduler& scheduler, ScheduleTypeIndex index) noexcept
89 : ScheduleOrdering(scheduler, index.Hash()) {}
90
91 template <ScheduleTrait T>
92 explicit ScheduleOrdering(const T& schedule, Scheduler& scheduler) noexcept
93 : ScheduleOrdering(scheduler, ScheduleTypeIndex::From(schedule)) {}
94
95 ScheduleOrdering(ScheduleOrdering&&) noexcept = default;
97 ~ScheduleOrdering() noexcept = default;
98
99 ScheduleOrdering& operator=(const ScheduleOrdering&) = delete;
100 ScheduleOrdering& operator=(ScheduleOrdering&&) noexcept = default;
101
102 auto After(this auto&& self, ScheduleTypeIndex index)
103 -> decltype(std::forward<decltype(self)>(self));
104
105 template <ScheduleTrait T>
106 auto After(this auto&& self, const T& schedule = {})
107 -> decltype(std::forward<decltype(self)>(self)) {
108 return std::forward<decltype(self)>(self).After(
109 ScheduleTypeIndex::From(schedule));
110 }
111
112 auto Before(this auto&& self, ScheduleTypeIndex index)
113 -> decltype(std::forward<decltype(self)>(self));
114
115 template <ScheduleTrait T>
116 auto Before(this auto&& self, const T& schedule = {})
117 -> decltype(std::forward<decltype(self)>(self)) {
118 return std::forward<decltype(self)>(self).Before(
119 ScheduleTypeIndex::From(schedule));
120 }
121
122 auto InStage(this auto&& self, StageTypeIndex stage)
123 -> decltype(std::forward<decltype(self)>(self));
124
125 template <StageTrait T>
126 auto InStage(this auto&& self, const T& stage = {})
127 -> decltype(std::forward<decltype(self)>(self)) {
128 return std::forward<decltype(self)>(self).InStage(
129 StageTypeIndex::From(stage));
130 }
131
132 Scheduler& Done() noexcept { return scheduler_.get(); }
133
134private:
135 std::reference_wrapper<Scheduler> scheduler_;
136 size_t hash_ = 0;
137
138 friend class Scheduler;
139};
140
141/**
142 * @brief Orchestrates multiple named schedules, handles transitions, and
143 * executes them in topological order.
144 * @details Schedules are identified by types (empty structs satisfying
145 * `ScheduleTrait`). Stages group schedules via `InStage` and are ordered with
146 * `OrderStage`. When `Run()` is called, all schedules are executed in
147 * topological order; system-local data (commands, messages) is flushed at each
148 * schedule boundary.
149 */
151public:
152 Scheduler() = default;
153 Scheduler(const Scheduler&) = delete;
154 Scheduler(Scheduler&&) noexcept = default;
155 ~Scheduler() = default;
156
157 Scheduler& operator=(const Scheduler&) = delete;
158 Scheduler& operator=(Scheduler&&) noexcept = default;
159
160 void Build();
161 void Build(async::Executor& executor);
162
163 void Run(World& world, Executor& executor);
164 void Run(World& world);
165
166 void RunStage(StageTypeIndex stage, World& world);
167 void RunStage(StageTypeIndex stage, World& world, Executor& executor);
168
169 template <StageTrait T>
170 void RunStage(const T& stage, World& world);
171
172 template <StageTrait T>
173 void RunStage(const T& stage, World& world, Executor& executor);
174
175 void Clear();
176
177 [[nodiscard]] ScheduleOrdering Order(ScheduleTypeIndex index);
178
179 template <ScheduleTrait T>
180 [[nodiscard]] ScheduleOrdering Order(const T& schedule = {}) {
181 return Order(ScheduleTypeIndex::From(schedule));
182 }
183
184 [[nodiscard]] StageOrdering OrderStage(StageTypeIndex index);
185
186 template <StageTrait T>
187 [[nodiscard]] StageOrdering OrderStage(const T& stage = {}) {
188 return OrderStage(StageTypeIndex::From(stage));
189 }
190
191 Schedule& In(size_t schedule_hash);
192 Schedule& In(ScheduleTypeIndex index);
193
194 template <ScheduleTrait T>
195 Schedule& In(const T& schedule = {});
196
197 ScheduleOrdering Add(ScheduleTypeId id, Schedule&& schedule);
198
199 template <ScheduleTrait T>
200 ScheduleOrdering Add(const T& schedule, ecs::Schedule&& sched) {
201 return Add(ScheduleTypeId::From(schedule), std::move(sched));
202 }
203
205
206 template <StageTrait T>
207 StageOrdering AddStage(const T& stage = {}) {
208 return AddStage(StageTypeId::From(stage));
209 }
210
211 bool Remove(ScheduleTypeIndex index);
212
213 template <ScheduleTrait T>
214 bool Remove(const T& schedule = {}) {
215 return Remove(ScheduleTypeIndex::From(schedule));
216 }
217
218 [[nodiscard]] Schedule* TryGetSchedule(ScheduleTypeIndex index);
219 [[nodiscard]] const Schedule* TryGetSchedule(ScheduleTypeIndex index) const;
220
221 template <ScheduleTrait T>
222 [[nodiscard]] Schedule* TryGetSchedule(const T& schedule = {}) {
223 return TryGetSchedule(ScheduleTypeIndex::From(schedule));
224 }
225
226 template <ScheduleTrait T>
227 [[nodiscard]] const Schedule* TryGetSchedule(const T& schedule = {}) const {
228 return TryGetSchedule(ScheduleTypeIndex::From(schedule));
229 }
230
231 [[nodiscard]] bool HasStage(StageTypeIndex index) const;
232
233 template <StageTrait T>
234 [[nodiscard]] bool HasStage(const T& stage = {}) const {
235 return HasStage(StageTypeIndex::From(stage));
236 }
237
238 [[nodiscard]] bool IsDirty() const noexcept;
239
240private:
241 struct ScheduleEntry {
242 Schedule schedule;
243 std::vector<size_t> after_schedules;
244 std::vector<size_t> before_schedules;
245 std::string_view name;
246 std::optional<size_t> stage_hash;
247 };
248
249 struct StageEntry {
250 std::vector<size_t> after_stages;
251 std::vector<size_t> before_stages;
252 std::string_view name;
253 };
254
255 [[nodiscard]] ScheduleEntry& GetEntry(size_t hash) {
256 HELIOS_ASSERT(schedules_.contains(hash),
257 "Schedule with hash '{}' not found!", hash);
258 return schedules_.at(hash);
259 }
260
261 [[nodiscard]] const ScheduleEntry& GetEntry(size_t hash) const {
262 HELIOS_ASSERT(schedules_.contains(hash),
263 "Schedule with hash '{}' not found!", hash);
264 return schedules_.at(hash);
265 }
266
267 [[nodiscard]] StageEntry& GetStageEntry(size_t hash) {
268 HELIOS_ASSERT(stages_.contains(hash), "Stage with hash '{}' not found!",
269 hash);
270 return stages_.at(hash);
271 }
272
273 void MarkDirty() noexcept { is_dirty_ = true; }
274 void MarkClean() noexcept { is_dirty_ = false; }
275
276 void BuildImpl(async::Executor* async_executor);
277
278 [[nodiscard]] auto ValidateOrdering() -> ScheduleResult<void>;
279 void ComputeExecutionOrder();
280
281 [[nodiscard]] auto CreateExecutor(ExecutorKind kind,
282 async::Executor* async_executor)
283 -> std::unique_ptr<Executor>;
284
285 std::unordered_map<size_t, ScheduleEntry> schedules_;
286 std::unordered_map<size_t, StageEntry> stages_;
287 std::vector<size_t> execution_order_cache_;
288 std::vector<size_t> stage_order_;
289 std::unordered_map<size_t, std::vector<size_t>> stage_member_order_;
290 bool is_dirty_ = true;
291
292 friend class ScheduleOrdering;
293 friend class StageOrdering;
294};
295
296inline auto StageOrdering::After(this auto&& self, StageTypeIndex index)
297 -> decltype(std::forward<decltype(self)>(self)) {
298 const size_t other_hash = index.Hash();
299 auto& scheduler = self.scheduler_.get();
300 auto& entry = scheduler.GetStageEntry(self.hash_);
301
302 if (std::ranges::find(entry.after_stages, other_hash) ==
303 entry.after_stages.end()) {
304 entry.after_stages.push_back(other_hash);
305 }
306 scheduler.MarkDirty();
307 return std::forward<decltype(self)>(self);
308}
309
310inline auto StageOrdering::Before(this auto&& self, StageTypeIndex index)
311 -> decltype(std::forward<decltype(self)>(self)) {
312 const size_t other_hash = index.Hash();
313 auto& scheduler = self.scheduler_.get();
314 auto& entry = scheduler.GetStageEntry(self.hash_);
315
316 if (std::ranges::find(entry.before_stages, other_hash) ==
317 entry.before_stages.end()) {
318 entry.before_stages.push_back(other_hash);
319 }
320 scheduler.MarkDirty();
321 return std::forward<decltype(self)>(self);
322}
323
324inline auto ScheduleOrdering::After(this auto&& self, ScheduleTypeIndex index)
325 -> decltype(std::forward<decltype(self)>(self)) {
326 const size_t other_hash = index.Hash();
327 auto& scheduler = self.scheduler_.get();
328 auto& entry = scheduler.GetEntry(self.hash_);
329
330 if (std::ranges::find(entry.after_schedules, other_hash) ==
331 entry.after_schedules.end()) {
332 entry.after_schedules.push_back(other_hash);
333 }
334 scheduler.MarkDirty();
335 return std::forward<decltype(self)>(self);
336}
337
338inline auto ScheduleOrdering::Before(this auto&& self, ScheduleTypeIndex index)
339 -> decltype(std::forward<decltype(self)>(self)) {
340 const size_t other_hash = index.Hash();
341 auto& scheduler = self.scheduler_.get();
342 auto& entry = scheduler.GetEntry(self.hash_);
343
344 if (std::ranges::find(entry.before_schedules, other_hash) ==
345 entry.before_schedules.end()) {
346 entry.before_schedules.push_back(other_hash);
347 }
348 scheduler.MarkDirty();
349 return std::forward<decltype(self)>(self);
350}
351
352inline auto ScheduleOrdering::InStage(this auto&& self, StageTypeIndex stage)
353 -> decltype(std::forward<decltype(self)>(self)) {
354 auto& scheduler = self.scheduler_.get();
355 auto& entry = scheduler.GetEntry(self.hash_);
356 entry.stage_hash = stage.Hash();
357 scheduler.MarkDirty();
358 return std::forward<decltype(self)>(self);
359}
360
361inline void Scheduler::Build() {
362 HELIOS_ECS_PROFILE_SCOPE_N("helios::ecs::Scheduler::Build");
363 HELIOS_ECS_PROFILE_ZONE_VALUE(schedules_.size());
364
365 BuildImpl(nullptr);
366}
367
368inline void Scheduler::Build(async::Executor& executor) {
369 HELIOS_ECS_PROFILE_SCOPE_N("helios::ecs::Scheduler::Build");
370 HELIOS_ECS_PROFILE_ZONE_VALUE(schedules_.size());
371
372 BuildImpl(&executor);
373}
374
375template <StageTrait T>
376inline void Scheduler::RunStage(const T& stage, World& world) {
377 HELIOS_ECS_PROFILE_SCOPE();
378 HELIOS_ECS_PROFILE_ZONE_NAME(std::format(
379 "helios::ecs::Scheduler::RunStage{{name: {}}}", StageNameOf(stage)));
380 HELIOS_ECS_PROFILE_ZONE_VALUE(schedules_.size());
381
382 RunStage(StageTypeIndex::From(stage), world);
383}
384
385template <StageTrait T>
386inline void Scheduler::RunStage(const T& stage, World& world,
387 Executor& executor) {
388 HELIOS_ECS_PROFILE_SCOPE();
389 HELIOS_ECS_PROFILE_ZONE_NAME(std::format(
390 "helios::ecs::Scheduler::RunStage{{name: {}}}", StageNameOf(stage)));
391 HELIOS_ECS_PROFILE_ZONE_VALUE(schedules_.size());
392
393 RunStage(StageTypeIndex::From(stage), world, executor);
394}
395
396inline void Scheduler::Clear() {
397 schedules_.clear();
398 stages_.clear();
399 execution_order_cache_.clear();
400 stage_order_.clear();
401 stage_member_order_.clear();
402 MarkDirty();
403}
404
405inline Schedule& Scheduler::In(size_t schedule_hash) {
406 const auto it = schedules_.find(schedule_hash);
407 HELIOS_ASSERT(it != schedules_.end(),
408 "Schedule with hash '{}' does not exist!", schedule_hash);
409 return it->second.schedule;
410}
411
413 return In(index.Hash());
414}
415
416template <ScheduleTrait T>
417inline Schedule& Scheduler::In(const T& schedule) {
418 const auto it = schedules_.find(ScheduleTypeIndex::From(schedule).Hash());
419 HELIOS_ASSERT(it != schedules_.end(), "Schedule '{}' does not exist!",
420 ScheduleNameOf(schedule));
421 return it->second.schedule;
422}
423
425 HELIOS_ASSERT(schedules_.contains(index.Hash()),
426 "Schedule with hash '{}' does not exist!", index.Hash());
427 return {*this, index.Hash()};
428}
429
431 HELIOS_ASSERT(stages_.contains(index.Hash()),
432 "Stage with hash '{}' does not exist!", index.Hash());
433 return {*this, index.Hash()};
434}
435
437 const size_t hash = id.Index().Hash();
438 schedules_[hash] = ScheduleEntry{
439 .schedule = std::move(schedule),
440 .after_schedules = {},
441 .before_schedules = {},
442 .name = id.Name(),
443 .stage_hash = std::nullopt,
444 };
445
446 if (schedules_[hash].schedule.GetName().empty()) {
447 schedules_[hash].schedule.SetName(std::string(id.Name()));
448 }
449
450 MarkDirty();
451 return {*this, hash};
452}
453
455 const size_t hash = id.Index().Hash();
456 stages_[hash] = StageEntry{
457 .after_stages = {},
458 .before_stages = {},
459 .name = id.Name(),
460 };
461 MarkDirty();
462 return {*this, hash};
463}
464
466 const auto it = schedules_.find(index.Hash());
467 if (it == schedules_.end()) {
468 return false;
469 }
470 schedules_.erase(it);
471 MarkDirty();
472 return true;
473}
474
476 const auto it = schedules_.find(index.Hash());
477 if (it == schedules_.end()) {
478 return nullptr;
479 }
480 return &it->second.schedule;
481}
482
484 ScheduleTypeIndex index) const {
485 const auto it = schedules_.find(index.Hash());
486 if (it == schedules_.end()) {
487 return nullptr;
488 }
489 return &it->second.schedule;
490}
491
492inline bool Scheduler::HasStage(StageTypeIndex index) const {
493 return stages_.contains(index.Hash());
494}
495
496inline bool Scheduler::IsDirty() const noexcept {
497 if (is_dirty_) {
498 return true;
499 }
500
501 return std::ranges::any_of(schedules_, [](const auto& pair) {
502 return pair.second.schedule.IsDirty();
503 });
504}
505
506} // namespace helios::ecs
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:32
Abstract interface for schedule executors.
Definition executor.hpp:27
ScheduleOrdering(const T &schedule, Scheduler &scheduler) noexcept
Definition scheduler.hpp:92
auto Before(this auto &&self, ScheduleTypeIndex index) -> decltype(std::forward< decltype(self)>(self))
Scheduler & Done() noexcept
ScheduleOrdering(Scheduler &scheduler, ScheduleTypeIndex index) noexcept
Definition scheduler.hpp:88
auto InStage(this auto &&self, StageTypeIndex stage) -> decltype(std::forward< decltype(self)>(self))
auto Before(this auto &&self, const T &schedule={}) -> decltype(std::forward< decltype(self)>(self))
ScheduleOrdering(Scheduler &scheduler, size_t hash) noexcept
Definition scheduler.hpp:85
auto After(this auto &&self, ScheduleTypeIndex index) -> decltype(std::forward< decltype(self)>(self))
ScheduleOrdering(ScheduleOrdering &&) noexcept=default
auto InStage(this auto &&self, const T &stage={}) -> decltype(std::forward< decltype(self)>(self))
A collection of systems, sets, and run conditions with ordering constraints.
Definition schedule.hpp:182
Orchestrates multiple named schedules, handles transitions, and executes them in topological order.
ScheduleOrdering Add(ScheduleTypeId id, Schedule &&schedule)
Schedule * TryGetSchedule(ScheduleTypeIndex index)
bool Remove(ScheduleTypeIndex index)
StageOrdering AddStage(const T &stage={})
const Schedule * TryGetSchedule(const T &schedule={}) const
Scheduler(Scheduler &&) noexcept=default
friend class StageOrdering
Schedule * TryGetSchedule(const T &schedule={})
ScheduleOrdering Add(const T &schedule, ecs::Schedule &&sched)
bool HasStage(const T &stage={}) const
Scheduler(const Scheduler &)=delete
Schedule & In(size_t schedule_hash)
void RunStage(StageTypeIndex stage, World &world)
friend class ScheduleOrdering
StageOrdering OrderStage(const T &stage={})
bool HasStage(StageTypeIndex index) const
ScheduleOrdering Order(ScheduleTypeIndex index)
void Run(World &world, Executor &executor)
bool Remove(const T &schedule={})
bool IsDirty() const noexcept
StageOrdering OrderStage(StageTypeIndex index)
StageOrdering AddStage(StageTypeId id)
Handle returned when configuring stage ordering.
Definition scheduler.hpp:33
Scheduler & Done() noexcept
Definition scheduler.hpp:72
auto After(this auto &&self, StageTypeIndex index) -> decltype(std::forward< decltype(self)>(self))
StageOrdering(StageOrdering &&) noexcept=default
StageOrdering(Scheduler &scheduler, StageTypeIndex index) noexcept
Definition scheduler.hpp:38
auto Before(this auto &&self, StageTypeIndex index) -> decltype(std::forward< decltype(self)>(self))
StageOrdering(const T &stage, Scheduler &scheduler) noexcept
Definition scheduler.hpp:42
StageOrdering(Scheduler &scheduler, size_t hash) noexcept
Definition scheduler.hpp:35
auto Before(this auto &&self, const T &stage={}) -> decltype(std::forward< decltype(self)>(self))
Definition scheduler.hpp:66
The World class manages entities with their components and systems.
Definition world.hpp:39
static constexpr TypeId From() noexcept
Constructs a TypeId from a type T.
static constexpr TypeIndex From() noexcept
Constructs a TypeIndex from a type T.
constexpr size_t Hash() const noexcept
Retrieves the hash value associated with this TypeIndex.
Concept for schedules.
Definition schedule.hpp:51
Concept for stages.
Definition stage.hpp:22
constexpr std::string_view StageNameOf(const T &={}) noexcept
Stage name for debugging and serialization.
Definition stage.hpp:42
utils::TypeId StageTypeId
Type id for stages.
Definition stage.hpp:15
constexpr std::string_view ScheduleNameOf(const T &={}) noexcept
Schedule name for debugging and serialization.
Definition schedule.hpp:71
ExecutorKind
Kind of executor to use for schedule execution.
Definition executor.hpp:11
utils::TypeIndex ScheduleTypeIndex
Type index for schedules.
Definition schedule.hpp:41
std::expected< T, ScheduleError > ScheduleResult
Result type for schedule operations that can fail.
Definition schedule.hpp:173
utils::TypeId ScheduleTypeId
Type id for schedules.
Definition schedule.hpp:44
utils::TypeIndex StageTypeIndex
Type index for stages.
Definition stage.hpp:12
STL namespace.