Object Pooling API Reference
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 used in pathfinding
- QuaternionPool.ts: Pool for quaternion objects used in rotation calculations
- EntityPool.ts: Pool for entity instances to reduce allocation overhead
Event Payload Pools
Basic Usage
release() after processing. Failure to release causes pool exhaustion and memory leaks.
Available Combat Event Pools
| Pool | Event Type | Initial Size | Growth Size | Use Case |
|---|---|---|---|---|
damageDealt | COMBAT_DAMAGE_DEALT | 64 | 32 | Every successful attack |
projectileLaunched | COMBAT_PROJECTILE_LAUNCHED | 32 | 16 | Ranged/magic attacks |
faceTarget | COMBAT_FACE_TARGET | 64 | 32 | Entity faces target |
clearFaceTarget | COMBAT_CLEAR_FACE_TARGET | 64 | 32 | Entity stops facing |
attackFailed | COMBAT_ATTACK_FAILED | 32 | 16 | Attack blocked/failed |
followTarget | COMBAT_FOLLOW_TARGET | 32 | 16 | Move toward target |
combatStarted | COMBAT_STARTED | 32 | 16 | Combat session begins |
combatEnded | COMBAT_ENDED | 32 | 16 | Combat session ends |
projectileHit | COMBAT_PROJECTILE_HIT | 32 | 16 | Projectile hits target |
combatKill | COMBAT_KILL | 16 | 8 | Entity dies in combat |
Pool Configuration
Pools are configured with:- Initial size: Pre-allocated objects on pool creation
- Growth size: Objects added when pool is exhausted
- Leak detection: Warns when payloads not released at end of tick
- Statistics tracking: Acquire/release counts, peak usage, leak warnings
Monitoring
Global Registry
All pools are registered with a global registry for centralized monitoring:Creating Custom Event Pools
When adding new high-frequency events, create a pool to eliminate allocations:Pool Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Pool name for debugging and monitoring |
factory | () => T | required | Function to create new payload objects |
reset | (p: T) => void | required | Function to reset payload to initial state |
initialSize | number | 64 | Initial pool size |
growthSize | number | 32 | Objects added when exhausted |
warnOnLeaks | boolean | true | Enable leak detection warnings |
Best Practices
-
Set
initialSizebased on expected concurrent usage- Example: Max concurrent combatants = 64 → initialSize: 64
- Prevents pool exhaustion during normal gameplay
-
Set
growthSizeto ~50% ofinitialSize- Balanced growth without excessive allocation
- Example: initialSize: 64 → growthSize: 32
-
Always register pools with
eventPayloadPoolRegistry- Enables centralized monitoring
- Helps detect leaks across all pools
-
Use descriptive names
- Makes debugging easier
- Shows up in leak warnings and statistics
-
Call
checkLeaks()at the end of each game tick- Detects unreleased payloads early
- Prevents memory leaks from accumulating
Position Pool
Pre-configured pool for 3D position objects.Usage
API
Features
- O(1) acquire/release operations
- Zero allocations after warmup
- Automatic pool growth when exhausted
- Helper methods for common operations
- Statistics tracking for monitoring
Tile Pool
Pool for tile coordinate objects used in pathfinding.Usage
API
Quaternion Pool
Pool for quaternion objects used in rotation calculations.Usage
Performance Impact
Before Object Pooling
After Object Pooling
Benchmarks
60-second stress test with 10 agents in combat:| Metric | Before | After | Improvement |
|---|---|---|---|
| Allocations/tick | 120 | 0 | 100% reduction |
| GC frequency | 2.5s | 35s | 14x less frequent |
| Memory growth | 45 MB | 0 MB | Flat memory |
| Frame drops | 12 | 0 | 100% reduction |
packages/shared/src/systems/shared/combat/__tests__/CombatSystemPerformance.test.ts- Memory stays flat during 60s stress test with agents in combat
- Zero-allocation event emission in CombatSystem and CombatTickProcessor
Leak Detection
How It Works
Pools track acquired payloads and warn when they’re not released at the end of a game tick:Warning Output
Debugging Leaks
- Check event listeners - Ensure all listeners call
release() - Check error paths - Release payloads even when errors occur
- Use try/finally - Guarantee release even on exceptions
Disabling Leak Detection
For specific pools where leak warnings are expected (e.g., long-lived payloads):Pool Statistics
Statistics Interface
Getting Statistics
Monitoring Pool Health
Healthy pool:inUse < total(not exhausted)available > 0(objects available)leakWarnings === 0(no leaks detected)acquireCount === releaseCount(balanced acquire/release)
inUse === total(pool exhausted, will auto-grow)leakWarnings > 0(payloads not being released)acquireCount > releaseCount(leak accumulating)- Frequent auto-growth warnings in logs
Advanced Usage
Auto-Release Pattern
UsewithPayload() for automatic release:
Pool Reset
Reset a pool to initial state (clears all statistics):Custom Pool Sizes
Override default sizes for specific use cases:Integration with Game Systems
Combat System Integration
The CombatSystem uses pools for all event emissions:Event Listener Integration
All combat event listeners must release payloads:Tick-End Leak Detection
CallcheckAllLeaks() at the end of each game tick:
Troubleshooting
Pool Exhaustion Warnings
- Increase initial size if this happens frequently
- Check for leaks - payloads may not be released
- Optimize event frequency - reduce unnecessary events
Memory Leaks
release()
Solutions:
- Find missing release() calls - search for event listeners
- Use try/finally - guarantee release even on errors
- Use withPayload() - automatic release pattern
- Check error paths - ensure release on all code paths
Performance Degradation
Symptoms:- Increasing memory usage over time
- Frequent GC pauses
- Frame drops during combat
- Fix leaks (acquire count should equal release count)
- Increase pool sizes if exhaustion is frequent
- Reduce event frequency if possible
Migration Guide
Converting Existing Code to Use Pools
Before (allocates on every event):Checklist
- Create pool with
createEventPayloadPool() - Register pool with
eventPayloadPoolRegistry - Replace
emit()withacquire()+emitTypedEvent() - Add
release()calls to all listeners - Add try/finally blocks for error safety
- Test for leaks with
checkAllLeaks() - Monitor statistics with
getStats()
References
- Source Code:
packages/shared/src/utils/pools/ - Tests:
packages/shared/src/utils/pools/__tests__/ - Performance Tests:
packages/shared/src/systems/shared/combat/__tests__/CombatSystemPerformance.test.ts - Integration:
packages/shared/src/systems/shared/combat/CombatSystem.ts
Related Documentation
- AGENTS.md - Memory Management section
- CLAUDE.md - Performance Optimizations section
- Memory Leak Fixes - Cleanup patterns