Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
schedule.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
11#include <helios/ecs/world.hpp>
13
14#include <algorithm>
15#include <array>
16#include <concepts>
17#include <cstddef>
18#include <cstdint>
19#include <expected>
20#include <functional>
21#include <memory>
22#include <optional>
23#include <string>
24#include <string_view>
25#include <type_traits>
26#include <unordered_map>
27#include <vector>
28
29#if defined(HELIOS_ECS_ENABLE_PROFILE) && \
30 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
31#include <format>
32#endif
33
37
38namespace helios::ecs {
39
40/// @brief Type index for schedules.
42
43/// @brief Type id for schedules.
45
46/**
47 * @brief Concept for schedules.
48 * @details A schedule type must be an object and be empty.
49 */
50template <typename T>
51concept ScheduleTrait = std::is_object_v<std::remove_cvref_t<T>> &&
52 std::is_empty_v<std::remove_cvref_t<T>>;
53
54/**
55 * @brief Concept for schedules that provide a name.
56 * @details A schedule with name trait must satisfy `ScheduleTrait` and provide:
57 * - `static constexpr std::string_view kName` variable
58 */
59template <typename T>
61 { std::remove_cvref_t<T>::kName } -> std::convertible_to<std::string_view>;
62};
63
64/**
65 * @brief Schedule name for debugging and serialization.
66 * @tparam T Schedule type
67 * @param instance Schedule instance
68 * @return Schedule name
69 */
70template <ScheduleTrait T>
71[[nodiscard]] constexpr std::string_view ScheduleNameOf(
72 const T& /*instance*/ = {}) noexcept {
73 if constexpr (ScheduleWithNameTrait<T>) {
74 return std::remove_cvref_t<T>::kName;
75 } else {
77 }
78}
79
80class Schedule;
81
82/// @brief Metadata attached to a system entry for ordering and configuration.
84 std::vector<SystemId> before_targets;
85 std::vector<SystemId> after_targets;
86 std::vector<SystemSetId> before_set_targets;
87 std::vector<SystemSetId> after_set_targets;
88 std::vector<SystemSetId> member_of_sets;
89 std::vector<RunConditionStorage> conditions;
90
91 /**
92 * @brief Adds a system ID to the list of targets that must run before this
93 * system.
94 * @details If the system ID is already present, it is not added again.
95 * @param id The system ID to add
96 */
97 constexpr void AddBefore(SystemId id) { AppendUnique(before_targets, id); }
98
99 /**
100 * @brief Adds a system ID to the list of targets that must run after this
101 * system.
102 * @details If the system ID is already present, it is not added again.
103 * @param id The system ID to add
104 */
105 constexpr void AddAfter(SystemId id) { AppendUnique(after_targets, id); }
106
107 /**
108 * @brief Adds a system set ID to the list of targets that must run before
109 * this system.
110 * @details If the system set ID is already present, it is not added again.
111 * @param id The system set ID to add
112 */
113 constexpr void AddBefore(SystemSetId id) {
115 }
116
117 /**
118 * @brief Adds a system set ID to the list of targets that must run after this
119 * system.
120 * @details If the system set ID is already present, it is not added again.
121 * @param id The system set ID to add
122 */
123 constexpr void AddAfter(SystemSetId id) {
125 }
126
127 /**
128 * @brief Adds a system set membership if not already present.
129 * @details If the set id is already present, it is not added again.
130 * @param id Set to assign this system to
131 */
132 constexpr void AddMemberOfSet(SystemSetId id) {
134 }
135
136 /**
137 * @brief Appends a value to a vector if it is not already present.
138 * @tparam T The type of the value
139 * @param vec The vector to append to
140 * @param value The value to append
141 */
142 template <typename T>
143 static void AppendUnique(std::vector<T>& vec, const T& value) {
144 if (std::ranges::find(vec, value) == vec.end()) {
145 vec.push_back(value);
146 }
147 }
148};
149
150/// @brief Settings that control how a schedule is compiled and executed.
154
155/// @brief Kind of error produced during schedule compilation.
163
164/// @brief Error type returned when schedule compilation fails.
167 std::string message;
168 std::vector<SystemId> involved_systems;
169};
170
171/// @brief Result type for schedule operations that can fail.
172template <typename T>
173using ScheduleResult = std::expected<T, ScheduleError>;
174
175/**
176 * @brief A collection of systems, sets, and run conditions with ordering
177 * constraints.
178 * @details Compile the schedule with `Build()` to produce an executable plan,
179 * then call `Run()` with an executor to execute it. Adding, removing, or
180 * modifying any configuration invalidates the plan and requires recompilation.
181 */
182class Schedule {
183public:
184 Schedule() = default;
185
186 /**
187 * @brief Constructs a schedule with the given name.
188 * @param name Human-readable schedule name
189 */
190 explicit Schedule(std::string name) : name_(std::move(name)) {}
191
192 Schedule(const Schedule&) = delete;
193 Schedule(Schedule&&) noexcept = default;
194 ~Schedule() = default;
195
196 Schedule& operator=(const Schedule&) = delete;
197 Schedule& operator=(Schedule&&) noexcept = default;
198
199 /**
200 * @brief Creates a schedule named after the given label type.
201 * @tparam T Schedule type satisfying `ScheduleTrait`
202 * @param schedule Schedule type instance
203 * @return Schedule with name derived from the type
204 */
205 template <ScheduleTrait T>
206 [[nodiscard]] static Schedule From(const T& schedule = {}) {
207 return Schedule(std::string(ScheduleNameOf(schedule)));
208 }
209
210 /**
211 * @brief Submits the compiled schedule for execution using the provided
212 * executor.
213 * @details Resets per-system arenas and submits systems for execution. Does
214 * **not** call `World::Flush()` or `SystemLocalData::Update`. When execution
215 * finishes (`Executor::Wait()` on the same executor), call
216 * `ApplyDeferred(world)` before the next `Run()` — otherwise pending commands
217 * and messages are discarded on the next arena reset.
218 * @warning Triggers assertion if the schedule has not been built, is dirty,
219 * or still has unapplied local data from a prior `Run()`.
220 * @param world World to pass to each system
221 * @param executor Executor strategy to use
222 */
223 void Run(World& world, Executor& executor);
224
225 /**
226 * @brief Submits the compiled schedule for execution using the stored
227 * executor.
228 * @details Same semantics as `Run(world, executor)`; see that overload.
229 * @warning Triggers assertion if the schedule has not been built, is dirty,
230 * no executor has been set, or still has unapplied local data from a prior
231 * `Run()`.
232 * @param world World to pass to each system
233 */
234 void Run(World& world);
235
236 /**
237 * @brief Executes the compiled schedule and blocks until completion.
238 * @details Equivalent to `Run()`, waiting on the executor, then
239 * `ApplyDeferred(world)`.
240 * @warning Triggers assertion if the schedule has not been built or is dirty.
241 * @param world World to pass to each system
242 * @param executor Executor strategy to use
243 */
244 void RunAndWait(World& world, Executor& executor);
245
246 /**
247 * @brief Executes the compiled schedule using the stored executor and blocks
248 * until completion.
249 * @details Same semantics as `RunAndWait(world, executor)`; see that
250 * overload.
251 * @warning Triggers assertion if the schedule has not been built, is dirty,
252 * or no executor has been set.
253 * @param world World to pass to each system
254 */
255 void RunAndWait(World& world);
256
257 /**
258 * @brief Flushes world state and applies all pending system-local data.
259 * @details Calls `World::Flush()` (entity reservations and
260 * `World::EnqueueCommand` queue), then runs `SystemLocalData::Update` for
261 * every system in this schedule. Required after `Run()` once the executor has
262 * finished; called automatically by `RunAndWait()`.
263 * @param world World to flush and update
264 */
265 void ApplyDeferred(World& world);
266
267 /**
268 * @brief Compiles the schedule into an executable plan.
269 * @details Validates for cycles, unknown references, and ambiguous access.
270 * Produces a topological order and conflict matrix for parallel execution.
271 * @return Empty on success, or a `ScheduleError` with diagnostics
272 */
273 [[nodiscard]] auto Build() -> ScheduleResult<void>;
274
275 /**
276 * @brief Clears all systems, sets, and compiled state from this schedule.
277 * @details Preserves `Settings()` and the stored executor for reuse.
278 */
279 void Clear();
280
281 /**
282 * @brief Adds a param-style functor system to the schedule.
283 * @details Auto-deduces the access policy from the system's parameter types
284 * and derives the system name and `SystemId` from the functor type.
285 * Lambdas are rejected; use `Add(std::string, T&&, ...)` to register them
286 * with an explicit name.
287 * @tparam T System type satisfying `FunctorSystemTrait`
288 * @param system System instance
289 * @param options Local data options
290 * @return Handle for further configuration
291 */
292 template <FunctorSystemTrait T>
293 SystemHandle Add(T&& system, SystemLocalDataOptions options = {});
294
295 /**
296 * @brief Adds a param-style system to the schedule with an explicit name.
297 * @details Auto-deduces the access policy from the system's parameter types
298 * and derives the `SystemId` from the provided name. Required for lambdas
299 * and recommended when multiple instances of the same functor type need
300 * distinct identities within a single schedule.
301 * @tparam T System type satisfying `SystemTrait`
302 * @param name Human-readable system name
303 * @param system System instance
304 * @param options Local data options
305 * @return Handle for further configuration
306 */
307 template <SystemTrait T>
308 SystemHandle Add(std::string name, T&& system,
309 SystemLocalDataOptions options = {});
310
311 /**
312 * @brief Adds multiple param-style functor systems as a system group.
313 * @details Each system is added to the schedule and assigned to a unique
314 * anonymous group. The returned handle configures all members collectively
315 * and can assign them to a named set via `InSet`. Lambdas must be registered
316 * individually with explicit names.
317 * @tparam Ts System types satisfying `FunctorSystemTrait`
318 * @param systems System instances
319 * @return Handle for configuring the system group
320 */
321 template <FunctorSystemTrait... Ts>
322 requires(sizeof...(Ts) > 1)
323 SystemGroupHandle Add(Ts&&... systems);
324
325 /**
326 * @brief Adds a callable system to the schedule with explicit name and access
327 * policy.
328 * @param name System name
329 * @param system Callable that accepts `(World&, SystemLocalData&)`
330 * @param policy Access policy for the system
331 * @param options Local data options
332 * @return Handle for further configuration
333 */
334 SystemHandle Add(std::string name, SystemCallable system,
335 AccessPolicy policy = {},
336 SystemLocalDataOptions options = {});
337
338 /**
339 * @brief Gets or creates a system set with the given identifier.
340 * @param id Set identifier
341 * @return Handle for configuring the set
342 */
343 [[nodiscard]] SystemSetHandle Set(SystemSetId id);
344
345 /**
346 * @brief Gets or creates a system set with the given label type.
347 * @tparam T Set type satisfying `SystemSetTrait`
348 * @return Handle for configuring the set
349 */
350 template <SystemSetTrait T>
351 [[nodiscard]] SystemSetHandle Set(const T& /*set*/ = {}) {
352 return Set(SystemSetId::From<T>());
353 }
354
355 /**
356 * @brief Sets the executor for this schedule.
357 * @param executor Executor to use when Run/AndWait is called without an
358 * explicit executor
359 */
360 void SetExecutor(std::unique_ptr<Executor> executor) noexcept {
361 executor_ = std::move(executor);
362 }
363
364 /**
365 * @brief Sets the human-readable schedule name.
366 * @param name Schedule name
367 */
368 void SetName(std::string name) noexcept { name_ = std::move(name); }
369
370 /**
371 * @brief Checks whether the schedule requires recompilation.
372 * @return True if any configuration has changed since the last `Build()`
373 */
374 [[nodiscard]] bool IsDirty() const noexcept { return is_dirty_; }
375
376 /**
377 * @brief Checks whether any system still has unapplied local commands or
378 * messages.
379 * @return True if `ApplyDeferred` is still required before the next `Run()`
380 */
381 [[nodiscard]] bool HasPendingLocalData() const noexcept {
382 return std::ranges::any_of(system_entries_, [](const auto& entry) {
383 return entry.storage.local_data.HasPendingWork();
384 });
385 }
386
387 /**
388 * @brief Gets the human-readable schedule name.
389 * @return Schedule name
390 */
391 [[nodiscard]] const std::string& GetName() const noexcept { return name_; }
392
393 /**
394 * @brief Gets scheduler-local settings for this schedule.
395 * @return Mutable reference to the settings
396 */
397 [[nodiscard]] ScheduleSettings& Settings() noexcept { return settings_; }
398
399 /**
400 * @brief Gets scheduler-local settings for this schedule.
401 * @return Const reference to the settings
402 */
403 [[nodiscard]] const ScheduleSettings& Settings() const noexcept {
404 return settings_;
405 }
406
407 /**
408 * @brief Gets the stored executor.
409 * @return Pointer to the stored executor, or nullptr if not set
410 */
411 [[nodiscard]] Executor* GetExecutor() noexcept { return executor_.get(); }
412
413 /**
414 * @brief Gets the stored executor.
415 * @return Const pointer to the stored executor, or nullptr if not set
416 */
417 [[nodiscard]] const Executor* GetExecutor() const noexcept {
418 return executor_.get();
419 }
420
421private:
422 /// @brief Entry stored for each system added to a schedule.
423 struct SystemEntry {
424 SystemStorage storage;
425 ScheduleSystemMetadata metadata;
426 bool is_sync_point = false;
427 };
428
429 /**
430 * @brief Holds the conflict data between systems in the compiled plan.
431 *
432 * @details Two representations are maintained:
433 * - A dense `uint64_t` bitmask per system when `system_count <= 64` (fast
434 * path, avoids heap indirection on every executor dispatch).
435 * - A full `vector<vector<bool>>` matrix for larger schedules.
436 *
437 * Callers always use `Conflicts(i, j)` and never inspect the fields
438 * directly, so the switch between representations is invisible at use sites.
439 */
440 struct ConflictData {
441 /// Active when system_count <= 64; bitmask[i] has bit j set iff i and j
442 /// conflict.
443 std::vector<uint64_t> bitmask;
444 /// Active when system_count > 64; empty when bitmask is in use.
445 std::vector<std::vector<bool>> matrix;
446
447 /// Returns true when systems at indices `i` and `j` have a
448 /// data-access conflict and no explicit ordering constraint between them.
449 [[nodiscard]] constexpr bool Conflicts(size_t i, size_t j) const noexcept {
450 if (!bitmask.empty()) {
451 return static_cast<bool>((bitmask[i] >> j) & 1U);
452 }
453 return matrix[i][j];
454 }
455 };
456
457 /// @brief The result of a successful schedule compilation.
458 struct CompiledPlan {
459 std::vector<SystemId> execution_order;
460 /// Maps SystemId::id -> index in system_entries_ for O(1) lookup.
461 std::unordered_map<size_t, size_t> system_id_to_entry_idx;
462 ConflictData conflicts;
463 };
464
465 size_t AddEntry(SystemStorage&& storage);
466
467 [[nodiscard]] auto ResolveSetReferences() -> ScheduleResult<void>;
468
469 [[nodiscard]] auto BuildDag() -> ScheduleResult<Dag>;
470 void BuildConflictData(CompiledPlan& plan);
471
472 void MergeRunConditions(
473 std::vector<std::vector<RunConditionStorage*>>& out_conditions,
474 const std::vector<SystemId>& execution_order);
475
476 void ApplySequenceOrdering(
477 const std::unordered_map<size_t, std::vector<SystemId>>& set_members);
478
479 void WarnAmbiguousAccess(const Dag& dag);
480
481 /**
482 * @brief Gets or creates a system set entry in the schedule.
483 * @details Marks the schedule dirty when a new set is created.
484 * @param set_id Set identifier
485 */
486 void EnsureSet(SystemSetId set_id);
487
488 /**
489 * @brief Assigns a system to a set if it is not already a member.
490 * @details Creates the target set if needed and marks the schedule dirty only
491 * when membership changes.
492 * @param slot Index of the system entry to update
493 * @param set_id Set to assign the system to
494 * @return True if membership was added
495 */
496 bool AddSystemToSet(size_t slot, SystemSetId set_id);
497
498 /**
499 * @brief Assigns every member of an anonymous group to a named set.
500 * @details Creates the target set if needed. Each matching member is updated
501 * through `AddSystemToSet`.
502 * @param group_id Anonymous group id returned by variadic `Add`
503 * @param target_set_id Named set to assign group members to
504 */
505 void AssignGroupToSet(SystemSetId group_id, SystemSetId target_set_id);
506
507 /**
508 * @brief Allocates a unique id for an anonymous system group.
509 * @details Each variadic `Add` call receives a distinct id so batches with
510 * the same system types do not share configuration.
511 * @return New anonymous group id
512 */
513 [[nodiscard]] SystemSetId AllocateAnonymousGroupId() noexcept;
514
515 /// @brief Bumps the generation counter and marks the schedule dirty.
516 void MarkDirty() noexcept {
517 is_dirty_ = true;
518 ++generation_;
519 }
520
521 /**
522 * @brief Returns the system entry for the system at `slot`.
523 * @warning Triggers asserion if `slot` is out of bounds.
524 * @param slot Index of the system entry to retrieve
525 * @return Reference to the system entry at the given slot
526 */
527 [[nodiscard]] SystemEntry& GetSystemEntry(size_t slot) {
528 HELIOS_ASSERT(slot < system_entries_.size(), "Invalid system slot '{}'!",
529 slot);
530 return system_entries_[slot];
531 }
532
533 /**
534 * @brief Returns the system set with the given id.
535 * @warning Triggers assertion if the set is not found.
536 * @param id System set id
537 * @return Reference to the system set with the given id
538 */
539 [[nodiscard]] SystemSet& GetSystemSet(size_t id) {
540 const auto it = sets_.find(id);
541 HELIOS_ASSERT(it != sets_.end(), "Unknown system set '{}'!", id);
542 return it->second;
543 }
544
545 std::vector<SystemEntry> system_entries_;
546 std::unordered_map<size_t, SystemSet> sets_;
547 std::optional<CompiledPlan> plan_;
548 std::vector<std::vector<RunConditionStorage*>> conditions_cache_;
549
550 std::string name_;
551 ScheduleSettings settings_;
552
553 std::unique_ptr<Executor> executor_;
554
555 size_t generation_ = 0; ///< Bumped on every structural modification
556 size_t next_anonymous_group_id_ = 1;
557 bool is_dirty_ = true;
558
559 friend class MainThreadExecutor;
562 friend class SystemHandle;
563 friend class SystemSetHandle;
564 friend class SystemGroupHandle;
565};
566
567inline void Schedule::Run(World& world) {
568 HELIOS_ASSERT(executor_ != nullptr,
569 "Schedule::Run() called but no executor is set! "
570 "Use SetExecutor() before running.");
571
572 HELIOS_ECS_PROFILE_SCOPE_N("helios::ecs::Schedule::Run");
573 HELIOS_ECS_PROFILE_ZONE_NAME(
574 std::format("helios::ecs::Schedule::Run{{name: {}}}", name_));
575 HELIOS_ECS_PROFILE_ZONE_VALUE(
576 plan_.has_value() ? plan_->execution_order.size() : 0U);
577
578 Run(world, *executor_);
579}
580
581inline void Schedule::RunAndWait(World& world) {
582 HELIOS_ASSERT(executor_ != nullptr,
583 "Schedule::RunAndWait() called but no executor is set! "
584 "Use SetExecutor() before running.");
585
586 HELIOS_ECS_PROFILE_SCOPE_N("helios::ecs::Schedule::RunAndWait");
587 HELIOS_ECS_PROFILE_ZONE_NAME(
588 std::format("helios::ecs::Schedule::RunAndWait{{name: {}}}", name_));
589 HELIOS_ECS_PROFILE_ZONE_VALUE(
590 plan_.has_value() ? plan_->execution_order.size() : 0U);
591
592 RunAndWait(world, *executor_);
593}
594
595template <FunctorSystemTrait T>
597 const size_t slot =
598 AddEntry(SystemStorage::FromParam(std::forward<T>(system), options));
599 return SystemHandle(ScheduleSystemId{.id = system_entries_[slot].storage.id,
600 .slot = slot,
601 .generation = generation_},
602 *this);
603}
604
605template <SystemTrait T>
606inline SystemHandle Schedule::Add(std::string name, T&& system,
607 SystemLocalDataOptions options) {
608 const size_t slot = AddEntry(SystemStorage::FromParamNamed(
609 std::move(name), std::forward<T>(system), options));
610 return SystemHandle(ScheduleSystemId{.id = system_entries_[slot].storage.id,
611 .slot = slot,
612 .generation = generation_},
613 *this);
614}
615
616template <FunctorSystemTrait... Ts>
617 requires(sizeof...(Ts) > 1)
618inline SystemGroupHandle Schedule::Add(Ts&&... systems) {
619 std::array handles = {Add(std::forward<Ts>(systems))...};
620
621 const SystemSetId group_id = AllocateAnonymousGroupId();
622 EnsureSet(group_id);
623
624 for (SystemHandle& handle : handles) {
625 AddSystemToSet(handle.Id().slot, group_id);
626 }
627
628 return {group_id, *this};
629}
630
631inline SystemHandle Schedule::Add(std::string name, SystemCallable system,
632 AccessPolicy policy,
633 SystemLocalDataOptions options) {
634 const size_t slot = AddEntry(SystemStorage::From(
635 std::move(name), std::move(system), std::move(policy), options));
636 return SystemHandle(ScheduleSystemId{.id = system_entries_[slot].storage.id,
637 .slot = slot,
638 .generation = generation_},
639 *this);
640}
641
643 EnsureSet(id);
644 MarkDirty();
645 return {id, *this};
646}
647
648inline void Schedule::EnsureSet(SystemSetId set_id) {
649 if (sets_.contains(set_id.id)) {
650 return;
651 }
652
653 sets_.emplace(set_id.id, SystemSet(set_id));
654 MarkDirty();
655}
656
657inline bool Schedule::AddSystemToSet(size_t slot, SystemSetId set_id) {
658 EnsureSet(set_id);
659
660 auto& entry = GetSystemEntry(slot);
661 const size_t before = entry.metadata.member_of_sets.size();
662 entry.metadata.AddMemberOfSet(set_id);
663 if (entry.metadata.member_of_sets.size() != before) {
664 MarkDirty();
665 return true;
666 }
667 return false;
668}
669
670inline void Schedule::AssignGroupToSet(SystemSetId group_id,
671 SystemSetId target_set_id) {
672 EnsureSet(target_set_id);
673
674 for (size_t slot = 0; slot < system_entries_.size(); ++slot) {
675 const auto& entry = system_entries_[slot];
676 if (std::ranges::find(entry.metadata.member_of_sets, group_id) !=
677 entry.metadata.member_of_sets.end()) {
678 AddSystemToSet(slot, target_set_id);
679 }
680 }
681}
682
683inline SystemSetId Schedule::AllocateAnonymousGroupId() noexcept {
684 static constexpr size_t kAnonymousGroupIdTag = static_cast<size_t>(1) << 63;
685 return SystemSetId{.id = kAnonymousGroupIdTag ^ next_anonymous_group_id_++};
686}
687
688constexpr auto SystemHandle::Before(this auto&& self, SystemId target)
689 -> decltype(std::forward<decltype(self)>(self)) {
690 auto& schedule = self.schedule_.get();
691
692 auto& entry = schedule.GetSystemEntry(self.id_.slot);
693 entry.metadata.AddBefore(target);
694 schedule.MarkDirty();
695
696 return std::forward<decltype(self)>(self);
697}
698
699constexpr auto SystemHandle::Before(this auto&& self, SystemSetId target)
700 -> decltype(std::forward<decltype(self)>(self)) {
701 auto& schedule = self.schedule_.get();
702
703 auto& entry = schedule.GetSystemEntry(self.id_.slot);
704 entry.metadata.AddBefore(target);
705 schedule.MarkDirty();
706
707 return std::forward<decltype(self)>(self);
708}
709
710constexpr auto SystemHandle::Before(this auto&& self,
711 const SystemSetHandle& target)
712 -> decltype(std::forward<decltype(self)>(self)) {
713 return std::forward<decltype(self)>(self).Before(target.Id());
714}
715
716constexpr auto SystemHandle::Before(this auto&& self,
717 const SystemGroupHandle& target)
718 -> decltype(std::forward<decltype(self)>(self)) {
719 return std::forward<decltype(self)>(self).Before(target.Id());
720}
721
722constexpr auto SystemHandle::After(this auto&& self, SystemId target)
723 -> decltype(std::forward<decltype(self)>(self)) {
724 auto& schedule = self.schedule_.get();
725
726 auto& entry = schedule.GetSystemEntry(self.id_.slot);
727 entry.metadata.AddAfter(target);
728 schedule.MarkDirty();
729
730 return std::forward<decltype(self)>(self);
731}
732
733constexpr auto SystemHandle::After(this auto&& self, SystemSetId target)
734 -> decltype(std::forward<decltype(self)>(self)) {
735 auto& schedule = self.schedule_.get();
736
737 auto& entry = schedule.GetSystemEntry(self.id_.slot);
738 entry.metadata.AddAfter(target);
739 schedule.MarkDirty();
740
741 return std::forward<decltype(self)>(self);
742}
743
744constexpr auto SystemHandle::After(this auto&& self,
745 const SystemSetHandle& target)
746 -> decltype(std::forward<decltype(self)>(self)) {
747 return std::forward<decltype(self)>(self).After(target.Id());
748}
749
750constexpr auto SystemHandle::After(this auto&& self,
751 const SystemGroupHandle& target)
752 -> decltype(std::forward<decltype(self)>(self)) {
753 return std::forward<decltype(self)>(self).After(target.Id());
754}
755
756constexpr auto SystemHandle::RunIf(this auto&& self, RunCondition condition,
757 AccessPolicy policy,
759 -> decltype(std::forward<decltype(self)>(self)) {
760 auto& schedule = self.schedule_.get();
761
762 auto& entry = schedule.GetSystemEntry(self.id_.slot);
763 entry.metadata.conditions.push_back(RunConditionStorage::From(
764 std::move(condition), std::move(policy), options));
765 schedule.MarkDirty();
766
767 return std::forward<decltype(self)>(self);
768}
769
770template <FunctorSystemTrait T>
771constexpr auto SystemHandle::RunIf(this auto&& self, T&& condition)
772 -> decltype(std::forward<decltype(self)>(self)) {
773 auto& schedule = self.schedule_.get();
774
775 auto& entry = schedule.GetSystemEntry(self.id_.slot);
776 entry.metadata.conditions.push_back(
777 RunConditionStorage::FromParam(std::forward<T>(condition)));
778 schedule.MarkDirty();
779
780 return std::forward<decltype(self)>(self);
781}
782
783template <SystemTrait T>
784constexpr auto SystemHandle::RunIf(this auto&& self, std::string name,
785 T&& condition)
786 -> decltype(std::forward<decltype(self)>(self)) {
787 auto& schedule = self.schedule_.get();
788
789 auto& entry = schedule.GetSystemEntry(self.id_.slot);
790 entry.metadata.conditions.push_back(RunConditionStorage::FromParamNamed(
791 std::move(name), std::forward<T>(condition)));
792 schedule.MarkDirty();
793
794 return std::forward<decltype(self)>(self);
795}
796
797constexpr auto SystemHandle::InSet(this auto&& self, SystemSetId set_id)
798 -> decltype(std::forward<decltype(self)>(self)) {
799 auto& schedule = self.schedule_.get();
800 schedule.AddSystemToSet(self.id_.slot, set_id);
801 return std::forward<decltype(self)>(self);
802}
803
804constexpr auto SystemSetHandle::Before(this auto&& self, SystemId target)
805 -> decltype(std::forward<decltype(self)>(self)) {
806 auto& schedule = self.schedule_.get();
807
808 auto& set_entry = schedule.GetSystemSet(self.id_.id);
809 set_entry.Before(target);
810 schedule.MarkDirty();
811
812 return std::forward<decltype(self)>(self);
813}
814
815constexpr auto SystemSetHandle::Before(this auto&& self, SystemSetId target)
816 -> decltype(std::forward<decltype(self)>(self)) {
817 auto& schedule = self.schedule_.get();
818
819 auto& set_entry = schedule.GetSystemSet(self.id_.id);
820 set_entry.Before(target);
821 schedule.MarkDirty();
822
823 return std::forward<decltype(self)>(self);
824}
825
826constexpr auto SystemSetHandle::Before(this auto&& self,
827 const SystemGroupHandle& target)
828 -> decltype(std::forward<decltype(self)>(self)) {
829 return std::forward<decltype(self)>(self).Before(target.Id());
830}
831
832constexpr auto SystemSetHandle::After(this auto&& self, SystemId target)
833 -> decltype(std::forward<decltype(self)>(self)) {
834 auto& schedule = self.schedule_.get();
835
836 auto& set_entry = schedule.GetSystemSet(self.id_.id);
837 set_entry.After(target);
838 schedule.MarkDirty();
839
840 return std::forward<decltype(self)>(self);
841}
842
843constexpr auto SystemSetHandle::After(this auto&& self, SystemSetId target)
844 -> decltype(std::forward<decltype(self)>(self)) {
845 auto& schedule = self.schedule_.get();
846
847 auto& set_entry = schedule.GetSystemSet(self.id_.id);
848 set_entry.After(target);
849 schedule.MarkDirty();
850
851 return std::forward<decltype(self)>(self);
852}
853
854constexpr auto SystemSetHandle::After(this auto&& self,
855 const SystemGroupHandle& target)
856 -> decltype(std::forward<decltype(self)>(self)) {
857 return std::forward<decltype(self)>(self).After(target.Id());
858}
859
860constexpr auto SystemSetHandle::RunIf(this auto&& self, RunCondition condition,
861 AccessPolicy policy,
863 -> decltype(std::forward<decltype(self)>(self)) {
864 auto& schedule = self.schedule_.get();
865
866 auto& set_entry = schedule.GetSystemSet(self.id_.id);
867 set_entry.RunIf(RunConditionStorage::From(std::move(condition),
868 std::move(policy), options));
869 schedule.MarkDirty();
870
871 return std::forward<decltype(self)>(self);
872}
873
874template <FunctorSystemTrait T>
875constexpr auto SystemSetHandle::RunIf(this auto&& self, T&& condition)
876 -> decltype(std::forward<decltype(self)>(self)) {
877 auto& schedule = self.schedule_.get();
878
879 auto& set_entry = schedule.GetSystemSet(self.id_.id);
880 set_entry.RunIf(RunConditionStorage::FromParam(std::forward<T>(condition)));
881 schedule.MarkDirty();
882
883 return std::forward<decltype(self)>(self);
884}
885
886template <SystemTrait T>
887constexpr auto SystemSetHandle::RunIf(this auto&& self, std::string name,
888 T&& condition)
889 -> decltype(std::forward<decltype(self)>(self)) {
890 auto& schedule = self.schedule_.get();
891
892 auto& set_entry = schedule.GetSystemSet(self.id_.id);
894 std::move(name), std::forward<T>(condition)));
895 schedule.MarkDirty();
896
897 return std::forward<decltype(self)>(self);
898}
899
900constexpr auto SystemSetHandle::Sequence(this auto&& self)
901 -> decltype(std::forward<decltype(self)>(self)) {
902 auto& schedule = self.schedule_.get();
903
904 auto& set_entry = schedule.GetSystemSet(self.id_.id);
905 set_entry.Sequence();
906 schedule.MarkDirty();
907
908 return std::forward<decltype(self)>(self);
909}
910
911constexpr auto SystemGroupHandle::Before(this auto&& self, SystemId target)
912 -> decltype(std::forward<decltype(self)>(self)) {
913 auto& schedule = self.schedule_.get();
914
915 auto& set_entry = schedule.GetSystemSet(self.id_.id);
916 set_entry.Before(target);
917 schedule.MarkDirty();
918
919 return std::forward<decltype(self)>(self);
920}
921
922constexpr auto SystemGroupHandle::Before(this auto&& self, SystemSetId target)
923 -> decltype(std::forward<decltype(self)>(self)) {
924 auto& schedule = self.schedule_.get();
925
926 auto& set_entry = schedule.GetSystemSet(self.id_.id);
927 set_entry.Before(target);
928 schedule.MarkDirty();
929
930 return std::forward<decltype(self)>(self);
931}
932
933constexpr auto SystemGroupHandle::After(this auto&& self, SystemId target)
934 -> decltype(std::forward<decltype(self)>(self)) {
935 auto& schedule = self.schedule_.get();
936
937 auto& set_entry = schedule.GetSystemSet(self.id_.id);
938 set_entry.After(target);
939 schedule.MarkDirty();
940
941 return std::forward<decltype(self)>(self);
942}
943
944constexpr auto SystemGroupHandle::After(this auto&& self, SystemSetId target)
945 -> decltype(std::forward<decltype(self)>(self)) {
946 auto& schedule = self.schedule_.get();
947
948 auto& set_entry = schedule.GetSystemSet(self.id_.id);
949 set_entry.After(target);
950 schedule.MarkDirty();
951
952 return std::forward<decltype(self)>(self);
953}
954
955constexpr auto SystemGroupHandle::RunIf(this auto&& self,
956 RunCondition condition,
957 AccessPolicy policy,
959 -> decltype(std::forward<decltype(self)>(self)) {
960 auto& schedule = self.schedule_.get();
961
962 auto& set_entry = schedule.GetSystemSet(self.id_.id);
963 set_entry.RunIf(RunConditionStorage::From(std::move(condition),
964 std::move(policy), options));
965 schedule.MarkDirty();
966
967 return std::forward<decltype(self)>(self);
968}
969
970template <FunctorSystemTrait T>
971constexpr auto SystemGroupHandle::RunIf(this auto&& self, T&& condition)
972 -> decltype(std::forward<decltype(self)>(self)) {
973 auto& schedule = self.schedule_.get();
974
975 auto& set_entry = schedule.GetSystemSet(self.id_.id);
976 set_entry.RunIf(RunConditionStorage::FromParam(std::forward<T>(condition)));
977 schedule.MarkDirty();
978
979 return std::forward<decltype(self)>(self);
980}
981
982template <SystemTrait T>
983constexpr auto SystemGroupHandle::RunIf(this auto&& self, std::string name,
984 T&& condition)
985 -> decltype(std::forward<decltype(self)>(self)) {
986 auto& schedule = self.schedule_.get();
987
988 auto& set_entry = schedule.GetSystemSet(self.id_.id);
990 std::move(name), std::forward<T>(condition)));
991 schedule.MarkDirty();
992
993 return std::forward<decltype(self)>(self);
994}
995
996constexpr auto SystemGroupHandle::InSet(this auto&& self, SystemSetId target)
997 -> decltype(std::forward<decltype(self)>(self)) {
998 auto& schedule = self.schedule_.get();
999 schedule.AssignGroupToSet(self.id_, target);
1000 return std::forward<decltype(self)>(self);
1001}
1002
1003constexpr auto SystemGroupHandle::Sequence(this auto&& self)
1004 -> decltype(std::forward<decltype(self)>(self)) {
1005 auto& schedule = self.schedule_.get();
1006
1007 auto& set_entry = schedule.GetSystemSet(self.id_.id);
1008 set_entry.Sequence();
1009 schedule.MarkDirty();
1010
1011 return std::forward<decltype(self)>(self);
1012}
1013
1014} // namespace helios::ecs
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Stores data access requirements for a system at compile time.
Abstract interface for schedule executors.
Definition executor.hpp:27
A collection of systems, sets, and run conditions with ordering constraints.
Definition schedule.hpp:182
bool IsDirty() const noexcept
Checks whether the schedule requires recompilation.
Definition schedule.hpp:374
static Schedule From(const T &schedule={})
Creates a schedule named after the given label type.
Definition schedule.hpp:206
const ScheduleSettings & Settings() const noexcept
Gets scheduler-local settings for this schedule.
Definition schedule.hpp:403
SystemSetHandle Set(const T &={})
Gets or creates a system set with the given label type.
Definition schedule.hpp:351
void SetName(std::string name) noexcept
Sets the human-readable schedule name.
Definition schedule.hpp:368
auto Build() -> ScheduleResult< void >
Compiles the schedule into an executable plan.
Definition schedule.cpp:117
void RunAndWait(World &world, Executor &executor)
Executes the compiled schedule and blocks until completion.
Definition schedule.cpp:80
ScheduleSettings & Settings() noexcept
Gets scheduler-local settings for this schedule.
Definition schedule.hpp:397
void SetExecutor(std::unique_ptr< Executor > executor) noexcept
Sets the executor for this schedule.
Definition schedule.hpp:360
SystemHandle Add(T &&system, SystemLocalDataOptions options={})
Adds a param-style functor system to the schedule.
Definition schedule.hpp:596
SystemSetHandle Set(SystemSetId id)
Gets or creates a system set with the given identifier.
Definition schedule.hpp:642
Executor * GetExecutor() noexcept
Gets the stored executor.
Definition schedule.hpp:411
friend class SystemHandle
Definition schedule.hpp:562
Schedule(Schedule &&) noexcept=default
friend class MainThreadExecutor
Definition schedule.hpp:559
Schedule(std::string name)
Constructs a schedule with the given name.
Definition schedule.hpp:190
void ApplyDeferred(World &world)
Flushes world state and applies all pending system-local data.
Definition schedule.cpp:106
bool HasPendingLocalData() const noexcept
Checks whether any system still has unapplied local commands or messages.
Definition schedule.hpp:381
friend class SingleThreadedExecutor
Definition schedule.hpp:560
friend class SystemGroupHandle
Definition schedule.hpp:564
void Run(World &world, Executor &executor)
Submits the compiled schedule for execution using the provided executor.
Definition schedule.cpp:55
friend class MultiThreadedExecutor
Definition schedule.hpp:561
Schedule(const Schedule &)=delete
friend class SystemSetHandle
Definition schedule.hpp:563
void Clear()
Clears all systems, sets, and compiled state from this schedule.
Definition schedule.cpp:160
const std::string & GetName() const noexcept
Gets the human-readable schedule name.
Definition schedule.hpp:391
const Executor * GetExecutor() const noexcept
Gets the stored executor.
Definition schedule.hpp:417
Handle returned by variadic Schedule::Add for configuring a batch of systems added together.
constexpr auto Sequence(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Marks this group as a sequence: members run in insertion order.
constexpr auto Before(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: all members of this group must run before the given system.
Definition schedule.hpp:911
constexpr auto RunIf(this auto &&self, RunCondition condition, AccessPolicy policy={}, SystemLocalDataOptions options={}) -> decltype(std::forward< decltype(self)>(self))
Adds a run condition predicate that applies to all group members.
Definition schedule.hpp:955
constexpr auto After(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: all members of this group must run after the given system.
Definition schedule.hpp:933
constexpr auto InSet(this auto &&self, SystemSetId target) -> decltype(std::forward< decltype(self)>(self))
Assigns every member of this group to the given named set.
Definition schedule.hpp:996
constexpr auto RunIf(this auto &&self, RunCondition condition, AccessPolicy policy={}, SystemLocalDataOptions options={}) -> decltype(std::forward< decltype(self)>(self))
Adds a run condition predicate that must return true for this system to execute.
Definition schedule.hpp:756
constexpr auto After(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: this system must run after the given system id.
Definition schedule.hpp:722
constexpr auto InSet(this auto &&self, SystemSetId set_id) -> decltype(std::forward< decltype(self)>(self))
Assigns this system to a labeled set by id.
Definition schedule.hpp:797
constexpr auto Before(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: this system must run before the given system id.
Definition schedule.hpp:688
Handle returned by Schedule::Set for configuring a named system set.
constexpr auto After(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: all members of this set must run after the given system.
Definition schedule.hpp:832
constexpr auto Before(this auto &&self, SystemId target) -> decltype(std::forward< decltype(self)>(self))
Adds an ordering constraint: all members of this set must run before the given system.
Definition schedule.hpp:804
constexpr auto Sequence(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Marks this set as a sequence: systems run in insertion order.
Definition schedule.hpp:900
constexpr auto RunIf(this auto &&self, RunCondition condition, AccessPolicy policy={}, SystemLocalDataOptions options={}) -> decltype(std::forward< decltype(self)>(self))
Adds a run condition predicate that applies to all members.
Definition schedule.hpp:860
The World class manages entities with their components and systems.
Definition world.hpp:39
Concept for system types that are not lambda closures.
Definition system.hpp:105
Concept for schedules.
Definition schedule.hpp:51
Concept for schedules that provide a name.
Definition schedule.hpp:60
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
@ kMultiThreaded
Systems run in parallel using work-stealing.
Definition executor.hpp:18
std::function< bool(World &, SystemLocalData &)> RunCondition
Type-erased run condition: returns bool, receives World& + local data.
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
std::function< void(World &, SystemLocalData &)> SystemCallable
ScheduleErrorKind
Kind of error produced during schedule compilation.
Definition schedule.hpp:156
constexpr std::string_view QualifiedTypeNameOf() noexcept
Retrieves the fully qualified type name of T.
STL namespace.
static RunConditionStorage From(RunCondition fn, AccessPolicy policy={}, SystemLocalDataOptions options={}, std::string name={})
Factory from a raw predicate + explicit policy.
static RunConditionStorage FromParam(T &&instance, SystemLocalDataOptions options={})
Factory from a param-style run condition functor.
static RunConditionStorage FromParamNamed(std::string name, T &&instance, SystemLocalDataOptions options={})
Factory from a param-style run condition with an explicit name.
Error type returned when schedule compilation fails.
Definition schedule.hpp:165
std::vector< SystemId > involved_systems
Definition schedule.hpp:168
ScheduleErrorKind kind
Definition schedule.hpp:166
Settings that control how a schedule is compiled and executed.
Definition schedule.hpp:151
Unique identifier for a system within a schedule.
Metadata attached to a system entry for ordering and configuration.
Definition schedule.hpp:83
constexpr void AddBefore(SystemSetId id)
Adds a system set ID to the list of targets that must run before this system.
Definition schedule.hpp:113
static void AppendUnique(std::vector< T > &vec, const T &value)
Appends a value to a vector if it is not already present.
Definition schedule.hpp:143
std::vector< SystemId > after_targets
Definition schedule.hpp:85
constexpr void AddBefore(SystemId id)
Adds a system ID to the list of targets that must run before this system.
Definition schedule.hpp:97
std::vector< SystemId > before_targets
Definition schedule.hpp:84
std::vector< SystemSetId > before_set_targets
Definition schedule.hpp:86
std::vector< SystemSetId > member_of_sets
Definition schedule.hpp:88
constexpr void AddMemberOfSet(SystemSetId id)
Adds a system set membership if not already present.
Definition schedule.hpp:132
std::vector< RunConditionStorage > conditions
Definition schedule.hpp:89
std::vector< SystemSetId > after_set_targets
Definition schedule.hpp:87
constexpr void AddAfter(SystemId id)
Adds a system ID to the list of targets that must run after this system.
Definition schedule.hpp:105
constexpr void AddAfter(SystemSetId id)
Adds a system set ID to the list of targets that must run after this system.
Definition schedule.hpp:123
Id for systems.
Definition system.hpp:146
Options for system local data.
Identifier for a system set.
static constexpr SystemSetId From(SystemSetTypeIndex index) noexcept
Creates system set id from a system set type index.
Storage for a system.
static SystemStorage FromParamNamed(std::string name, T &&system, SystemLocalDataOptions options={})
Factory from a param-style system functor with an explicit name.
static SystemStorage FromParam(T &&system, SystemLocalDataOptions options={})
Factory from a param-style system functor.
static SystemStorage From(std::string name, SystemCallable system, AccessPolicy access_policy={}, SystemLocalDataOptions options={})
Factory for a raw callable with explicit name and access policy.