Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
sub_app.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/app/details/profile.hpp>
5#include <helios/assert.hpp>
15#include <helios/ecs/world.hpp>
16#include <helios/log/logger.hpp>
18
19#include <atomic>
20#include <concepts>
21#include <cstddef>
22#include <functional>
23#include <mutex>
24#include <optional>
25#include <string>
26#include <string_view>
27#include <utility>
28
29#if defined(HELIOS_APP_ENABLE_PROFILE) && \
30 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
31#include <format>
32#endif
33
34namespace helios::app {
35
36class App;
37
38/// @brief Type index for sub-apps.
40
41/// @brief Type id for sub-apps.
43
44/**
45 * @brief Trait to identify valid sub-app types.
46 * @details A valid sub-app type must be an empty struct or class.
47 */
48template <typename T>
49concept SubAppTrait = std::is_object_v<std::remove_cvref_t<T>> &&
50 std::is_empty_v<std::remove_cvref_t<T>>;
51
52/// @brief Concept for sub-apps that provide custom names.
53template <typename T>
54concept SubAppWithNameTrait = SubAppTrait<T> && requires {
55 { std::remove_cvref_t<T>::kName } -> std::convertible_to<std::string_view>;
56};
57
58/// @brief Concept for sub-apps that allow skipping extraction while updating.
59template <typename T>
61 requires std::remove_cvref_t<T>::kAllowOverlappingUpdates;
62};
63
64/// @brief Concept for sub-apps with a maximum extraction-skip budget.
65template <typename T>
68 {
69 std::remove_cvref_t<T>::kMaxOverlappingUpdates
70 } -> std::convertible_to<size_t>;
71 };
72
73/// @brief Concept for sub-apps that run updates on a background loop.
74template <typename T>
76 SubAppTrait<T> && requires { requires std::remove_cvref_t<T>::kAsync; };
77
78/**
79 * @brief Returns the name of the sub-app.
80 * @tparam T The type of the sub-app
81 * @param sub_app The sub-app to get the name of
82 * @return The name of the sub-app
83 */
84template <SubAppTrait T>
85[[nodiscard]] constexpr std::string_view SubAppNameOf(
86 const T& sub_app = {}) noexcept {
87 if constexpr (SubAppWithNameTrait<T>) {
88 return std::remove_cvref_t<T>::kName;
89 } else {
90 return utils::QualifiedTypeNameOf(sub_app);
91 }
92}
93
94/**
95 * @brief Returns whether the sub-app allows extraction skips while updating.
96 * @tparam T The type of the sub-app
97 * @return Whether overlapping extraction skips are enabled
98 */
99template <SubAppTrait T>
100[[nodiscard]] consteval bool IsSubAppAllowsOverlappingUpdates(
101 const T& /*sub_app*/ = {}) noexcept {
102 return OverlappingUpdatesSubAppTrait<std::remove_cvref_t<T>>;
103}
104
105/**
106 * @brief Returns the maximum consecutive extraction skips for the sub-app.
107 * @details `0` means unlimited skips while an update is in flight.
108 * @tparam T The type of the sub-app
109 * @return Maximum extraction skip budget
110 */
111template <OverlappingUpdatesSubAppTrait T>
112[[nodiscard]] consteval size_t SubAppMaxOverlappingUpdates(
113 const T& /*sub_app*/ = {}) noexcept {
114 if constexpr (SubAppWithMaxOverlappingUpdatesTrait<T>) {
115 return std::remove_cvref_t<T>::kMaxOverlappingUpdates;
116 } else {
117 return 0;
118 }
119}
120
121/**
122 * @brief Returns whether the sub-app runs updates on a background loop.
123 * @tparam T The type of the sub-app
124 * @return True when `kAsync` is set on the label type
125 */
126template <SubAppTrait T>
127[[nodiscard]] consteval bool IsSubAppAsync(const T& /*sub_app*/ = {}) noexcept {
128 return AsyncSubAppTrait<std::remove_cvref_t<T>>;
129}
130
131/**
132 * @brief A sub-application with its own ECS world and scheduler.
133 * @details Use `SetExtractFunction` to copy data from the main world before
134 * each update. Overlapping sub-apps may skip extraction while an update is in
135 * flight, up to `kMaxOverlappingUpdates` consecutive frames (`0` = unlimited).
136 * Async sub-apps run their update pass on a background loop; extraction runs
137 * every main frame and thread safety is the user's responsibility.
138 * @warning Async sub-apps must use a looping runner such as `RunDefaultSubApp`
139 * or `RunFixedSubApp` via `SetRunner()` before initialization. The default
140 * `RunOnceSubApp` runs a single update pass and is intended for blocking
141 * sub-apps.
142 * @note Partially thread-safe.
143 */
144class SubApp {
145public:
146#ifdef HELIOS_MOVEONLY_FUNCTION_AVAILABLE
147 using RunnerFn = std::move_only_function<void(SubApp&, async::Executor&)>;
148 using ExtractFn =
149 std::move_only_function<void(const ecs::World&, ecs::World&)>;
150#else
151 using RunnerFn = std::function<void(SubApp&, async::Executor&)>;
152 using ExtractFn = std::function<void(const ecs::World&, ecs::World&)>;
153#endif
154
155 /// @brief Constructs a sub-app with built-in schedules registered.
156 SubApp();
157
158 /**
159 * @brief Constructs a sub-app with the given name and built-in schedules
160 * registered.
161 * @param name Human-readable sub-app name
162 */
163 explicit SubApp(std::string name);
164
165 SubApp(const SubApp&) = delete;
166
167 /**
168 * @brief Move-constructs a sub-app from another.
169 * @warning Triggers assertion if an update is in progress.
170 * @param other The sub-app to move from
171 */
172 SubApp(SubApp&& other) noexcept;
173 ~SubApp() = default;
174
175 SubApp& operator=(const SubApp&) = delete;
176
177 /**
178 * @brief Move-assigns a sub-app from another.
179 * @warning Triggers assertion if an update is in progress.
180 * @param other The sub-app to move from
181 */
182 SubApp& operator=(SubApp&& other) noexcept;
183
184 /**
185 * @brief Creates a sub-app named after the given label type.
186 * @tparam T Sub-app label type satisfying `SubAppTrait`
187 * @param sub_app Sub-app label instance
188 * @return Sub-app with name derived from the label
189 */
190 template <SubAppTrait T>
191 [[nodiscard]] static SubApp From(const T& sub_app = {}) {
192 return SubApp(std::string(SubAppNameOf(sub_app)));
193 }
194
195 /**
196 * @brief Clears the sub-app world, ECS scheduler, and extract function.
197 * @note Not thread-safe.
198 * @warning Triggers assertion if an update is in progress.
199 */
200 void Clear();
201
202 /**
203 * @brief Runs the configured update stage.
204 * @note Only valid within an active update pass (IsUpdating() is true).
205 * @warning Triggers assertion if not called from within an active update
206 * pass.
207 * @param executor Async executor used to build and run schedules
208 */
209 void Update(async::Executor& executor);
210
211 /**
212 * @brief Copies data from the main world into this sub-app's world.
213 * @details Calls the function passed to `SetExtractFunction` when set.
214 * @note Not thread-safe.
215 * @warning Triggers assertion if an update is in progress and @p
216 * allow_while_updating is false.
217 * @param main_world Main application world to read from
218 * @param allow_while_updating When true, extraction may run during an update
219 */
220 void Extract(const ecs::World& main_world, bool allow_while_updating = false);
221
222 /**
223 * @brief Blocks until no update is in flight.
224 * @note Thread-safe.
225 */
226 void WaitUntilFullyIdle() const noexcept;
227
228 /**
229 * @brief Builds all ECS schedules when any are dirty.
230 * @note Not thread-safe.
231 * @warning Triggers assertion if an update is in progress.
232 * @param executor Async executor used to create threaded schedule executors
233 */
234 void BuildScheduler(async::Executor& executor);
235
236 /**
237 * @brief Adds a schedule with the given label type id.
238 * @note Not thread-safe.
239 * @warning Triggers assertion if an update is in progress.
240 * @param id Schedule type id
241 * @param schedule Schedule instance
242 * @return Ordering handle for configuring placement
243 */
244 ecs::ScheduleOrdering AddSchedule(ecs::ScheduleTypeId id,
245 ecs::Schedule&& schedule) {
246 return scheduler_.Add(id, std::move(schedule));
247 }
248
249 /**
250 * @brief Adds a schedule with the given label type.
251 * @note Not thread-safe.
252 * @warning Triggers assertion if an update is in progress.
253 * @tparam T Schedule label type
254 * @param schedule Schedule instance
255 * @param label Label value
256 * @return Ordering handle for configuring placement
257 */
258 template <ecs::ScheduleTrait T>
259 ecs::ScheduleOrdering AddSchedule(const T& label, ecs::Schedule&& schedule) {
260 return scheduler_.Add(label, std::move(schedule));
261 }
262
263 /**
264 * @brief Creates an empty schedule for the label when it does not exist.
265 * @note Not thread-safe.
266 * @warning Triggers assertion if an update is in progress.
267 * @tparam T Schedule label type
268 * @param label Label value
269 * @return Reference to this sub-app for chaining
270 */
271 template <ecs::ScheduleTrait T>
272 auto InitSchedule(this auto&& self, const T& label = {})
273 -> decltype(std::forward<decltype(self)>(self));
274
275 /**
276 * @brief Mutates an existing schedule in place.
277 * @note Not thread-safe.
278 * @warning Triggers assertion if an update is in progress.
279 * @tparam L Schedule label type
280 * @param label Target schedule label
281 * @param fn Callable receiving `ecs::Schedule&`
282 * @return Reference to this sub-app for chaining
283 */
284 template <ecs::ScheduleTrait L, typename F>
285 requires std::invocable<F, ecs::Schedule&>
286 auto EditSchedule(this auto&& self, const L& label, const F& fn)
287 -> decltype(std::forward<decltype(self)>(self));
288
289 /**
290 * @brief Adds a functor system to the given schedule.
291 * @details Derives name and `SystemId` from the functor type. Lambdas
292 * are rejected; use the `AddSystem(label, name, system)` overload.
293 * @note Not thread-safe.
294 * @warning Triggers assertion if an update is in progress.
295 * @tparam L Schedule label type
296 * @tparam S System type satisfying `FunctorSystemTrait`
297 * @param label Target schedule label
298 * @param system System instance
299 * @param options Local data options for the system
300 * @return Handle for ordering and run conditions
301 */
302 template <ecs::ScheduleTrait L, ecs::FunctorSystemTrait S>
303 ecs::SystemHandle AddSystem(const L& label, S&& system,
304 ecs::SystemLocalDataOptions options = {}) {
305 return scheduler_.In(label).Add(std::forward<S>(system), options);
306 }
307
308 /**
309 * @brief Adds a system to the given schedule with an explicit name.
310 * @details Required for lambdas. Derives the `SystemId` from the
311 * provided name, enabling multiple instances of the same type.
312 * @note Not thread-safe.
313 * @warning Triggers assertion if an update is in progress.
314 * @tparam L Schedule label type
315 * @tparam S System type satisfying `SystemTrait`
316 * @param label Target schedule label
317 * @param name Human-readable system name
318 * @param system System instance
319 * @param options Local data options for the system
320 * @return Handle for ordering and run conditions
321 */
322 template <ecs::ScheduleTrait L, ecs::SystemTrait S>
323 ecs::SystemHandle AddSystem(const L& label, std::string name, S&& system,
324 ecs::SystemLocalDataOptions options = {}) {
325 return scheduler_.In(label).Add(std::move(name), std::forward<S>(system),
326 options);
327 }
328
329 /**
330 * @brief Adds multiple functor systems to the same schedule as a system
331 * group.
332 * @details Returns a `SystemGroupHandle` for configuring the batch. Use
333 * `InSet` on the returned handle to assign every system to a named set.
334 * @note Not thread-safe.
335 * @warning Triggers assertion if an update is in progress.
336 * @tparam L Schedule label type
337 * @tparam Systems System types satisfying `FunctorSystemTrait`
338 * @param label Target schedule label
339 * @param systems System instances
340 * @return Handle for configuring the system group
341 */
342 template <ecs::ScheduleTrait L, ecs::FunctorSystemTrait... Systems>
343 requires(sizeof...(Systems) > 1)
344 ecs::SystemGroupHandle AddSystems(const L& label, Systems&&... systems) {
345 return scheduler_.In(label).Add(std::forward<Systems>(systems)...);
346 }
347
348 /**
349 * @brief Gets or creates a system set in the given schedule.
350 * @note Not thread-safe.
351 * @warning Triggers assertion if an update is in progress.
352 * @tparam L Schedule label type
353 * @tparam S System set type
354 * @param label Target schedule label
355 * @param set System set instance
356 * @return Handle for configuring the set
357 */
358 template <ecs::ScheduleTrait L, ecs::SystemSetTrait S>
359 ecs::SystemSetHandle ConfigureSet(const L& label = {}, const S& set = {}) {
360 return scheduler_.In(label).Set(set);
361 }
362
363 /**
364 * @brief Inserts or replaces resources in this sub-app's world.
365 * @note Not thread-safe.
366 * @warning Triggers assertion if an update is in progress.
367 * @tparam Ts Resource types
368 * @param resources Resource values
369 * @return Reference to this sub-app for chaining
370 */
371 template <ecs::ResourceTrait... Ts>
372 auto InsertResources(this auto&& self, Ts&&... resources)
373 -> decltype(std::forward<decltype(self)>(self));
374
375 /**
376 * @brief Inserts resources only when no instance exists yet.
377 * @note Not thread-safe.
378 * @warning Triggers assertion if an update is in progress.
379 * @tparam Ts Resource types
380 * @param resources Resource values
381 * @return Reference to this sub-app for chaining
382 */
383 template <ecs::ResourceTrait... Ts>
384 auto TryInsertResources(this auto&& self, Ts&&... resources)
385 -> decltype(std::forward<decltype(self)>(self));
386
387 /**
388 * @brief Registers multiple message types on this sub-app's world.
389 * @note Not thread-safe.
390 * @warning Triggers assertion if an update is in progress.
391 * @tparam Ts Message types
392 * @return Reference to this sub-app for chaining
393 */
394 template <ecs::AnyMessageTrait... Ts>
395 requires(sizeof...(Ts) > 0)
396 auto AddMessages(this auto&& self)
397 -> decltype(std::forward<decltype(self)>(self));
398
399 /**
400 * @brief Sets the human-readable sub-app name.
401 * @note Not thread-safe.
402 * @warning Triggers assertion if an update is in progress.
403 * @param name Sub-app name
404 */
405 void SetName(std::string name) noexcept;
406
407 /**
408 * @brief Gets the human-readable sub-app name.
409 * @note Not thread-safe.
410 * @return Sub-app name
411 */
412 [[nodiscard]] const std::string& GetName() const noexcept { return name_; }
413
414 /**
415 * @brief Sets the function invoked each frame to run this sub-app's update
416 * pass.
417 * @note Not thread-safe.
418 * @warning Triggers assertion if an update is in progress.
419 * @param runner Callable receiving `SubApp&` and `async::Executor&`; when
420 * set, this is called instead of running the update stage directly
421 */
422 void SetRunner(RunnerFn runner) noexcept;
423
424 /**
425 * @brief Sets the function invoked by `Extract`.
426 * @note Not thread-safe.
427 * @warning Triggers assertion if an update is in progress.
428 * @tparam F Callable receiving `(const ecs::World&, ecs::World&)`
429 * @param fn The function to set
430 */
431 template <typename F>
432 requires std::invocable<F, const ecs::World&, ecs::World&>
433 void SetExtractFunction(F&& fn);
434
435 /**
436 * @brief Sets whether extraction may be skipped while an update is in flight.
437 * @note Not thread-safe.
438 * @warning Triggers assertion if an update is in progress.
439 * @param allow_overlapping_updates True to permit extraction skips
440 */
441 void SetAllowOverlappingUpdates(bool allow_overlapping_updates) noexcept;
442
443 /**
444 * @brief Sets the maximum consecutive extraction skips while updating.
445 * @details `0` means unlimited skips. Values `1`, `2`, ... cap how many main
446 * frames may skip extraction before waiting for the in-flight update.
447 * @note Not thread-safe.
448 * @warning Triggers assertion if an update is in progress.
449 * @param max_skips Maximum consecutive extraction skips
450 */
451 void SetMaxExtractionSkips(size_t max_skips) noexcept;
452
453 /**
454 * @brief Sets whether this sub-app runs updates on a background loop.
455 * @note Not thread-safe.
456 * @warning Triggers assertion if an update is in progress.
457 * @warning Does not change the runner. When @p is_async is true, call
458 * `SetRunner(RunDefaultSubApp)` or `SetRunner(RunFixedSubApp)` (or a custom
459 * equivalent) before `App::Initialize()` so the background loop keeps
460 * updating until `ShouldExit()` becomes true.
461 * @param is_async True for continuous background updates
462 */
463 void SetAsync(bool is_async) noexcept;
464
465 /**
466 * @brief Sets which stage label `Update` executes.
467 * @note Not thread-safe.
468 * @warning Triggers assertion if an update is in progress.
469 * @param index Stage label type index
470 */
471 void SetUpdateStage(ecs::StageTypeIndex index) noexcept;
472
473 /**
474 * @brief Sets which stage label `Update` executes.
475 * @note Not thread-safe.
476 * @warning Triggers assertion if an update is in progress.
477 * @tparam T Stage label type
478 * @param stage Stage label instance
479 */
480 template <ecs::StageTrait T>
481 void SetUpdateStage(const T& stage = {}) noexcept;
482
483 /**
484 * @brief Tries to get the schedule for a label.
485 * @note Not thread-safe.
486 * @tparam T Schedule label type
487 * @param label Target schedule label
488 * @return Pointer to the schedule, or `nullptr` if missing
489 */
490 template <ecs::ScheduleTrait T>
491 [[nodiscard]] ecs::Schedule* TryGetSchedule(const T& label = {}) {
492 return scheduler_.TryGetSchedule(label);
493 }
494
495 /**
496 * @brief Tries to get the schedule for a label.
497 * @note Not thread-safe.
498 * @tparam T Schedule label type
499 * @param label Target schedule label
500 * @return Pointer to the schedule, or `nullptr` if missing
501 */
502 template <ecs::ScheduleTrait T>
503 [[nodiscard]] const ecs::Schedule* TryGetSchedule(const T& label = {}) const {
504 return scheduler_.TryGetSchedule(label);
505 }
506
507 /**
508 * @brief Returns whether this sub-app should stop its update loop.
509 * @details Propagates `App::ShouldExit()` from the owning application when
510 * set. Also returns true when an async loop stop was requested during
511 * shutdown.
512 * @note Thread-safe.
513 * @return True when the sub-app should exit its runner loop
514 */
515 [[nodiscard]] bool ShouldExit() const noexcept;
516
517 /**
518 * @brief Returns whether an update is in flight.
519 * @note Thread-safe.
520 * @return True while an update pass is running, false otherwise
521 */
522 [[nodiscard]] bool IsUpdating() const noexcept {
523 return is_updating_.load(std::memory_order_acquire);
524 }
525
526 /**
527 * @brief Returns whether extraction may be skipped while updating.
528 * @note Not thread-safe.
529 * @return True if overlapping extraction skips are enabled
530 */
531 [[nodiscard]] bool AllowsOverlappingUpdates() const noexcept {
532 return allow_overlapping_updates_;
533 }
534
535 /**
536 * @brief Returns whether this sub-app uses a background update loop.
537 * @note Not thread-safe.
538 * @return True for async sub-apps
539 */
540 [[nodiscard]] bool IsAsync() const noexcept { return is_async_; }
541
542 /**
543 * @brief Returns the configured maximum consecutive extraction skips.
544 * @note Not thread-safe.
545 * @return Skip budget; `0` means unlimited
546 */
547 [[nodiscard]] size_t MaxExtractionSkips() const noexcept {
548 return max_extraction_skips_;
549 }
550
551 /**
552 * @brief Gets this sub-app's ECS world.
553 * @note Thread-safe for read access to returned world.
554 * @return ECS world reference
555 */
556 [[nodiscard]] ecs::World& GetWorld() noexcept { return world_; }
557
558 /**
559 * @brief Gets this sub-app's ECS world.
560 * @return ECS world reference
561 */
562 [[nodiscard]] const ecs::World& GetWorld() const noexcept { return world_; }
563
564 /**
565 * @brief Gets this sub-app's ECS schedule collection.
566 * @note Thread-safe.
567 * @return ECS scheduler reference
568 */
569 [[nodiscard]] ecs::Scheduler& GetScheduler() noexcept { return scheduler_; }
570
571 /**
572 * @brief Gets this sub-app's ECS schedule collection.
573 * @note Thread-safe.
574 * @return ECS scheduler reference
575 */
576 [[nodiscard]] const ecs::Scheduler& GetScheduler() const noexcept {
577 return scheduler_;
578 }
579
580private:
581 [[nodiscard]] bool TryBeginUpdate() noexcept;
582 void EndUpdate() noexcept {
583 is_updating_.store(false, std::memory_order_release);
584 }
585
586 void RunStartupUnchecked(async::Executor& executor) {
587 RunStageUnchecked(executor, ecs::StageTypeIndex::From(kStartupStage),
588 world_);
589 }
590
591 void RunShutdownUnchecked(async::Executor& executor) {
592 RunStageUnchecked(executor, ecs::StageTypeIndex::From(kShutdownStage),
593 world_);
594 }
595
596 void RunUpdatePass(async::Executor& executor);
597 void RunStageUnchecked(async::Executor& executor, ecs::StageTypeIndex stage,
598 ecs::World& world);
599
600 void RequestAsyncLoopStop() noexcept {
601 async_loop_stop_.store(true, std::memory_order_release);
602 }
603
604 void ResetAsyncLoopStop() noexcept {
605 async_loop_stop_.store(false, std::memory_order_release);
606 }
607
608 void SetOwnerApp(const App& app) noexcept { owner_app_ = &app; }
609
610 [[nodiscard]] bool AsyncLoopStopRequested() const noexcept {
611 return async_loop_stop_.load(std::memory_order_acquire);
612 }
613
614 std::string name_;
615
616 ecs::World world_;
617 ecs::Scheduler scheduler_;
618 std::optional<ecs::StageTypeIndex> update_stage_ =
620
621 RunnerFn runner_;
622 ExtractFn extract_fn_;
623
624 std::atomic<bool> is_updating_{false};
625 std::atomic<bool> async_loop_stop_{false};
626 bool allow_overlapping_updates_ = false;
627 bool is_async_ = false;
628 size_t max_extraction_skips_ = 0;
629
630 const App* owner_app_ = nullptr;
631
632 mutable std::mutex stage_run_mutex_;
633
634 friend class App;
635 friend class Scheduler;
636};
637
638inline SubApp::SubApp(SubApp&& other) noexcept
639 : name_(std::move(other.name_)),
640 world_(std::move(other.world_)),
641 scheduler_(std::move(other.scheduler_)),
642 update_stage_(other.update_stage_),
643 runner_(std::move(other.runner_)),
644 extract_fn_(std::move(other.extract_fn_)),
645 is_updating_(other.is_updating_.load(std::memory_order_acquire)),
646 async_loop_stop_(other.async_loop_stop_.load(std::memory_order_acquire)),
647 allow_overlapping_updates_(other.allow_overlapping_updates_),
648 is_async_(other.is_async_),
649 max_extraction_skips_(other.max_extraction_skips_),
650 owner_app_(other.owner_app_) {
651 HELIOS_ASSERT(!other.IsUpdating(), "Cannot move from updating sub-app!");
652}
653
654inline SubApp& SubApp::operator=(SubApp&& other) noexcept {
655 HELIOS_ASSERT(!IsUpdating(), "Cannot assign while updating!");
656 HELIOS_ASSERT(!other.IsUpdating(), "Cannot assign from updating sub-app!");
657
658 if (this == &other) [[unlikely]] {
659 return *this;
660 }
661
662 name_ = std::move(other.name_);
663 world_ = std::move(other.world_);
664 scheduler_ = std::move(other.scheduler_);
665 update_stage_ = other.update_stage_;
666 extract_fn_ = std::move(other.extract_fn_);
667 runner_ = std::move(other.runner_);
668 is_updating_.store(other.is_updating_.load(std::memory_order_acquire),
669 std::memory_order_release);
670 async_loop_stop_.store(other.async_loop_stop_.load(std::memory_order_acquire),
671 std::memory_order_release);
672 allow_overlapping_updates_ = other.allow_overlapping_updates_;
673 is_async_ = other.is_async_;
674 max_extraction_skips_ = other.max_extraction_skips_;
675 owner_app_ = other.owner_app_;
676
677 return *this;
678}
679
680inline void SubApp::Update(async::Executor& executor) {
681 HELIOS_APP_PROFILE_SCOPE();
682 HELIOS_APP_PROFILE_ZONE_NAME(
683 std::format("helios::app::SubApp::Update{{name: {}}}", GetName()));
684 HELIOS_APP_PROFILE_ZONE_TEXT(std::format(
685 "allow_overlapping_updates: {}, max_extraction_skips: {}, is_async: "
686 "{}",
687 allow_overlapping_updates_, max_extraction_skips_, is_async_));
688
690 "Update must be called within an active update pass!");
691 if (!update_stage_.has_value()) [[unlikely]] {
692 log::Warn("Sub-app update stage is not set!");
693 return;
694 }
695
696 RunStageUnchecked(executor, *update_stage_, world_);
697}
698
699inline void SubApp::Extract(const ecs::World& main_world,
700 [[maybe_unused]] bool allow_while_updating) {
701 HELIOS_APP_PROFILE_SCOPE();
702 HELIOS_APP_PROFILE_ZONE_NAME(
703 std::format("helios::app::SubApp::Extract{{name: {}}}", GetName()));
704 HELIOS_APP_PROFILE_ZONE_TEXT(std::format(
705 "allow_overlapping_updates: {}, max_extraction_skips: {}, is_async: {}",
706 allow_overlapping_updates_, max_extraction_skips_, is_async_));
707
708 HELIOS_ASSERT(!IsUpdating() || allow_while_updating,
709 "Cannot extract while sub-app is updating!");
710
711 if (extract_fn_) [[likely]] {
712 extract_fn_(main_world, world_);
713 }
714}
715
717 HELIOS_APP_PROFILE_SCOPE();
718 HELIOS_APP_PROFILE_ZONE_NAME(std::format(
719 "helios::app::SubApp::BuildScheduler{{name: {}}}", GetName()));
720 HELIOS_APP_PROFILE_ZONE_TEXT(std::format(
721 "allow_overlapping_updates: {}, max_extraction_skips: {}, is_async: {}",
722 allow_overlapping_updates_, max_extraction_skips_, is_async_));
723
724 if (scheduler_.IsDirty()) {
725 scheduler_.Build(executor);
726 }
727}
728
729template <ecs::ScheduleTrait T>
730inline auto SubApp::InitSchedule(this auto&& self, const T& label)
731 -> decltype(std::forward<decltype(self)>(self)) {
732 if (self.scheduler_.TryGetSchedule(label) == nullptr) {
733 self.scheduler_.Add(label, ecs::Schedule::From(label)).Done();
734 }
735 return std::forward<decltype(self)>(self);
736}
737
738template <ecs::ScheduleTrait L, typename F>
739 requires std::invocable<F, ecs::Schedule&>
740inline auto SubApp::EditSchedule(this auto&& self, const L& label, const F& fn)
741 -> decltype(std::forward<decltype(self)>(self)) {
742 fn(self.scheduler_.In(label));
743 return std::forward<decltype(self)>(self);
744}
745
746template <ecs::ResourceTrait... Ts>
747inline auto SubApp::InsertResources(this auto&& self, Ts&&... resources)
748 -> decltype(std::forward<decltype(self)>(self)) {
749 self.world_.InsertResources(std::forward<Ts>(resources)...);
750 return std::forward<decltype(self)>(self);
751}
752
753template <ecs::ResourceTrait... Ts>
754inline auto SubApp::TryInsertResources(this auto&& self, Ts&&... resources)
755 -> decltype(std::forward<decltype(self)>(self)) {
756 self.world_.TryInsertResources(std::forward<Ts>(resources)...);
757 return std::forward<decltype(self)>(self);
758}
759
760template <ecs::AnyMessageTrait... Ts>
761 requires(sizeof...(Ts) > 0)
762inline auto SubApp::AddMessages(this auto&& self)
763 -> decltype(std::forward<decltype(self)>(self)) {
764 self.world_.template AddMessages<Ts...>();
765 return std::forward<decltype(self)>(self);
766}
767
768inline void SubApp::SetName(std::string name) noexcept {
769 HELIOS_ASSERT(!IsUpdating(), "Cannot set name while updating!");
770 name_ = std::move(name);
771}
772
773inline void SubApp::SetRunner(RunnerFn runner) noexcept {
774 HELIOS_ASSERT(!IsUpdating(), "Cannot set runner while updating!");
775 HELIOS_ASSERT(runner, "Sub-app runner cannot be null!");
776 runner_ = std::move(runner);
777}
778
779template <typename F>
780 requires std::invocable<F, const ecs::World&, ecs::World&>
781inline void SubApp::SetExtractFunction(F&& fn) {
782 HELIOS_ASSERT(!IsUpdating(), "Cannot set extract function while updating!");
783 extract_fn_ = std::forward<F>(fn);
784}
785
787 bool allow_overlapping_updates) noexcept {
789 "Cannot set allow overlapping updates while updating!");
790 allow_overlapping_updates_ = allow_overlapping_updates;
791}
792
793inline void SubApp::SetMaxExtractionSkips(size_t max_skips) noexcept {
795 "Cannot set max extraction skips while updating!");
796 max_extraction_skips_ = max_skips;
797}
798
799inline void SubApp::SetAsync(bool is_async) noexcept {
800 HELIOS_ASSERT(!IsUpdating(), "Cannot set async while updating!");
801 is_async_ = is_async;
802}
803
804inline void SubApp::SetUpdateStage(ecs::StageTypeIndex index) noexcept {
805 HELIOS_ASSERT(!IsUpdating(), "Cannot set update stage while updating!");
806 update_stage_ = index;
807}
808
809template <ecs::StageTrait T>
810inline void SubApp::SetUpdateStage(const T& stage) noexcept {
811 HELIOS_ASSERT(!IsUpdating(), "Cannot set update stage while updating!");
812 update_stage_ = ecs::StageTypeIndex::From(stage);
813}
814
815} // namespace helios::app
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Application class.
static SubApp From(const T &sub_app={})
Creates a sub-app named after the given label type.
Definition sub_app.hpp:191
void Clear()
Clears the sub-app world, ECS scheduler, and extract function.
Definition sub_app.cpp:34
const ecs::World & GetWorld() const noexcept
Gets this sub-app's ECS world.
Definition sub_app.hpp:562
ecs::SystemHandle AddSystem(const L &label, std::string name, S &&system, ecs::SystemLocalDataOptions options={})
Adds a system to the given schedule with an explicit name.
Definition sub_app.hpp:323
auto InitSchedule(this auto &&self, const T &label={}) -> decltype(std::forward< decltype(self)>(self))
Creates an empty schedule for the label when it does not exist.
Definition sub_app.hpp:730
void Update(async::Executor &executor)
Runs the configured update stage.
Definition sub_app.hpp:680
ecs::Scheduler & GetScheduler() noexcept
Gets this sub-app's ECS schedule collection.
Definition sub_app.hpp:569
void SetRunner(RunnerFn runner) noexcept
Sets the function invoked each frame to run this sub-app's update pass.
Definition sub_app.hpp:773
std::function< void(const ecs::World &, ecs::World &)> ExtractFn
Definition sub_app.hpp:152
void SetExtractFunction(F &&fn)
Sets the function invoked by Extract.
Definition sub_app.hpp:781
ecs::Schedule * TryGetSchedule(const T &label={})
Tries to get the schedule for a label.
Definition sub_app.hpp:491
size_t MaxExtractionSkips() const noexcept
Returns the configured maximum consecutive extraction skips.
Definition sub_app.hpp:547
ecs::ScheduleOrdering AddSchedule(const T &label, ecs::Schedule &&schedule)
Adds a schedule with the given label type.
Definition sub_app.hpp:259
friend class App
Definition sub_app.hpp:634
void SetAsync(bool is_async) noexcept
Sets whether this sub-app runs updates on a background loop.
Definition sub_app.hpp:799
const ecs::Schedule * TryGetSchedule(const T &label={}) const
Tries to get the schedule for a label.
Definition sub_app.hpp:503
bool IsAsync() const noexcept
Returns whether this sub-app uses a background update loop.
Definition sub_app.hpp:540
std::function< void(SubApp &, async::Executor &)> RunnerFn
Definition sub_app.hpp:151
SubApp & operator=(const SubApp &)=delete
const ecs::Scheduler & GetScheduler() const noexcept
Gets this sub-app's ECS schedule collection.
Definition sub_app.hpp:576
void SetAllowOverlappingUpdates(bool allow_overlapping_updates) noexcept
Sets whether extraction may be skipped while an update is in flight.
Definition sub_app.hpp:786
bool AllowsOverlappingUpdates() const noexcept
Returns whether extraction may be skipped while updating.
Definition sub_app.hpp:531
ecs::SystemSetHandle ConfigureSet(const L &label={}, const S &set={})
Gets or creates a system set in the given schedule.
Definition sub_app.hpp:359
bool ShouldExit() const noexcept
Returns whether this sub-app should stop its update loop.
Definition sub_app.cpp:60
SubApp(const SubApp &)=delete
auto InsertResources(this auto &&self, Ts &&... resources) -> decltype(std::forward< decltype(self)>(self))
Inserts or replaces resources in this sub-app's world.
Definition sub_app.hpp:747
ecs::SystemGroupHandle AddSystems(const L &label, Systems &&... systems)
Adds multiple functor systems to the same schedule as a system group.
Definition sub_app.hpp:344
ecs::ScheduleOrdering AddSchedule(ecs::ScheduleTypeId id, ecs::Schedule &&schedule)
Adds a schedule with the given label type id.
Definition sub_app.hpp:244
auto EditSchedule(this auto &&self, const L &label, const F &fn) -> decltype(std::forward< decltype(self)>(self))
Mutates an existing schedule in place.
Definition sub_app.hpp:740
void BuildScheduler(async::Executor &executor)
Builds all ECS schedules when any are dirty.
Definition sub_app.hpp:716
void SetMaxExtractionSkips(size_t max_skips) noexcept
Sets the maximum consecutive extraction skips while updating.
Definition sub_app.hpp:793
SubApp()
Constructs a sub-app with built-in schedules registered.
Definition sub_app.cpp:25
ecs::World & GetWorld() noexcept
Gets this sub-app's ECS world.
Definition sub_app.hpp:556
auto TryInsertResources(this auto &&self, Ts &&... resources) -> decltype(std::forward< decltype(self)>(self))
Inserts resources only when no instance exists yet.
Definition sub_app.hpp:754
auto AddMessages(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Registers multiple message types on this sub-app's world.
Definition sub_app.hpp:762
ecs::SystemHandle AddSystem(const L &label, S &&system, ecs::SystemLocalDataOptions options={})
Adds a functor system to the given schedule.
Definition sub_app.hpp:303
void SetName(std::string name) noexcept
Sets the human-readable sub-app name.
Definition sub_app.hpp:768
bool IsUpdating() const noexcept
Returns whether an update is in flight.
Definition sub_app.hpp:522
void Extract(const ecs::World &main_world, bool allow_while_updating=false)
Copies data from the main world into this sub-app's world.
Definition sub_app.hpp:699
void WaitUntilFullyIdle() const noexcept
Blocks until no update is in flight.
Definition sub_app.cpp:47
void SetUpdateStage(ecs::StageTypeIndex index) noexcept
Sets which stage label Update executes.
Definition sub_app.hpp:804
const std::string & GetName() const noexcept
Gets the human-readable sub-app name.
Definition sub_app.hpp:412
friend class Scheduler
Definition sub_app.hpp:635
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:32
Handle returned when adding a schedule to the scheduler for configuring ordering.
Definition scheduler.hpp:83
A collection of systems, sets, and run conditions with ordering constraints.
Definition schedule.hpp:182
static Schedule From(const T &schedule={})
Creates a schedule named after the given label type.
Definition schedule.hpp:206
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
Orchestrates multiple named schedules, handles transitions, and executes them in topological order.
Schedule * TryGetSchedule(ScheduleTypeIndex index)
Schedule & In(size_t schedule_hash)
Handle returned by variadic Schedule::Add for configuring a batch of systems added together.
Handle returned by Schedule::Add for configuring a system.
Handle returned by Schedule::Set for configuring a named system set.
The World class manages entities with their components and systems.
Definition world.hpp:39
static constexpr TypeIndex From() noexcept
Constructs a TypeIndex from a type T.
Concept for sub-apps that run updates on a background loop.
Definition sub_app.hpp:75
Concept for sub-apps that allow skipping extraction while updating.
Definition sub_app.hpp:60
Trait to identify valid sub-app types.
Definition sub_app.hpp:49
Concept for sub-apps with a maximum extraction-skip budget.
Definition sub_app.hpp:66
Concept for sub-apps that provide custom names.
Definition sub_app.hpp:54
Concept for any valid message type.
Definition message.hpp:55
Concept for system types that are not lambda closures.
Definition system.hpp:105
Concept for valid resource types.
Definition resource.hpp:25
Concept for schedules.
Definition schedule.hpp:51
consteval bool IsSubAppAsync(const T &={}) noexcept
Returns whether the sub-app runs updates on a background loop.
Definition sub_app.hpp:127
consteval size_t SubAppMaxOverlappingUpdates(const T &={}) noexcept
Returns the maximum consecutive extraction skips for the sub-app.
Definition sub_app.hpp:112
constexpr std::string_view SubAppNameOf(const T &sub_app={}) noexcept
Returns the name of the sub-app.
Definition sub_app.hpp:85
utils::TypeIndex SubAppTypeIndex
Type index for sub-apps.
Definition sub_app.hpp:39
utils::TypeId SubAppTypeId
Type id for sub-apps.
Definition sub_app.hpp:42
constexpr ShutdownStage kShutdownStage
Builtin stage for shutdown.
Definition schedules.hpp:42
constexpr StartupStage kStartupStage
Builtin stage for startup.
Definition schedules.hpp:18
consteval bool IsSubAppAllowsOverlappingUpdates(const T &={}) noexcept
Returns whether the sub-app allows extraction skips while updating.
Definition sub_app.hpp:100
constexpr UpdateStage kUpdateStage
Builtin stage for per-frame updates.
Definition schedules.hpp:26
utils::TypeIndex StageTypeIndex
Type index for stages.
Definition stage.hpp:12
void Warn(std::string_view message) noexcept
Logs a warning message with the default logger.
Definition logger.hpp:530
constexpr std::string_view QualifiedTypeNameOf() noexcept
Retrieves the fully qualified type name of T.
Options for system local data.