Skip to content

Instantly share code, notes, and snippets.

@EvidentlyCube
Last active April 26, 2019 10:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EvidentlyCube/1e6794756938eface0bbf7163511bf2f to your computer and use it in GitHub Desktop.
Save EvidentlyCube/1e6794756938eface0bbf7163511bf2f to your computer and use it in GitHub Desktop.
PIXI Optimized add/remove child container
import * as PIXI from 'pixi.js';
// Caution #1: it's written in TypeScript!
// Caution #2: it uses the fast element removal method (move last element into removed element's place and change array length)
// which changes the order of items in the array, so your rendering order will be affected
// Caution #3: events are not triggered
export class BufferableContainer extends PIXI.Container
{
private readonly childrenToAdd: PIXI.DisplayObject[];
private readonly childrenToRemove: PIXI.DisplayObject[];
private readonly bucketWidth: number;
constructor(bucketWidth: number = 24)
{
super();
// To optimize the removals we'll group all planned removals into groups based on their horizontal position
// So `bucketWidth` equal to 24 means that all children which have x between 0 and 24 (exclusive) will
// go to bucket #0, 24-48 to bucket #1 and so on
this.bucketWidth = bucketWidth;
this.childrenToAdd = [];
this.childrenToRemove = [];
}
public bufferAddChild(child: PIXI.DisplayObject)
{
this.childrenToAdd.push(child);
}
public bufferRemoveChild(child: PIXI.DisplayObject)
{
this.childrenToRemove.push(child);
}
public flushChanges()
{
this.flushRemovals();
this.flushAdditions();
this.childrenToAdd.length = 0;
this.childrenToRemove.length = 0;
this._boundsID++;
}
private flushRemovals()
{
const buckets = this.buildRemovalBucket();
// 1. Iterate through each child
// 2. Find the bucket to which the child would belong to
// 3. Check against all children in the bucket
// 4. If something is found in the bucket, use fast-removal, stop checking this bucket and go to the next child
// 5. Once everything is removed, change the children array length
// A possible optimization would be to also shrink the bucket after an item from it is used, but I figured it'd be such a negligible
// change I didn't implement it in my case.
// Also notice that the iteration is inverted - for each child in the container I see if it's supposed to be removed.
// This won't work that well if you work with small number of removed elements, but could be easily optimized
// to use the old method if there aren't too many things to remove.
let newLength = this.children.length;
for(let i = 0; i < newLength; i++) {
const child = this.children[i];
const bucketId = (child.x / this.bucketWidth) | 0;
const bucket = buckets[bucketId] || [];
for(let j = 0; j < bucket.length; j++) {
const childToRemove = bucket[j];
if (child === childToRemove) {
(child as any).parent = null;
(child.transform as any)._parentID = -1;
this.children[i--] = this.children[--newLength];
break;
}
}
}
this.children.length = newLength;
}
private buildRemovalBucket()
{
const buckets: PIXI.DisplayObject[][] = [];
for(let i = 0; i < this.childrenToRemove.length; i++) {
const child = this.childrenToRemove[i];
// ` <something> | 0` is marginally faster than flooring (microoptimization) but is also nicer to the eye: https://jsperf.com/floor-or-or/6
const bucketId = (child.x / this.bucketWidth) | 0;
if (!buckets[bucketId]) {
buckets[bucketId] = [];
}
buckets[bucketId].push(child);
}
return buckets;
}
private flushAdditions()
{
for(let i = 0; i < this.childrenToAdd.length; i++) {
const child: PIXI.DisplayObject = this.childrenToAdd[i];
if (child.parent) {
if (child.parent === this) {
continue;
}
child.parent.removeChild(child);
}
child.parent = this;
(child.transform as any)._parentID = -1;
this.children.push(child);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment