Skip to content

Instantly share code, notes, and snippets.

@mattdesl
Last active July 1, 2021 22:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mattdesl/cdabb60ba7132ea550e56b0a5c73f87f to your computer and use it in GitHub Desktop.
Save mattdesl/cdabb60ba7132ea550e56b0a5c73f87f to your computer and use it in GitHub Desktop.
object pooling for zero-allocation JS game dev

ObjectPool

A simple interface for pooling any type of object (such as a mesh, game asset, or even just a data structure of some kind).

import ObjectPool from './ObjectPool';

const geometry = new THREE.IcosahedronGeometry(1);

const pool = new ObjectPool({
  // set initial size to avoid expansion
  initialCapacity: 10,
  create () {
    // create a mesh once with a shared geometry
    const material = new THREE.MeshBasicMaterial({ color: 'red' });
    return new THREE.Mesh(geometry, material);
  },
  renew (mesh) {
    // when the mesh is requested from the pool
    // e.g. set initial random states
  },
  release (mesh) {
    // when the mesh is placed back into the pool
  }
});

// Get a mesh from the pool
const particle = pool.next();

// Do something with it

// Free them once you're finished
pool.release(particle);
// Modified version of ECSY's ObjectPool, see here:
// https://github.com/ecsyjs/ecsy/blob/dev/src/ObjectPool.js
const ret = (e) => e;
const und = () => undefined;
export default class ObjectPool {
static isObjectPool = true;
constructor(opt = {}) {
const {
renew = ret,
create = und,
release = ret,
dispose = ret,
initialCapacity,
name = undefined,
maxCapacity = Infinity,
} = opt;
this.name = name;
this._renew = renew.bind(this);
this._create = create.bind(this);
this._release = release.bind(this);
this._dispose = dispose.bind(this);
this.pool = [];
this.capacity = 0;
this.maxCapacity = maxCapacity;
if (typeof initialCapacity !== "undefined") {
this.expand(initialCapacity);
}
}
next() {
// Grow the list by 20% ish if we're out
if (this.pool.length <= 0) {
this.expand(Math.round(this.capacity * 0.2) + 1);
}
// reached max capacity and no more left...
if (this.pool.length === 0) return undefined;
const item = this.pool.pop();
this._renew(item);
return item;
}
release(item) {
this._release(item);
this.pool.push(item);
}
expand(N = 0) {
N = Math.max(0, Math.min(N, this.maxCapacity - this.capacity));
for (let n = 0; n < N; n++) {
const clone = this._create();
this.pool.push(clone);
}
this.capacity += N;
}
dispose() {
for (let i = 0; i < this.pool.length; i++) {
this._dispose(this.pool[i]);
}
this.pool.length = 0;
this.capacity = 0;
}
log () {
const name = this.name ? `${this.name} - ` : '';
console.log(`${name}Capacity: %d, Free: %d, Used: %d`, this.totalCapacity(), this.totalFree(), this.totalUsed());
}
totalCapacity() {
return this.capacity;
}
totalFree() {
return this.pool.length;
}
totalUsed() {
return this.capacity - this.pool.length;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment