Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
scheduler.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
18
19#include <algorithm>
20#include <cstddef>
21#include <memory>
22#include <optional>
23#include <span>
24#include <string>
25#include <unordered_map>
26#include <utility>
27#include <vector>
28
29namespace helios::app::details {
30
31/**
32 * @brief Storage for a system with its metadata and local storage.
33 */
35 std::unique_ptr<ecs::System> system;
38
39 SystemStorage() = default;
40 SystemStorage(const SystemStorage&) = delete;
41 SystemStorage(SystemStorage&&) noexcept = default;
42 ~SystemStorage() = default;
43
44 SystemStorage& operator=(const SystemStorage&) = delete;
45 SystemStorage& operator=(SystemStorage&&) noexcept = default;
46};
47
48/**
49 * @brief Ordering constraints for a system.
50 */
52 std::vector<ecs::SystemTypeId> before;
53 std::vector<ecs::SystemTypeId> after;
54};
55
56/**
57 * @brief Ordering constraints for a schedule.
58 */
60 std::vector<ScheduleId> before; ///< Schedules that must run before this schedule
61 std::vector<ScheduleId> after; ///< Schedules that must run after this schedule
62 ScheduleId stage_id = 0; ///< Stage that this schedule belongs to (0 if none)
63};
64
65/**
66 * @brief Manages system scheduling and execution for a single schedule.
67 * @details Builds a dependency graph based on AccessPolicy conflicts and executes systems
68 * concurrently when possible using TaskGraph.
69 * @note Not thread-safe.
70 */
72public:
73 ScheduleExecutor() = default;
74 explicit ScheduleExecutor(ScheduleId schedule_id) : schedule_id_(schedule_id) {}
76 ScheduleExecutor(ScheduleExecutor&&) noexcept = default;
77 ~ScheduleExecutor() = default;
78
79 ScheduleExecutor& operator=(const ScheduleExecutor&) = delete;
80 ScheduleExecutor& operator=(ScheduleExecutor&&) noexcept = default;
81
82 /**
83 * @brief Clears all systems and resets the schedule.
84 */
85 void Clear();
86
87 /**
88 * @brief Adds a system to this schedule.
89 * @param system_storage_index Index in the global system storage
90 */
91 void AddSystem(size_t system_storage_index);
92
93 /**
94 * @brief Registers ordering constraints for a system.
95 * @param system_id System type ID
96 * @param ordering Ordering constraints (before/after)
97 */
98 void RegisterOrdering(ecs::SystemTypeId system_id, SystemOrdering ordering);
99
100 /**
101 * @brief Builds the execution graph based on system access policies.
102 * @details Analyzes conflicts and creates dependency chains.
103 * @param world World to execute systems on
104 * @param system_storage Reference to global system storage
105 */
106 void BuildExecutionGraph(ecs::World& world, std::span<SystemStorage> system_storage,
107 const std::unordered_map<SystemSetId, SystemSetInfo>& system_sets);
108
109 /**
110 * @brief Executes all systems in this schedule.
111 * @warning Triggers assertion if execution graph has not been built.
112 * @param world World to execute systems on
113 * @param executor Async executor for parallel execution (ignored if use_async_ is false)
114 * @param system_storage Reference to global system storage
115 */
116 void Execute(ecs::World& world, async::Executor& executor, std::span<SystemStorage> system_storage);
117
118 /**
119 * @brief Finds the storage index of a system by type ID within this schedule.
120 * @param system_id System type ID to find
121 * @param system_storage Reference to global system storage
122 * @return Index in system storage if found, std::nullopt otherwise
123 */
124 [[nodiscard]] auto FindSystemIndexByType(ecs::SystemTypeId system_id,
125 std::span<const SystemStorage> system_storage) const noexcept
126 -> std::optional<size_t>;
127
128 /**
129 * @brief Checks if this schedule is the Main stage.
130 * @details Main stage executes synchronously on the main thread.
131 * All other stages execute asynchronously via the executor.
132 * @return True if this is the Main stage, false otherwise
133 */
134 [[nodiscard]] bool IsMainStage() const noexcept { return schedule_id_ == ScheduleIdOf<Main>(); }
135
136 /**
137 * @brief Checks if a system is in this schedule by storage index.
138 * @param system_storage_index Index in the global system storage
139 * @return True if system is present, false otherwise
140 */
141 [[nodiscard]] bool Contains(size_t system_storage_index) const noexcept {
142 return std::ranges::find(system_indices_, system_storage_index) != system_indices_.end();
143 }
144
145 /**
146 * @brief Checks if a system of given type is in this schedule.
147 * @param system_id System type ID to check
148 * @param system_storage Reference to global system storage
149 * @return True if system of this type is present, false otherwise
150 */
151 [[nodiscard]] bool ContainsSystemOfType(ecs::SystemTypeId system_id,
152 std::span<const SystemStorage> system_storage) const noexcept {
153 return std::ranges::any_of(system_indices_, [system_storage, system_id](size_t index) {
154 return index < system_storage.size() && system_storage[index].info.type_id == system_id;
155 });
156 }
157
158 /**
159 * @brief Checks if this schedule has any systems.
160 * @return True if empty, false otherwise
161 */
162 [[nodiscard]] bool Empty() const noexcept { return system_indices_.empty(); }
163
164 /**
165 * @brief Gets the schedule ID.
166 * @return Schedule ID
167 */
168 [[nodiscard]] ScheduleId GetScheduleId() const noexcept { return schedule_id_; }
169
170 /**
171 * @brief Gets the number of systems in this schedule.
172 * @return Number of systems
173 */
174 [[nodiscard]] size_t SystemCount() const noexcept { return system_indices_.size(); }
175
176 /**
177 * @brief Gets const reference to system indices in this schedule.
178 * @return Const reference to vector of system storage indices
179 */
180 [[nodiscard]] const std::vector<size_t>& SystemIndices() const noexcept { return system_indices_; }
181
182private:
183 /**
184 * @brief Creates tasks for all systems in this schedule.
185 * @param system_storage Global system storage
186 * @param world World to execute systems on
187 * @return Vector of created tasks and map from system ID to task index
188 */
189 auto CreateSystemTasks(std::span<SystemStorage> system_storage, ecs::World& world)
190 -> std::pair<std::vector<async::Task>, std::unordered_map<ecs::SystemTypeId, size_t>>;
191
192 /**
193 * @brief Applies explicit ordering constraints between systems.
194 * @param tasks Vector of system tasks
195 * @param system_id_to_task_index Map from system ID to task index
196 * @param system_storage Global system storage
197 */
198 void ApplyExplicitOrdering(std::vector<async::Task>& tasks,
199 const std::unordered_map<ecs::SystemTypeId, size_t>& system_id_to_task_index,
200 std::span<SystemStorage> system_storage);
201
202 /**
203 * @brief Applies set-level ordering constraints.
204 * @param tasks Vector of system tasks
205 * @param system_id_to_task_index Map from system ID to task index
206 * @param system_sets System set information
207 */
208 void ApplySetOrdering(std::vector<async::Task>& tasks,
209 const std::unordered_map<ecs::SystemTypeId, size_t>& system_id_to_task_index,
210 const std::unordered_map<SystemSetId, SystemSetInfo>& system_sets);
211
212 /**
213 * @brief Applies access policy based ordering for conflicting systems.
214 * @param tasks Vector of system tasks
215 * @param system_storage Global system storage
216 */
217 void ApplyAccessPolicyOrdering(std::vector<async::Task>& tasks, std::span<SystemStorage> system_storage);
218
219 ScheduleId schedule_id_ = 0; ///< ID of this schedule
220 std::vector<size_t> system_indices_; ///< Indices into global system storage
221 std::unordered_map<ecs::SystemTypeId, SystemOrdering> system_orderings_; ///< Explicit ordering constraints
222 async::TaskGraph execution_graph_; ///< Task graph for executing systems
223
224 bool graph_built_ = false; ///< Whether the execution graph has been built
225};
226
228 system_indices_.clear();
229 system_orderings_.clear();
230 execution_graph_.Clear();
231 graph_built_ = false;
232}
233
234inline void ScheduleExecutor::AddSystem(size_t system_storage_index) {
235 system_indices_.push_back(system_storage_index);
236 graph_built_ = false;
237}
238
240 system_orderings_[system_id] = std::move(ordering);
241 graph_built_ = false;
242}
243
245 std::span<const SystemStorage> system_storage) const noexcept
246 -> std::optional<size_t> {
247 std::optional<size_t> result;
248 const auto it = std::ranges::find_if(system_indices_, [system_storage, system_id](size_t index) {
249 return index < system_storage.size() && system_storage[index].info.type_id == system_id;
250 });
251
252 if (it != system_indices_.end()) {
253 result = *it;
254 }
255 return result;
256}
257
258/**
259 * @brief Main scheduler that manages all schedules.
260 * @details Holds multiple ScheduleExecutors for different execution stages.
261 * @note Not thread-safe.
262 */
264public:
265 Scheduler() = default;
266 Scheduler(const Scheduler&) = delete;
267 Scheduler(Scheduler&&) noexcept = default;
268 ~Scheduler() = default;
269
270 Scheduler& operator=(const Scheduler&) = delete;
271 Scheduler& operator=(Scheduler&&) noexcept = default;
272
273 /**
274 * @brief Clears all schedules and systems.
275 */
276 void Clear();
277
278 /**
279 * @brief Registers a schedule type.
280 * @tparam T Schedule type
281 */
282 template <ScheduleTrait T>
283 void RegisterSchedule();
284
285 /**
286 * @brief Adds a system to the specified schedule.
287 * @warning Triggers assertion if system T is already added to the schedule.
288 * @tparam T System type
289 * @tparam S Schedule type
290 * @param schedule Schedule to add system to
291 */
292 template <ecs::SystemTrait T, ScheduleTrait S>
293 void AddSystem(S schedule = {});
294
295 /**
296 * @brief Registers ordering constraint for a system.
297 * @tparam T System type
298 * @tparam S Schedule type
299 * @param schedule Schedule to register ordering for
300 * @param ordering Ordering constraints
301 */
302 template <ecs::SystemTrait T, ScheduleTrait S>
303 void RegisterOrdering(S schedule, SystemOrdering ordering);
304
305 /**
306 * @brief Executes all systems in the specified schedule.
307 * @tparam S Schedule type
308 * @param world World to execute systems on
309 * @param executor Async executor for parallel execution
310 */
311 template <ScheduleTrait S>
312 void ExecuteSchedule(ecs::World& world, async::Executor& executor);
313
314 /**
315 * @brief Executes all systems in the specified schedule by ID.
316 * @param schedule_id Schedule ID to execute
317 * @param world World to execute systems on
318 * @param executor Async executor for parallel execution
319 */
320 void ExecuteScheduleById(ScheduleId schedule_id, ecs::World& world, async::Executor& executor);
321
322 /**
323 * @brief Executes all schedules in the specified stage.
324 * @details Executes schedules in topologically sorted order based on Before/After relationships.
325 * @tparam S Stage type (must satisfy StageTrait)
326 * @param world World to execute systems on
327 * @param executor Async executor for parallel execution
328 */
329 template <StageTrait S>
330 void ExecuteStage(ecs::World& world, async::Executor& executor);
331
332 /**
333 * @brief Merges all system local commands into the world's main command queue.
334 * @details Should be called after Extract schedule to flush all pending commands.
335 */
336 void MergeCommandsToWorld(ecs::World& world);
337
338 /**
339 * @brief Resets all system frame allocators.
340 * @details Call this at frame boundaries to reclaim all temporary per-system allocations.
341 * Should typically be called at the end of App::Update() after all schedules have executed.
342 */
343 void ResetFrameAllocators() noexcept;
344
345 /**
346 * @brief Builds execution graphs for all schedules.
347 * @details Should be called after all systems are added and before execution.
348 * This method also propagates system set ordering constraints into explicit
349 * system-to-system ordering edges for each schedule.
350 * @param world World to execute systems on
351 */
352 void BuildAllGraphs(ecs::World& world);
353
354 /**
355 * @brief Appends system ordering constraints to a system's metadata in a specific schedule.
356 * @details This updates SystemInfo.before_systems and SystemInfo.after_systems for the given system type
357 * within the specified schedule. If the system has not been added to the schedule yet, this call is a no-op.
358 * @tparam T System type
359 * @tparam S Schedule type
360 * @param schedule Schedule where the system is registered
361 * @param before Systems that must run after T in this schedule
362 * @param after Systems that must run before T in this schedule
363 */
364 template <ecs::SystemTrait T, ScheduleTrait S>
365 void AppendSystemOrderingMetadata(S schedule, std::span<const ecs::SystemTypeId> before,
366 std::span<const ecs::SystemTypeId> after);
367
368 /**
369 * @brief Appends system set membership to a system's metadata in a specific schedule.
370 * @details This updates SystemInfo.system_sets for the given system type within the specified schedule.
371 * If the system has not been added to the schedule yet, this call is a no-op.
372 * @tparam T System type
373 * @tparam S Schedule type
374 * @param schedule Schedule where the system is registered
375 * @param sets System set identifiers to append
376 */
377 template <ecs::SystemTrait T, ScheduleTrait S>
378 void AppendSystemSetMetadata(S schedule, std::span<const SystemSetId> sets);
379
380 /**
381 * @brief Gets or registers a system set in the global registry.
382 * @details If the set is not present, it will be created and initialized with its static name and ID.
383 * @tparam Set System set type
384 * @return Reference to the SystemSetInfo for this set
385 */
386 template <SystemSetTrait Set>
387 SystemSetInfo& GetOrRegisterSystemSet();
388
389 /**
390 * @brief Adds a system to a system set's membership list.
391 * @details If the set is not present in the registry this call is a no-op.
392 * @param set_id Identifier of the system set
393 * @param system_id Identifier of the system to add
394 */
395 void AddSystemToSet(SystemSetId set_id, ecs::SystemTypeId system_id);
396
397 /**
398 * @brief Adds a set-level ordering constraint: set A runs before set B.
399 * @details All systems that are members of set A must run before systems that are members of set B.
400 * This relationship is schedule-agnostic at registration time; it is applied when building graphs
401 * for each schedule based on actual system membership in that schedule.
402 * @param before_id Identifier of the set that must run before
403 * @param after_id Identifier of the set that must run after
404 */
405 void AddSetRunsBefore(SystemSetId before_id, SystemSetId after_id);
406
407 /**
408 * @brief Adds a set-level ordering constraint: set A runs after set B.
409 * @details Convenience wrapper around AddSetRunsBefore(B, A).
410 * @param after_id Identifier of the set that must run after
411 * @param before_id Identifier of the set that must run before
412 */
413 void AddSetRunsAfter(SystemSetId after_id, SystemSetId before_id) { AddSetRunsBefore(before_id, after_id); }
414
415 /**
416 * @brief Checks if a system of type T is in any schedule.
417 * @tparam T System type
418 * @return True if system is present, false otherwise
419 */
420 template <ecs::SystemTrait T>
421 [[nodiscard]] bool ContainsSystem() const noexcept;
422
423 /**
424 * @brief Checks if a system of type T is in the specified schedule.
425 * @tparam T System type
426 * @tparam S Schedule type
427 * @param schedule Schedule to check
428 * @return True if system is present, false otherwise
429 */
430 template <ecs::SystemTrait T, ScheduleTrait S>
431 [[nodiscard]] bool ContainsSystem(S schedule = {}) const noexcept;
432
433 /**
434 * @brief Gets the total number of systems across all schedules.
435 * @return Total number of systems
436 */
437 [[nodiscard]] size_t SystemCount() const noexcept { return system_storage_.size(); }
438
439 /**
440 * @brief Gets the number of systems in the specified schedule.
441 * @tparam S Schedule type
442 * @param schedule Schedule to query
443 * @return Number of systems in the schedule
444 */
445 template <ScheduleTrait S>
446 [[nodiscard]] size_t SystemCount(S schedule = {}) const noexcept;
447
448 /**
449 * @brief Gets const reference to system storage.
450 * @return Const reference to vector of SystemStorage
451 */
452 [[nodiscard]] auto GetSystemStorage() const noexcept -> const std::vector<SystemStorage>& { return system_storage_; }
453
454 /**
455 * @brief Gets the schedule execution order (topologically sorted).
456 * @return Const reference to vector of Schedule IDs in execution order
457 */
458 [[nodiscard]] auto GetScheduleOrder() const noexcept -> const std::vector<ScheduleId>& { return schedule_order_; }
459
460 /**
461 * @brief Gets schedules that belong to a specific stage.
462 * @tparam S Stage type
463 * @return Vector of schedule IDs that execute within the stage
464 */
465 template <StageTrait S>
466 [[nodiscard]] auto GetSchedulesInStage() const noexcept -> std::vector<ScheduleId>;
467
468private:
469 /**
470 * @brief Collects all schedule IDs from the schedules map.
471 * @return Vector of all schedule IDs
472 */
473 auto CollectAllScheduleIds() const -> std::vector<ScheduleId>;
474
475 /**
476 * @brief Builds the schedule dependency graph for topological sorting.
477 * @param all_schedules Vector of all schedule IDs
478 * @return Pair of adjacency list and in-degree map
479 */
480 auto BuildScheduleDependencyGraph(const std::vector<ScheduleId>& all_schedules) const
481 -> std::pair<std::unordered_map<ScheduleId, std::vector<ScheduleId>>, std::unordered_map<ScheduleId, size_t>>;
482
483 /**
484 * @brief Finds the index of a system by its type ID.</parameter>
485 * @param system_id System type ID
486 * @return Index in system_storage_ or -1 if not found
487 */
488 [[nodiscard]] auto FindSystemIndex(ecs::SystemTypeId system_id) const noexcept -> std::optional<size_t>;
489
490 /**
491 * @brief Performs topological sort using Kahn's algorithm.
492 * @param all_schedules Vector of all schedule IDs
493 * @param adjacency Adjacency list for schedule dependencies
494 * @param in_degree In-degree map for each schedule
495 * @return Topologically sorted vector of schedule IDs
496 */
497 static auto TopologicalSort(const std::vector<ScheduleId>& all_schedules,
498 const std::unordered_map<ScheduleId, std::vector<ScheduleId>>& adjacency,
499 std::unordered_map<ScheduleId, size_t>& in_degree) -> std::vector<ScheduleId>;
500
501 std::unordered_map<ScheduleId, ScheduleExecutor> schedules_; ///< Executors for each schedule
502 std::unordered_map<ScheduleId, ScheduleOrdering> schedule_constraints_; ///< Ordering constraints for schedules
503 std::vector<ScheduleId> schedule_order_; ///< Topologically sorted schedule IDs
504 std::vector<SystemStorage> system_storage_; ///< Global storage for all systems
505 std::unordered_map<SystemSetId, SystemSetInfo> system_sets_; ///< Registry of all system sets
506};
507
508inline void Scheduler::Clear() {
509 system_storage_.clear();
510 for (auto& [id, schedule] : schedules_) {
511 schedule.Clear();
512 }
513 schedules_.clear();
514 schedule_constraints_.clear();
515 schedule_order_.clear();
516 system_sets_.clear();
517}
518
519template <ScheduleTrait S>
520inline void Scheduler::RegisterSchedule() {
521 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
522 if (schedules_.contains(schedule_id)) [[unlikely]] {
523 return; // Already registered
524 }
525
526 schedules_.emplace(schedule_id, ScheduleExecutor(schedule_id));
527
528 // Store schedule ordering constraints
529 ScheduleOrdering ordering;
530
531 // Extract Before constraints from schedule type
532 if constexpr (ScheduleWithBeforeTrait<S>) {
533 constexpr auto before = ScheduleBeforeOf<S>();
534 ordering.before.reserve(before.size());
535 for (const ScheduleId id : before) {
536 ordering.before.push_back(id);
537 }
538 }
539
540 // Extract After constraints from schedule type
541 if constexpr (ScheduleWithAfterTrait<S>) {
542 constexpr auto after = ScheduleAfterOf<S>();
543 ordering.after.reserve(after.size());
544 for (const ScheduleId id : after) {
545 ordering.after.push_back(id);
546 }
547 }
548
549 // Store stage membership
550 ordering.stage_id = ScheduleStageOf<S>();
551
552 schedule_constraints_[schedule_id] = std::move(ordering);
553}
554
555template <ecs::SystemTrait T, ScheduleTrait S>
556inline void Scheduler::RegisterOrdering(S /*schedule*/, SystemOrdering ordering) {
557 RegisterSchedule<S>();
558 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
559 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
560
561 schedules_.at(schedule_id).RegisterOrdering(system_id, std::move(ordering));
562}
563
564template <ecs::SystemTrait T, ScheduleTrait S>
565inline void Scheduler::AddSystem(S /*schedule*/) {
566 HELIOS_ASSERT((!ContainsSystem<T>(S{})), "Failed to add system '{}': System already exists in schedule '{}'!",
567 ecs::SystemNameOf<T>(), ScheduleNameOf<S>());
568
569 RegisterSchedule<S>();
570 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
571 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
572
573 // Create system storage
574 SystemStorage storage{};
575 storage.system = std::make_unique<T>();
576 storage.info.name = std::string(ecs::SystemNameOf<T>());
577 storage.info.type_id = system_id;
578 storage.info.access_policy = T::GetAccessPolicy();
579 storage.info.execution_count = 0;
580
581 const size_t index = system_storage_.size();
582 system_storage_.push_back(std::move(storage));
583
584 schedules_.at(schedule_id).AddSystem(index);
585}
586
587template <ScheduleTrait S>
588inline void Scheduler::ExecuteSchedule(ecs::World& world, async::Executor& executor) {
589 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
590 ExecuteScheduleById(schedule_id, world, executor);
591}
592
593inline void Scheduler::ExecuteScheduleById(ScheduleId schedule_id, ecs::World& world, async::Executor& executor) {
594 const auto it = schedules_.find(schedule_id);
595 if (it == schedules_.end()) [[unlikely]] {
596 return; // Schedule not registered or has no systems
597 }
598
599 it->second.Execute(world, executor, system_storage_);
600
601 // After each schedule, merge all commands into world's main queue
602 MergeCommandsToWorld(world);
603}
604
605inline void Scheduler::MergeCommandsToWorld(ecs::World& world) {
606 for (auto& storage : system_storage_) {
607 auto& commands = storage.local_storage.GetCommands();
608 if (!commands.empty()) {
609 world.MergeCommands(std::move(commands));
610 }
611 }
612}
613
614inline void Scheduler::ResetFrameAllocators() noexcept {
615 for (auto& storage : system_storage_) {
616 storage.local_storage.ResetFrameAllocator();
617 }
618}
619
620template <StageTrait S>
621inline void Scheduler::ExecuteStage(ecs::World& world, async::Executor& executor) {
622 constexpr ScheduleId stage_id = ScheduleIdOf<S>();
623
624 // Execute all schedules that belong to this stage in topological order
625 for (const ScheduleId schedule_id : schedule_order_) {
626 const auto constraint_it = schedule_constraints_.find(schedule_id);
627 if (constraint_it == schedule_constraints_.end()) {
628 continue;
629 }
630
631 // Check if this schedule belongs to this stage
632 if (constraint_it->second.stage_id == stage_id) {
633 ExecuteScheduleById(schedule_id, world, executor);
634 }
635 }
636}
637
638inline void Scheduler::AddSetRunsBefore(SystemSetId before_id, SystemSetId after_id) {
639 if (before_id == after_id) [[unlikely]] {
640 return;
641 }
642
643 auto before_it = system_sets_.find(before_id);
644 if (before_it == system_sets_.end()) {
645 // Set may not have been explicitly registered yet; create a minimal entry.
646 SystemSetInfo info{};
647 info.id = before_id;
648 before_it = system_sets_.emplace(before_id, std::move(info)).first;
649 }
650
651 auto after_it = system_sets_.find(after_id);
652 if (after_it == system_sets_.end()) {
653 SystemSetInfo info{};
654 info.id = after_id;
655 after_it = system_sets_.emplace(after_id, std::move(info)).first;
656 }
657
658 auto& before_info = before_it->second;
659 auto& after_info = after_it->second;
660
661 // Encode relationship:
662 // - before_info.before_sets contains after_id (before runs before after)
663 // - after_info.after_sets contains before_id
664 if (std::ranges::find(before_info.before_sets, after_id) == before_info.before_sets.end()) {
665 before_info.before_sets.push_back(after_id);
666 }
667 if (std::ranges::find(after_info.after_sets, before_id) == after_info.after_sets.end()) {
668 after_info.after_sets.push_back(before_id);
669 }
670}
671
672template <SystemSetTrait Set>
673inline SystemSetInfo& Scheduler::GetOrRegisterSystemSet() {
674 constexpr SystemSetId id = SystemSetIdOf<Set>();
675 const auto it = system_sets_.find(id);
676 if (it != system_sets_.end()) {
677 return it->second;
678 }
679
680 SystemSetInfo info{};
681 info.id = id;
682 info.name = std::string(SystemSetNameOf<Set>());
683
684 const auto [insert_it, _] = system_sets_.emplace(id, std::move(info));
685 return insert_it->second;
686}
687
688inline void Scheduler::AddSystemToSet(SystemSetId set_id, ecs::SystemTypeId system_id) {
689 const auto it = system_sets_.find(set_id);
690 if (it == system_sets_.end()) {
691 return;
692 }
693
694 auto& members = it->second.member_systems;
695 if (std::ranges::find(members, system_id) == members.end()) {
696 members.push_back(system_id);
697 }
698}
699
700template <ecs::SystemTrait T, ScheduleTrait S>
701inline void Scheduler::AppendSystemOrderingMetadata(S /*schedule*/, std::span<const ecs::SystemTypeId> before,
702 std::span<const ecs::SystemTypeId> after) {
703 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
704 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
705
706 // Find the schedule
707 const auto schedule_it = schedules_.find(schedule_id);
708 if (schedule_it == schedules_.end()) {
709 return;
710 }
711
712 // Find the system index within this specific schedule
713 const auto index_opt = schedule_it->second.FindSystemIndexByType(system_id, system_storage_);
714 if (!index_opt.has_value()) {
715 return;
716 }
717
718 auto& info = system_storage_[index_opt.value()].info;
719 if (!before.empty()) {
720#ifdef HELIOS_CONTAINERS_RANGES_AVALIABLE
721 info.before_systems.append_range(before);
722#else
723 info.before_systems.insert(info.before_systems.end(), before.begin(), before.end());
724#endif
725 }
726 if (!after.empty()) {
727#ifdef HELIOS_CONTAINERS_RANGES_AVALIABLE
728 info.after_systems.append_range(after);
729#else
730 info.after_systems.insert(info.after_systems.end(), after.begin(), after.end());
731#endif
732 }
733}
734
735template <ecs::SystemTrait T, ScheduleTrait S>
736inline void Scheduler::AppendSystemSetMetadata(S /*schedule*/, std::span<const SystemSetId> sets) {
737 if (sets.empty()) [[unlikely]] {
738 return;
739 }
740
741 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
742 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
743
744 // Find the schedule
745 const auto schedule_it = schedules_.find(schedule_id);
746 if (schedule_it == schedules_.end()) {
747 return;
748 }
749
750 // Find the system index within this specific schedule
751 const auto index_opt = schedule_it->second.FindSystemIndexByType(system_id, system_storage_);
752 if (!index_opt.has_value()) {
753 return;
754 }
755
756 auto& info = system_storage_[index_opt.value()].info;
757#ifdef HELIOS_CONTAINERS_RANGES_AVALIABLE
758 info.system_sets.append_range(sets);
759#else
760 info.system_sets.insert(info.system_sets.end(), sets.begin(), sets.end());
761#endif
762}
763
764inline auto Scheduler::FindSystemIndex(ecs::SystemTypeId system_id) const noexcept -> std::optional<size_t> {
765 for (size_t i = 0; i < system_storage_.size(); ++i) {
766 if (system_storage_[i].info.type_id == system_id) {
767 return i;
768 }
769 }
770 return std::nullopt;
771}
772
773template <ecs::SystemTrait T>
774inline bool Scheduler::ContainsSystem() const noexcept {
775 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
776 // Check if any schedule contains a system of this type
777 return std::ranges::any_of(schedules_, [this, system_id](const auto& pair) {
778 return pair.second.ContainsSystemOfType(system_id, system_storage_);
779 });
780}
781
782template <ecs::SystemTrait T, ScheduleTrait S>
783inline bool Scheduler::ContainsSystem(S /*schedule*/) const noexcept {
784 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
785 const auto it = schedules_.find(schedule_id);
786 if (it == schedules_.end()) {
787 return false;
788 }
789
790 constexpr ecs::SystemTypeId system_id = ecs::SystemTypeIdOf<T>();
791 return it->second.ContainsSystemOfType(system_id, system_storage_);
792}
793
794template <ScheduleTrait S>
795inline size_t Scheduler::SystemCount(S /*schedule*/) const noexcept {
796 constexpr ScheduleId schedule_id = ScheduleIdOf<S>();
797 const auto it = schedules_.find(schedule_id);
798 if (it == schedules_.end()) [[unlikely]] {
799 return 0;
800 }
801 return it->second.SystemCount();
802}
803
804template <StageTrait S>
805inline auto Scheduler::GetSchedulesInStage() const noexcept -> std::vector<ScheduleId> {
806 constexpr ScheduleId stage_id = ScheduleIdOf<S>();
807 std::vector<ScheduleId> result;
808
809 // Iterate through all schedules in topological order and collect those belonging to this stage
810 for (const ScheduleId schedule_id : schedule_order_) {
811 const auto constraint_it = schedule_constraints_.find(schedule_id);
812 if (constraint_it == schedule_constraints_.end()) {
813 continue;
814 }
815
816 // Check if this schedule belongs to this stage
817 if (constraint_it->second.stage_id == stage_id) {
818 result.push_back(schedule_id);
819 }
820 }
821
822 return result;
823}
824
825} // namespace helios::app::details
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
Manages system scheduling and execution for a single schedule.
Definition scheduler.hpp:71
auto FindSystemIndexByType(ecs::SystemTypeId system_id, std::span< const SystemStorage > system_storage) const noexcept -> std::optional< size_t >
Finds the storage index of a system by type ID within this schedule.
void RegisterOrdering(ecs::SystemTypeId system_id, SystemOrdering ordering)
Registers ordering constraints for a system.
ScheduleId GetScheduleId() const noexcept
Gets the schedule ID.
void AddSystem(size_t system_storage_index)
Adds a system to this schedule.
bool Empty() const noexcept
Checks if this schedule has any systems.
bool Contains(size_t system_storage_index) const noexcept
Checks if a system is in this schedule by storage index.
ScheduleExecutor(const ScheduleExecutor &)=delete
void Clear()
Clears all systems and resets the schedule.
const std::vector< size_t > & SystemIndices() const noexcept
Gets const reference to system indices in this schedule.
bool ContainsSystemOfType(ecs::SystemTypeId system_id, std::span< const SystemStorage > system_storage) const noexcept
Checks if a system of given type is in this schedule.
size_t SystemCount() const noexcept
Gets the number of systems in this schedule.
ScheduleExecutor(ScheduleExecutor &&) noexcept=default
ScheduleExecutor(ScheduleId schedule_id)
Definition scheduler.hpp:74
Main scheduler that manages all schedules.
Scheduler(const Scheduler &)=delete
void Clear()
Clears all schedules and systems.
auto GetScheduleOrder() const noexcept -> const std::vector< ScheduleId > &
Gets the schedule execution order (topologically sorted).
size_t SystemCount() const noexcept
Gets the total number of systems across all schedules.
Scheduler(Scheduler &&) noexcept=default
auto GetSystemStorage() const noexcept -> const std::vector< SystemStorage > &
Gets const reference to system storage.
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:33
Represents a task dependency graph that can be executed by an Executor.
The World class manages entities with their components and systems.
Definition world.hpp:53
void MergeCommands(R &&commands)
Merges commands from a command range into the main queue.
Definition world.hpp:673
Local storage for system-specific data (commands, events, and temporary allocations).
Concept for schedules that provide After() ordering constraints.
Definition schedule.hpp:114
Concept for schedules that provide Before() ordering constraints.
Definition schedule.hpp:104
Trait to identify valid system set types.
size_t ScheduleId
Type alias for schedule type IDs.
Definition schedule.hpp:20
size_t SystemSetId
Type alias for system set type IDs.
size_t SystemTypeId
Type ID for systems.
Definition system.hpp:93
STL namespace.
Ordering constraints for a schedule.
Definition scheduler.hpp:59
std::vector< ScheduleId > before
Schedules that must run before this schedule.
Definition scheduler.hpp:60
std::vector< ScheduleId > after
Schedules that must run after this schedule.
Definition scheduler.hpp:61
ScheduleId stage_id
Stage that this schedule belongs to (0 if none)
Definition scheduler.hpp:62
Metadata about a system.
std::vector< ecs::SystemTypeId > after_systems
Systems that must run before this system.
ecs::SystemTypeId type_id
Unique type identifier.
std::vector< SystemSetId > system_sets
System sets this system belongs to.
std::vector< ecs::SystemTypeId > before_systems
Systems that must run after this system.
std::string name
System name (for debugging/profiling)
Ordering constraints for a system.
Definition scheduler.hpp:51
std::vector< ecs::SystemTypeId > after
Definition scheduler.hpp:53
std::vector< ecs::SystemTypeId > before
Definition scheduler.hpp:52
Metadata about a system set.
Storage for a system with its metadata and local storage.
Definition scheduler.hpp:34
ecs::details::SystemLocalStorage local_storage
Definition scheduler.hpp:37
SystemStorage(const SystemStorage &)=delete
std::unique_ptr< ecs::System > system
Definition scheduler.hpp:35
SystemStorage(SystemStorage &&) noexcept=default