Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
application.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/app/details/profile.hpp>
18#include <helios/ecs/world.hpp>
20
21#include <array>
22#include <atomic>
23#include <concepts>
24#include <condition_variable>
25#include <cstddef>
26#include <cstdint>
27#include <functional>
28#include <memory>
29#include <mutex>
30#include <optional>
31#include <string_view>
32
33#if defined(HELIOS_APP_ENABLE_PROFILE) && \
34 defined(HELIOS_MODULE_PROFILE_AVAILABLE)
35#include <format>
36#endif
37
38#ifdef HELIOS_STL_FLAT_MAP_AVAILABLE
39#include <flat_map>
40#else
41#include <boost/container/flat_map.hpp>
42#endif
43
44namespace helios::app {
45
46/// @brief Application exit codes.
47enum class ExitCode : uint8_t {
48 kSuccess = 0, ///< Successful execution
49 kFailure = 1, ///< General failure
50};
51
52/**
53 * @brief Message that requests the application to exit.
54 * @details Written by systems; `RunDefault` stops when this message is present.
55 * Uses manual clear policy so it survives per-schedule `World::Update()` sync
56 * points within a frame.
57 */
58struct AppExit {
59 static constexpr std::string_view kName = "AppExit";
62 static constexpr bool kAsync = false;
63 static constexpr bool kConsumable = false;
64
66};
67
68/**
69 * @brief Application class.
70 * @details Manages the application lifecycle, including initialization,
71 * updating, and shutdown.
72 * @note Partially thread-safe.
73 */
74class App {
75public:
76#ifdef HELIOS_MOVEONLY_FUNCTION_AVAILABLE
77 using RunnerFn = std::move_only_function<ExitCode(App&)>;
78 using ExtractFn =
79 std::move_only_function<void(const ecs::World&, ecs::World&)>;
80#else
81 using RunnerFn = std::function<ExitCode(App&)>;
82 using ExtractFn = std::function<void(const ecs::World&, ecs::World&)>;
83#endif
84
85 App();
86
87 /**
88 * @brief Constructs an App with a specific number of worker threads.
89 * @param worker_thread_count Number of worker threads for the executor
90 */
91 explicit App(size_t worker_thread_count);
92 App(const App&) = delete;
93 App(App&&) = delete;
94
95 /**
96 * @brief Destructor that ensures all async work is completed before
97 * destruction.
98 * @details Waits for all overlapping updates and pending executor tasks to
99 * complete.
100 */
101 ~App();
102
103 App& operator=(const App&) = delete;
104 App& operator=(App&&) = delete;
105
106 /**
107 * @brief Clears the application state, removing all data.
108 * @note Not thread-safe.
109 * @warning Triggers assertion if app is running.
110 */
111 void Clear();
112
113 /**
114 * @brief Initializes the application and its subsystems.
115 * @details Called before the main loop starts.
116 * Spawns tasks on the executor for parallel initialization.
117 *
118 * Automaticly called in `Run()`.
119 * @note Not thread-safe.
120 * @warning Triggers assertion if app is already initialized or running.
121 */
122 void Initialize();
123
124 /**
125 * @brief Updates the application and its subsystems.
126 * @details Calls Update on the main sub-app and all registered sub-apps.
127 * Spawns async tasks as needed.
128 * @note Not thread-safe.
129 * @warning Triggers assertion if app is not initialized.
130 */
131 void Update();
132
133 /**
134 * @brief Runs the application with the given arguments.
135 * @note Not thread-safe.
136 * @warning Triggers assertion if app is initialized or running.
137 * @return Exit code of the application.
138 */
139 ExitCode Run();
140
141 /**
142 * @brief Notifies the app that plugin readiness may have changed.
143 * @details Async plugins should call this after completing background setup
144 * so `WaitForPluginsReady` can resume without busy-waiting.
145 * @note Not thread-safe.
146 */
148 plugins_ready_cv_.notify_all();
149 }
150
151 /**
152 * @brief Adds plugin instance, if plugin of the same type is not already
153 * registered.
154 * @note Not thread-safe.
155 * @warning Triggers assertion if app is initialized or running.
156 * @tparam Plugin Plugin type
157 * @param plugin Plugin instance to add
158 * @return Reference to app for method chaining
159 */
160 template <PluginTrait Plugin>
161 auto AddPlugins(this auto&& self, Plugin&& plugin)
162 -> decltype(std::forward<decltype(self)>(self));
163
164 /**
165 * @brief Adds multiple plugin instances, if plugins of the same types are not
166 * already registered.
167 * @note Not thread-safe.
168 * @warning Triggers assertion if app is initialized or running.
169 * @tparam Plugins Plugin types
170 * @param plugins Plugin instances to add
171 * @return Reference to app for method chaining
172 */
173 template <PluginTrait... Plugins>
174 requires utils::UniqueTypes<Plugins...> && (sizeof...(Plugins) > 1)
175 auto AddPlugins(this auto&& self, Plugins&&... plugins)
176 -> decltype(std::forward<decltype(self)>(self));
177
178 /**
179 * @brief Adds one plugin, constructing it in-place with the given arguments
180 * if a plugin of the same type is not already registered.
181 * @note Not thread-safe.
182 * @warning Triggers assertion if app is initialized or running.
183 * @tparam Plugin Plugin type
184 * @param args Arguments to construct the plugin
185 * @return Reference to app for method chaining
186 */
187 template <PluginTrait Plugin, typename... Args>
188 requires std::constructible_from<Plugin, Args...>
189 auto EmplacePlugin(this auto&& self, Args&&... args)
190 -> decltype(std::forward<decltype(self)>(self));
191
192 /**
193 * @brief Adds a dynamic plugin instance, if a plugin of the same type is not
194 * already registered.
195 * @note Not thread-safe.
196 * @warning Triggers assertion if app is initialized or running.
197 * @param plugin Dynamic plugin instance to add
198 * @return Reference to app for method chaining
199 */
200 auto AddDynamicPlugin(this auto&& self, DynamicPlugin plugin)
201 -> decltype(std::forward<decltype(self)>(self));
202
203 /**
204 * @brief Inserts a sub-app under the given label, if a sub-app with the same
205 * label is not already registered.
206 * @note Not thread-safe.
207 * @warning Triggers assertion if app is initialized or running.
208 * @tparam T Sub-app label type
209 * @param label Label value
210 * @param sub_app Sub-app instance (executor is injected by `App`)
211 * @return Reference to this app for chaining
212 */
213 template <SubAppTrait T>
214 auto InsertSubApp(this auto&& self, const T& label, SubApp sub_app)
215 -> decltype(std::forward<decltype(self)>(self));
216
217 /**
218 * @brief Removes a registered sub-app.
219 * @note Not thread-safe.
220 * @warning Triggers assertion if app is initialized or running.
221 * @tparam T Sub-app label type
222 * @return True if a sub-app was removed
223 */
224 template <SubAppTrait T>
225 bool RemoveSubApp(const T& label = {});
226
227 /**
228 * @brief Adds a schedule to the main sub-app.
229 * @note Not thread-safe.
230 * @warning Triggers assertion if app is initialized or running.
231 * @param id Schedule type id
232 * @param schedule Schedule instance
233 * @return Ordering handle for configuring placement
234 */
237
238 /**
239 * @brief Adds a schedule to the main sub-app.
240 * @note Not thread-safe.
241 * @warning Triggers assertion if app is initialized or running.
242 * @tparam T Schedule label type
243 * @param label Label value
244 * @param schedule Schedule instance
245 * @return Ordering handle for configuring placement
246 */
247 template <ecs::ScheduleTrait T>
248 auto AddSchedule(const T& label, ecs::Schedule&& schedule)
250
251 /**
252 * @brief Creates an empty schedule on the main sub-app when missing.
253 * @note Not thread-safe.
254 * @warning Triggers assertion if app is initialized or running.
255 * @tparam T Schedule label type
256 * @param label Label value
257 * @return Reference to this app for chaining
258 */
259 template <ecs::ScheduleTrait T>
260 auto InitSchedule(this auto&& self, const T& label = {})
261 -> decltype(std::forward<decltype(self)>(self));
262
263 /**
264 * @brief Mutates a main sub-app schedule in place.
265 * @note Not thread-safe.
266 * @warning Triggers assertion if app is initialized or running.
267 * @tparam L Schedule label type
268 * @param label Target schedule label
269 * @param fn Callable receiving `ecs::Schedule&`
270 * @return Reference to this app for chaining
271 */
272 template <ecs::ScheduleTrait L, typename F>
273 requires std::invocable<F, ecs::Schedule&>
274 auto EditSchedule(this auto&& self, const L& label, const F& fn)
275 -> decltype(std::forward<decltype(self)>(self));
276
277 /**
278 * @brief Adds a functor system to a main sub-app schedule.
279 * @details Derives name and `SystemId` from the functor type. Lambdas
280 * are rejected; use the `AddSystem(label, name, system)` overload.
281 * @note Not thread-safe.
282 * @warning Triggers assertion if app is initialized or running.
283 * @tparam L Schedule label type
284 * @tparam S System type satisfying `FunctorSystemTrait`
285 * @param label Target schedule label
286 * @param system System instance
287 * @param options Local data options
288 * @return System configuration handle
289 */
290 template <ecs::ScheduleTrait L, ecs::FunctorSystemTrait S>
291 auto AddSystem(const L& label, S&& system,
292 ecs::SystemLocalDataOptions options = {})
294 return main_sub_app_.AddSystem(label, std::forward<S>(system), options);
295 }
296
297 /**
298 * @brief Adds a system to a main sub-app schedule with an explicit name.
299 * @details Required for lambdas. Derives the `SystemId` from the
300 * provided name, enabling multiple instances of the same type.
301 * @note Not thread-safe.
302 * @warning Triggers assertion if app is initialized or running.
303 * @tparam L Schedule label type
304 * @tparam S System type satisfying `SystemTrait`
305 * @param label Target schedule label
306 * @param name Human-readable system name
307 * @param system System instance
308 * @param options Local data options
309 * @return System configuration handle
310 */
311 template <ecs::ScheduleTrait L, ecs::SystemTrait S>
312 auto AddSystem(const L& label, std::string name, S&& system,
313 ecs::SystemLocalDataOptions options = {})
315 return main_sub_app_.AddSystem(label, std::move(name),
316 std::forward<S>(system), options);
317 }
318
319 /**
320 * @brief Adds multiple functor systems to the same main sub-app schedule.
321 * @details Returns a `SystemGroupHandle` for configuring the batch. Use
322 * `InSet` on the returned handle to assign every system to a named set.
323 * @note Not thread-safe.
324 * @warning Triggers assertion if app is initialized or running.
325 * @tparam L Schedule label type
326 * @tparam Systems System types satisfying `FunctorSystemTrait`
327 * @param label Target schedule label
328 * @param systems System instances
329 * @return Handle for configuring the system group
330 */
331 template <ecs::ScheduleTrait L, ecs::FunctorSystemTrait... Systems>
332 requires(sizeof...(Systems) > 1)
333 ecs::SystemGroupHandle AddSystems(const L& label, Systems&&... systems);
334
335 /**
336 * @brief Gets or creates a system set in a main sub-app schedule.
337 * @note Not thread-safe.
338 * @warning Triggers assertion if app is initialized or running.
339 * @tparam L Schedule label type
340 * @tparam S System set type
341 * @param label Target schedule label
342 * @param set System set instance
343 * @return System set handle
344 */
345 template <ecs::ScheduleTrait L, ecs::SystemSetTrait S>
346 auto ConfigureSet(const L& label, const S& set = {}) -> ecs::SystemSetHandle {
347 return main_sub_app_.ConfigureSet(label, set);
348 }
349
350 /**
351 * @brief Inserts or replaces resources in the main world.
352 * @note Not thread-safe.
353 * @warning Triggers assertion if app is initialized or running.
354 * @tparam Ts Resource types
355 * @param resources Resource values
356 * @return Reference to this app for chaining
357 */
358 template <ecs::ResourceTrait... Ts>
359 auto InsertResources(this auto&& self, Ts&&... resources)
360 -> decltype(std::forward<decltype(self)>(self));
361
362 /**
363 * @brief Inserts resources in the main world only when absent.
364 * @note Not thread-safe.
365 * @warning Triggers assertion if app is initialized or running.
366 * @tparam Ts Resource types
367 * @param resources Resource values
368 * @return Reference to this app for chaining
369 */
370 template <ecs::ResourceTrait... Ts>
371 auto TryInsertResources(this auto&& self, Ts&&... resources)
372 -> decltype(std::forward<decltype(self)>(self));
373
374 /**
375 * @brief Registers multiple message types on the main world.
376 * @note Not thread-safe.
377 * @warning Triggers assertion if app is initialized or running.
378 * @tparam Ts Message types
379 * @return Reference to this app for chaining
380 */
381 template <ecs::AnyMessageTrait... Ts>
382 requires(sizeof...(Ts) > 0)
383 auto AddMessages(this auto&& self)
384 -> decltype(std::forward<decltype(self)>(self));
385
386 /**
387 * @brief Sets runner function for the application.
388 * @note Not thread-safe.
389 * @warning Triggers assertion if app is initialized or running.
390 * @warning Triggers assertion in next cases:
391 * - App is initialized
392 * - App is running
393 * - Runner is null
394 * @param runner A move-only function that takes an `App` reference and
395 * returns an `ExitCode`.
396 * @return Reference to app for method chaining
397 */
398 auto SetRunner(this auto&& self, RunnerFn runner) noexcept
399 -> decltype(std::forward<decltype(self)>(self));
400
401 /**
402 * @brief Returns an exit code if an `AppExit` message was written this frame.
403 * @note Not thread-safe.
404 * @return Exit code when `AppExit` is present, otherwise `std::nullopt`
405 */
406 [[nodiscard]] auto ShouldExit() const noexcept -> std::optional<ExitCode>;
407
408 /**
409 * @brief Checks if the app has been initialized.
410 * @note Not thread-safe.
411 * @return True if initialized, false otherwise
412 */
413 [[nodiscard]] bool IsInitialized() const noexcept { return is_initialized_; }
414
415 /**
416 * @brief Checks if the app is currently running.
417 * @note Thread-safe.
418 * @return True if running, false otherwise
419 */
420 [[nodiscard]] bool IsRunning() const noexcept {
421 return is_running_.load(std::memory_order_acquire);
422 }
423
424 /**
425 * @brief Checks if the app has a plugin of the given type.
426 * @note Not thread-safe.
427 * @return True if the plugin is registered, false otherwise
428 */
429 [[nodiscard]] bool HasPlugin(PluginTypeIndex index) const noexcept {
430 return plugins_.contains(index) || dynamic_plugins_.contains(index);
431 }
432
433 /**
434 * @brief Checks if the app has a plugin of the given type.
435 * @note Not thread-safe.
436 * @return True if the plugin is registered, false otherwise
437 */
438 template <PluginTrait Plugin>
439 [[nodiscard]] bool HasPlugins() const noexcept {
441 }
442
443 /**
444 * @brief Checks if the app has plugins of given types.
445 * @note Not thread-safe.
446 * @return Array of bools indicating whether each plugin is registered
447 */
448 template <PluginTrait... Plugins>
449 requires utils::UniqueTypes<Plugins...> && (sizeof...(Plugins) > 1)
450 [[nodiscard]] auto HasPlugins() const noexcept
451 -> std::array<bool, sizeof...(Plugins)> {
453 }
454
455 /**
456 * @brief Returns whether a sub-app is registered for the label.
457 * @tparam T Sub-app label type
458 * @param label Sub-app label to check
459 * @return True if the sub-app is registered, false otherwise
460 */
461 template <SubAppTrait T>
462 [[nodiscard]] bool HasSubApp(const T& label = {}) const noexcept {
463 return sub_apps_.contains(SubAppTypeIndex::From(label));
464 }
465
466 /**
467 * @brief Gets the main sub-application.
468 * @note Thread-safe.
469 * @return Reference to the main sub-application
470 */
471 [[nodiscard]] SubApp& GetMainSubApp() noexcept { return main_sub_app_; }
472
473 /**
474 * @brief Gets the main sub-application (const version).
475 * @note Thread-safe.
476 * @return Const reference to the main sub-application
477 */
478 [[nodiscard]] const SubApp& GetMainSubApp() const noexcept {
479 return main_sub_app_;
480 }
481
482 /**
483 * @brief Gets the sub-app of the given type.
484 * @note Not thread-safe.
485 * @warning Triggers assertion if the sub-app is not found.
486 * @param index The sub-app type index
487 * @return Reference to the sub-app
488 */
489 [[nodiscard]] SubApp& GetSubApp(SubAppTypeIndex index) noexcept;
490
491 /**
492 * @brief Gets the sub-app of the given type (const version).
493 * @note Not thread-safe.
494 * @warning Triggers assertion if the sub-app is not found.
495 * @param index The sub-app type index
496 * @return Const reference to the sub-app
497 */
498 [[nodiscard]] const SubApp& GetSubApp(SubAppTypeIndex index) const noexcept;
499
500 /**
501 * @brief Gets the sub-app of the given type.
502 * @note Not thread-safe.
503 * @warning Triggers assertion if the sub-app is not found.
504 * @tparam T The sub-app type
505 * @param sub_app The sub-app instance to find
506 * @return Reference to the sub-app
507 */
508 template <SubAppTrait T>
509 [[nodiscard]] SubApp& GetSubApp(const T& sub_app = {}) noexcept;
510
511 /**
512 * @brief Gets the sub-app of the given type (const version).
513 * @note Not thread-safe.
514 * @warning Triggers assertion if the sub-app is not found.
515 * @tparam T The sub-app type
516 * @param sub_app The sub-app instance to find
517 * @return Const reference to the sub-app
518 */
519 template <SubAppTrait T>
520 [[nodiscard]] const SubApp& GetSubApp(const T& sub_app = {}) const noexcept;
521
522 /**
523 * @brief Tries to get a main sub-app schedule by label.
524 * @note Not thread-safe.
525 * @tparam T Schedule label type
526 * @param label Schedule label to search for
527 * @return Pointer to the schedule, or `nullptr` if not found
528 */
529 template <ecs::ScheduleTrait T>
530 [[nodiscard]] ecs::Schedule* TryGetSchedule(const T& label = {}) {
531 return main_sub_app_.TryGetSchedule(label);
532 }
533
534 /**
535 * @brief Tries to get a main sub-app schedule by label (const version).
536 * @note Not thread-safe.
537 * @tparam T Schedule label type
538 * @param label Schedule label to search for
539 * @return Pointer to the schedule, or `nullptr` if not found
540 */
541 template <ecs::ScheduleTrait T>
542 [[nodiscard]] const ecs::Schedule* TryGetSchedule(const T& label = {}) const {
543 return main_sub_app_.TryGetSchedule(label);
544 }
545
546 /**
547 * @brief Gets the async executor.
548 * @note Thread-safe.
549 * @return Reference to the async executor
550 */
551 [[nodiscard]] async::Executor& GetExecutor() noexcept { return executor_; }
552
553 /**
554 * @brief Gets the async executor (const version).
555 * @note Thread-safe.
556 * @return Const reference to the async executor
557 */
558 [[nodiscard]] const async::Executor& GetExecutor() const noexcept {
559 return executor_;
560 }
561
562 /**
563 * @brief Gets the main sub-app's ECS world.
564 * @note Thread-safe.
565 * @return Reference to the main sub-app's ECS world
566 */
567 [[nodiscard]] ecs::World& GetWorld() noexcept {
568 return main_sub_app_.GetWorld();
569 }
570
571 /**
572 * @brief Gets the main sub-app's ECS world (const version).
573 * @note Thread-safe.
574 * @return Const reference to the main sub-app's ECS world
575 */
576 [[nodiscard]] const ecs::World& GetWorld() const noexcept {
577 return main_sub_app_.GetWorld();
578 }
579
580 /**
581 * @brief Gets the application frame scheduler.
582 * @note Thread-safe.
583 * @return Reference to the application frame scheduler
584 */
585 [[nodiscard]] Scheduler& GetScheduler() noexcept { return scheduler_; }
586
587 /**
588 * @brief Gets the application frame scheduler (const version).
589 * @note Thread-safe.
590 * @return Const reference to the application frame scheduler
591 */
592 [[nodiscard]] const Scheduler& GetScheduler() const noexcept {
593 return scheduler_;
594 }
595
596private:
597 struct MainSubAppLabel {};
598 static constexpr MainSubAppLabel kMainSubApp{};
599
600 struct PluginStorage {
601 std::unique_ptr<Plugin> plugin;
602 std::string_view name;
603
604 template <PluginTrait Plugin, typename... Args>
605 requires std::constructible_from<Plugin, Args...>
606 [[nodiscard]] static PluginStorage From(Args&&... args) {
607 return PluginStorage{
608 .plugin = std::make_unique<Plugin>(std::forward<Args>(args)...),
609 .name = PluginNameOf<Plugin>(),
610 };
611 }
612 };
613
614#ifdef HELIOS_STL_FLAT_MAP_AVAILABLE
615 template <typename K, typename V>
616 using FlatMap = std::flat_map<K, V>;
617#else
618 template <typename K, typename V>
619 using FlatMap = boost::container::flat_map<K, V>;
620#endif
621
622 /**
623 * @brief Cleans up the application and its subsystems.
624 * @details Called after the main loop ends.
625 * Spawns tasks on the executor for parallel cleanup.
626 */
627 void CleanUp();
628
629 void RegisterMessages();
630
631 void BuildPlugins();
632 void PollPlugins();
633 void WaitForPluginsReady();
634 [[nodiscard]] bool PluginsReady() const;
635 void FinishPlugins();
636 void DestroyPlugins();
637
638 template <SubAppTrait T>
639 static void ApplySubAppLabelTraits(SubApp& sub_app, const T& label);
640
641 bool is_initialized_ = false; ///< Whether the app has been initialized
642
643 /// Whether the app is currently running
644 std::atomic<bool> is_running_{false};
645
646 FlatMap<PluginTypeIndex, PluginStorage> plugins_; ///< Plugins
647
648 /// Dynamic plugins
649 FlatMap<PluginTypeIndex, DynamicPlugin> dynamic_plugins_;
650
651 async::Executor executor_; ///< Async executor for parallel execution
652 Scheduler scheduler_; ///< Frame scheduler for main and sub-apps
653
654 SubApp main_sub_app_; ///< The main sub-app
655 FlatMap<SubAppTypeIndex, SubApp> sub_apps_; ///< Additional sub-apps
656
657 RunnerFn runner_; ///< The runner function
658
659 // Tracy lockables are incompatible with std::condition_variable.
660 mutable std::mutex plugins_ready_mutex_;
661 std::condition_variable plugins_ready_cv_;
662
663 friend class Scheduler;
664};
665
666inline App::~App() {
667 // Ensure we're not running (should already be false if properly shut down)
668 is_running_.store(false, std::memory_order_release);
669
670 // Wait for all pending executor tasks to complete
671 executor_.WaitForAll();
672}
673
674inline void App::Update() {
675 HELIOS_APP_PROFILE_SCOPE_N("helios::app::App::Update");
676
677 HELIOS_ASSERT(is_initialized_, "App is not initialized!");
678 scheduler_.RunFrame(*this);
679
680 HELIOS_APP_PROFILE_FRAME();
681}
682
683template <PluginTrait Plugin>
684inline auto App::AddPlugins(this auto&& self, Plugin&& plugin)
685 -> decltype(std::forward<decltype(self)>(self)) {
686 HELIOS_ASSERT(!self.IsInitialized(),
687 "Cannot add plugin after app initialization!");
688 HELIOS_ASSERT(!self.IsRunning(), "Cannot add plugin while app is running!");
689
690 auto storage = PluginStorage::From<std::remove_cvref_t<Plugin>>(
691 std::forward<Plugin>(plugin));
692 self.plugins_.try_emplace(PluginTypeIndex::From(plugin), std::move(storage));
693 return std::forward<decltype(self)>(self);
694}
695
696template <PluginTrait... Plugins>
697 requires utils::UniqueTypes<Plugins...> && (sizeof...(Plugins) > 1)
698inline auto App::AddPlugins(this auto&& self, Plugins&&... plugins)
699 -> decltype(std::forward<decltype(self)>(self)) {
700 HELIOS_ASSERT(!self.IsInitialized(),
701 "Cannot add plugin after app initialization!");
702 HELIOS_ASSERT(!self.IsRunning(), "Cannot add plugin while app is running!");
703
704 (self.AddPlugins(std::forward<Plugins>(plugins)), ...);
705 return std::forward<decltype(self)>(self);
706}
707
708template <PluginTrait Plugin, typename... Args>
709 requires std::constructible_from<Plugin, Args...>
710inline auto App::EmplacePlugin(this auto&& self, Args&&... args)
711 -> decltype(std::forward<decltype(self)>(self)) {
712 HELIOS_ASSERT(!self.IsInitialized(),
713 "Cannot add plugin after app initialization!");
714 HELIOS_ASSERT(!self.IsRunning(), "Cannot add plugin while app is running!");
715
716 auto storage = PluginStorage::From<Plugin>(std::forward<Args>(args)...);
717 self.plugins_.try_emplace(PluginTypeIndex::From<Plugin>(),
718 std::move(storage));
719 return std::forward<decltype(self)>(self);
720}
721
722inline auto App::AddDynamicPlugin(this auto&& self, DynamicPlugin plugin)
723 -> decltype(std::forward<decltype(self)>(self)) {
724 HELIOS_ASSERT(!self.IsInitialized(),
725 "Cannot add dynamic plugin after app initialization!");
726 HELIOS_ASSERT(!self.IsRunning(),
727 "Cannot add dynamic plugin while app is running!");
728
729 self.dynamic_plugins_.try_emplace(PluginTypeIndex::From(plugin),
730 std::move(plugin));
731 return std::forward<decltype(self)>(self);
732}
733
734template <SubAppTrait T>
735inline auto App::InsertSubApp(this auto&& self, const T& label, SubApp sub_app)
736 -> decltype(std::forward<decltype(self)>(self)) {
737 HELIOS_ASSERT(!self.IsInitialized(),
738 "Cannot insert sub-app after app initialization!");
739 HELIOS_ASSERT(!self.IsRunning(),
740 "Cannot insert sub-app while app is running!");
741
742 ApplySubAppLabelTraits(sub_app, label);
743 self.sub_apps_.try_emplace(SubAppTypeIndex::From(label), std::move(sub_app));
744 return std::forward<decltype(self)>(self);
745}
746
747template <SubAppTrait T>
748inline bool App::RemoveSubApp(const T& label) {
750 "Cannot remove sub-app after app initialization!");
751 HELIOS_ASSERT(!IsRunning(), "Cannot remove sub-app while app is running!");
752
753 return sub_apps_.erase(SubAppTypeIndex::From(label)) > 0;
754}
755
759 "Cannot add schedule after app initialization!");
760 HELIOS_ASSERT(!IsRunning(), "Cannot add schedule while app is running!");
761
762 return main_sub_app_.AddSchedule(id, std::move(schedule));
763}
764
765template <ecs::ScheduleTrait T>
766inline auto App::AddSchedule(const T& label, ecs::Schedule&& schedule)
769 "Cannot add schedule after app initialization!");
770 HELIOS_ASSERT(!IsRunning(), "Cannot add schedule while app is running!");
771
772 return main_sub_app_.AddSchedule(label, std::move(schedule));
773}
774
775template <ecs::ScheduleTrait T>
776inline auto App::InitSchedule(this auto&& self, const T& label)
777 -> decltype(std::forward<decltype(self)>(self)) {
778 self.main_sub_app_.InitSchedule(label);
779 return std::forward<decltype(self)>(self);
780}
781
782template <ecs::ScheduleTrait L, ecs::FunctorSystemTrait... Systems>
783 requires(sizeof...(Systems) > 1)
785 Systems&&... systems) {
786 return main_sub_app_.AddSystems(label, std::forward<Systems>(systems)...);
787}
788
789template <ecs::ScheduleTrait L, typename F>
790 requires std::invocable<F, ecs::Schedule&>
791inline auto App::EditSchedule(this auto&& self, const L& label, const F& fn)
792 -> decltype(std::forward<decltype(self)>(self)) {
793 self.main_sub_app_.EditSchedule(label, fn);
794 return std::forward<decltype(self)>(self);
795}
796
797template <ecs::ResourceTrait... Ts>
798inline auto App::InsertResources(this auto&& self, Ts&&... resources)
799 -> decltype(std::forward<decltype(self)>(self)) {
800 self.main_sub_app_.InsertResources(std::forward<Ts>(resources)...);
801 return std::forward<decltype(self)>(self);
802}
803
804template <ecs::ResourceTrait... Ts>
805inline auto App::TryInsertResources(this auto&& self, Ts&&... resources)
806 -> decltype(std::forward<decltype(self)>(self)) {
807 self.main_sub_app_.TryInsertResources(std::forward<Ts>(resources)...);
808 return std::forward<decltype(self)>(self);
809}
810
811template <ecs::AnyMessageTrait... Ts>
812 requires(sizeof...(Ts) > 0)
813inline auto App::AddMessages(this auto&& self)
814 -> decltype(std::forward<decltype(self)>(self)) {
815 self.main_sub_app_.template AddMessages<Ts...>();
816 return std::forward<decltype(self)>(self);
817}
818
819inline auto App::SetRunner(this auto&& self, RunnerFn runner) noexcept
820 -> decltype(std::forward<decltype(self)>(self)) {
821 HELIOS_ASSERT(!self.IsInitialized(),
822 "Cannot set runner after app initialization!");
823 HELIOS_ASSERT(!self.IsRunning(), "Cannot set runner while app is running!");
824 HELIOS_ASSERT(runner, "Runner function cannot be null!");
825
826 self.runner_ = std::move(runner);
827 return std::forward<decltype(self)>(self);
828}
829
830inline SubApp& App::GetSubApp(SubAppTypeIndex index) noexcept {
831 const auto it = sub_apps_.find(index);
832 HELIOS_ASSERT(it != sub_apps_.end(), "Sub-app not found!");
833 return it->second;
834}
835
836inline const SubApp& App::GetSubApp(SubAppTypeIndex index) const noexcept {
837 const auto it = sub_apps_.find(index);
838 HELIOS_ASSERT(it != sub_apps_.end(), "Sub-app not found!");
839 return it->second;
840}
841
842template <SubAppTrait T>
843inline SubApp& App::GetSubApp(const T& sub_app) noexcept {
844 const auto it = sub_apps_.find(SubAppTypeIndex::From(sub_app));
845 HELIOS_ASSERT(it != sub_apps_.end(), "Sub-app '{}' not found!",
846 SubAppNameOf(sub_app));
847 return it->second;
848}
849
850template <SubAppTrait T>
851inline const SubApp& App::GetSubApp(const T& sub_app) const noexcept {
852 const auto it = sub_apps_.find(SubAppTypeIndex::From(sub_app));
853 HELIOS_ASSERT(it != sub_apps_.end(), "Sub-app '{}' not found!",
854 SubAppNameOf(sub_app));
855 return it->second;
856}
857
858template <SubAppTrait T>
859inline void App::ApplySubAppLabelTraits(SubApp& sub_app, const T& label) {
861 "kAsync and kAllowOverlappingUpdates cannot be used together!");
862
863 sub_app.SetName(std::string(SubAppNameOf(label)));
864
865 if constexpr (AsyncSubAppTrait<T>) {
866 sub_app.SetAsync(true);
867 } else if constexpr (OverlappingUpdatesSubAppTrait<T>) {
868 sub_app.SetAllowOverlappingUpdates(true);
870 }
871}
872
873} // namespace helios::app
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
auto ShouldExit() const noexcept -> std::optional< ExitCode >
Returns an exit code if an AppExit message was written this frame.
auto ConfigureSet(const L &label, const S &set={}) -> ecs::SystemSetHandle
Gets or creates a system set in a main sub-app schedule.
Scheduler & GetScheduler() noexcept
Gets the application frame scheduler.
std::function< void(const ecs::World &, ecs::World &)> ExtractFn
auto AddSchedule(ecs::ScheduleTypeId id, ecs::Schedule &&schedule) -> ecs::ScheduleOrdering
Adds a schedule to the main sub-app.
bool IsRunning() const noexcept
Checks if the app is currently running.
const async::Executor & GetExecutor() const noexcept
Gets the async executor (const version).
const ecs::World & GetWorld() const noexcept
Gets the main sub-app's ECS world (const version).
SubApp & GetMainSubApp() noexcept
Gets the main sub-application.
auto EditSchedule(this auto &&self, const L &label, const F &fn) -> decltype(std::forward< decltype(self)>(self))
Mutates a main sub-app schedule in place.
ecs::World & GetWorld() noexcept
Gets the main sub-app's ECS world.
App(const App &)=delete
ecs::Schedule * TryGetSchedule(const T &label={})
Tries to get a main sub-app schedule by label.
std::function< ExitCode(App &)> RunnerFn
bool HasPlugin(PluginTypeIndex index) const noexcept
Checks if the app has a plugin of the given type.
App & operator=(const App &)=delete
auto EmplacePlugin(this auto &&self, Args &&... args) -> decltype(std::forward< decltype(self)>(self))
Adds one plugin, constructing it in-place with the given arguments if a plugin of the same type is no...
SubApp & GetSubApp(SubAppTypeIndex index) noexcept
Gets the sub-app of the given type.
App(App &&)=delete
auto AddMessages(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Registers multiple message types on the main world.
auto AddPlugins(this auto &&self, Plugin &&plugin) -> decltype(std::forward< decltype(self)>(self))
Adds plugin instance, if plugin of the same type is not already registered.
const Scheduler & GetScheduler() const noexcept
Gets the application frame scheduler (const version).
auto InsertSubApp(this auto &&self, const T &label, SubApp sub_app) -> decltype(std::forward< decltype(self)>(self))
Inserts a sub-app under the given label, if a sub-app with the same label is not already registered.
auto InitSchedule(this auto &&self, const T &label={}) -> decltype(std::forward< decltype(self)>(self))
Creates an empty schedule on the main sub-app when missing.
auto SetRunner(this auto &&self, RunnerFn runner) noexcept -> decltype(std::forward< decltype(self)>(self))
Sets runner function for the application.
auto TryInsertResources(this auto &&self, Ts &&... resources) -> decltype(std::forward< decltype(self)>(self))
Inserts resources in the main world only when absent.
bool RemoveSubApp(const T &label={})
Removes a registered sub-app.
void Initialize()
Initializes the application and its subsystems.
App & operator=(App &&)=delete
bool IsInitialized() const noexcept
Checks if the app has been initialized.
const ecs::Schedule * TryGetSchedule(const T &label={}) const
Tries to get a main sub-app schedule by label (const version).
auto AddSystem(const L &label, S &&system, ecs::SystemLocalDataOptions options={}) -> ecs::SystemHandle
Adds a functor system to a main sub-app schedule.
auto HasPlugins() const noexcept -> std::array< bool, sizeof...(Plugins)>
Checks if the app has plugins of given types.
auto AddSystem(const L &label, std::string name, S &&system, ecs::SystemLocalDataOptions options={}) -> ecs::SystemHandle
Adds a system to a main sub-app schedule with an explicit name.
void NotifyPluginReadinessChanged() noexcept
Notifies the app that plugin readiness may have changed.
void Clear()
Clears the application state, removing all data.
bool HasSubApp(const T &label={}) const noexcept
Returns whether a sub-app is registered for the label.
~App()
Destructor that ensures all async work is completed before destruction.
auto InsertResources(this auto &&self, Ts &&... resources) -> decltype(std::forward< decltype(self)>(self))
Inserts or replaces resources in the main world.
ecs::SystemGroupHandle AddSystems(const L &label, Systems &&... systems)
Adds multiple functor systems to the same main sub-app schedule.
void Update()
Updates the application and its subsystems.
bool HasPlugins() const noexcept
Checks if the app has a plugin of the given type.
ExitCode Run()
Runs the application with the given arguments.
async::Executor & GetExecutor() noexcept
Gets the async executor.
auto AddDynamicPlugin(this auto &&self, DynamicPlugin plugin) -> decltype(std::forward< decltype(self)>(self))
Adds a dynamic plugin instance, if a plugin of the same type is not already registered.
const SubApp & GetMainSubApp() const noexcept
Gets the main sub-application (const version).
friend class Scheduler
Wrapper for dynamically loaded plugins.
Base class for all plugins.
Definition plugin.hpp:14
A sub-application with its own ECS world and scheduler.
Definition sub_app.hpp:144
ecs::Schedule * TryGetSchedule(const T &label={})
Tries to get the schedule for a label.
Definition sub_app.hpp:491
void SetAsync(bool is_async) noexcept
Sets whether this sub-app runs updates on a background loop.
Definition sub_app.hpp:799
void SetAllowOverlappingUpdates(bool allow_overlapping_updates) noexcept
Sets whether extraction may be skipped while an update is in flight.
Definition sub_app.hpp:786
ecs::SystemSetHandle ConfigureSet(const L &label={}, const S &set={})
Gets or creates a system set in the given schedule.
Definition sub_app.hpp:359
void SetMaxExtractionSkips(size_t max_skips) noexcept
Sets the maximum consecutive extraction skips while updating.
Definition sub_app.hpp:793
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
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
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
Concept to ensure a type is a valid Plugin.
Definition plugin.hpp:76
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
Concept that checks if all types in a pack are unique (after removing cv/ref qualifiers).
consteval size_t SubAppMaxOverlappingUpdates(const T &={}) noexcept
Returns the maximum consecutive extraction skips for the sub-app.
Definition sub_app.hpp:112
utils::TypeIndex PluginTypeIndex
Type index for plugins.
Definition plugin.hpp:65
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
constexpr std::string_view PluginNameOf() noexcept
Gets name for a plugin type.
Definition plugin.hpp:95
ExitCode
Application exit codes.
@ kFailure
General failure.
@ kSuccess
Successful execution.
MessageClearPolicy
Policy for message clearing behavior.
Definition message.hpp:17
@ kManual
Messages persist until manually cleared.
Definition message.hpp:19
utils::TypeId ScheduleTypeId
Type id for schedules.
Definition schedule.hpp:44
STL namespace.
Message that requests the application to exit.
static constexpr std::string_view kName
static constexpr bool kConsumable
static constexpr ecs::MessageClearPolicy kClearPolicy
static constexpr bool kAsync
Options for system local data.