Skip to main content

SpawnManager.ixx File

Manager for processing spawn and despawn commands from pools. More...

Included Headers

#include <cassert> #include <memory> #include <span> #include <unordered_map> #include <vector> #include <helios.math> #include <helios.engine.modules.physics.collision.components.AabbColliderComponent> #include <helios.engine.ecs.GameObject> #include <helios.engine.runtime.pooling.GameObjectPoolManager> #include <helios.engine.runtime.world.GameWorld> #include <helios.engine.modules.scene.components.SceneNodeComponent> #include <helios.engine.runtime.spawn.types> #include <helios.engine.runtime.spawn.types.SpawnPlanCursor> #include <helios.engine.modules.rendering.model.components.ModelAabbComponent> #include <helios.engine.modules.spatial.transform.components.ScaleStateComponent> #include <helios.engine.runtime.spawn.types.SpawnContext> #include <helios.engine.runtime.spawn.scheduling.SpawnScheduler> #include <helios.engine.common.tags.ManagerRole> #include <helios.engine.runtime.spawn.types.SpawnProfile> #include <helios.engine.runtime.spawn.events.SpawnPlanCommandExecutedEvent> #include <helios.engine.modules.physics.collision.Bounds> #include <helios.engine.mechanics.spawn.components.SpawnedByProfileComponent> #include <helios.engine.mechanics.spawn.components.EmittedByComponent> #include <helios.engine.runtime.spawn.commands.DespawnCommand> #include <helios.engine.runtime.world.UpdateContext> #include <helios.engine.runtime.spawn.commands.SpawnCommand> #include <helios.engine.modules.spatial.transform.components.TranslationStateComponent> #include <helios.engine.runtime.spawn.commands.ScheduledSpawnPlanCommand> #include <helios.engine.modules.spatial.transform.components.RotationStateComponent>

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespaceruntime

Runtime infrastructure for game execution and lifecycle orchestration. More...

namespacespawn

Entity spawning infrastructure for the helios engine. More...

Classes Index

classSpawnManager

Manager for processing spawn and despawn commands. More...

Description

Manager for processing spawn and despawn commands from pools.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file SpawnManager.ixx
3 * @brief Manager for processing spawn and despawn commands from pools.
4 */
5module;
6
7#include <cassert>
8#include <memory>
9#include <span>
10#include <unordered_map>
11#include <vector>
12
13export module helios.engine.runtime.spawn.SpawnManager;
14
15import helios.engine.runtime.spawn.types.SpawnPlanCursor;
16import helios.engine.runtime.spawn.types.SpawnProfile;
17import helios.engine.runtime.spawn.types.SpawnContext;
18import helios.engine.runtime.spawn.events.SpawnPlanCommandExecutedEvent;
19
20
21import helios.engine.runtime.spawn.scheduling.SpawnScheduler;
22
23import helios.engine.mechanics.spawn.components.EmittedByComponent;
24
25import helios.engine.runtime.spawn.commands.SpawnCommand;
26import helios.engine.runtime.spawn.commands.ScheduledSpawnPlanCommand;
27import helios.engine.runtime.spawn.commands.DespawnCommand;
28
29import helios.engine.modules.rendering.model.components.ModelAabbComponent;
30import helios.engine.modules.spatial.transform.components.ScaleStateComponent;
31import helios.engine.modules.spatial.transform.components.RotationStateComponent;
32
33import helios.engine.modules.physics.collision.Bounds;
34import helios.engine.modules.scene.components.SceneNodeComponent;
35
36import helios.engine.runtime.world.GameWorld;
37import helios.engine.runtime.world.UpdateContext;
38import helios.engine.runtime.pooling.GameObjectPoolManager;
39
40
41import helios.engine.runtime.spawn.types;
42import helios.engine.ecs.GameObject;
43
44import helios.engine.modules.spatial.transform.components.TranslationStateComponent;
45import helios.engine.mechanics.spawn.components.SpawnedByProfileComponent;
46
47import helios.engine.modules.physics.collision.components.AabbColliderComponent;
48
49import helios.math;
50
51import helios.engine.common.tags.ManagerRole;
52
56export namespace helios::engine::runtime::spawn {
57
58 /**
59 * @brief Manager for processing spawn and despawn commands.
60 *
61 * @details SpawnManager is a Manager that handles the lifecycle of pooled
62 * GameObjects. It receives commands via SpawnCommandHandler::submit() and
63 * queues them for deferred processing during the manager flush phase.
64 *
65 * ## Command Processing Order
66 *
67 * During flush(), commands are processed in this order:
68 * 1 **Despawn commands** - Entities are returned to their pools
69 * 2 **Spawn commands** - Single entities are acquired and initialized
70 * 3 **Scheduled spawn plan commands** - Batch spawns from scheduler
71 *
72 * ## Spawn Profiles
73 *
74 * Each spawn profile defines:
75 * - Which pool to acquire entities from
76 * - How to position spawned entities (SpawnPlacer)
77 * - How to initialize spawned entities (SpawnInitializer)
78 *
79 * ## Integration
80 *
81 * SpawnManager registers itself with the GameWorld for each profile ID,
82 * allowing commands to route to the correct handler.
83 *
84 * Example:
85 * ```cpp
86 * auto spawnManager = std::make_unique<SpawnManager>();
87 *
88 * spawnManager->addSpawnProfile(bulletProfileId, std::make_unique<SpawnProfile>(
89 * SpawnProfile{
90 * .gameObjectPoolId = bulletPoolId,
91 * .spawnPlacer = std::make_unique<EmitterSpawnPlacer>(),
92 * .spawnInitializer = std::make_unique<EmitterInitializer>()
93 * }
94 * ));
95 *
96 * gameWorld.addManager(std::move(spawnManager));
97 * ```
98 *
99 * @see SpawnCommand
100 * @see DespawnCommand
101 * @see ScheduledSpawnPlanCommand
102 * @see SpawnProfile
103 * @see SpawnCommandHandler
104 */
106
107
108 /**
109 * @brief Collection of schedulers that manage spawn rules and conditions.
110 *
111 * @details Each scheduler owns and evaluates its registered spawn rules
112 * independently. When conditions are met, the scheduler produces
113 * ScheduledSpawnPlan instances for execution. Multiple schedulers allow
114 * grouping spawn rules by category (e.g., enemies, powerups, projectiles).
115 */
116 std::vector<std::unique_ptr<helios::engine::runtime::spawn::scheduling::SpawnScheduler>> spawnSchedulers_;
117
118 /**
119 * @brief Queue of pending spawn commands.
120 */
121 std::vector<SpawnCommand> spawnCommands_;
122
123 /**
124 * @brief Queue of pending despawn commands.
125 */
126 std::vector<DespawnCommand> despawnCommands_;
127
128 /**
129 * @brief Queue of pending scheduled spawn plan commands.
130 */
131 std::vector<ScheduledSpawnPlanCommand> scheduledSpawnPlanCommands_;
132
133 /**
134 * @brief Pointer to the pool manager for acquire/release operations.
135 */
136 helios::engine::runtime::pooling::GameObjectPoolManager* gameObjectPoolManager_ = nullptr;
137
138 /**
139 * @brief Map from profile IDs to their spawn profiles.
140 */
141 std::unordered_map<
143 std::unique_ptr<const SpawnProfile>> spawnProfiles_;
144
145 /**
146 * @brief Ensures that the bounds are properly computed..
147 *
148 * @details Checks if the bounding box was initialized (i.e. has valid
149 * min/max values: min <= max).
150 * If the bounds are inverted (min > max), it recomputes the world AABB
151 * using the GameObject's components (ModelAabb, ScaleState, RotationState,
152 * SceneNode, TranslationState).
153 *
154 * @param go The GameObject to compute bounds for.
155 * @param bounds The bounding box to check and potentially update.
156 *
157 * @see helios::engine::modules::physics::collision::Bounds::computeWorldAabb
158 */
159 void ensureBounds(helios::engine::ecs::GameObject go, helios::math::aabbf& bounds) {
160 if (bounds.min()[0] > bounds.max()[0]) {
166
167 assert(mab && scn && tsc && sca && rsc && "Missing Components for AABB computation");
169 *mab, *scn, *tsc, *sca, *rsc
170 );
171 }
172 }
173
174 /**
175 * @brief Processes scheduled spawn plan commands.
176 *
177 * @details Iterates through each plan, acquires entities from the pool,
178 * positions them using the profile's placer, initializes them using
179 * the profile's initializer, and pushes a frame event for confirmation.
180 *
181 * @param commands Span of scheduled spawn plan commands to process.
182 * @param gameWorld The game world.
183 * @param updateContext The current update context.
184 */
185 void executeScheduledSpawnPlanCommands(
186 std::span<ScheduledSpawnPlanCommand> commands,
188 ) {
189
190 for (auto& scheduledSpawnPlanCommand: commands) {
191
192 const auto& spawnPlan = scheduledSpawnPlanCommand.spawnPlan();
193
194 const auto spawnProfileId = scheduledSpawnPlanCommand.spawnProfileId();
195 const auto spawnRuleId = spawnPlan.spawnRuleId;
196 const auto amount = spawnPlan.amount;
197
198 const auto it = spawnProfiles_.find(spawnProfileId);
199 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
200
201 const auto spawnProfile = it->second.get();
202
203 const auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
204
205
206
207 const auto poolSnapshot = gameObjectPoolManager_->poolSnapshot(gameObjectPoolId);
208
209 if (amount == 0) {
210 assert(false && "Amount must not be 0");
211 }
212 if (!spawnProfiles_.contains(spawnProfileId)) {
213 assert(false && "SpawnManager does not manage this SpawnProfileId");
214 }
215
216
217
218 const auto spawnCount = std::min(amount, poolSnapshot.inactiveCount);
219 for (size_t i = 0; i < spawnCount; i++) {
220
221 auto go = gameObjectPoolManager_->acquire(gameObjectPoolId);
222 assert(go && "Failed to acquire GameObject");
223
225
227 assert(sbp && "unexpected missing SpawnedByProfileComponent");
228
230 assert(aabb && "unexpected missing AabbColliderComponent");
231
232 auto spawnCursor = SpawnPlanCursor{spawnCount, i};
233 const auto& spawnContext = scheduledSpawnPlanCommand.spawnContext();
234
235 const auto& emitter = spawnContext.emitterContext;
237 if (emitter.has_value() && ebc) {
238 ebc->setSource(emitter.value().source);
239 }
240
241 if (tsc) {
242
243 auto bounds = aabb->bounds();
244 ensureBounds(go.value(), bounds);
245
246 const auto position = spawnProfile->spawnPlacer->getPosition(
247 go->entityHandle(),
248 bounds,
249 updateContext.level()->bounds(),
250 spawnCursor,
251 spawnContext
252
253 );
254 tsc->setTranslation(position);
255 }
256
257 assert(spawnProfile->spawnInitializer && "Unexpected missing spawn initializer");
258
259 spawnProfile->spawnInitializer->initialize(*go, spawnCursor, spawnContext);
260 sbp->setSpawnProfileId(spawnProfileId);
261 go->setActive(true);
262 }
263
265 spawnRuleId, spawnCount
266 );
267 }
268 }
269
270
271 /**
272 * @brief Processes spawn commands, acquiring and initializing objects.
273 *
274 * @param commands Span of spawn commands to process.
275 * @param gameWorld The game world.
276 * @param updateContext The current update context.
277 */
278 void spawnObjects(
279 std::span<SpawnCommand> commands,
281
282 for (auto& spawnCommand: commands) {
283 const auto spawnProfileId = spawnCommand.spawnProfileId();
284 const auto spawnContext = spawnCommand.spawnContext();
285
286 const auto it = spawnProfiles_.find(spawnProfileId);
287 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
288
289 const auto spawnProfile = it->second.get();
290 const auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
291
292 if (gameObjectPoolManager_->poolSnapshot(gameObjectPoolId).inactiveCount == 0) {
293 /**
294 * @todo log
295 */
296 continue;
297 }
298
299 auto go = gameObjectPoolManager_->acquire(gameObjectPoolId);
300 assert(go && "Failed to acquire GameObject");
301
304 assert(sbp && "unexpected missing SpawnedByProfileComponent");
305
307 assert(aabb && "unexpected missing AabbColliderComponent");
308
309 const auto& emitter = spawnContext.emitterContext;
311 if (emitter.has_value() && ebc) {
312 ebc->setSource(emitter.value().source);
313 }
314
315 if (tsc) {
316
317 auto bounds = aabb->bounds();
318 ensureBounds(go.value(), bounds);
319
320 const auto position = spawnProfile->spawnPlacer->getPosition(
321 go->entityHandle(),
322 bounds,
323 updateContext.level()->bounds(),
324 {1, 1},
325 spawnContext
326
327 );
328 tsc->setTranslation(position);
329 }
330
331
332 assert(spawnProfile->spawnInitializer && "Unexpected missing spawn initializer");
333
334 spawnProfile->spawnInitializer->initialize(*go, {1, 1}, spawnContext);
335 sbp->setSpawnProfileId(spawnProfileId);
336
337 go->setActive(true);
338 }
339 }
340
341
342 /**
343 * @brief Processes despawn commands, releasing objects back to pools.
344 *
345 * @param commands Span of despawn commands to process.
346 * @param gameWorld The game world.
347 * @param updateContext The current update context.
348 */
349 void despawnObjects(
350 std::span<DespawnCommand> commands,
352
353 for (auto& despawnCommand : commands) {
354 const auto spawnProfileId = despawnCommand.spawnProfileId();
355
356 const auto it = spawnProfiles_.find(spawnProfileId);
357 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
358 const auto spawnProfile = it->second.get();
359 auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
360
361 gameObjectPoolManager_->release(gameObjectPoolId, despawnCommand.entityHandle());
362
363 }
364 }
365
366
367 public:
369
370 /**
371 * @brief Default constructor.
372 */
373 SpawnManager() = default;
374
375 /**
376 * @brief Submits a spawn command for deferred processing.
377 *
378 * @param command The spawn command to queue.
379 *
380 * @return Always returns true.
381 */
382 bool submit(const SpawnCommand command) noexcept {
383 spawnCommands_.push_back(command);
384 return true;
385 }
386
387 /**
388 * @brief Adds a spawn scheduler to this manager.
389 *
390 * @details Schedulers evaluate spawn rules and produce spawn plans based
391 * on game conditions. Multiple schedulers can be added for different
392 * spawn categories.
393 *
394 * @param scheduler The scheduler to add. Ownership is transferred.
395 */
396 void addScheduler(std::unique_ptr<helios::engine::runtime::spawn::scheduling::SpawnScheduler> scheduler) {
397 spawnSchedulers_.push_back(std::move(scheduler));
398 }
399
400 /**
401 * @brief Submits a despawn command for deferred processing.
402 *
403 * @param command The despawn command to queue.
404 *
405 * @return Always returns true.
406 */
407 bool submit(const DespawnCommand command) noexcept {
408 despawnCommands_.push_back(command);
409 return true;
410 }
411
412
413 /**
414 * @brief Submits a scheduled spawn plan command for deferred processing.
415 *
416 * @param scheduledSpawnPlanCommand The scheduled plan command to queue.
417 *
418 * @return Always returns true.
419 */
420 bool submit(
421 const ScheduledSpawnPlanCommand scheduledSpawnPlanCommand
422 ) noexcept {
423 scheduledSpawnPlanCommands_.push_back(scheduledSpawnPlanCommand);
424 return true;
425 }
426
427 /**
428 * @brief Flushes pending commands, processing despawns then spawns.
429 *
430 * @param gameWorld The game world.
431 * @param updateContext The current update context.
432 */
433 void flush(
435 ) noexcept {
436 if (!despawnCommands_.empty()) {
437 despawnObjects(despawnCommands_, updateContext);
438 despawnCommands_.clear();
439 }
440
441 if (!spawnCommands_.empty()) {
442 spawnObjects(spawnCommands_, updateContext);
443 spawnCommands_.clear();
444 }
445
446 if (!scheduledSpawnPlanCommands_.empty()) {
447 executeScheduledSpawnPlanCommands(scheduledSpawnPlanCommands_, updateContext);
448 scheduledSpawnPlanCommands_.clear();
449 }
450 };
451
452
453 /**
454 * @brief Adds a spawn profile for a profile ID.
455 *
456 * @details Registers a spawn profile that defines how entities should
457 * be spawned for this profile. The profile contains the pool ID,
458 * placer, and initializer.
459 *
460 * @param spawnProfileId The unique ID for this profile.
461 * @param spawnProfile The profile configuration. Ownership transferred.
462 *
463 * @return Reference to this manager for chaining.
464 *
465 * @pre No profile is already registered for this ID.
466 */
468 const SpawnProfileId& spawnProfileId,
469 std::unique_ptr<const SpawnProfile> spawnProfile) {
470
471 assert(!spawnProfiles_.contains(spawnProfileId) && "SpawnProfileId already added");
472
473 spawnProfiles_[spawnProfileId] = std::move(spawnProfile);
474
475 return *this;
476 }
477
478 /**
479 * @brief Returns a spawn profile by ID.
480 *
481 * @param spawnProfileId The profile ID to look up.
482 *
483 * @return Pointer to the profile, or nullptr if not found.
484 */
485 [[nodiscard]] const SpawnProfile* spawnProfile(
486 const SpawnProfileId& spawnProfileId) const {
487
488 if (!spawnProfiles_.contains(spawnProfileId)) {
489 return nullptr;
490 }
491
492 return spawnProfiles_.find(spawnProfileId)->second.get();
493 }
494
495 /**
496 * @brief Initializes the manager with the game world.
497 *
498 * @details Acquires a reference to the GameObjectPoolManager and
499 * registers this manager as the SpawnCommandHandler for all
500 * configured spawn profiles.
501 *
502 * @param gameWorld The game world to initialize with.
503 */
505
506 assert(gameWorld.hasManager<helios::engine::runtime::pooling::GameObjectPoolManager>() && "Unexpected missing GameObjectPoolManager");
507 gameObjectPoolManager_ = gameWorld.tryManager<helios::engine::runtime::pooling::GameObjectPoolManager>();
508
509 gameWorld.registerCommandHandler<SpawnCommand, DespawnCommand, ScheduledSpawnPlanCommand>(*this);
510 }
511
512 /**
513 * @brief Resets all schedulers, profiles, and pending commands.
514 *
515 * @details Called during level transitions or game restarts to clear
516 * all spawn state. Resets each scheduler, calls `onReset()` on all
517 * placers and initializers, and clears all pending command queues.
518 */
519 void reset() {
520
521 for (auto& scheduler: spawnSchedulers_) {
522 scheduler->reset();
523 }
524
525 for (auto& [_, profile]: spawnProfiles_) {
526 profile->spawnPlacer->onReset();
527 profile->spawnInitializer->onReset();
528 }
529
530
531 despawnCommands_.clear();
532 spawnCommands_.clear();
533 scheduledSpawnPlanCommands_.clear();
534 }
535
536 /**
537 * @brief Returns a span of all registered spawn schedulers.
538 *
539 * @return Span of spawn scheduler unique pointers.
540 */
541 std::span<std::unique_ptr<helios::engine::runtime::spawn::scheduling::SpawnScheduler>> spawnSchedulers() {
542 return spawnSchedulers_;
543 }
544 };
545
546
547}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.