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.engine.core.data.SpawnProfileId> #include <helios.engine.modules.physics.collision.components.AabbColliderComponent> #include <helios.engine.runtime.pooling.GameObjectPoolManager> #include <helios.engine.runtime.spawn.SpawnCommandHandler> #include <helios.engine.runtime.world.UpdateContext> #include <helios.engine.runtime.world.GameWorld> #include <helios.engine.runtime.world.Manager> #include <helios.engine.mechanics.spawn.components.SpawnedByProfileComponent> #include <helios.engine.runtime.spawn.SpawnPlanCursor> #include <helios.engine.modules.physics.collision.Bounds> #include <helios.engine.modules.spatial.transform.components.TranslationStateComponent> #include <helios.engine.runtime.spawn.commands.SpawnCommand> #include <helios.engine.runtime.spawn.SpawnContext> #include <helios.engine.runtime.spawn.SpawnProfile> #include <helios.engine.runtime.spawn.events.SpawnPlanCommandExecutedEvent> #include <helios.engine.mechanics.spawn.components.EmittedByComponent> #include <helios.math> #include <helios.engine.runtime.spawn.commands.ScheduledSpawnPlanCommand> #include <helios.engine.runtime.spawn.commands.DespawnCommand> #include <helios.engine.ecs.GameObject> #include <helios.engine.modules.spatial.transform.components.ScaleStateComponent> #include <helios.engine.modules.rendering.model.components.ModelAabbComponent> #include <helios.engine.modules.spatial.transform.components.RotationStateComponent> #include <helios.engine.modules.scene.components.SceneNodeComponent>

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.SpawnPlanCursor;
16import helios.engine.runtime.spawn.SpawnProfile;
17import helios.engine.runtime.spawn.SpawnContext;
18import helios.engine.runtime.spawn.events.SpawnPlanCommandExecutedEvent;
19
20import helios.engine.mechanics.spawn.components.EmittedByComponent;
21
22import helios.engine.runtime.spawn.commands.SpawnCommand;
23import helios.engine.runtime.spawn.commands.ScheduledSpawnPlanCommand;
24import helios.engine.runtime.spawn.commands.DespawnCommand;
25
26import helios.engine.modules.rendering.model.components.ModelAabbComponent;
27import helios.engine.modules.spatial.transform.components.ScaleStateComponent;
28import helios.engine.modules.spatial.transform.components.RotationStateComponent;
29
30import helios.engine.modules.physics.collision.Bounds;
31import helios.engine.modules.scene.components.SceneNodeComponent;
32
33import helios.engine.runtime.world.Manager;
34import helios.engine.runtime.world.GameWorld;
35import helios.engine.runtime.world.UpdateContext;
36import helios.engine.runtime.pooling.GameObjectPoolManager;
37import helios.engine.runtime.spawn.SpawnCommandHandler;
38import helios.engine.core.data.SpawnProfileId;
39import helios.engine.ecs.GameObject;
40import helios.engine.modules.spatial.transform.components.TranslationStateComponent;
41import helios.engine.mechanics.spawn.components.SpawnedByProfileComponent;
42
43import helios.engine.modules.physics.collision.components.AabbColliderComponent;
44
45import helios.math;
46
47export namespace helios::engine::runtime::spawn {
48
49 /**
50 * @brief Manager for processing spawn and despawn commands.
51 *
52 * @details SpawnManager is a Manager that handles the lifecycle of pooled
53 * GameObjects. It receives commands via SpawnCommandHandler::submit() and
54 * queues them for deferred processing during the manager flush phase.
55 *
56 * ## Command Processing Order
57 *
58 * During flush(), commands are processed in this order:
59 * 1 **Despawn commands** — Entities are returned to their pools
60 * 2 **Spawn commands** — Single entities are acquired and initialized
61 * 3 **Scheduled spawn plan commands** — Batch spawns from scheduler
62 *
63 * ## Spawn Profiles
64 *
65 * Each spawn profile defines:
66 * - Which pool to acquire entities from
67 * - How to position spawned entities (SpawnPlacer)
68 * - How to initialize spawned entities (SpawnInitializer)
69 *
70 * ## Integration
71 *
72 * SpawnManager registers itself with the GameWorld for each profile ID,
73 * allowing commands to route to the correct handler.
74 *
75 * Example:
76 * ```cpp
77 * auto spawnManager = std::make_unique<SpawnManager>();
78 *
79 * spawnManager->addSpawnProfile(bulletProfileId, std::make_unique<SpawnProfile>(
80 * SpawnProfile{
81 * .gameObjectPoolId = bulletPoolId,
82 * .spawnPlacer = std::make_unique<EmitterSpawnPlacer>(),
83 * .spawnInitializer = std::make_unique<EmitterInitializer>()
84 * }
85 * ));
86 *
87 * gameWorld.addManager(std::move(spawnManager));
88 * ```
89 *
90 * @see SpawnCommand
91 * @see DespawnCommand
92 * @see ScheduledSpawnPlanCommand
93 * @see SpawnProfile
94 * @see SpawnCommandHandler
95 */
98
99 /**
100 * @brief Queue of pending spawn commands.
101 */
102 std::vector<helios::engine::runtime::spawn::commands::SpawnCommand> spawnCommands_;
103
104 /**
105 * @brief Queue of pending despawn commands.
106 */
107 std::vector<helios::engine::runtime::spawn::commands::DespawnCommand> despawnCommands_;
108
109 /**
110 * @brief Queue of pending scheduled spawn plan commands.
111 */
112 std::vector<helios::engine::runtime::spawn::commands::ScheduledSpawnPlanCommand> scheduledSpawnPlanCommands_;
113
114 /**
115 * @brief Pointer to the pool manager for acquire/release operations.
116 */
117 helios::engine::runtime::pooling::GameObjectPoolManager* gameObjectPoolManager_ = nullptr;
118
119 /**
120 * @brief Map from profile IDs to their spawn profiles.
121 */
122 std::unordered_map<
124 std::unique_ptr<const helios::engine::runtime::spawn::SpawnProfile>> spawnProfiles_;
125
126 /**
127 * @brief Ensures that the bounds are properly computed..
128 *
129 * @details Checks if the bounding box was initialized (i.e. has valid
130 * min/max values: min <= max).
131 * If the bounds are inverted (min > max), it recomputes the world AABB
132 * using the GameObject's components (ModelAabb, ScaleState, RotationState,
133 * SceneNode, TranslationState).
134 *
135 * @param go The GameObject to compute bounds for.
136 * @param bounds The bounding box to check and potentially update.
137 *
138 * @see helios::engine::modules::physics::collision::Bounds::computeWorldAabb
139 */
140 void ensureBounds(helios::engine::ecs::GameObject go, helios::math::aabbf& bounds) {
141 if (bounds.min()[0] > bounds.max()[0]) {
147
148 assert(mab && scn && tsc && sca && rsc && "Missing Components for AABB computation");
150 *mab, *scn, *tsc, *sca, *rsc
151 );
152 }
153 }
154
155 /**
156 * @brief Processes scheduled spawn plan commands.
157 *
158 * @details Iterates through each plan, acquires entities from the pool,
159 * positions them using the profile's placer, initializes them using
160 * the profile's initializer, and pushes a frame event for confirmation.
161 *
162 * @param commands Span of scheduled spawn plan commands to process.
163 * @param gameWorld The game world.
164 * @param updateContext The current update context.
165 */
166 void executeScheduledSpawnPlanCommands(
167 std::span<helios::engine::runtime::spawn::commands::ScheduledSpawnPlanCommand> commands,
170 ) {
171
172 for (auto& scheduledSpawnPlanCommand: commands) {
173
174 const auto& spawnPlan = scheduledSpawnPlanCommand.spawnPlan();
175
176 const auto spawnProfileId = scheduledSpawnPlanCommand.spawnProfileId();
177 const auto spawnRuleId = spawnPlan.spawnRuleId;
178 const auto amount = spawnPlan.amount;
179
180 const auto it = spawnProfiles_.find(spawnProfileId);
181 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
182
183 const auto spawnProfile = it->second.get();
184
185 const auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
186
187
188
189 const auto poolSnapshot = gameObjectPoolManager_->poolSnapshot(gameObjectPoolId);
190
191 if (amount == 0) {
192 assert(false && "Amount must not be 0");
193 }
194 if (!spawnProfiles_.contains(spawnProfileId)) {
195 assert(false && "SpawnManager does not manage this SpawnProfileId");
196 }
197
198
199
200 const auto spawnCount = std::min(amount, poolSnapshot.inactiveCount);
201 for (size_t i = 0; i < spawnCount; i++) {
202
203 auto go = gameObjectPoolManager_->acquire(gameObjectPoolId);
204 assert(go && "Failed to acquire GameObject");
205
207
209 assert(sbp && "unexpected missing SpawnedByProfileComponent");
210
212 assert(aabb && "unexpected missing AabbColliderComponent");
213
214 auto spawnCursor = helios::engine::runtime::spawn::SpawnPlanCursor{spawnCount, i};
215 const auto& spawnContext = scheduledSpawnPlanCommand.spawnContext();
216
217 const auto& emitter = spawnContext.emitterContext;
219 if (emitter.has_value() && ebc) {
220 ebc->setSource(emitter.value().source);
221 }
222
223 if (tsc) {
224
225 auto bounds = aabb->bounds();
226 ensureBounds(go.value(), bounds);
227
228 const auto position = spawnProfile->spawnPlacer->getPosition(
229 go->entityHandle(),
230 bounds,
231 gameWorld_->level().bounds(),
232 spawnCursor,
233 spawnContext
234
235 );
236 tsc->setTranslation(position);
237 }
238
239 assert(spawnProfile->spawnInitializer && "Unexpected missing spawn initializer");
240
241 spawnProfile->spawnInitializer->initialize(*go, spawnCursor, spawnContext);
242 sbp->setSpawnProfileId(spawnProfileId);
243 go->setActive(true);
244 }
245
247 spawnRuleId, spawnCount
248 );
249 }
250 }
251
252
253 /**
254 * @brief Processes spawn commands, acquiring and initializing objects.
255 *
256 * @param commands Span of spawn commands to process.
257 * @param gameWorld The game world.
258 * @param updateContext The current update context.
259 */
260 void spawnObjects(
261 std::span<helios::engine::runtime::spawn::commands::SpawnCommand> commands,
264
265 for (auto& spawnCommand: commands) {
266 const auto spawnProfileId = spawnCommand.spawnProfileId();
267 const auto spawnContext = spawnCommand.spawnContext();
268
269 const auto it = spawnProfiles_.find(spawnProfileId);
270 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
271
272 const auto spawnProfile = it->second.get();
273 const auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
274
275 if (gameObjectPoolManager_->poolSnapshot(gameObjectPoolId).inactiveCount == 0) {
276 /**
277 * @todo log
278 */
279 continue;
280 }
281
282 auto go = gameObjectPoolManager_->acquire(gameObjectPoolId);
283 assert(go && "Failed to acquire GameObject");
284
287 assert(sbp && "unexpected missing SpawnedByProfileComponent");
288
290 assert(aabb && "unexpected missing AabbColliderComponent");
291
292 const auto& emitter = spawnContext.emitterContext;
294 if (emitter.has_value() && ebc) {
295 ebc->setSource(emitter.value().source);
296 }
297
298 if (tsc) {
299
300 auto bounds = aabb->bounds();
301 ensureBounds(go.value(), bounds);
302
303 const auto position = spawnProfile->spawnPlacer->getPosition(
304 go->entityHandle(),
305 bounds,
306 gameWorld_->level().bounds(),
307 {1, 1},
308 spawnContext
309
310 );
311 tsc->setTranslation(position);
312 }
313
314
315 assert(spawnProfile->spawnInitializer && "Unexpected missing spawn initializer");
316
317 spawnProfile->spawnInitializer->initialize(*go, {1, 1}, spawnContext);
318 sbp->setSpawnProfileId(spawnProfileId);
319
320 go->setActive(true);
321 }
322 }
323
324
325 /**
326 * @brief Processes despawn commands, releasing objects back to pools.
327 *
328 * @param commands Span of despawn commands to process.
329 * @param gameWorld The game world.
330 * @param updateContext The current update context.
331 */
332 void despawnObjects(
333 std::span<helios::engine::runtime::spawn::commands::DespawnCommand> commands,
336
337 for (auto& despawnCommand : commands) {
338 const auto spawnProfileId = despawnCommand.spawnProfileId();
339
340 const auto it = spawnProfiles_.find(spawnProfileId);
341 assert(it != spawnProfiles_.end() && "SpawnProfile not part of SpawnManager");
342 const auto spawnProfile = it->second.get();
343 auto gameObjectPoolId = spawnProfile->gameObjectPoolId;
344
345 gameObjectPoolManager_->release(gameObjectPoolId, despawnCommand.entityHandle());
346
347 }
348 }
349
350
351 public:
352
353 /**
354 * @brief Default constructor.
355 */
356 SpawnManager() = default;
357
358 /**
359 * @brief Submits a spawn command for deferred processing.
360 *
361 * @param command The spawn command to queue.
362 *
363 * @return Always returns true.
364 */
365 bool submit(const helios::engine::runtime::spawn::commands::SpawnCommand& command) noexcept override {
366 spawnCommands_.push_back(command);
367 return true;
368 }
369
370 /**
371 * @brief Submits a despawn command for deferred processing.
372 *
373 * @param command The despawn command to queue.
374 *
375 * @return Always returns true.
376 */
378 despawnCommands_.push_back(command);
379 return true;
380 }
381
382
383 /**
384 * @brief Submits a scheduled spawn plan command for deferred processing.
385 *
386 * @param scheduledSpawnPlanCommand The scheduled plan command to queue.
387 *
388 * @return Always returns true.
389 */
390 bool submit(
392 ) noexcept override {
393 scheduledSpawnPlanCommands_.push_back(scheduledSpawnPlanCommand);
394 return true;
395 }
396
397 /**
398 * @brief Flushes pending commands, processing despawns then spawns.
399 *
400 * @param gameWorld The game world.
401 * @param updateContext The current update context.
402 */
403 void flush(
406 ) noexcept override {
407 if (!despawnCommands_.empty()) {
408 despawnObjects(despawnCommands_, gameWorld, updateContext);
409 despawnCommands_.clear();
410 }
411
412 if (!spawnCommands_.empty()) {
413 spawnObjects(spawnCommands_, gameWorld, updateContext);
414 spawnCommands_.clear();
415 }
416
417 if (!scheduledSpawnPlanCommands_.empty()) {
418 executeScheduledSpawnPlanCommands(scheduledSpawnPlanCommands_, gameWorld, updateContext);
419 scheduledSpawnPlanCommands_.clear();
420 }
421 };
422
423
424 /**
425 * @brief Adds a spawn profile for a profile ID.
426 *
427 * @details Registers a spawn profile that defines how entities should
428 * be spawned for this profile. The profile contains the pool ID,
429 * placer, and initializer.
430 *
431 * @param spawnProfileId The unique ID for this profile.
432 * @param spawnProfile The profile configuration. Ownership transferred.
433 *
434 * @return Reference to this manager for chaining.
435 *
436 * @pre No profile is already registered for this ID.
437 */
440 std::unique_ptr<const helios::engine::runtime::spawn::SpawnProfile> spawnProfile) {
441
442 assert(!spawnProfiles_.contains(spawnProfileId) && "SpawnProfileId already added");
443
444 spawnProfiles_[spawnProfileId] = std::move(spawnProfile);
445
446 return *this;
447 }
448
449 /**
450 * @brief Returns a spawn profile by ID.
451 *
452 * @param spawnProfileId The profile ID to look up.
453 *
454 * @return Pointer to the profile, or nullptr if not found.
455 */
457 const helios::engine::core::data::SpawnProfileId& spawnProfileId) const {
458
459 if (!spawnProfiles_.contains(spawnProfileId)) {
460 return nullptr;
461 }
462
463 return spawnProfiles_.find(spawnProfileId)->second.get();
464 }
465
466 /**
467 * @brief Initializes the manager with the game world.
468 *
469 * @details Acquires a reference to the GameObjectPoolManager and
470 * registers this manager as the SpawnCommandHandler for all
471 * configured spawn profiles.
472 *
473 * @param gameWorld The game world to initialize with.
474 */
475 void init(helios::engine::runtime::world::GameWorld& gameWorld) noexcept override {
476
477 gameObjectPoolManager_ = gameWorld.getManager<helios::engine::runtime::pooling::GameObjectPoolManager>();
478
479 for (const auto& [spawnProfileId, _]: spawnProfiles_) {
480 gameWorld.registerSpawnCommandHandler(spawnProfileId, *this);
481 }
482
483 }
484
485 };
486
487
488}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.