Object Pooling API Documentation
Overview
Hyperscape implements comprehensive object pooling to eliminate garbage collection 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.Core Modules
EventPayloadPool
Location:packages/shared/src/utils/pools/EventPayloadPool.ts
Factory for creating type-safe event payload pools that eliminate per-event allocations.
Types
Functions
createEventPayloadPool<T>(config: EventPayloadPoolConfig<T>): EventPayloadPool<T>
Creates a type-safe event payload pool.
Parameters:
config.factory- Function to create a new payload objectconfig.reset- Function to reset payload state before returning to poolconfig.name- Pool name for debugging and monitoringconfig.initialSize- Initial pool size (default: 64)config.growthSize- Growth size when exhausted (default: 32)config.warnOnLeaks- Enable leak detection warnings (default: true)
Pool Methods
acquire(): T
Acquires a payload from the pool. Automatically grows pool if exhausted.
Returns: Payload object ready for use
Example:
release(payload: T): void
Releases a payload back to the pool. Resets payload state before returning.
Parameters:
payload- The payload to release
release() after processing. Failure to release causes pool exhaustion and memory leaks.
Example:
withPayload<R>(fn: (payload: T) => R): R
Acquires, uses, and automatically releases a payload. Convenience method for short-lived usage.
Parameters:
fn- Callback function that receives the payload
getStats(): EventPayloadPoolStats
Returns pool statistics for monitoring and debugging.
Returns: Statistics object with usage metrics
Example:
reset(): void
Resets pool to initial state. Use with caution - invalidates all acquired payloads.
Example:
checkLeaks(): number
Checks for unreleased payloads. Should be called at end of tick.
Returns: Number of payloads still in use
Example:
Registry
eventPayloadPoolRegistry
Global registry of all event payload pools for monitoring.
Methods:
register<T>(pool: EventPayloadPool<T>): void- Register a poolunregister(name: string): void- Unregister a poolgetAllStats(): EventPayloadPoolStats[]- Get stats for all poolscheckAllLeaks(): Map<string, number>- Check all pools for leaksresetAll(): void- Reset all pools
CombatEventPools
Location:packages/shared/src/utils/pools/CombatEventPools.ts
Pre-configured pools for high-frequency combat events.
Available Pools
Payload Types
PooledCombatDamageDealtPayload
PooledCombatProjectileLaunchedPayload
PooledCombatFaceTargetPayload
PooledCombatClearFaceTargetPayload
PooledCombatAttackFailedPayload
PooledCombatFollowTargetPayload
PooledCombatStartedPayload
PooledCombatEndedPayload
PooledCombatProjectileHitPayload
PooledCombatKillPayload
Utility Methods
getAllStats()
Returns statistics for all combat pools.
Returns: Object with stats for each pool
Example:
checkAllLeaks(): number
Checks all combat pools for unreleased payloads.
Returns: Total number of leaked payloads across all pools
Example:
resetAll(): void
Resets all combat pools to initial state.
Example:
Usage Example
PositionPool
Location:packages/shared/src/utils/pools/PositionPool.ts
Object pool for {x, y, z} position objects. Eliminates allocations in hot paths like position updates, movement, and combat.
Types
Global Instance
Methods
acquire(x = 0, y = 0, z = 0): PooledPosition
Acquires a position from the pool, initialized to the given values.
Parameters:
x- X coordinate (default: 0)y- Y coordinate (default: 0)z- Z coordinate (default: 0)
release() when done to return to pool.
Example:
release(pos: PooledPosition): void
Releases a position back to the pool. Resets position to origin before returning.
Parameters:
pos- The position to release
withPosition<T>(x: number, y: number, z: number, fn: (pos: PooledPosition) => T): T
Acquires, uses, and automatically releases a position.
Parameters:
x- X coordinatey- Y coordinatez- Z coordinatefn- Callback function that receives the position
set(pos: PooledPosition, x: number, y: number, z: number): void
Sets position values in-place.
Parameters:
pos- Position to modifyx- New X coordinatey- New Y coordinatez- New Z coordinate
copy(target: PooledPosition, source: { x: number; y: number; z: number }): void
Copies values from another position-like object.
Parameters:
target- Position to modifysource- Source position to copy from
distanceSquared(a: PooledPosition, b: { x: number; y: number; z: number }): number
Calculates distance squared between two positions (avoids sqrt for performance).
Parameters:
a- First positionb- Second position
getStats()
Returns pool statistics for monitoring.
Returns: Statistics object
Example:
reset(): void
Resets pool to initial state. Use with caution - invalidates all acquired positions.
Example:
Performance Characteristics
EventPayloadPool
- Acquire: O(1) - Pop from available array
- Release: O(1) - Push to available array
- Memory: Fixed after warmup (unless pool exhausted)
- Growth: Automatic when exhausted, warns once per minute
PositionPool
- Acquire: O(1) - Pop from available array
- Release: O(1) - Push to available array
- Memory: Fixed after warmup (unless pool exhausted)
- Initial Size: 128 positions
- Growth Size: 64 positions
CombatEventPools
- Pool Sizes: 16-64 objects (varies by event frequency)
- Growth Sizes: 8-32 objects
- Memory Impact: Eliminates per-tick allocations in combat hot paths
- Verified: Memory stays flat during 60s stress test with agents in combat
Best Practices
1. Always Release Payloads
CRITICAL: Event listeners MUST callrelease() after processing.
2. Use withPayload for Short-Lived Usage
3. Monitor Pool Statistics
4. Check for Leaks at End of Tick
5. Register Custom Pools
Migration Guide
Before (Without Pooling)
After (With Pooling)
Troubleshooting
Pool Exhaustion Warnings
Symptom: Console warnings about pool exhaustion Cause: High event frequency or payloads not being released Solution:- Check that all event listeners call
release() - Increase initial pool size if legitimate high frequency
- Use
checkLeaks()to identify unreleased payloads
Memory Leaks
Symptom: Memory usage grows over time Cause: Payloads not being released back to pool Solution:- Call
CombatEventPools.checkAllLeaks()at end of tick - Review event listeners for missing
release()calls - Use Chrome DevTools Memory Profiler to identify leaking objects
Performance Degradation
Symptom: Frame drops or tick slowdowns Cause: Pool growth causing allocations Solution:- Check pool statistics with
getStats() - Increase initial pool size to avoid growth
- Verify payloads are being released promptly
See Also
- AGENTS.md - Complete memory management documentation
- CLAUDE.md - Development guidelines
- Memory Management Best Practices