Skip to main content

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

CRITICAL: Event listeners MUST call release() after processing. Failure to release causes pool exhaustion and memory leaks.

Available Combat Event Pools

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 monitoring
  • factory: Function to create new payload objects (without _poolIndex)
  • reset: Function to reset payload to initial state
  • initialSize: Initial pool size (default: 64)
  • growthSize: Number of objects to add when exhausted (default: 32)
  • warnOnLeaks: Enable leak detection warnings (default: true)

Best Practices

  1. Set initialSize based on expected concurrent usage (e.g., max concurrent combatants)
  2. Set growthSize to ~50% of initialSize for balanced growth
  3. Always register pools with eventPayloadPoolRegistry for monitoring
  4. Use descriptive names for easier debugging
  5. 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:
This indicates high concurrent usage. Consider:
  1. Increasing initialSize to reduce growth frequency
  2. Checking for missing release() calls (memory leaks)
  3. Optimizing event emission frequency

Memory Leaks

If you see leak warnings:
This means event listeners are not calling release(). Find the missing release() calls:

Performance Monitoring

Monitor pool performance during development:

Implementation Details

PooledPayload Interface

All pooled payloads must extend PooledPayload:
The _poolIndex property is used internally for tracking and should never be modified by user code.

Pool Lifecycle

  1. Initialization: Pool creates initialSize objects
  2. Acquire: Returns available object, grows pool if exhausted
  3. Use: Caller populates object with data
  4. Release: Caller returns object to pool, object is reset
  5. Growth: Pool automatically expands by growthSize when 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
  • 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):
After (uses pool):
Listener (must release):

References