# ObjectPool _(no submodules)_ ## Purpose `ObjectPool` is a generic typed object pool that dampens GC pressure in hot allocation paths — game-loop frame allocations, network message envelopes, session metadata containers — by recycling typed objects instead of letting them churn through the GC. Each pool is configured with a `factory` (creates a new instance), a `reset` (clears state before an instance returns to the pool), and an optional `dispose` (invoked on cleanup paths so callers can release native handles). The pool is dependency-free apart from the typed `GameServerError` registry, Zod (for numeric-config validation), and the OpenTelemetry `metrics` API. Misuse is loud: releasing an object that didn't come from this pool raises `RELEASE_FOREIGN_OBJECT`; acquiring past the configured `maxSize` raises `POOL_EXHAUSTED`; using a pool after `dispose()` raises `POOL_DISPOSED`. ## Features - Configurable `initialSize`, `maxSize`, and pre-allocation toggle. - Typed factory + reset + optional dispose hooks. - `acquire`, `release`, and `withObject(callback)` (auto-release with finally semantics). - Hard cap enforcement at acquisition time, with typed-error rejection. - Lifecycle: `clear()` (resets stats + drops pool, fires dispose), `dispose()` (terminal, all subsequent calls throw). - Two OTel counters — `pool.acquire_total{outcome=reused|created}` and `pool.release_total`. - Stats snapshot — size, available, in-use, totals, hit-rate. ## Usage: Basic ```typescript import { ObjectPool } from "@suaveplan/game-server/performance/object-pool"; interface Vec3 { x: number; y: number; z: number; } const pool = new ObjectPool({ factory: () => ({ x: 0, y: 0, z: 0 }), reset: (v) => { v.x = v.y = v.z = 0; }, initialSize: 64, }); const v = pool.acquire(); v.x = 1; v.y = 2; pool.release(v); ``` ## Usage: Advanced ```typescript import { ObjectPool } from "@suaveplan/game-server/performance/object-pool"; import { metrics } from "@opentelemetry/api"; interface PacketBuffer { bytes: Uint8Array; offset: number; } const pool = new ObjectPool( { factory: () => ({ bytes: new Uint8Array(2048), offset: 0 }), reset: (b) => { b.bytes.fill(0); b.offset = 0; }, dispose: (b) => { // Optionally hand the underlying buffer to an async ring. ringFree.push(b.bytes); }, initialSize: 32, maxSize: 512, }, { meter: metrics.getMeter("game-server.network") }, ); // Hot loop: borrow a buffer per packet, auto-release on errors. await pool.withObject(async (buffer) => { await readInto(buffer.bytes); await transport.send(buffer.bytes); }); const stats = pool.getStats(); console.log(`hit rate ${stats.hitRate}`); ``` ## API Reference - `new ObjectPool(config: ObjectPoolConfig, deps?: ObjectPoolDeps)` — validates the numeric config via Zod; throws `INVALID_POOL_CONFIG` on missing `factory`/`reset` or invalid numeric fields. - `acquire(): T` — returns a recycled or freshly factory-allocated object. Throws `POOL_EXHAUSTED` when the pool is empty AND `pool+inUse >= maxSize`. Throws `POOL_DISPOSED` after dispose. - `release(obj: T): void` — throws `RELEASE_FOREIGN_OBJECT` if the object was not handed out by this pool; throws `POOL_DISPOSED` after dispose. - `withObject(cb): Promise` — borrows + finally-releases. - `clear()` — drops every pooled object and resets stats (fires dispose hook). - `dispose()` — terminal, idempotent. Subsequent acquire/release/clear raise `POOL_DISPOSED`. Fires dispose hook on every pooled object. - `getStats(): PoolStats`. - `disposed: boolean`. ## Implementation Notes The cap check at acquire is `pool.length + inUse.size >= maxSize`. When hit AND `pool.length === 0`, acquisition fails with `POOL_EXHAUSTED`. When hit AND `pool.length > 0`, the pool reuses an existing instance — this is the steady-state happy path for a saturated pool. Release always succeeds back into the pool because the cap is enforced at acquire, never letting `pool + inUse` exceed `maxSize`, so there is no unreachable eviction branch to test. The `dispose` hook fires only on `clear()` and `dispose()` paths, where the pool is being torn down or emptied. Pre-allocation runs the factory synchronously inside the constructor, which keeps every `acquire` constant-time in steady state.