Skip to main content

GameObjectPoolManager.ixx File

Manager for GameObject pooling and lifecycle management. More...

Included Headers

Namespaces Index

namespacehelios
namespaceengine

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

namespaceruntime

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

namespacepooling

GameObject pooling for efficient object recycling. More...

Classes Index

classGameObjectPoolManager

High-level manager for GameObject pooling operations. More...

Description

Manager for GameObject pooling and lifecycle management.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file GameObjectPoolManager.ixx
3 * @brief Manager for GameObject pooling and lifecycle management.
4 */
5module;
6
7#include <cassert>
8#include <memory>
9#include <optional>
10#include <stdexcept>
11#include <unordered_map>
12
13
14export module helios.engine.runtime.pooling.GameObjectPoolManager;
15
16import helios.engine.runtime.pooling.types.GameObjectPoolId;
17
18import helios.engine.ecs.GameObject;
19import helios.engine.runtime.world.UpdateContext;
20
21import helios.engine.runtime.world.GameWorld;
22import helios.engine.runtime.pooling.GameObjectPool;
23import helios.engine.runtime.pooling.GameObjectPoolRegistry;
24
25import helios.engine.runtime.pooling.GameObjectPoolConfig;
26import helios.engine.runtime.pooling.components.PrefabIdComponent;
27
28import helios.engine.ecs.EntityHandle;
29import helios.core.types;
30import helios.engine.common.tags;
31
32import helios.engine.runtime.pooling.GameObjectPoolSnapshot;
33
35
36 /**
37 * @brief High-level manager for GameObject pooling operations.
38 *
39 * @details GameObjectPoolManager provides a unified interface for managing
40 * multiple GameObjectPools. It handles pool configuration, initialization,
41 * and the acquire/release lifecycle of pooled GameObjects.
42 *
43 * The manager integrates with the GameWorld to clone prefabs during pool
44 * initialization and to look up GameObjects by their EntityHandle during
45 * acquire/release operations.
46 *
47 * ## Lifecycle
48 *
49 * 1 **Configuration**: Add pool configurations via `addPoolConfig()`.
50 * 2 **Initialization**: Call `init()` to create pools and populate them
51 * with cloned prefab instances. This **locks** the pools.
52 * 3 **Runtime**: Use `acquire()` and `release()` to manage entity lifecycle.
53 *
54 * ## Pool Locking
55 *
56 * After `init()` completes, each pool is **locked**. A locked pool:
57 * - Cannot accept new EntityHandles via `addInactive()`
58 * - Has its sparse arrays sized based on min/max EntityIds
59 * - Is ready for O(1) acquire/release operations
60 *
61 * This design optimizes memory layout but means the pool size is fixed
62 * at initialization time.
63 *
64 * ## Trade-offs
65 *
66 * - **Fixed Capacity**: Pool size cannot grow after initialization. If more
67 * entities are needed, `acquire()` returns nullptr when exhausted.
68 * - **Memory Overhead**: Sparse arrays are sized for the EntityId range,
69 * which may waste memory if EntityIds are not contiguous.
70 * - **Initialization Cost**: All prefab clones are created upfront during
71 * `init()`, which may cause a startup delay for large pools.
72 *
73 * ## Example
74 *
75 * ```cpp
76 * GameObjectPoolManager poolManager;
77 *
78 * // Create prefab
79 * auto bulletPrefab = gameWorld.addGameObject();
80 * bulletPrefab.add<RenderableComponent>(mesh, material);
81 * bulletPrefab.add<Move2DComponent>();
82 * bulletPrefab.setActive(false);
83 *
84 * auto config = std::make_unique<GameObjectPoolConfig>(
85 * bulletPoolId, bulletPrefab, 100
86 * );
87 * poolManager.addPoolConfig(std::move(config));
88 *
89 * poolManager.init(gameWorld);
90 *
91 * // Acquire returns std::optional<GameObject>
92 * if (auto bullet = poolManager.acquire(bulletPoolId)) {
93 * bullet->get<TranslationStateComponent>()->setTranslation(spawnPos);
94 * bullet->setActive(true);
95 * }
96 *
97 * // Release back to pool
98 * poolManager.release(bulletPoolId, bullet->entityHandle());
99 * ```
100 *
101 * @see GameObjectPool
102 * @see GameObjectPoolRegistry
103 * @see Manager
104 */
106
107 /**
108 * @brief Registry of GameObjectPools for entity recycling.
109 *
110 * @details Pools enable efficient reuse of GameObjects without repeated
111 * allocation/deallocation. Each pool is identified by a GameObjectPoolId.
112 */
114
115 /**
116 * @brief Non-owning pointer to the associated GameWorld.
117 *
118 * @details Set during `init()`. Used for cloning prefabs and looking up
119 * GameObjects by their EntityHandle.
120 */
122
123 /**
124 * @brief Pending pool configurations awaiting initialization.
125 *
126 * @details Configurations are consumed during `init()` to create and
127 * populate the actual pools.
128 */
129 std::unordered_map<
131 std::unique_ptr<GameObjectPoolConfig>> poolConfigs_;
132
133 /**
134 * @brief Populates a pool with cloned prefab instances and locks it.
135 *
136 * @details Clones the prefab GameObject until the pool reaches its
137 * configured capacity. Each clone is:
138 * 1 Added to the GameWorld
139 * 2 Immediately deactivated (`setActive(false)`)
140 * 3 Prepared for pooling (`onRelease()`)
141 * 4 Registered as inactive in the pool
142 *
143 * After all clones are created, the pool is **locked** via `lock()`.
144 * Locking finalizes the sparse array sizing based on the min/max EntityIds
145 * and enables O(1) acquire/release operations. Once locked, no new
146 * EntityHandles can be added to the pool.
147 *
148 * @param gameObjectPoolId The pool to fill.
149 * @param gameObjectPrefab The prefab to clone.
150 *
151 * @post The pool is locked and ready for acquire/release operations.
152 */
153 void fillPool(
155 helios::engine::ecs::GameObject gameObjectPrefab
156 ) {
158
159 auto* gameObjectPool = pool(gameObjectPoolId);
160
161 const size_t used = gameObjectPool->activeCount() + gameObjectPool->inactiveCount();
162 const size_t space = used < gameObjectPool->size() ? gameObjectPool->size() - used : 0;
163
164 for (size_t i = 0; i < space; i++) {
165 helios::engine::ecs::GameObject go = gameWorld_->clone(gameObjectPrefab);
166 go.setActive(false);
167 go.onRelease();
168 gameObjectPool->addInactive(go.entityHandle());
169 }
170
171 gameObjectPool->lock();
172 }
173
174 public:
176
177 /**
178 * @brief Registers a pool configuration for later initialization.
179 *
180 * @details The configuration specifies the pool ID, capacity, and prefab
181 * to use for cloning. Configurations are processed during `init()`.
182 *
183 * @param gameObjectPoolConfig The pool configuration to register.
184 *
185 * @return Reference to this manager for method chaining.
186 *
187 * @throws std::runtime_error If a pool with the same ID is already registered.
188 */
189 GameObjectPoolManager& addPoolConfig(std::unique_ptr<GameObjectPoolConfig> gameObjectPoolConfig) {
190
191 if (poolConfigs_.contains(gameObjectPoolConfig->gameObjectPoolId)) {
192 throw std::runtime_error("Pool already registered with this manager");
193 }
194
195 poolConfigs_[gameObjectPoolConfig->gameObjectPoolId] = std::move(gameObjectPoolConfig);
196
197 return *this;
198 }
199
200 /**
201 * @brief Returns a snapshot of the pool's current state.
202 *
203 * @details Provides the active and inactive counts for monitoring and
204 * debugging purposes.
205 *
206 * @param gameObjectPoolId The pool to query.
207 *
208 * @return A GameObjectPoolSnapshot containing active and inactive counts.
209 */
212 ) const {
213 auto* gameObjectPool = pool(gameObjectPoolId);
214
215 return {
216 gameObjectPool->activeCount(), gameObjectPool->inactiveCount()
217 };
218 }
219
220 /**
221 * @brief Releases a GameObject back to its pool.
222 *
223 * @details Marks the entity as inactive in both the pool and the
224 * GameWorld. Calls `onRelease()` on the GameObject to trigger
225 * component cleanup hooks, then deactivates it.
226 *
227 * @param gameObjectPoolId The pool that owns this entity.
228 * @param entityHandle The EntityHandle of the entity to release.
229 *
230 * @return Optional containing the released GameObject if found,
231 * std::nullopt if the entity was not found in the GameWorld.
232 */
233 std::optional<helios::engine::ecs::GameObject> release(
235 const helios::engine::ecs::EntityHandle& entityHandle
236 ) {
237 auto* gameObjectPool = pool(gameObjectPoolId);
238
239 auto worldGo = gameWorld_->find(entityHandle);
240
241 if (worldGo) {
242 if (gameObjectPool->release(entityHandle)) {
243 worldGo->onRelease();
244 worldGo->setActive(false);
245 }
246 }
247
248 return worldGo;
249 }
250
251 /**
252 * @brief Acquires an inactive GameObject from the pool.
253 *
254 * @details Retrieves the next available inactive entity and calls
255 * `onAcquire()` to trigger component initialization hooks. The caller
256 * is responsible for activating the entity via `setActive(true)`.
257 *
258 * If an acquired entity is no longer valid in the GameWorld (stale handle),
259 * it is removed from the pool and the next available entity is tried.
260 *
261 * @param gameObjectPoolId The pool to acquire from.
262 *
263 * @return Optional containing the acquired GameObject if available,
264 * std::nullopt if the pool is exhausted.
265 */
266 [[nodiscard]] std::optional<helios::engine::ecs::GameObject> acquire(
268 ) {
270
271 auto* gameObjectPool = pool(gameObjectPoolId);
272
273 while (gameObjectPool->acquire(entityHandle)) {
274
275 auto worldGo = gameWorld_->find(entityHandle);
276
277 if (worldGo) {
278 worldGo->onAcquire();
279 return worldGo;
280 }
281
282 // we assume the pool is owned by this gameWorld,
283 // so removing this entityHandle does not impact another gameWorld that is
284 // using this pool
285 gameObjectPool->releaseAndRemove(entityHandle);
286 }
287
288 return std::nullopt;
289
290 }
291
292 /**
293 * @brief Initializes all registered pools.
294 *
295 * @details Creates GameObjectPool instances from the pending configurations
296 * and populates them by cloning the specified prefabs into the GameWorld.
297 *
298 * @param gameWorld The GameWorld to associate with this manager.
299 */
301
302 gameWorld_ = &gameWorld;
303
304 for (const auto& [gameObjectPoolId, poolConfig] : poolConfigs_) {
305
306 auto pool = std::make_unique<helios::engine::runtime::pooling::GameObjectPool>(
307 poolConfig->amount);
308
309 pools_.addPool(gameObjectPoolId, std::move(pool));
310
311 for (auto [entity, pic] : gameWorld.view<helios::engine::runtime::pooling::components::PrefabIdComponent>().whereEnabled()) {
312 if (pic->prefabId() == poolConfig->prefabId) {
313 fillPool(gameObjectPoolId, entity);
314 break;
315 }
316 }
317
318 }
319
320 };
321
322 /**
323 * @brief Manager flush callback (currently unused).
324 *
325 * @details Reserved for future use. May be used to process deferred
326 * release requests or pool maintenance operations.
327 *
328 * @param gameWorld The associated GameWorld.
329 * @param update_context The current frame's update context.
330 */
331 void flush(
333 ) noexcept {
334
335 }
336
337 /**
338 * @brief Retrieves a pool by its ID.
339 *
340 * @param gameObjectPoolId The pool ID to look up.
341 *
342 * @return Pointer to the GameObjectPool.
343 *
344 * @pre The pool must be registered with this manager.
345 */
346 [[nodiscard]] GameObjectPool* pool(
348 ) const {
349 assert(pools_.has(gameObjectPoolId) && "GameObjectPoolId not registered with this manager");
350 return pools_.pool(gameObjectPoolId);
351 }
352
353 /**
354 * @brief Resets all pools by releasing all active GameObjects.
355 *
356 * @details Iterates through all registered pools and releases every active
357 * entity back to its pool. This effectively returns all pooled objects to
358 * their inactive state without destroying them.
359 *
360 * Useful for level transitions or game restarts where all pooled objects
361 * should be recycled.
362 *
363 * @see reset(const types::GameObjectPoolId)
364 */
365 void reset() {
366 for (auto& [poolId, _] : pools_.pools()) {
367 reset(poolId);
368 }
369 }
370
371 /**
372 * @brief Reset the pool with the specified poolId.
373 *
374 * @details Releases every active entity back to its pool specified by poolId.
375 * This effectively returns all pooled objects to their inactive state without
376 * destroying them.
377 *
378 * @param poolId The ID of the pool to reset.
379 */
380 void reset(const types::GameObjectPoolId poolId) {
381
382 const auto poolPtr = pool(poolId);
383
384 auto activeSpan = poolPtr->activeGameObjects();
385 auto toRelease = std::vector(activeSpan.begin(), activeSpan.end());
386 for (auto entityHandle : toRelease) {
387 release(poolId, entityHandle);
388 }
389 }
390 };
391
392}
393

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.