@pixi-essentials/object-pool
Version:
Custom-tailored object pool for PixiJS-based applications
308 lines (298 loc) • 10.6 kB
JavaScript
/*!
* @pixi-essentials/object-pool - v0.0.1-alpha.23
* Compiled Sat, 18 Apr 2020 16:37:59 UTC
*
* @pixi-essentials/object-pool is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
import { Ticker, UPDATE_PRIORITY } from '@pixi/ticker';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
/**
* Provides the exponential moving average of a sequence.
*
* Ignored because not directly exposed.
*
* @internal
* @ignore
* @class
*/
var AverageProvider = /** @class */ (function () {
/**
* @ignore
* @param {number} windowSize - no. of inputs used to calculate window
* @param {number} decayRatio - quantifies the weight of previous values (b/w 0 and 1)
*/
function AverageProvider(windowSize, decayRatio) {
this._history = new Array(windowSize);
this._decayRatio = decayRatio;
this._currentIndex = 0;
for (var i = 0; i < windowSize; i++) {
this._history[i] = 0;
}
}
/**
* @ignore
* @param {number} input - the next value in the sequence
* @returns {number} - the moving average
*/
AverageProvider.prototype.next = function (input) {
var _a = this, history = _a._history, decayRatio = _a._decayRatio;
var historyLength = history.length;
this._currentIndex = this._currentIndex < historyLength - 1 ? this._currentIndex + 1 : 0;
history[this._currentIndex] = input;
var weightedSum = 0;
var weight = 0;
for (var i = this._currentIndex + 1; i < historyLength; i++) {
weightedSum = (weightedSum + history[i]) * decayRatio;
weight = (weight + 1) * decayRatio;
}
for (var i = 0; i <= this._currentIndex; i++) {
weightedSum = (weightedSum + history[i]) * decayRatio;
weight = (weight + 1) * decayRatio;
}
this._average = weightedSum / weight;
return this._average;
};
AverageProvider.prototype.absDev = function () {
var errSum = 0;
for (var i = 0, j = this._history.length; i < j; i++) {
errSum += Math.abs(this._history[i] - this._average);
}
return errSum / this._history.length;
};
return AverageProvider;
}());
/**
* `ObjectPool` provides the framework necessary for pooling minus the object instantiation
* method. You can use `ObjectPoolFactory` for objects that can be created using a default
* constructor.
*
* @template T
* @class
* @public
*/
var ObjectPool = /** @class */ (function () {
/**
* @param {IObjectPoolOptions} options
*/
function ObjectPool(options) {
var _this = this;
if (options === void 0) { options = {}; }
this._gcTick = function () {
_this._borrowRateAverage = _this._borrowRateAverageProvider.next(_this._borrowRate);
_this._marginAverage = _this._marginAverageProvider.next(_this._freeCount - _this._borrowRate);
var absDev = _this._borrowRateAverageProvider.absDev();
_this._flowRate = 0;
_this._borrowRate = 0;
_this._returnRate = 0;
var poolSize = _this._freeCount;
var poolCapacity = _this._freeList.length;
// If the pool is small enough, it shouldn't really matter
if (poolSize < 128 && _this._borrowRateAverage < 128 && poolCapacity < 128) {
return;
}
// If pool is say, 2x, larger than borrowing rate on average (adjusted for variance/abs-dev), then downsize.
var threshold = Math.max(_this._borrowRateAverage * (_this._capacityRatio - 1), _this._reserveCount);
if (_this._freeCount > threshold + absDev) {
var newCap = threshold + absDev;
_this.capacity = Math.min(_this._freeList.length, Math.ceil(newCap));
_this._freeCount = _this._freeList.length;
}
};
/**
* Supply pool of objects that can be used to immediately lend.
*
* @member {Array<T>}
* @protected
*/
this._freeList = [];
/**
* Number of objects in the pool. This is less than or equal to `_pool.length`.
*
* @member {number}
* @protected
*/
this._freeCount = 0;
this._borrowRate = 0;
this._returnRate = 0;
this._flowRate = 0;
this._borrowRateAverage = 0;
this._reserveCount = options.reserve || 0;
this._capacityRatio = options.capacityRatio || 1.2;
this._decayRatio = options.decayRatio || 0.67;
this._marginAverage = 0;
this._borrowRateAverageProvider = new AverageProvider(128, this._decayRatio);
this._marginAverageProvider = new AverageProvider(128, this._decayRatio);
}
Object.defineProperty(ObjectPool.prototype, "capacity", {
// TODO: Support object destruction. It might not be so good for perf tho.
// /**
// * Destroys the object before discarding it.
// *
// * @param {T} object
// */
// abstract destroyObject(object: T): void;
/**
* The number of objects that can be stored in the pool without allocating more space.
*
* @member {number}
*/
get: function () {
return this._freeList.length;
},
set: function (cp) {
this._freeList.length = Math.ceil(cp);
},
enumerable: true,
configurable: true
});
/**
* Obtains an instance from this pool.
*
* @returns {T}
*/
ObjectPool.prototype.allocate = function () {
++this._borrowRate;
++this._flowRate;
if (this._freeCount > 0) {
return this._freeList[--this._freeCount];
}
return this.create();
};
/**
* Returns the object to the pool.
*
* @param {T} object
*/
ObjectPool.prototype.release = function (object) {
++this._returnRate;
--this._flowRate;
if (this._freeCount === this.capacity) {
this.capacity *= this._capacityRatio;
}
this._freeList[this._freeCount] = object;
++this._freeCount;
};
/**
* Preallocates objects so that the pool size is at least `count`.
*
* @param {number} count
*/
ObjectPool.prototype.reserve = function (count) {
this._reserveCount = count;
if (this._freeCount < count) {
var diff = this._freeCount - count;
for (var i = 0; i < diff; i++) {
this._freeList[this._freeCount] = this.create();
++this._freeCount;
}
}
};
/**
* Dereferences objects for the GC to collect and brings the pool size down to `count`.
*
* @param {number} count
*/
ObjectPool.prototype.limit = function (count) {
if (this._freeCount > count) {
var oldCapacity = this.capacity;
if (oldCapacity > count * this._capacityRatio) {
this.capacity = count * this._capacityRatio;
}
var excessBound = Math.min(this._freeCount, this.capacity);
for (var i = count; i < excessBound; i++) {
this._freeList[i] = null;
}
}
};
/**
* Install the GC on the shared ticker.
*
* @param {Ticker}[ticker=Ticker.shared]
*/
ObjectPool.prototype.startGC = function (ticker) {
if (ticker === void 0) { ticker = Ticker.shared; }
ticker.add(this._gcTick, null, UPDATE_PRIORITY.UTILITY);
};
/**
* Stops running the GC on the pool.
*
* @param {Ticker}[ticker=Ticker.shared]
*/
ObjectPool.prototype.stopGC = function (ticker) {
if (ticker === void 0) { ticker = Ticker.shared; }
ticker.remove(this._gcTick);
};
return ObjectPool;
}());
var poolMap = new Map();
/**
* Factory for creating pools of objects with default constructors. It will store the pool of
* a given type and reuse it on further builds.
*
* @class
* @public
* @example
* ```js
* import { ObjectPool, ObjectPoolFactory } from 'pixi-object-pool';
*
* class AABB {}
*
* const opool: ObjectPool<AABB> = ObjectPoolFactory.build(AABB) as ObjectPool<AABB>;
*
* const temp = opool.borrowObject();
* // do something
* opool.returnObject(temp);
* ```
*/
var ObjectPoolFactory = /** @class */ (function () {
function ObjectPoolFactory() {
}
/**
* @param {Class} Type
*/
ObjectPoolFactory.build = function (Type) {
var pool = poolMap.get(Type);
if (pool) {
return pool;
}
pool = new (/** @class */ (function (_super) {
__extends(DefaultObjectPool, _super);
function DefaultObjectPool() {
return _super !== null && _super.apply(this, arguments) || this;
}
DefaultObjectPool.prototype.create = function () {
return new Type();
};
return DefaultObjectPool;
}(ObjectPool)))();
poolMap.set(Type, pool);
return pool;
};
return ObjectPoolFactory;
}());
export { ObjectPool, ObjectPoolFactory };
//# sourceMappingURL=pixi-object-pool.mjs.map