Created
March 30, 2026 20:47
-
-
Save jkoppel/39e541099314db9c0e922a37e5f3df7e to your computer and use it in GitHub Desktop.
Survev.io Object Pool Performance Benchmark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Realistic Combat Benchmark: 4-Player Squad Fight | |
| * | |
| * Scenario: | |
| * - 4 Players with Vectors (26 shots/sec). | |
| * - Vector stats: 33 round magazine, 1.6s reload time. | |
| * - Simulates 30 seconds of a squad fight with reloads. | |
| * - Initial pool contains 100,000 "historical" objects. | |
| */ | |
| // --- TYPES & MOCKS --- | |
| interface AbstractObject { | |
| active: boolean; | |
| __poolIdx: number; | |
| m_init(): void; | |
| m_free(): void; | |
| } | |
| class Projectile implements AbstractObject { | |
| active = false; | |
| __poolIdx = -1; | |
| x = 0; | |
| y = 0; | |
| m_init() { | |
| this.x = Math.random(); | |
| this.y = Math.random(); | |
| } | |
| m_free() {} | |
| update() { | |
| this.x += 0.01; | |
| this.y += 0.01; | |
| return Math.sqrt(this.x * this.y); | |
| } | |
| } | |
| // --- POOL IMPLEMENTATIONS --- | |
| class OldPool<T extends AbstractObject> { | |
| m_pool: T[] = []; | |
| constructor(private classFn: new () => T) {} | |
| m_alloc() { | |
| let obj: T | null = null; | |
| for (let i = 0; i < this.m_pool.length; i++) { | |
| if (!this.m_pool[i].active) { | |
| obj = this.m_pool[i]; | |
| break; | |
| } | |
| } | |
| if (!obj) { | |
| obj = new this.classFn(); | |
| this.m_pool.push(obj); | |
| } | |
| obj.active = true; | |
| obj.m_init(); | |
| return obj; | |
| } | |
| m_free(obj: T) { | |
| obj.active = false; | |
| } | |
| m_getPool() { | |
| return this.m_pool; | |
| } | |
| } | |
| class NewPool<T extends AbstractObject> { | |
| m_pool: T[] = []; | |
| m_freeList: T[] = []; | |
| constructor(private classFn: new () => T) {} | |
| m_alloc() { | |
| let obj = this.m_freeList.pop(); | |
| if (!obj) obj = new this.classFn(); | |
| obj.active = true; | |
| obj.__poolIdx = this.m_pool.length; | |
| this.m_pool.push(obj); | |
| obj.m_init(); | |
| return obj; | |
| } | |
| m_free(obj: T) { | |
| obj.active = false; | |
| const idx = obj.__poolIdx; | |
| if (idx !== undefined && idx !== -1) { | |
| const last = this.m_pool.pop()!; | |
| if (last !== obj) { | |
| this.m_pool[idx] = last; | |
| last.__poolIdx = idx; | |
| } | |
| obj.__poolIdx = -1; | |
| } | |
| this.m_freeList.push(obj); | |
| } | |
| m_getPool() { | |
| return this.m_pool; | |
| } | |
| } | |
| // --- PLAYER STATE --- | |
| class Player { | |
| ammo = 33; | |
| reloading = 0; | |
| update(dt: number, frame: number, pool: any, activeRefs: any[]) { | |
| if (this.reloading > 0) { | |
| this.reloading -= dt; | |
| if (this.reloading <= 0) this.ammo = 33; | |
| return; | |
| } | |
| if (frame % 2 === 0) { | |
| const p = pool.m_alloc(); | |
| activeRefs.push({ obj: p, end: frame + 45 }); | |
| this.ammo--; | |
| if (this.ammo <= 0) this.reloading = 1.6; | |
| } | |
| } | |
| } | |
| function runBenchmark(label: string, pool: any) { | |
| const FPS = 60; | |
| const DURATION = 30; | |
| const players = [new Player(), new Player(), new Player(), new Player()]; | |
| const activeRefs: { obj: Projectile; end: number }[] = []; | |
| // Simulate high session churn (100,000 objects already processed) | |
| for (let i = 0; i < 100000; i++) { | |
| const p = pool.m_alloc(); | |
| pool.m_free(p); | |
| } | |
| const start = performance.now(); | |
| let sum = 0; | |
| for (let frame = 0; frame < FPS * DURATION; frame++) { | |
| for (const p of players) { | |
| p.update(1 / FPS, frame, pool, activeRefs); | |
| } | |
| const arr = pool.m_getPool(); | |
| for (let i = 0; i < arr.length; i++) { | |
| if (arr[i].active) { | |
| sum += arr[i].update(); | |
| } | |
| } | |
| while (activeRefs.length > 0 && activeRefs[0].end <= frame) { | |
| pool.m_free(activeRefs.shift()!.obj); | |
| } | |
| } | |
| const end = performance.now(); | |
| console.log(`${label}:`); | |
| console.log(`- Time: ${(end - start).toFixed(2)}ms (chk: ${sum.toFixed(0)})`); | |
| } | |
| console.log("--- 30S SQUAD FIGHT (4 Players, Vectors, Reloads, 100k processed) ---"); | |
| runBenchmark("Old Implementation", new OldPool(Projectile)); | |
| runBenchmark("New Implementation", new NewPool(Projectile)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment