Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
app.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/core_pch.hpp>
4
15#include <helios/core/core.hpp>
22
23#include <algorithm>
24#include <atomic>
25#include <chrono>
26#include <concepts>
27#include <cstddef>
28#include <cstdint>
29#include <functional>
30#include <future>
31#include <memory>
32#include <unordered_map>
33#include <utility>
34#include <vector>
35
36namespace helios::app {
37
38namespace details {
39
40/**
41 * @brief Tracked future with ready flag to reduce syscall churn.
42 * @details Wraps a shared_future with a cached ready flag to avoid repeated wait_for(0) calls.
43 * Once marked ready, no further syscalls are made to check status.
44 */
46 std::shared_future<void> future; ///< The underlying future
47 bool ready = false; ///< Cached ready state
48
49 /**
50 * @brief Waits for the future to complete if not already ready.
51 */
52 void Wait() {
53 if (!ready && future.valid()) {
54 future.wait();
55 ready = true;
56 }
57 }
58
59 /**
60 * @brief Checks if the future is ready, caching the result.
61 * @details Only makes a syscall if not already marked ready.
62 * @return True if the future has completed
63 */
64 [[nodiscard]] bool IsReady() noexcept {
65 if (ready) {
66 return true;
67 }
68 if (future.valid() && future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
69 ready = true;
70 return true;
71 }
72 return false;
73 }
74
75 /**
76 * @brief Checks if the future is valid.
77 * @return True if the underlying future is valid
78 */
79 [[nodiscard]] bool Valid() const noexcept { return future.valid(); }
80};
81
82} // namespace details
83
84/**
85 * @brief Application exit codes.
86 */
87enum class AppExitCode : uint8_t {
88 kSuccess = 0, ///< Successful execution
89 kFailure = 1, ///< General failure
90};
91
92/**
93 * @brief Application class.
94 * @details Manages the application lifecycle, including initialization, updating, and shutdown.
95 * @note Not thread-safe.
96 */
97class App {
98public:
99#ifdef HELIOS_MOVEONLY_FUNCTION_AVALIABLE
100 using RunnerFn = std::move_only_function<AppExitCode(App&)>;
101 using ExtractFn = std::move_only_function<void(const ecs::World&, ecs::World&)>;
102#else
103 using RunnerFn = std::function<AppExitCode(App&)>;
104 using ExtractFn = std::function<void(const ecs::World&, ecs::World&)>;
105#endif
106
107 App();
108
109 /**
110 * @brief Constructs an App with a specific number of worker threads.
111 * @param worker_thread_count Number of worker threads for the executor
112 */
113 explicit App(size_t worker_thread_count);
114
115 App(const App&) = delete;
116 App(App&&) = delete;
117
118 /**
119 * @brief Destructor that ensures all async work is completed before destruction.
120 * @details Waits for all overlapping updates and pending executor tasks to complete.
121 */
122 ~App();
123
124 App& operator=(const App&) = delete;
125 App& operator=(App&&) = delete;
126
127 /**
128 * @brief Clears the application state, removing all data.
129 * @warning Triggers assertion if app is running.
130 */
131 void Clear();
132
133 /**
134 * @brief Initializes the application and its subsystems.
135 * @details Called before the main loop starts.
136 * Spawns tasks on the executor for parallel initialization.
137 * @note Automaticly called in Run().
138 * @warning Triggers assertion if app is already initialized or running.
139 */
140 void Initialize();
141
142 /**
143 * @brief Updates the application and its subsystems.
144 * @details Calls Update on the main sub-app and all registered sub-apps.
145 * Spawns async tasks as needed.
146 * @note Should not be called directly - use the runner function instead.
147 * @warning Triggers assertion if app is not initialized.
148 */
149 void Update();
150
151 /**
152 * @brief Runs the application with the given arguments.
153 * @warning Triggers assertion if app is initialized or running.
154 * @return Exit code of the application.
155 */
157
158 /**
159 * @brief Waits for all overlapping sub-app updates to complete.
160 * @details Can be called explicitly when synchronization is needed.
161 * Automatically called during CleanUp().
162 */
164
165 /**
166 * @brief Waits for overlapping updates of a specific sub-app type to complete.
167 * @details Can be called explicitly when synchronization is needed for a specific sub-app.
168 * @tparam T Sub-app type
169 * @param sub_app Sub-app type tag
170 */
171 template <SubAppTrait T>
172 void WaitForOverlappingUpdates(T sub_app = {});
173
174 /**
175 * @brief Waits for overlapping updates of a specific sub-app instance to complete.
176 * @details Can be called explicitly when synchronization is needed for a specific sub-app.
177 * @param sub_app Reference to the sub-app to wait for
178 */
179 void WaitForOverlappingUpdates(const SubApp& sub_app);
180
181 /**
182 * @brief Updates the Time resource for the current frame.
183 * @details Should be called at the beginning of each frame by the runner.
184 * Does nothing if Time resource is not registered.
185 */
186 void TickTime() noexcept;
187
188 /**
189 * @brief Adds a new sub-application of type T.
190 * @warning Triggers an assertion if app is initialized or running.
191 * @tparam T Sub-application type
192 * @param sub_app Sub-app type tag
193 * @return Reference to app for method chaining
194 */
195 template <SubAppTrait T>
196 auto AddSubApp(this auto&& self, T sub_app = {}) -> decltype(std::forward<decltype(self)>(self));
197
198 /**
199 * @brief Adds an existing sub-application instance.
200 * @warning Triggers an assertion if app is initialized or running.
201 * @tparam T Sub-application type
202 * @param sub_app Sub-application instance to add
203 * @param sub_app_tag Sub-app type tag
204 * @return Reference to app for method chaining
205 */
206 template <SubAppTrait T>
207 auto AddSubApp(this auto&& self, SubApp sub_app, T sub_app_tag = {}) -> decltype(std::forward<decltype(self)>(self));
208
209 /**
210 * @brief Adds a default-constructed module to the main sub-app.
211 * @details Modules can add their own systems, resources, and sub-apps.
212 * @warning Triggers an assertion if app is initialized or running.
213 * @tparam T Module type (must be default constructible)
214 * @return Reference to app for method chaining
215 */
216 template <DefaultConstructibleModuleTrait T>
217 auto AddModule(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
218
219 /**
220 * @brief Adds a pre-constructed module instance to the main sub-app.
221 * @details Allows adding modules with non-default constructors or pre-configured state.
222 * @warning Triggers an assertion if app is initialized or running.
223 * @tparam T Module type
224 * @param module Module instance to add (will be moved)
225 * @return Reference to app for method chaining
226 */
227 template <ModuleTrait T>
228 auto AddModule(this auto&& self, T module) -> decltype(std::forward<decltype(self)>(self));
229
230 /**
231 * @brief Adds multiple default-constructed modules to the main sub-app.
232 * @warning Triggers an assertion if app is initialized or running.
233 * @tparam Modules Module types (must be default constructible)
234 * @return Reference to app for method chaining
235 */
236 template <DefaultConstructibleModuleTrait... Modules>
237 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
238 auto AddModules(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
239
240 /**
241 * @brief Adds multiple pre-constructed module instances to the main sub-app.
242 * @details Allows adding modules with non-default constructors or pre-configured state.
243 * @warning Triggers an assertion if app is initialized or running.
244 * @tparam Modules Module types
245 * @param modules Module instances to add (will be moved)
246 * @return Reference to app for method chaining
247 */
248 template <ModuleTrait... Modules>
249 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
250 auto AddModules(this auto&& self, Modules&&... modules) -> decltype(std::forward<decltype(self)>(self));
251
252 /**
253 * @brief Adds a dynamic module to the main sub-app, taking ownership.
254 * @details Modules can add their own systems, resources, and sub-apps.
255 * The App takes ownership of the module via move semantics.
256 * @warning Triggers an assertion if app is initialized or running.
257 * @param module Dynamic module to add (moved)
258 * @return Reference to app for method chaining
259 */
260 auto AddDynamicModule(this auto&& self, DynamicModule module) -> decltype(std::forward<decltype(self)>(self));
261
262 /**
263 * @brief Adds a system to the specified schedule in the main sub-app.
264 * @warning Triggers an assertion if app is initialized or running.
265 * @tparam T System type
266 * @tparam Schedule Schedule type
267 * @param schedule Schedule to add system to
268 * @return Reference to app for method chaining
269 */
270 template <ecs::SystemTrait T, ScheduleTrait Schedule>
271 auto AddSystem(this auto&& self, Schedule schedule = {}) -> decltype(std::forward<decltype(self)>(self));
272
273 /**
274 * @brief Adds multiple systems to the specified schedule in the main sub-app.
275 * @warning Triggers an assertion if app is initialized or running.
276 * @tparam Systems System types
277 * @tparam Schedule Schedule type
278 * @param schedule Schedule to add systems to
279 * @return Reference to app for method chaining
280 */
281 template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
282 requires(sizeof...(Systems) > 1 && utils::UniqueTypes<Systems...>)
283 auto AddSystems(this auto&& self, Schedule schedule = {}) -> decltype(std::forward<decltype(self)>(self));
284
285 /**
286 * @brief Adds systems with fluent configuration builder.
287 * @details Returns a builder that allows chaining configuration methods like
288 * .After(), .Before(), .InSet(), and .Sequence().
289 * @warning Triggers an assertion if app is initialized or running.
290 * @tparam Systems System types to add
291 * @tparam Schedule Schedule type
292 * @param schedule Schedule to add systems to
293 * @return SystemConfig builder for fluent configuration
294 *
295 * @example
296 * @code
297 * app.AddSystemsBuilder<MovementSystem, CollisionSystem>(kUpdate)
298 * .After<InputSystem>()
299 * .Before<RenderSystem>()
300 * .InSet<PhysicsSet>()
301 * .Sequence();
302 * @endcode
303 */
304 template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
305 requires(sizeof...(Systems) > 0 && utils::UniqueTypes<Systems...>)
306 auto AddSystemsBuilder(this auto&& self, Schedule schedule = {}) -> SystemConfig<Schedule, Systems...>;
307
308 /**
309 * @brief Adds a single system with fluent configuration builder.
310 * @details Returns a builder that allows chaining configuration methods.
311 * @warning Triggers an assertion if app is initialized or running.
312 * @tparam T System type to add
313 * @tparam Schedule Schedule type
314 * @param schedule Schedule to add system to
315 * @return SystemConfig builder for fluent configuration
316 */
317 template <ecs::SystemTrait T, ScheduleTrait Schedule>
318 auto AddSystemBuilder(this auto&& self, Schedule schedule = {}) -> SystemConfig<Schedule, T>;
319
320 /**
321 * @brief Configures a system set with fluent builder.
322 * @details Returns a builder that allows configuring set ordering.
323 * @warning Triggers an assertion if app is initialized or running.
324 * @tparam Set System set type to configure
325 * @tparam Schedule Schedule type
326 * @param schedule Schedule where the set is configured
327 * @return SystemSetConfig builder for fluent configuration
328 *
329 * @example
330 * @code
331 * app.ConfigureSet<PhysicsSet>(kUpdate).After<InputSet>().Before<RenderSet>();
332 * @endcode
333 */
334 template <SystemSetTrait Set, ScheduleTrait Schedule>
335 auto ConfigureSet(this auto&& self, Schedule schedule = {}) -> SystemSetConfig<Schedule, Set>;
336
337 /**
338 * @brief Inserts a resource into the main sub-app.
339 * @warning Triggers assertion if app is initialized or running.
340 * @tparam T Resource type
341 * @param resource Resource to insert
342 * @return Reference to app for method chaining
343 */
344 template <ecs::ResourceTrait T>
345 auto InsertResource(this auto&& self, T&& resource) -> decltype(std::forward<decltype(self)>(self));
346
347 /**
348 * @brief Emplaces a resource into the main sub-app's world.
349 * @warning Triggers assertion if app is initialized or running.
350 * @tparam T Resource type
351 * @tparam Args Constructor argument types
352 * @param args Arguments to forward to resource constructor
353 * @return Reference to app for method chaining
354 */
355 template <ecs::ResourceTrait T, typename... Args>
356 requires std::constructible_from<T, Args...>
357 auto EmplaceResource(this auto&& self, Args&&... args) -> decltype(std::forward<decltype(self)>(self));
358
359 /**
360 * @brief Registers an event type for use in the main sub-app.
361 * @details Events must be registered before they can be written or read.
362 * @warning Triggers assertion if app is initialized or running.
363 * @tparam T Event type
364 * @return Reference to app for method chaining
365 */
366 template <ecs::EventTrait T>
367 auto AddEvent(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
368
369 /**
370 * @brief Registers multiple event types for use in the main sub-app.
371 * @warning Triggers assertion if app is initialized or running.
372 * @tparam Events Event types to register
373 * @return Reference to app for method chaining
374 */
375 template <ecs::EventTrait... Events>
376 requires(sizeof...(Events) > 1)
377 auto AddEvents(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
378
379 /**
380 * @brief Sets runner function for the application.
381 * @warning Triggers assertion if app is initialized, running, or runner is null.
382 * @param runner A move-only function that takes an App reference and returns an AppExitCode.
383 * @return Reference to app for method chaining
384 */
385 auto SetRunner(this auto&& self, RunnerFn runner) noexcept -> decltype(std::forward<decltype(self)>(self));
386
387 /**
388 * @brief Sets extraction function for a sub-app.
389 * @details The extraction function is called before the sub-app's Update to copy data from the main world.
390 * @warning Triggers assertion if app is initialized, running, extraction function is null, or sub-app doesn't exist.
391 * @tparam T Sub-application type
392 * @param extract_fn Function that takes main world and sub-app world references
393 * @return Reference to app for method chaining
394 */
395 template <SubAppTrait T>
396 auto SetSubAppExtraction(this auto&& self, ExtractFn extract_fn) -> decltype(std::forward<decltype(self)>(self));
397
398 /**
399 * @brief Checks if a sub app of type T exists in main sub-app.
400 * @tparam T SubApp type
401 * @return True if sub app exists, false otherwise
402 */
403 template <SubAppTrait T>
404 [[nodiscard]] bool ContainsSubApp() const noexcept;
405
406 /**
407 * @brief Checks if a module of type T exists in main sub-app.
408 * @tparam T Module type
409 * @return True if module exists, false otherwise
410 */
411 template <ModuleTrait T>
412 [[nodiscard]] bool ContainsModule() const noexcept;
413
414 /**
415 * @brief Checks if a dynamic module with the given ID exists.
416 * @param module_id Module type ID
417 * @return True if module exists, false otherwise
418 */
419 [[nodiscard]] bool ContainsDynamicModule(ModuleTypeId module_id) const noexcept;
420
421 /**
422 * @brief Checks if a system of type T exists in any schedule of main sub-app.
423 * @tparam T System type
424 * @return True if system exists, false otherwise
425 */
426 template <ecs::SystemTrait T>
427 [[nodiscard]] bool ContainsSystem() const noexcept {
428 return main_sub_app_.ContainsSystem<T>();
429 }
430
431 /**
432 * @brief Checks if a system of type T exists in the specified schedule of main sub-app.
433 * @tparam T System type
434 * @tparam Schedule Schedule type
435 * @param schedule Schedule to check
436 * @return True if system exists, false otherwise
437 */
438 template <ecs::SystemTrait T, ScheduleTrait Schedule>
439 [[nodiscard]] bool ContainsSystem(Schedule schedule = {}) const noexcept {
440 return main_sub_app_.ContainsSystem<T>(schedule);
441 }
442
443 /**
444 * @brief Checks if the app has been initialized.
445 * @return True if initialized, false otherwise
446 */
447 [[nodiscard]] bool IsInitialized() const noexcept { return is_initialized_; }
448
449 /**
450 * @brief Checks if the app is currently running.
451 * @return True if running, false otherwise
452 */
453 [[nodiscard]] bool IsRunning() const noexcept { return is_running_.load(std::memory_order_acquire); }
454
455 /**
456 * @brief Checks if a resource exists in main sub_app.
457 * @tparam T Resource type
458 * @return True if resource exists, false otherwise
459 */
460 template <ecs::ResourceTrait T>
461 [[nodiscard]] bool HasResource() const noexcept {
462 return main_sub_app_.HasResource<T>();
463 }
464
465 /**
466 * @brief Checks if a event is registered in main sub_app.
467 * @tparam T Event type
468 * @return True if event exists, false otherwise
469 */
470 template <ecs::EventTrait T>
471 [[nodiscard]] bool HasEvent() const noexcept {
472 return main_sub_app_.HasEvent<T>();
473 }
474
475 /**
476 * @brief Gets the number of modules in main sub-app.
477 * @return Number of modules
478 */
479 [[nodiscard]] size_t ModuleCount() const noexcept { return modules_.size() + dynamic_modules_.size(); }
480
481 /**
482 * @brief Gets the total number of systems across all schedules in main sub-app.
483 * @return Total number of systems
484 */
485 [[nodiscard]] size_t SystemCount() const noexcept { return main_sub_app_.SystemCount(); }
486
487 /**
488 * @brief Gets the number of systems in the specified schedule of main sub-app.
489 * @tparam Schedule Schedule type
490 * @param schedule Schedule to query
491 * @return Number of systems in the schedule
492 */
493 template <ScheduleTrait Schedule>
494 [[nodiscard]] size_t SystemCount(Schedule schedule = {}) const noexcept {
495 return main_sub_app_.SystemCount(schedule);
496 }
497
498 /**
499 * @brief Gets a sub-application by type.
500 * @warning Triggers an assertion if the sub-app does not exist.
501 * Reference might be invalidated due to reallocation of storage after addition of new sub apps.
502 * @tparam T Sub-application type
503 * @return Reference to the sub-application
504 */
505 template <SubAppTrait T>
506 [[nodiscard]] SubApp& GetSubApp() noexcept;
507
508 /**
509 * @brief Gets a sub-application by type (const version).
510 * @warning Triggers an assertion if the sub-app does not exist.
511 * @tparam T Sub-application type
512 * @return Const reference to the sub-application
513 */
514 template <SubAppTrait T>
515 [[nodiscard]] const SubApp& GetSubApp() const noexcept;
516
517 /**
518 * @brief Gets the main sub-application.
519 * @return Reference to the main sub-application
520 */
521 [[nodiscard]] SubApp& GetMainSubApp() noexcept { return main_sub_app_; }
522
523 /**
524 * @brief Gets the main sub-application (const version).
525 * @return Const reference to the main sub-application
526 */
527 [[nodiscard]] const SubApp& GetMainSubApp() const noexcept { return main_sub_app_; }
528
529 /**
530 * @brief Gets the main world.
531 * @return Reference to the main world
532 */
533 [[nodiscard]] const ecs::World& GetMainWorld() const noexcept { return main_sub_app_.GetWorld(); }
534
535 /**
536 * @brief Gets the async executor.
537 * @return Reference to the async executor
538 */
539 [[nodiscard]] async::Executor& GetExecutor() noexcept { return executor_; }
540
541 /**
542 * @brief Gets the async executor (const version).
543 * @return Const reference to the async executor
544 */
545 [[nodiscard]] const async::Executor& GetExecutor() const noexcept { return executor_; }
546
547private:
548 /**
549 * @brief Cleans up the application and its subsystems.
550 * @details Called after the main loop ends.
551 * Spawns tasks on the executor for parallel cleanup.
552 */
553 void CleanUp();
554
555 /**
556 * @brief Builds all registered modules.
557 * @details Calls Build on each module after all modules have been added.
558 */
559 void BuildModules();
560
561 /**
562 * @brief Waits for all modules to be ready and calls Finish on them.
563 * @details Called after BuildModules. Polls IsReady on each module
564 * and calls Finish once all modules are ready.
565 */
566 void FinishModules();
567
568 /**
569 * @brief Destroys all registered modules.
570 * @details Calls Destroy on each module.
571 */
572 void DestroyModules();
573
574 /**
575 * @brief Registers built-in resources and events.
576 * @details Called automatically before initialization.
577 * Registers Time resource and ShutdownEvent if not already present.
578 */
579 void RegisterBuiltins();
580
581 [[nodiscard]] static AppExitCode DefaultRunnerWrapper(App& app);
582
583 bool is_initialized_ = false; ///< Whether the app has been initialized
584 std::atomic<bool> is_running_{false}; ///< Whether the app is currently running
585
586 SubApp main_sub_app_; ///< The main sub-application
587 std::vector<SubApp> sub_apps_; ///< List of additional sub-applications
588 std::unordered_map<SubAppTypeId, size_t> sub_app_index_map_; ///< Map of sub-application type IDs to their indices
589
590 std::vector<std::unique_ptr<Module>> modules_; ///< Owned static modules
591 std::unordered_map<ModuleTypeId, size_t> module_index_map_; ///< Map of module type IDs to their indices
592
593 std::vector<DynamicModule> dynamic_modules_; ///< Owned dynamic modules
594 std::unordered_map<ModuleTypeId, size_t> dynamic_module_index_map_; ///< Map of dynamic module IDs to indices
595
596 async::Executor executor_; ///< Async executor for parallel execution
597 async::TaskGraph update_graph_; ///< Task graph for managing updates
598
599 RunnerFn runner_; ///< The runner function
600
601 /// Map from sub-app index to their overlapping tracked futures.
602 std::unordered_map<size_t, std::vector<details::TrackedFuture>> sub_app_overlapping_futures_;
603};
604
605template <SubAppTrait T>
606inline auto App::AddSubApp(this auto&& self, T /*sub_app*/) -> decltype(std::forward<decltype(self)>(self)) {
607 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add sub app '{}': Cannot add sub apps after app initialization!",
608 SubAppNameOf<T>());
609
610 HELIOS_ASSERT(!self.IsRunning(), "Failed to add sub app '{}': Cannot add sub apps while app is running!",
611 SubAppNameOf<T>());
612
613 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
614 if (self.sub_app_index_map_.contains(id)) [[unlikely]] {
615 HELIOS_WARN("Sub app '{}' is already exist in app!", SubAppNameOf<T>());
616 return std::forward<decltype(self)>(self);
617 }
618
619 self.sub_app_index_map_[id] = self.sub_apps_.size();
620 self.sub_apps_.emplace_back();
621
622 // Set overlapping updates flag based on trait
623 constexpr bool allow_overlapping = SubAppAllowsOverlappingUpdates<T>();
624 self.sub_apps_.back().SetAllowOverlappingUpdates(allow_overlapping);
625
626 // Set max overlapping updates based on trait
627 constexpr size_t max_overlapping = SubAppMaxOverlappingUpdates<T>();
628 self.sub_apps_.back().SetMaxOverlappingUpdates(max_overlapping);
629
630 return std::forward<decltype(self)>(self);
631}
632
633template <SubAppTrait T>
634inline auto App::AddSubApp(this auto&& self, SubApp sub_app, T /*sub_app_tag*/)
635 -> decltype(std::forward<decltype(self)>(self)) {
636 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add sub app '{}': Cannot add sub apps after app initialization!",
637 SubAppNameOf<T>());
638
639 HELIOS_ASSERT(!self.IsRunning(), "Failed to add sub app '{}': Cannot add sub apps while app is running!",
640 SubAppNameOf<T>());
641
642 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
643 if (self.sub_app_index_map_.contains(id)) [[unlikely]] {
644 HELIOS_WARN("Sub app '{}' is already exist in app!", SubAppNameOf<T>());
645 return std::forward<decltype(self)>(self);
646 }
647
648 // Set overlapping updates flag based on trait before adding
649 constexpr bool allow_overlapping = SubAppAllowsOverlappingUpdates<T>();
650 sub_app.SetAllowOverlappingUpdates(allow_overlapping);
651
652 // Set max overlapping updates based on trait
653 constexpr size_t max_overlapping = SubAppMaxOverlappingUpdates<T>();
654 sub_app.SetMaxOverlappingUpdates(max_overlapping);
655
656 self.sub_app_index_map_[id] = self.sub_apps_.size();
657 self.sub_apps_.push_back(std::move(sub_app));
658
659 return std::forward<decltype(self)>(self);
660}
661
662template <DefaultConstructibleModuleTrait T>
663inline auto App::AddModule(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
664 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add module '{}': Cannot add modules after app initialization!",
665 ModuleNameOf<T>());
666
667 HELIOS_ASSERT(!self.IsRunning(), "Failed to add module '{}': Cannot add modules while app is running!",
668 ModuleNameOf<T>());
669
670 // module->Build(self); // Will be called after all modules are added
671 constexpr ModuleTypeId id = ModuleTypeIdOf<T>();
672 if (self.module_index_map_.contains(id)) [[unlikely]] {
673 HELIOS_WARN("Module '{}' is already exist in app!", ModuleNameOf<T>());
674 return std::forward<decltype(self)>(self);
675 }
676
677 self.module_index_map_[id] = self.modules_.size();
678 auto module = std::make_unique<T>();
679 self.modules_.push_back(std::move(module));
680 return std::forward<decltype(self)>(self);
681}
682
683template <ModuleTrait T>
684inline auto App::AddModule(this auto&& self, T module) -> decltype(std::forward<decltype(self)>(self)) {
685 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add module '{}': Cannot add modules after app initialization!",
686 ModuleNameOf<T>());
687
688 HELIOS_ASSERT(!self.IsRunning(), "Failed to add module '{}': Cannot add modules while app is running!",
689 ModuleNameOf<T>());
690
691 constexpr ModuleTypeId id = ModuleTypeIdOf<T>();
692 if (self.module_index_map_.contains(id)) [[unlikely]] {
693 HELIOS_WARN("Module '{}' is already exist in app!", ModuleNameOf<T>());
694 return std::forward<decltype(self)>(self);
695 }
696
697 self.module_index_map_[id] = self.modules_.size();
698 self.modules_.push_back(std::make_unique<T>(std::move(module)));
699 return std::forward<decltype(self)>(self);
700}
701
702template <DefaultConstructibleModuleTrait... Modules>
703 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
704inline auto App::AddModules(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
705 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add modules: Cannot add modules after app initialization!");
706 HELIOS_ASSERT(!self.IsRunning(), "Failed to add modules: Cannot add modules while app is running!");
707
708 (self.template AddModule<Modules>(), ...);
709 return std::forward<decltype(self)>(self);
710}
711
712template <ModuleTrait... Modules>
713 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
714inline auto App::AddModules(this auto&& self, Modules&&... modules) -> decltype(std::forward<decltype(self)>(self)) {
715 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add modules: Cannot add modules after app initialization!");
716 HELIOS_ASSERT(!self.IsRunning(), "Failed to add modules: Cannot add modules while app is running!");
717
718 (self.AddModule(std::forward<Modules>(modules)), ...);
719 return std::forward<decltype(self)>(self);
720}
721
722inline auto App::AddDynamicModule(this auto&& self, DynamicModule module)
723 -> decltype(std::forward<decltype(self)>(self)) {
724 const std::string_view name = module.GetModuleName();
725 const ModuleTypeId id = module.GetModuleId();
726
727 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add module '{}': Cannot add modules after app initialization!", name);
728 HELIOS_ASSERT(!self.IsRunning(), "Failed to add module '{}': Cannot add modules while app is running!", name);
729 HELIOS_ASSERT(module.Loaded(), "Failed to add module '{}': Module is not loaded!", name);
730
731 // Check both static and dynamic module maps for duplicates
732 if (self.module_index_map_.contains(id) || self.dynamic_module_index_map_.contains(id)) [[unlikely]] {
733 HELIOS_WARN("Module '{}' already exists in app!", name);
734 return std::forward<decltype(self)>(self);
735 }
736
737 self.dynamic_module_index_map_[id] = self.dynamic_modules_.size();
738 self.dynamic_modules_.push_back(std::move(module));
739 return std::forward<decltype(self)>(self);
740}
741
742template <ecs::SystemTrait T, ScheduleTrait Schedule>
743inline auto App::AddSystem(this auto&& self, Schedule schedule) -> decltype(std::forward<decltype(self)>(self)) {
744 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add system '{}': Cannot add systems after app initialization!",
746
747 HELIOS_ASSERT(!self.IsRunning(), "Failed to add system '{}': Cannot add systems while app is running!",
749
750 if (self.template ContainsSystem<T>(schedule)) [[unlikely]] {
751 HELIOS_WARN("System '{}' is already exist in app schedule '{}'!", ecs::SystemNameOf<T>(),
752 ScheduleNameOf<Schedule>());
753 return std::forward<decltype(self)>(self);
754 }
755
756 self.GetMainSubApp().template AddSystem<T>(schedule);
757 return std::forward<decltype(self)>(self);
758}
759
760template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
761 requires(sizeof...(Systems) > 1 && utils::UniqueTypes<Systems...>)
762inline auto App::AddSystems(this auto&& self, Schedule schedule) -> decltype(std::forward<decltype(self)>(self)) {
763 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add systems: Cannot add systems after app initialization!");
764 HELIOS_ASSERT(!self.IsRunning(), "Failed to add systems: Cannot add systems while app is running!");
765
766 self.GetMainSubApp().template AddSystems<Systems...>(schedule);
767 return std::forward<decltype(self)>(self);
768}
769
770template <ecs::ResourceTrait T>
771inline auto App::InsertResource(this auto&& self, T&& resource) -> decltype(std::forward<decltype(self)>(self)) {
772 HELIOS_ASSERT(!self.IsInitialized(), "Failed to insert resource '{}': Cannot add resources after app initialization!",
774
775 self.GetMainSubApp().InsertResource(std::forward<T>(resource));
776 return std::forward<decltype(self)>(self);
777}
778
779template <ecs::ResourceTrait T, typename... Args>
780 requires std::constructible_from<T, Args...>
781inline auto App::EmplaceResource(this auto&& self, Args&&... args) -> decltype(std::forward<decltype(self)>(self)) {
782 HELIOS_ASSERT(!self.IsInitialized(),
783 "Failed to emplace resource '{}': Cannot add resources after app initialization!",
785
786 HELIOS_ASSERT(!self.IsRunning(), "Failed to emplace resource '{}': Cannot add resources while app is running!",
788
789 self.GetMainSubApp().template EmplaceResource<T>(std::forward<Args>(args)...);
790 return std::forward<decltype(self)>(self);
791}
792
793template <ecs::EventTrait T>
794inline auto App::AddEvent(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
795 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add event '{}': Cannot add events after app initialization!",
797
798 HELIOS_ASSERT(!self.IsRunning(), "Failed to add event '{}': Cannot add events while app is running!",
800
801 if (self.template HasEvent<T>()) [[unlikely]] {
802 HELIOS_WARN("Event '{}' is already exist in app!", ecs::EventNameOf<T>());
803 return std::forward<decltype(self)>(self);
804 }
805
806 self.GetMainSubApp().template AddEvent<T>();
807 return std::forward<decltype(self)>(self);
808}
809
810template <ecs::EventTrait... Events>
811 requires(sizeof...(Events) > 1)
812inline auto App::AddEvents(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
813 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add events: Cannot add events after app initialization!");
814 HELIOS_ASSERT(!self.IsRunning(), "Failed to add events: Cannot add events while app is running!");
815
816 self.GetMainSubApp().template AddEvents<Events...>();
817 return std::forward<decltype(self)>(self);
818}
819
820inline auto App::SetRunner(this auto&& self, RunnerFn runner) noexcept -> decltype(std::forward<decltype(self)>(self)) {
821 HELIOS_ASSERT(!self.IsInitialized(), "Failed to set runner: Cannot set runner after app initialization!");
822 HELIOS_ASSERT(!self.IsRunning(), "Failed to set runner: Cannot set runner while app is running!");
823
824 HELIOS_ASSERT(runner, "Failed to set runner: Runner function cannot be null!");
825
826 self.runner_ = std::move(runner);
827 return std::forward<decltype(self)>(self);
828}
829
830template <SubAppTrait T>
831inline auto App::SetSubAppExtraction(this auto&& self, ExtractFn extract_fn)
832 -> decltype(std::forward<decltype(self)>(self)) {
834 !self.IsInitialized(),
835 "Failed to set extraction function for sub app '{}': Cannot set extraction function after app initialization!",
836 SubAppNameOf<T>());
837
839 !self.IsRunning(),
840 "Failed to set extraction function for sub app '{}': Cannot set extraction function while app is running!",
841 SubAppNameOf<T>());
842
843 HELIOS_ASSERT(extract_fn, "Failed to set extraction function for sub app '{}': Extraction function cannot be null!",
844 SubAppNameOf<T>());
845
846 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
847 const auto it = self.sub_app_index_map_.find(id);
848 HELIOS_ASSERT(it != self.sub_app_index_map_.end(),
849 "Failed to set extraction function for sub app '{}': Sub app does not exist!", SubAppNameOf<T>());
850
851 self.sub_apps_.at(it->second).SetExtractFunction(std::move(extract_fn));
852 return std::forward<decltype(self)>(self);
853}
854
855template <SubAppTrait T>
856inline bool App::ContainsSubApp() const noexcept {
857 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
858 return sub_app_index_map_.contains(id);
859}
860
861template <ModuleTrait T>
862inline bool App::ContainsModule() const noexcept {
863 constexpr ModuleTypeId id = ModuleTypeIdOf<T>();
864 return module_index_map_.contains(id) || dynamic_module_index_map_.contains(id);
865}
866
867inline bool App::ContainsDynamicModule(ModuleTypeId module_id) const noexcept {
868 return dynamic_module_index_map_.contains(module_id);
869}
870
871template <SubAppTrait T>
872inline SubApp& App::GetSubApp() noexcept {
873 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
874 const auto it = sub_app_index_map_.find(id);
875 HELIOS_ASSERT(it != sub_app_index_map_.end(), "Failed to get sub app '{}': Sub app does not exist!",
876 SubAppNameOf<T>());
877 return sub_apps_.at(it->second);
878}
879
880template <SubAppTrait T>
881inline const SubApp& App::GetSubApp() const noexcept {
882 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
883 const auto it = sub_app_index_map_.find(id);
884 HELIOS_ASSERT(it != sub_app_index_map_.end(), "Failed to get sub app '{}': Sub app does not exist!",
885 SubAppNameOf<T>());
886 return sub_apps_.at(it->second);
887}
888
889template <SubAppTrait T>
890inline void App::WaitForOverlappingUpdates(T /*sub_app*/) {
891 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
892 const auto it = sub_app_index_map_.find(id);
893 if (it == sub_app_index_map_.end()) [[unlikely]] {
894 return; // Sub-app doesn't exist
895 }
896
897 const size_t index = it->second;
898 const auto futures_it = sub_app_overlapping_futures_.find(index);
899 if (futures_it == sub_app_overlapping_futures_.end()) {
900 return; // No overlapping futures for this sub-app
901 }
902
903 for (auto& tracked : futures_it->second) {
904 tracked.Wait();
905 }
906 futures_it->second.clear();
907}
908
909template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
910 requires(sizeof...(Systems) > 0 && utils::UniqueTypes<Systems...>)
911inline auto App::AddSystemsBuilder(this auto&& self, Schedule schedule) -> SystemConfig<Schedule, Systems...> {
912 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add systems: Cannot add systems after app initialization!");
913 HELIOS_ASSERT(!self.IsRunning(), "Failed to add systems: Cannot add systems while app is running!");
914
915 return self.GetMainSubApp().template AddSystemsBuilder<Systems...>(schedule);
916}
917
918template <ecs::SystemTrait T, ScheduleTrait Schedule>
919inline auto App::AddSystemBuilder(this auto&& self, Schedule schedule) -> SystemConfig<Schedule, T> {
920 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add system '{}': Cannot add system after app initialization!",
922 HELIOS_ASSERT(!self.IsRunning(), "Failed to add system '{}': Cannot add system while app is running!",
924
925 return self.GetMainSubApp().template AddSystemBuilder<T>(schedule);
926}
927
928template <SystemSetTrait Set, ScheduleTrait Schedule>
929inline auto App::ConfigureSet(this auto&& self, Schedule schedule) -> SystemSetConfig<Schedule, Set> {
930 HELIOS_ASSERT(!self.IsInitialized(), "Failed to configure set '{}': Cannot configure set after app initialization!",
931 SystemSetNameOf<Set>());
932 HELIOS_ASSERT(!self.IsRunning(), "Failed to configure set '{}': Cannot configure set while app is running!",
933 SystemSetNameOf<Set>());
934
935 return self.GetMainSubApp().template ConfigureSet<Set>(schedule);
936}
937
938} // namespace helios::app
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
Application class.
Definition app.hpp:97
bool ContainsSubApp() const noexcept
Checks if a sub app of type T exists in main sub-app.
Definition app.hpp:856
auto EmplaceResource(this auto &&self, Args &&... args) -> decltype(std::forward< decltype(self)>(self))
Emplaces a resource into the main sub-app's world.
Definition app.hpp:781
SubApp & GetSubApp() noexcept
Gets a sub-application by type.
Definition app.hpp:872
bool HasResource() const noexcept
Checks if a resource exists in main sub_app.
Definition app.hpp:461
std::function< void(const ecs::World &, ecs::World &)> ExtractFn
Definition app.hpp:104
bool IsRunning() const noexcept
Checks if the app is currently running.
Definition app.hpp:453
const async::Executor & GetExecutor() const noexcept
Gets the async executor (const version).
Definition app.hpp:545
auto AddEvents(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Registers multiple event types for use in the main sub-app.
Definition app.hpp:812
SubApp & GetMainSubApp() noexcept
Gets the main sub-application.
Definition app.hpp:521
auto InsertResource(this auto &&self, T &&resource) -> decltype(std::forward< decltype(self)>(self))
Inserts a resource into the main sub-app.
Definition app.hpp:771
auto AddEvent(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Registers an event type for use in the main sub-app.
Definition app.hpp:794
App(const App &)=delete
auto AddModules(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Adds multiple default-constructed modules to the main sub-app.
Definition app.hpp:704
auto ConfigureSet(this auto &&self, Schedule schedule={}) -> SystemSetConfig< Schedule, Set >
Definition app.hpp:929
auto AddSystemsBuilder(this auto &&self, Schedule schedule={}) -> SystemConfig< Schedule, Systems... >
Definition app.hpp:911
App & operator=(const App &)=delete
App(App &&)=delete
AppExitCode Run()
Runs the application with the given arguments.
Definition app.cpp:49
auto SetRunner(this auto &&self, RunnerFn runner) noexcept -> decltype(std::forward< decltype(self)>(self))
Sets runner function for the application.
Definition app.hpp:820
auto AddSystemBuilder(this auto &&self, Schedule schedule={}) -> SystemConfig< Schedule, T >
Adds a single system with fluent configuration builder.
Definition app.hpp:919
auto AddSystems(this auto &&self, Schedule schedule={}) -> decltype(std::forward< decltype(self)>(self))
Adds multiple systems to the specified schedule in the main sub-app.
Definition app.hpp:762
bool ContainsSystem() const noexcept
Checks if a system of type T exists in any schedule of main sub-app.
Definition app.hpp:427
size_t SystemCount(Schedule schedule={}) const noexcept
Gets the number of systems in the specified schedule of main sub-app.
Definition app.hpp:494
bool ContainsDynamicModule(ModuleTypeId module_id) const noexcept
Checks if a dynamic module with the given ID exists.
Definition app.hpp:867
void Initialize()
Initializes the application and its subsystems.
Definition app.cpp:116
App & operator=(App &&)=delete
const ecs::World & GetMainWorld() const noexcept
Gets the main world.
Definition app.hpp:533
bool ContainsSystem(Schedule schedule={}) const noexcept
Checks if a system of type T exists in the specified schedule of main sub-app.
Definition app.hpp:439
bool IsInitialized() const noexcept
Checks if the app has been initialized.
Definition app.hpp:447
auto AddSubApp(this auto &&self, T sub_app={}) -> decltype(std::forward< decltype(self)>(self))
Adds a new sub-application of type T.
Definition app.hpp:606
std::function< AppExitCode(App &)> RunnerFn
Definition app.hpp:103
void WaitForOverlappingUpdates()
Waits for all overlapping sub-app updates to complete.
Definition app.cpp:172
void Clear()
Clears the application state, removing all data.
Definition app.cpp:37
auto AddSystem(this auto &&self, Schedule schedule={}) -> decltype(std::forward< decltype(self)>(self))
Adds a system to the specified schedule in the main sub-app.
Definition app.hpp:743
void TickTime() noexcept
Updates the Time resource for the current frame.
Definition app.cpp:206
size_t ModuleCount() const noexcept
Gets the number of modules in main sub-app.
Definition app.hpp:479
~App()
Destructor that ensures all async work is completed before destruction.
Definition app.cpp:26
auto AddModule(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Adds a default-constructed module to the main sub-app.
Definition app.hpp:663
auto SetSubAppExtraction(this auto &&self, ExtractFn extract_fn) -> decltype(std::forward< decltype(self)>(self))
Sets extraction function for a sub-app.
Definition app.hpp:831
bool HasEvent() const noexcept
Checks if a event is registered in main sub_app.
Definition app.hpp:471
size_t SystemCount() const noexcept
Gets the total number of systems across all schedules in main sub-app.
Definition app.hpp:485
bool ContainsModule() const noexcept
Checks if a module of type T exists in main sub-app.
Definition app.hpp:862
void Update()
Updates the application and its subsystems.
Definition app.cpp:84
auto AddDynamicModule(this auto &&self, DynamicModule module) -> decltype(std::forward< decltype(self)>(self))
Adds a dynamic module to the main sub-app, taking ownership.
Definition app.hpp:722
async::Executor & GetExecutor() noexcept
Gets the async executor.
Definition app.hpp:539
const SubApp & GetMainSubApp() const noexcept
Gets the main sub-application (const version).
Definition app.hpp:527
A sub-application with its own ECS world, systems, and resources.
Definition sub_app.hpp:167
const ecs::World & GetWorld() const noexcept
Gets const reference to this sub-app's world.
Definition sub_app.hpp:449
size_t SystemCount() const noexcept
Gets the total number of systems across all schedules.
Definition sub_app.hpp:432
bool ContainsSystem() const noexcept
Checks if a system of type T is in any schedule.
Definition sub_app.hpp:380
bool HasEvent() const noexcept
Checks if an event type is registered in this sub-app.
Definition sub_app.hpp:412
bool HasResource() const noexcept
Checks if a resource of type T exists in this sub-app.
Definition sub_app.hpp:402
Manages worker threads and executes task graphs using work-stealing scheduling.
Definition executor.hpp:33
Represents a task dependency graph that can be executed by an Executor.
The World class manages entities with their components and systems.
Definition world.hpp:53
Concept for modules that can be default constructed.
Definition module.hpp:76
Concept to ensure a type is a valid Module.
Definition module.hpp:69
Trait to identify valid sub-app types.
Definition sub_app.hpp:43
Concept for valid event types.
Definition event.hpp:44
Concept for valid resource types.
Definition resource.hpp:21
Concept for valid system types.
Definition system.hpp:76
#define HELIOS_WARN(...)
Definition logger.hpp:687
size_t SubAppTypeId
Type alias for sub-app type IDs.
Definition sub_app.hpp:128
AppExitCode
Application exit codes.
Definition app.hpp:87
@ kFailure
General failure.
@ kSuccess
Successful execution.
size_t ModuleTypeId
Definition module.hpp:88
STL namespace.
CleanUp schedule - main cleanup.
Tracked future with ready flag to reduce syscall churn.
Definition app.hpp:45
void Wait()
Waits for the future to complete if not already ready.
Definition app.hpp:52
bool IsReady() noexcept
Checks if the future is ready, caching the result.
Definition app.hpp:64
std::shared_future< void > future
The underlying future.
Definition app.hpp:46
bool ready
Cached ready state.
Definition app.hpp:47
bool Valid() const noexcept
Checks if the future is valid.
Definition app.hpp:79