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 Success = 0, ///< Successful execution
89 Failure = 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 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
214 * @return Reference to app for method chaining
215 */
216 template <ModuleTrait T>
217 auto AddModule(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
218
219 /**
220 * @brief Adds multiple modules to the main sub-app.
221 * @warning Triggers an assertion if app is initialized or running.
222 * @tparam Modules Module types
223 * @return Reference to app for method chaining
224 */
225 template <ModuleTrait... Modules>
226 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
227 auto AddModules(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
228
229 /**
230 * @brief Adds a dynamic module to the main sub-app, taking ownership.
231 * @details Modules can add their own systems, resources, and sub-apps.
232 * The App takes ownership of the module via move semantics.
233 * @warning Triggers an assertion if app is initialized or running.
234 * @param module Dynamic module to add (moved)
235 * @return Reference to app for method chaining
236 */
237 auto AddDynamicModule(this auto&& self, DynamicModule module) -> decltype(std::forward<decltype(self)>(self));
238
239 /**
240 * @brief Adds a system to the specified schedule in the main sub-app.
241 * @warning Triggers an assertion if app is initialized or running.
242 * @tparam T System type
243 * @tparam Schedule Schedule type
244 * @param schedule Schedule to add system to
245 * @return Reference to app for method chaining
246 */
247 template <ecs::SystemTrait T, ScheduleTrait Schedule>
248 auto AddSystem(this auto&& self, Schedule schedule = {}) -> decltype(std::forward<decltype(self)>(self));
249
250 /**
251 * @brief Adds multiple systems to the specified schedule in the main sub-app.
252 * @warning Triggers an assertion if app is initialized or running.
253 * @tparam Systems System types
254 * @tparam Schedule Schedule type
255 * @param schedule Schedule to add systems to
256 * @return Reference to app for method chaining
257 */
258 template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
259 requires(sizeof...(Systems) > 1 && utils::UniqueTypes<Systems...>)
260 auto AddSystems(this auto&& self, Schedule schedule = {}) -> decltype(std::forward<decltype(self)>(self));
261
262 /**
263 * @brief Adds systems with fluent configuration builder.
264 * @details Returns a builder that allows chaining configuration methods like
265 * .After(), .Before(), .InSet(), and .Sequence().
266 * @warning Triggers an assertion if app is initialized or running.
267 * @tparam Systems System types to add
268 * @tparam Schedule Schedule type
269 * @param schedule Schedule to add systems to
270 * @return SystemConfig builder for fluent configuration
271 *
272 * @example
273 * @code
274 * app.AddSystemsBuilder<MovementSystem, CollisionSystem>(kUpdate)
275 * .After<InputSystem>()
276 * .Before<RenderSystem>()
277 * .InSet<PhysicsSet>()
278 * .Sequence();
279 * @endcode
280 */
281 template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
282 requires(sizeof...(Systems) > 0 && utils::UniqueTypes<Systems...>)
283 auto AddSystemsBuilder(this auto&& self, Schedule schedule = {}) -> SystemConfig<Schedule, Systems...>;
284
285 /**
286 * @brief Adds a single system with fluent configuration builder.
287 * @details Returns a builder that allows chaining configuration methods.
288 * @warning Triggers an assertion if app is initialized or running.
289 * @tparam T System type to add
290 * @tparam Schedule Schedule type
291 * @param schedule Schedule to add system to
292 * @return SystemConfig builder for fluent configuration
293 */
294 template <ecs::SystemTrait T, ScheduleTrait Schedule>
295 auto AddSystemBuilder(this auto&& self, Schedule schedule = {}) -> SystemConfig<Schedule, T>;
296
297 /**
298 * @brief Configures a system set with fluent builder.
299 * @details Returns a builder that allows configuring set ordering.
300 * @warning Triggers an assertion if app is initialized or running.
301 * @tparam Set System set type to configure
302 * @tparam Schedule Schedule type
303 * @param schedule Schedule where the set is configured
304 * @return SystemSetConfig builder for fluent configuration
305 *
306 * @example
307 * @code
308 * app.ConfigureSet<PhysicsSet>(kUpdate).After<InputSet>().Before<RenderSet>();
309 * @endcode
310 */
311 template <SystemSetTrait Set, ScheduleTrait Schedule>
312 auto ConfigureSet(this auto&& self, Schedule schedule = {}) -> SystemSetConfig<Schedule, Set>;
313
314 /**
315 * @brief Inserts a resource into the main sub-app.
316 * @warning Triggers assertion if app is initialized or running.
317 * @tparam T Resource type
318 * @param resource Resource to insert
319 * @return Reference to app for method chaining
320 */
321 template <ecs::ResourceTrait T>
322 auto InsertResource(this auto&& self, T&& resource) -> decltype(std::forward<decltype(self)>(self));
323
324 /**
325 * @brief Emplaces a resource into the main sub-app's world.
326 * @warning Triggers assertion if app is initialized or running.
327 * @tparam T Resource type
328 * @tparam Args Constructor argument types
329 * @param args Arguments to forward to resource constructor
330 * @return Reference to app for method chaining
331 */
332 template <ecs::ResourceTrait T, typename... Args>
333 requires std::constructible_from<T, Args...>
334 auto EmplaceResource(this auto&& self, Args&&... args) -> decltype(std::forward<decltype(self)>(self));
335
336 /**
337 * @brief Registers an event type for use in the main sub-app.
338 * @details Events must be registered before they can be written or read.
339 * @warning Triggers assertion if app is initialized or running.
340 * @tparam T Event type
341 * @return Reference to app for method chaining
342 */
343 template <ecs::EventTrait T>
344 auto AddEvent(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
345
346 /**
347 * @brief Registers multiple event types for use in the main sub-app.
348 * @warning Triggers assertion if app is initialized or running.
349 * @tparam Events Event types to register
350 * @return Reference to app for method chaining
351 */
352 template <ecs::EventTrait... Events>
353 requires(sizeof...(Events) > 1)
354 auto AddEvents(this auto&& self) -> decltype(std::forward<decltype(self)>(self));
355
356 /**
357 * @brief Sets runner function for the application.
358 * @warning Triggers assertion if app is initialized, running, or runner is null.
359 * @param runner A move-only function that takes an App reference and returns an AppExitCode.
360 * @return Reference to app for method chaining
361 */
362 auto SetRunner(this auto&& self, RunnerFn runner) noexcept -> decltype(std::forward<decltype(self)>(self));
363
364 /**
365 * @brief Sets extraction function for a sub-app.
366 * @details The extraction function is called before the sub-app's Update to copy data from the main world.
367 * @warning Triggers assertion if app is initialized, running, extraction function is null, or sub-app doesn't exist.
368 * @tparam T Sub-application type
369 * @param extract_fn Function that takes main world and sub-app world references
370 * @return Reference to app for method chaining
371 */
372 template <SubAppTrait T>
373 auto SetSubAppExtraction(this auto&& self, ExtractFn extract_fn) -> decltype(std::forward<decltype(self)>(self));
374
375 /**
376 * @brief Checks if a sub app of type T exists in main sub-app.
377 * @tparam T SubApp type
378 * @return True if sub app exists, false otherwise
379 */
380 template <SubAppTrait T>
381 [[nodiscard]] bool ContainsSubApp() const noexcept;
382
383 /**
384 * @brief Checks if a module of type T exists in main sub-app.
385 * @tparam T Module type
386 * @return True if module exists, false otherwise
387 */
388 template <ModuleTrait T>
389 [[nodiscard]] bool ContainsModule() const noexcept;
390
391 /**
392 * @brief Checks if a dynamic module with the given ID exists.
393 * @param module_id Module type ID
394 * @return True if module exists, false otherwise
395 */
396 [[nodiscard]] bool ContainsDynamicModule(ModuleTypeId module_id) const noexcept;
397
398 /**
399 * @brief Checks if a system of type T exists in any schedule of main sub-app.
400 * @tparam T System type
401 * @return True if system exists, false otherwise
402 */
403 template <ecs::SystemTrait T>
404 [[nodiscard]] bool ContainsSystem() const noexcept {
405 return main_sub_app_.ContainsSystem<T>();
406 }
407
408 /**
409 * @brief Checks if a system of type T exists in the specified schedule of main sub-app.
410 * @tparam T System type
411 * @tparam Schedule Schedule type
412 * @param schedule Schedule to check
413 * @return True if system exists, false otherwise
414 */
415 template <ecs::SystemTrait T, ScheduleTrait Schedule>
416 [[nodiscard]] bool ContainsSystem(Schedule schedule = {}) const noexcept {
417 return main_sub_app_.ContainsSystem<T>(schedule);
418 }
419
420 /**
421 * @brief Checks if the app has been initialized.
422 * @return True if initialized, false otherwise
423 */
424 [[nodiscard]] bool IsInitialized() const noexcept { return is_initialized_; }
425
426 /**
427 * @brief Checks if the app is currently running.
428 * @return True if running, false otherwise
429 */
430 [[nodiscard]] bool IsRunning() const noexcept { return is_running_.load(std::memory_order_acquire); }
431
432 /**
433 * @brief Checks if a resource exists in main sub_app.
434 * @tparam T Resource type
435 * @return True if resource exists, false otherwise
436 */
437 template <ecs::ResourceTrait T>
438 [[nodiscard]] bool HasResource() const noexcept {
439 return main_sub_app_.HasResource<T>();
440 }
441
442 /**
443 * @brief Checks if a event is registered in main sub_app.
444 * @tparam T Event type
445 * @return True if event exists, false otherwise
446 */
447 template <ecs::EventTrait T>
448 [[nodiscard]] bool HasEvent() const noexcept {
449 return main_sub_app_.HasEvent<T>();
450 }
451
452 /**
453 * @brief Gets the number of modules in main sub-app.
454 * @return Number of modules
455 */
456 [[nodiscard]] size_t ModuleCount() const noexcept { return modules_.size() + dynamic_modules_.size(); }
457
458 /**
459 * @brief Gets the total number of systems across all schedules in main sub-app.
460 * @return Total number of systems
461 */
462 [[nodiscard]] size_t SystemCount() const noexcept { return main_sub_app_.SystemCount(); }
463
464 /**
465 * @brief Gets the number of systems in the specified schedule of main sub-app.
466 * @tparam Schedule Schedule type
467 * @param schedule Schedule to query
468 * @return Number of systems in the schedule
469 */
470 template <ScheduleTrait Schedule>
471 [[nodiscard]] size_t SystemCount(Schedule schedule = {}) const noexcept {
472 return main_sub_app_.SystemCount(schedule);
473 }
474
475 /**
476 * @brief Gets a sub-application by type.
477 * @warning Triggers an assertion if the sub-app does not exist.
478 * Reference might be invalidated due to reallocation of storage after addition of new sub apps.
479 * @tparam T Sub-application type
480 * @return Reference to the sub-application
481 */
482 template <SubAppTrait T>
483 [[nodiscard]] SubApp& GetSubApp() noexcept;
484
485 /**
486 * @brief Gets a sub-application by type (const version).
487 * @warning Triggers an assertion if the sub-app does not exist.
488 * @tparam T Sub-application type
489 * @return Const reference to the sub-application
490 */
491 template <SubAppTrait T>
492 [[nodiscard]] const SubApp& GetSubApp() const noexcept;
493
494 /**
495 * @brief Gets the main sub-application.
496 * @return Reference to the main sub-application
497 */
498 [[nodiscard]] SubApp& GetMainSubApp() noexcept { return main_sub_app_; }
499
500 /**
501 * @brief Gets the main sub-application (const version).
502 * @return Const reference to the main sub-application
503 */
504 [[nodiscard]] const SubApp& GetMainSubApp() const noexcept { return main_sub_app_; }
505
506 /**
507 * @brief Gets the main world.
508 * @return Reference to the main world
509 */
510 [[nodiscard]] const ecs::World& GetMainWorld() const noexcept { return main_sub_app_.GetWorld(); }
511
512 /**
513 * @brief Gets the async executor.
514 * @return Reference to the async executor
515 */
516 [[nodiscard]] async::Executor& GetExecutor() noexcept { return executor_; }
517
518 /**
519 * @brief Gets the async executor (const version).
520 * @return Const reference to the async executor
521 */
522 [[nodiscard]] const async::Executor& GetExecutor() const noexcept { return executor_; }
523
524private:
525 /**
526 * @brief Cleans up the application and its subsystems.
527 * @details Called after the main loop ends.
528 * Spawns tasks on the executor for parallel cleanup.
529 */
530 void CleanUp();
531
532 /**
533 * @brief Builds all registered modules.
534 * @details Calls Build on each module after all modules have been added.
535 */
536 void BuildModules();
537
538 /**
539 * @brief Waits for all modules to be ready and calls Finish on them.
540 * @details Called after BuildModules. Polls IsReady on each module
541 * and calls Finish once all modules are ready.
542 */
543 void FinishModules();
544
545 /**
546 * @brief Destroys all registered modules.
547 * @details Calls Destroy on each module.
548 */
549 void DestroyModules();
550
551 /**
552 * @brief Registers built-in resources and events.
553 * @details Called automatically before initialization.
554 * Registers Time resource and ShutdownEvent if not already present.
555 */
556 void RegisterBuiltins();
557
558 [[nodiscard]] static AppExitCode DefaultRunnerWrapper(App& app);
559
560 bool is_initialized_ = false; ///< Whether the app has been initialized
561 std::atomic<bool> is_running_{false}; ///< Whether the app is currently running
562
563 SubApp main_sub_app_; ///< The main sub-application
564 std::vector<SubApp> sub_apps_; ///< List of additional sub-applications
565 std::unordered_map<SubAppTypeId, size_t> sub_app_index_map_; ///< Map of sub-application type IDs to their indices
566
567 std::vector<std::unique_ptr<Module>> modules_; ///< Owned static modules
568 std::unordered_map<ModuleTypeId, size_t> module_index_map_; ///< Map of module type IDs to their indices
569
570 std::vector<DynamicModule> dynamic_modules_; ///< Owned dynamic modules
571 std::unordered_map<ModuleTypeId, size_t> dynamic_module_index_map_; ///< Map of dynamic module IDs to indices
572
573 async::Executor executor_; ///< Async executor for parallel execution
574 async::TaskGraph update_graph_; ///< Task graph for managing updates
575
576 RunnerFn runner_; ///< The runner function
577
578 /// Map from sub-app index to their overlapping tracked futures.
579 std::unordered_map<size_t, std::vector<details::TrackedFuture>> sub_app_overlapping_futures_;
580};
581
582template <SubAppTrait T>
583inline auto App::AddSubApp(this auto&& self, T /*sub_app*/) -> decltype(std::forward<decltype(self)>(self)) {
584 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add sub app '{}': Cannot add sub apps after app initialization!",
585 SubAppNameOf<T>());
586
587 HELIOS_ASSERT(!self.IsRunning(), "Failed to add sub app '{}': Cannot add sub apps while app is running!",
588 SubAppNameOf<T>());
589
590 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
591 if (self.sub_app_index_map_.contains(id)) [[unlikely]] {
592 HELIOS_WARN("Sub app '{}' is already exist in app!", SubAppNameOf<T>());
593 return std::forward<decltype(self)>(self);
594 }
595
596 self.sub_app_index_map_[id] = self.sub_apps_.size();
597 self.sub_apps_.emplace_back();
598
599 // Set overlapping updates flag based on trait
600 constexpr bool allow_overlapping = SubAppAllowsOverlappingUpdates<T>();
601 self.sub_apps_.back().SetAllowOverlappingUpdates(allow_overlapping);
602
603 // Set max overlapping updates based on trait
604 constexpr size_t max_overlapping = SubAppMaxOverlappingUpdates<T>();
605 self.sub_apps_.back().SetMaxOverlappingUpdates(max_overlapping);
606
607 return std::forward<decltype(self)>(self);
608}
609
610template <SubAppTrait T>
611inline auto App::AddSubApp(this auto&& self, SubApp sub_app, T /*sub_app_tag*/)
612 -> decltype(std::forward<decltype(self)>(self)) {
613 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add sub app '{}': Cannot add sub apps after app initialization!",
614 SubAppNameOf<T>());
615
616 HELIOS_ASSERT(!self.IsRunning(), "Failed to add sub app '{}': Cannot add sub apps while app is running!",
617 SubAppNameOf<T>());
618
619 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
620 if (self.sub_app_index_map_.contains(id)) [[unlikely]] {
621 HELIOS_WARN("Sub app '{}' is already exist in app!", SubAppNameOf<T>());
622 return std::forward<decltype(self)>(self);
623 }
624
625 // Set overlapping updates flag based on trait before adding
626 constexpr bool allow_overlapping = SubAppAllowsOverlappingUpdates<T>();
627 sub_app.SetAllowOverlappingUpdates(allow_overlapping);
628
629 // Set max overlapping updates based on trait
630 constexpr size_t max_overlapping = SubAppMaxOverlappingUpdates<T>();
631 sub_app.SetMaxOverlappingUpdates(max_overlapping);
632
633 self.sub_app_index_map_[id] = self.sub_apps_.size();
634 self.sub_apps_.push_back(std::move(sub_app));
635
636 return std::forward<decltype(self)>(self);
637}
638
639template <ModuleTrait T>
640inline auto App::AddModule(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
641 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add module '{}': Cannot add modules after app initialization!",
642 ModuleNameOf<T>());
643
644 HELIOS_ASSERT(!self.IsRunning(), "Failed to add module '{}': Cannot add modules while app is running!",
645 ModuleNameOf<T>());
646
647 // module->Build(self); // Will be called after all modules are added
648 constexpr ModuleTypeId id = ModuleTypeIdOf<T>();
649 if (self.module_index_map_.contains(id)) [[unlikely]] {
650 HELIOS_WARN("Module '{}' is already exist in app!", ModuleNameOf<T>());
651 return std::forward<decltype(self)>(self);
652 }
653
654 self.module_index_map_[id] = self.modules_.size();
655 auto module = std::make_unique<T>();
656 self.modules_.push_back(std::move(module));
657 return std::forward<decltype(self)>(self);
658}
659
660template <ModuleTrait... Modules>
661 requires(sizeof...(Modules) > 1 && utils::UniqueTypes<Modules...>)
662inline auto App::AddModules(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
663 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add modules: Cannot add modules after app initialization!");
664 HELIOS_ASSERT(!self.IsRunning(), "Failed to add modules: Cannot add modules while app is running!");
665
666 (self.template AddModule<Modules>(), ...);
667 return std::forward<decltype(self)>(self);
668}
669
670inline auto App::AddDynamicModule(this auto&& self, DynamicModule module)
671 -> decltype(std::forward<decltype(self)>(self)) {
672 const std::string_view name = module.GetModuleName();
673 const ModuleTypeId id = module.GetModuleId();
674
675 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add module '{}': Cannot add modules after app initialization!", name);
676 HELIOS_ASSERT(!self.IsRunning(), "Failed to add module '{}': Cannot add modules while app is running!", name);
677 HELIOS_ASSERT(module.Loaded(), "Failed to add module '{}': Module is not loaded!", name);
678
679 // Check both static and dynamic module maps for duplicates
680 if (self.module_index_map_.contains(id) || self.dynamic_module_index_map_.contains(id)) [[unlikely]] {
681 HELIOS_WARN("Module '{}' already exists in app!", name);
682 return std::forward<decltype(self)>(self);
683 }
684
685 self.dynamic_module_index_map_[id] = self.dynamic_modules_.size();
686 self.dynamic_modules_.push_back(std::move(module));
687 return std::forward<decltype(self)>(self);
688}
689
690template <ecs::SystemTrait T, ScheduleTrait Schedule>
691inline auto App::AddSystem(this auto&& self, Schedule schedule) -> decltype(std::forward<decltype(self)>(self)) {
692 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add system '{}': Cannot add systems after app initialization!",
694
695 HELIOS_ASSERT(!self.IsRunning(), "Failed to add system '{}': Cannot add systems while app is running!",
697
698 if (self.template ContainsSystem<T>(schedule)) [[unlikely]] {
699 HELIOS_WARN("System '{}' is already exist in app schedule '{}'!", ecs::SystemNameOf<T>(),
700 ScheduleNameOf<Schedule>());
701 return std::forward<decltype(self)>(self);
702 }
703
704 self.GetMainSubApp().template AddSystem<T>(schedule);
705 return std::forward<decltype(self)>(self);
706}
707
708template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
709 requires(sizeof...(Systems) > 1 && utils::UniqueTypes<Systems...>)
710inline auto App::AddSystems(this auto&& self, Schedule schedule) -> decltype(std::forward<decltype(self)>(self)) {
711 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add systems: Cannot add systems after app initialization!");
712 HELIOS_ASSERT(!self.IsRunning(), "Failed to add systems: Cannot add systems while app is running!");
713
714 self.GetMainSubApp().template AddSystems<Systems...>(schedule);
715 return std::forward<decltype(self)>(self);
716}
717
718template <ecs::ResourceTrait T>
719inline auto App::InsertResource(this auto&& self, T&& resource) -> decltype(std::forward<decltype(self)>(self)) {
720 HELIOS_ASSERT(!self.IsInitialized(), "Failed to insert resource '{}': Cannot add resources after app initialization!",
722
723 self.GetMainSubApp().InsertResource(std::forward<T>(resource));
724 return std::forward<decltype(self)>(self);
725}
726
727template <ecs::ResourceTrait T, typename... Args>
728 requires std::constructible_from<T, Args...>
729inline auto App::EmplaceResource(this auto&& self, Args&&... args) -> decltype(std::forward<decltype(self)>(self)) {
730 HELIOS_ASSERT(!self.IsInitialized(),
731 "Failed to emplace resource '{}': Cannot add resources after app initialization!",
733
734 HELIOS_ASSERT(!self.IsRunning(), "Failed to emplace resource '{}': Cannot add resources while app is running!",
736
737 self.GetMainSubApp().template EmplaceResource<T>(std::forward<Args>(args)...);
738 return std::forward<decltype(self)>(self);
739}
740
741template <ecs::EventTrait T>
742inline auto App::AddEvent(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
743 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add event '{}': Cannot add events after app initialization!",
745
746 HELIOS_ASSERT(!self.IsRunning(), "Failed to add event '{}': Cannot add events while app is running!",
748
749 if (self.template HasEvent<T>()) [[unlikely]] {
750 HELIOS_WARN("Event '{}' is already exist in app!", ecs::EventNameOf<T>());
751 return std::forward<decltype(self)>(self);
752 }
753
754 self.GetMainSubApp().template AddEvent<T>();
755 return std::forward<decltype(self)>(self);
756}
757
758template <ecs::EventTrait... Events>
759 requires(sizeof...(Events) > 1)
760inline auto App::AddEvents(this auto&& self) -> decltype(std::forward<decltype(self)>(self)) {
761 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add events: Cannot add events after app initialization!");
762 HELIOS_ASSERT(!self.IsRunning(), "Failed to add events: Cannot add events while app is running!");
763
764 self.GetMainSubApp().template AddEvents<Events...>();
765 return std::forward<decltype(self)>(self);
766}
767
768inline auto App::SetRunner(this auto&& self, RunnerFn runner) noexcept -> decltype(std::forward<decltype(self)>(self)) {
769 HELIOS_ASSERT(!self.IsInitialized(), "Failed to set runner: Cannot set runner after app initialization!");
770 HELIOS_ASSERT(!self.IsRunning(), "Failed to set runner: Cannot set runner while app is running!");
771
772 HELIOS_ASSERT(runner, "Failed to set runner: Runner function cannot be null!");
773
774 self.runner_ = std::move(runner);
775 return std::forward<decltype(self)>(self);
776}
777
778template <SubAppTrait T>
779inline auto App::SetSubAppExtraction(this auto&& self, ExtractFn extract_fn)
780 -> decltype(std::forward<decltype(self)>(self)) {
782 !self.IsInitialized(),
783 "Failed to set extraction function for sub app '{}': Cannot set extraction function after app initialization!",
784 SubAppNameOf<T>());
785
787 !self.IsRunning(),
788 "Failed to set extraction function for sub app '{}': Cannot set extraction function while app is running!",
789 SubAppNameOf<T>());
790
791 HELIOS_ASSERT(extract_fn, "Failed to set extraction function for sub app '{}': Extraction function cannot be null!",
792 SubAppNameOf<T>());
793
794 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
795 const auto it = self.sub_app_index_map_.find(id);
796 HELIOS_ASSERT(it != self.sub_app_index_map_.end(),
797 "Failed to set extraction function for sub app '{}': Sub app does not exist!", SubAppNameOf<T>());
798
799 self.sub_apps_.at(it->second).SetExtractFunction(std::move(extract_fn));
800 return std::forward<decltype(self)>(self);
801}
802
803template <SubAppTrait T>
804inline bool App::ContainsSubApp() const noexcept {
805 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
806 return sub_app_index_map_.contains(id);
807}
808
809template <ModuleTrait T>
810inline bool App::ContainsModule() const noexcept {
811 constexpr ModuleTypeId id = ModuleTypeIdOf<T>();
812 return module_index_map_.contains(id) || dynamic_module_index_map_.contains(id);
813}
814
815inline bool App::ContainsDynamicModule(ModuleTypeId module_id) const noexcept {
816 return dynamic_module_index_map_.contains(module_id);
817}
818
819template <SubAppTrait T>
820inline SubApp& App::GetSubApp() noexcept {
821 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
822 const auto it = sub_app_index_map_.find(id);
823 HELIOS_ASSERT(it != sub_app_index_map_.end(), "Failed to get sub app '{}': Sub app does not exist!",
824 SubAppNameOf<T>());
825 return sub_apps_.at(it->second);
826}
827
828template <SubAppTrait T>
829inline const SubApp& App::GetSubApp() const noexcept {
830 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
831 const auto it = sub_app_index_map_.find(id);
832 HELIOS_ASSERT(it != sub_app_index_map_.end(), "Failed to get sub app '{}': Sub app does not exist!",
833 SubAppNameOf<T>());
834 return sub_apps_.at(it->second);
835}
836
837template <SubAppTrait T>
838inline void App::WaitForOverlappingUpdates(T /*sub_app*/) {
839 constexpr SubAppTypeId id = SubAppTypeIdOf<T>();
840 const auto it = sub_app_index_map_.find(id);
841 if (it == sub_app_index_map_.end()) [[unlikely]] {
842 return; // Sub-app doesn't exist
843 }
844
845 const size_t index = it->second;
846 const auto futures_it = sub_app_overlapping_futures_.find(index);
847 if (futures_it == sub_app_overlapping_futures_.end()) {
848 return; // No overlapping futures for this sub-app
849 }
850
851 for (auto& tracked : futures_it->second) {
852 tracked.Wait();
853 }
854 futures_it->second.clear();
855}
856
857template <ecs::SystemTrait... Systems, ScheduleTrait Schedule>
858 requires(sizeof...(Systems) > 0 && utils::UniqueTypes<Systems...>)
859inline auto App::AddSystemsBuilder(this auto&& self, Schedule schedule) -> SystemConfig<Schedule, Systems...> {
860 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add systems: Cannot add systems after app initialization!");
861 HELIOS_ASSERT(!self.IsRunning(), "Failed to add systems: Cannot add systems while app is running!");
862
863 return self.GetMainSubApp().template AddSystemsBuilder<Systems...>(schedule);
864}
865
866template <ecs::SystemTrait T, ScheduleTrait Schedule>
867inline auto App::AddSystemBuilder(this auto&& self, Schedule schedule) -> SystemConfig<Schedule, T> {
868 HELIOS_ASSERT(!self.IsInitialized(), "Failed to add system '{}': Cannot add system after app initialization!",
870 HELIOS_ASSERT(!self.IsRunning(), "Failed to add system '{}': Cannot add system while app is running!",
872
873 return self.GetMainSubApp().template AddSystemBuilder<T>(schedule);
874}
875
876template <SystemSetTrait Set, ScheduleTrait Schedule>
877inline auto App::ConfigureSet(this auto&& self, Schedule schedule) -> SystemSetConfig<Schedule, Set> {
878 HELIOS_ASSERT(!self.IsInitialized(), "Failed to configure set '{}': Cannot configure set after app initialization!",
879 SystemSetNameOf<Set>());
880 HELIOS_ASSERT(!self.IsRunning(), "Failed to configure set '{}': Cannot configure set while app is running!",
881 SystemSetNameOf<Set>());
882
883 return self.GetMainSubApp().template ConfigureSet<Set>(schedule);
884}
885
886} // 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:804
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:729
SubApp & GetSubApp() noexcept
Gets a sub-application by type.
Definition app.hpp:820
bool HasResource() const noexcept
Checks if a resource exists in main sub_app.
Definition app.hpp:438
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:430
const async::Executor & GetExecutor() const noexcept
Gets the async executor (const version).
Definition app.hpp:522
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:760
SubApp & GetMainSubApp() noexcept
Gets the main sub-application.
Definition app.hpp:498
auto InsertResource(this auto &&self, T &&resource) -> decltype(std::forward< decltype(self)>(self))
Inserts a resource into the main sub-app.
Definition app.hpp:719
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:742
App(const App &)=delete
auto AddModules(this auto &&self) -> decltype(std::forward< decltype(self)>(self))
Adds multiple modules to the main sub-app.
Definition app.hpp:662
auto ConfigureSet(this auto &&self, Schedule schedule={}) -> SystemSetConfig< Schedule, Set >
Definition app.hpp:877
auto AddSystemsBuilder(this auto &&self, Schedule schedule={}) -> SystemConfig< Schedule, Systems... >
Definition app.hpp:859
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:768
auto AddSystemBuilder(this auto &&self, Schedule schedule={}) -> SystemConfig< Schedule, T >
Adds a single system with fluent configuration builder.
Definition app.hpp:867
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:710
bool ContainsSystem() const noexcept
Checks if a system of type T exists in any schedule of main sub-app.
Definition app.hpp:404
size_t SystemCount(Schedule schedule={}) const noexcept
Gets the number of systems in the specified schedule of main sub-app.
Definition app.hpp:471
bool ContainsDynamicModule(ModuleTypeId module_id) const noexcept
Checks if a dynamic module with the given ID exists.
Definition app.hpp:815
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:510
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:416
bool IsInitialized() const noexcept
Checks if the app has been initialized.
Definition app.hpp:424
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:583
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:691
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:456
~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 module to the main sub-app.
Definition app.hpp:640
auto SetSubAppExtraction(this auto &&self, ExtractFn extract_fn) -> decltype(std::forward< decltype(self)>(self))
Sets extraction function for a sub-app.
Definition app.hpp:779
bool HasEvent() const noexcept
Checks if a event is registered in main sub_app.
Definition app.hpp:448
size_t SystemCount() const noexcept
Gets the total number of systems across all schedules in main sub-app.
Definition app.hpp:462
bool ContainsModule() const noexcept
Checks if a module of type T exists in main sub-app.
Definition app.hpp:810
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:670
async::Executor & GetExecutor() noexcept
Gets the async executor.
Definition app.hpp:516
const SubApp & GetMainSubApp() const noexcept
Gets the main sub-application (const version).
Definition app.hpp:504
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 to ensure a type is a valid Module.
Definition module.hpp:72
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
@ Success
Successful execution.
@ Failure
General failure.
size_t ModuleTypeId
Definition module.hpp:84
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