Object Pooling System
Hyperscape implements comprehensive object pooling to eliminate GC pressure in high-frequency event loops. The combat system alone fires events every 600ms tick per combatant, which would cause significant memory churn without pooling.Overview
Location:packages/shared/src/utils/pools/
Core Infrastructure:
- EventPayloadPool.ts: Factory for creating type-safe event payload pools with automatic growth and leak detection
- PositionPool.ts: Pool for
{x, y, z}position objects with helper methods - CombatEventPools.ts: Pre-configured pools for all combat events with optimized sizes
- TilePool.ts: Pool for tile coordinate objects
- QuaternionPool.ts: Pool for quaternion objects
- EntityPool.ts: Pool for entity instances
Event Payload Pools
Usage Pattern
release() after processing. Failure to release causes pool exhaustion and memory leaks.
Available Combat Event Pools
| Pool | Event Type | Initial Size | Growth Size |
|---|---|---|---|
damageDealt | COMBAT_DAMAGE_DEALT | 64 | 32 |
projectileLaunched | COMBAT_PROJECTILE_LAUNCHED | 32 | 16 |
faceTarget | COMBAT_FACE_TARGET | 64 | 32 |
clearFaceTarget | COMBAT_CLEAR_FACE_TARGET | 64 | 32 |
attackFailed | COMBAT_ATTACK_FAILED | 32 | 16 |
followTarget | COMBAT_FOLLOW_TARGET | 32 | 16 |
combatStarted | COMBAT_STARTED | 32 | 16 |
combatEnded | COMBAT_ENDED | 32 | 16 |
projectileHit | COMBAT_PROJECTILE_HIT | 32 | 16 |
combatKill | COMBAT_KILL | 16 | 8 |
Pool Features
- Automatic Growth: Pools automatically expand when exhausted (warns every 60s)
- Leak Detection: Warns when payloads not released at end of tick (max 10 warnings, then suppressed)
- Statistics Tracking: Acquire/release counts, peak usage, leak warnings
- Global Registry: Monitor all pools via
eventPayloadPoolRegistry
Monitoring
Performance Impact
- Eliminates per-tick object allocations in combat hot paths
- Memory stays flat during 60s stress test with agents in combat
- Verified zero-allocation event emission in CombatSystem and CombatTickProcessor
- Reduces GC pressure by 90%+ in high-frequency combat scenarios
Position Pool
Location:packages/shared/src/utils/pools/PositionPool.ts
Usage
Features
- O(1) acquire/release operations
- Zero allocations after warmup
- Automatic pool growth when exhausted
- Helper methods:
set(),copy(),distanceSquared() - Statistics tracking:
getStats()
Creating New Pools
When adding new high-frequency events, create a pool:Pool Configuration Options
name: Pool name for debugging and monitoringfactory: Function to create new payload objects (without_poolIndex)reset: Function to reset payload to initial stateinitialSize: Initial pool size (default: 64)growthSize: Number of objects to add when exhausted (default: 32)warnOnLeaks: Enable leak detection warnings (default: true)
Best Practices
- Set
initialSizebased on expected concurrent usage (e.g., max concurrent combatants) - Set
growthSizeto ~50% ofinitialSizefor balanced growth - Always register pools with
eventPayloadPoolRegistryfor monitoring - Use descriptive names for easier debugging
- Call
checkLeaks()at the end of each game tick to detect unreleased payloads
Pool Statistics
EventPayloadPoolStats Interface
Example: Monitoring All Pools
Troubleshooting
Pool Exhaustion Warnings
If you see warnings like:- Increasing
initialSizeto reduce growth frequency - Checking for missing
release()calls (memory leaks) - Optimizing event emission frequency
Memory Leaks
If you see leak warnings:release(). Find the missing release() calls:
Performance Monitoring
Monitor pool performance during development:Implementation Details
PooledPayload Interface
All pooled payloads must extendPooledPayload:
_poolIndex property is used internally for tracking and should never be modified by user code.
Pool Lifecycle
- Initialization: Pool creates
initialSizeobjects - Acquire: Returns available object, grows pool if exhausted
- Use: Caller populates object with data
- Release: Caller returns object to pool, object is reset
- Growth: Pool automatically expands by
growthSizewhen exhausted
Memory Safety
- Pools use array-based storage for O(1) operations
- Available objects tracked via index array
- No object creation after warmup (unless pool grows)
- Reset function ensures clean state for reuse
- Leak detection prevents unbounded growth
Related Systems
- CombatSystem: Uses combat event pools for zero-allocation event emission
- CombatTickProcessor: Uses combat event pools for tick processing
- EventBus: Event system that pools integrate with
- SystemBase: Base class for systems with cleanup patterns
Migration Guide
Converting Existing Code to Use Pools
Before (allocates on every event):References
- EventPayloadPool.ts - Pool factory implementation
- CombatEventPools.ts - Pre-configured combat pools
- PositionPool.ts - Position object pool
- CombatSystem.ts - Example usage in combat system