Skip to main content

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 object
  • config.reset - Function to reset payload state before returning to pool
  • config.name - Pool name for debugging and monitoring
  • config.initialSize - Initial pool size (default: 64)
  • config.growthSize - Growth size when exhausted (default: 32)
  • config.warnOnLeaks - Enable leak detection warnings (default: true)
Returns: Pool instance for acquiring/releasing payloads Example:

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
CRITICAL: Event listeners MUST call 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
Returns: Return value of callback function Example:
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 pool
  • unregister(name: string): void - Unregister a pool
  • getAllStats(): EventPayloadPoolStats[] - Get stats for all pools
  • checkAllLeaks(): Map<string, number> - Check all pools for leaks
  • resetAll(): void - Reset all pools
Example:

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)
Returns: Position object ready for use IMPORTANT: Must call 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
Example:
withPosition<T>(x: number, y: number, z: number, fn: (pos: PooledPosition) => T): T
Acquires, uses, and automatically releases a position. Parameters:
  • x - X coordinate
  • y - Y coordinate
  • z - Z coordinate
  • fn - Callback function that receives the position
Returns: Return value of callback function Example:
set(pos: PooledPosition, x: number, y: number, z: number): void
Sets position values in-place. Parameters:
  • pos - Position to modify
  • x - New X coordinate
  • y - New Y coordinate
  • z - New Z coordinate
Example:
copy(target: PooledPosition, source: { x: number; y: number; z: number }): void
Copies values from another position-like object. Parameters:
  • target - Position to modify
  • source - Source position to copy from
Example:
distanceSquared(a: PooledPosition, b: { x: number; y: number; z: number }): number
Calculates distance squared between two positions (avoids sqrt for performance). Parameters:
  • a - First position
  • b - Second position
Returns: Distance squared Example:
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 call release() 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:
  1. Check that all event listeners call release()
  2. Increase initial pool size if legitimate high frequency
  3. Use checkLeaks() to identify unreleased payloads

Memory Leaks

Symptom: Memory usage grows over time Cause: Payloads not being released back to pool Solution:
  1. Call CombatEventPools.checkAllLeaks() at end of tick
  2. Review event listeners for missing release() calls
  3. Use Chrome DevTools Memory Profiler to identify leaking objects

Performance Degradation

Symptom: Frame drops or tick slowdowns Cause: Pool growth causing allocations Solution:
  1. Check pool statistics with getStats()
  2. Increase initial pool size to avoid growth
  3. Verify payloads are being released promptly

See Also