UNPKG

14.1 kBJavaScriptView Raw
1/* eslint-disable */
2
3/*!
4 * @pixi-essentials/object-pool - v0.1.0
5 * Compiled Sun, 05 Mar 2023 03:07:48 UTC
6 *
7 * @pixi-essentials/object-pool is licensed under the MIT License.
8 * http://www.opensource.org/licenses/mit-license
9 *
10 * Copyright 2019-2020, Shukant Pal <shukantpal@outlook.com>, All Rights Reserved
11 */
12this.PIXI = this.PIXI || {};
13(function (global, factory) {
14 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@pixi/ticker')) :
15 typeof define === 'function' && define.amd ? define(['exports', '@pixi/ticker'], factory) :
16 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global._pixi_essentials_object_pool = {}, global.PIXI));
17}(this, (function (exports, ticker) { 'use strict';
18
19 /**
20 * Provides the exponential moving average of a sequence.
21 *
22 * Ignored because not directly exposed.
23 *
24 * @internal
25 * @ignore
26 * @class
27 */
28 class AverageProvider
29 {
30
31
32
33
34
35
36 /**
37 * @ignore
38 * @param {number} windowSize - no. of inputs used to calculate window
39 * @param {number} decayRatio - quantifies the weight of previous values (b/w 0 and 1)
40 */
41 constructor(windowSize, decayRatio)
42 {
43 this._history = new Array(windowSize);
44 this._decayRatio = decayRatio;
45
46 this._currentIndex = 0;
47
48 for (let i = 0; i < windowSize; i++)
49 {
50 this._history[i] = 0;
51 }
52 }
53
54 /**
55 * @ignore
56 * @param {number} input - the next value in the sequence
57 * @returns {number} - the moving average
58 */
59 next(input)
60 {
61 const { _history: history, _decayRatio: decayRatio } = this;
62 const historyLength = history.length;
63
64 this._currentIndex = this._currentIndex < historyLength - 1 ? this._currentIndex + 1 : 0;
65 history[this._currentIndex] = input;
66
67 let weightedSum = 0;
68 let weight = 0;
69
70 for (let i = this._currentIndex + 1; i < historyLength; i++)
71 {
72 weightedSum = (weightedSum + history[i]) * decayRatio;
73 weight = (weight + 1) * decayRatio;
74 }
75 for (let i = 0; i <= this._currentIndex; i++)
76 {
77 weightedSum = (weightedSum + history[i]) * decayRatio;
78 weight = (weight + 1) * decayRatio;
79 }
80
81 this._average = weightedSum / weight;
82
83 return this._average;
84 }
85
86 absDev()
87 {
88 let errSum = 0;
89
90 for (let i = 0, j = this._history.length; i < j; i++)
91 {
92 errSum += Math.abs(this._history[i] - this._average);
93 }
94
95 return errSum / this._history.length;
96 }
97 }
98
99 /**
100 * @interface
101 * @public
102 */
103
104
105
106
107
108
109
110 /**
111 * `ObjectPool` provides the framework necessary for pooling minus the object instantiation
112 * method. You can use `ObjectPoolFactory` for objects that can be created using a default
113 * constructor.
114 *
115 * @template T
116 * @class
117 * @public
118 */
119 class ObjectPool
120 {
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 /**
137 * @param {IObjectPoolOptions} options
138 */
139 constructor(options = {})
140 {;ObjectPool.prototype.__init.call(this);
141 /**
142 * Supply pool of objects that can be used to immediately lend.
143 *
144 * @member {Array<T>}
145 * @protected
146 */
147 this._freeList = [];
148
149 /**
150 * Number of objects in the pool. This is less than or equal to `_pool.length`.
151 *
152 * @member {number}
153 * @protected
154 */
155 this._freeCount = 0;
156
157 this._borrowRate = 0;
158 this._returnRate = 0;
159 this._flowRate = 0;
160 this._borrowRateAverage = 0;
161
162 this._reserveCount = options.reserve || 0;
163 this._capacityRatio = options.capacityRatio || 1.2;
164 this._decayRatio = options.decayRatio || 0.67;
165 this._marginAverage = 0;
166 this._borrowRateAverageProvider = new AverageProvider(128, this._decayRatio);
167 this._marginAverageProvider = new AverageProvider(128, this._decayRatio);
168 }
169
170 /**
171 * Instantiates a new object of type `T`.
172 *
173 * @abstract
174 * @returns {T}
175 */
176
177
178 // TODO: Support object destruction. It might not be so good for perf tho.
179 // /**
180 // * Destroys the object before discarding it.
181 // *
182 // * @param {T} object
183 // */
184 // abstract destroyObject(object: T): void;
185
186 /**
187 * The number of objects that can be stored in the pool without allocating more space.
188 *
189 * @member {number}
190 */
191 get capacity()
192 {
193 return this._freeList.length;
194 }
195 set capacity(cp)
196 {
197 this._freeList.length = Math.ceil(cp);
198 }
199
200 /**
201 * Obtains an instance from this pool.
202 *
203 * @returns {T}
204 */
205 allocate()
206 {
207 ++this._borrowRate;
208
209 ++this._flowRate;
210
211 if (this._freeCount > 0)
212 {
213 return this._freeList[--this._freeCount];
214 }
215
216 return this.create();
217 }
218
219 /**
220 * Obtains an array of instances from this pool. This is faster than allocating multiple objects
221 * separately from this pool.
222 *
223 * @param {number | T[]} lengthOrArray - no. of objects to allocate OR the array itself into which
224 * objects are inserted. The amount to allocate is inferred from the array's length.
225 * @returns {T[]} array of allocated objects
226 */
227 allocateArray(lengthOrArray)
228 {
229 let array;
230 let length;
231
232 if (Array.isArray(lengthOrArray))
233 {
234 array = lengthOrArray;
235 length = lengthOrArray.length;
236 }
237 else
238 {
239 length = lengthOrArray;
240 array = new Array(length);
241 }
242
243 this._borrowRate += length;
244 this._flowRate += length;
245
246 let filled = 0;
247
248 // Allocate as many objects from the existing pool
249 if (this._freeCount > 0)
250 {
251 const pool = this._freeList;
252 const poolFilled = Math.min(this._freeCount, length);
253 let poolSize = this._freeCount;
254
255 for (let i = 0; i < poolFilled; i++)
256 {
257 array[filled] = pool[poolSize - 1];
258 ++filled;
259 --poolSize;
260 }
261
262 this._freeCount = poolSize;
263 }
264
265 // Construct the rest of the allocation
266 while (filled < length)
267 {
268 array[filled] = this.create();
269 ++filled;
270 }
271
272 return array;
273 }
274
275 /**
276 * Returns the object to the pool.
277 *
278 * @param {T} object
279 */
280 release(object)
281 {
282 ++this._returnRate;
283 --this._flowRate;
284
285 if (this._freeCount === this.capacity)
286 {
287 this.capacity *= this._capacityRatio;
288 }
289
290 this._freeList[this._freeCount] = object;
291 ++this._freeCount;
292 }
293
294 /**
295 * Releases all of the objects in the passed array. These need not be allocated using `allocateArray`, however.
296 *
297 * @param {T[]} array
298 */
299 releaseArray(array)
300 {
301 this._returnRate += array.length;
302 this._flowRate -= array.length;
303
304 if (this._freeCount + array.length > this.capacity)
305 {
306 // Ensure we have enough capacity to insert the release objects
307 this.capacity = Math.max(this.capacity * this._capacityRatio, this._freeCount + array.length);
308 }
309
310 // Place objects into pool list
311 for (let i = 0, j = array.length; i < j; i++)
312 {
313 this._freeList[this._freeCount] = array[i];
314 ++this._freeCount;
315 }
316 }
317
318 /**
319 * Preallocates objects so that the pool size is at least `count`.
320 *
321 * @param {number} count
322 */
323 reserve(count)
324 {
325 this._reserveCount = count;
326
327 if (this._freeCount < count)
328 {
329 const diff = this._freeCount - count;
330
331 for (let i = 0; i < diff; i++)
332 {
333 this._freeList[this._freeCount] = this.create();
334 ++this._freeCount;
335 }
336 }
337 }
338
339 /**
340 * Dereferences objects for the GC to collect and brings the pool size down to `count`.
341 *
342 * @param {number} count
343 */
344 limit(count)
345 {
346 if (this._freeCount > count)
347 {
348 const oldCapacity = this.capacity;
349
350 if (oldCapacity > count * this._capacityRatio)
351 {
352 this.capacity = count * this._capacityRatio;
353 }
354
355 const excessBound = Math.min(this._freeCount, this.capacity);
356
357 for (let i = count; i < excessBound; i++)
358 {
359 this._freeList[i] = null;
360 }
361 }
362 }
363
364 /**
365 * Install the GC on the shared ticker.
366 *
367 * @param {Ticker}[ticker=Ticker.shared]
368 */
369 startGC(ticker$1 = ticker.Ticker.shared)
370 {
371 ticker$1.add(this._gcTick, null, ticker.UPDATE_PRIORITY.UTILITY);
372 }
373
374 /**
375 * Stops running the GC on the pool.
376 *
377 * @param {Ticker}[ticker=Ticker.shared]
378 */
379 stopGC(ticker$1 = ticker.Ticker.shared)
380 {
381 ticker$1.remove(this._gcTick);
382 }
383
384 __init() {this._gcTick = () =>
385 {
386 this._borrowRateAverage = this._borrowRateAverageProvider.next(this._borrowRate);
387 this._marginAverage = this._marginAverageProvider.next(this._freeCount - this._borrowRate);
388
389 const absDev = this._borrowRateAverageProvider.absDev();
390
391 this._flowRate = 0;
392 this._borrowRate = 0;
393 this._returnRate = 0;
394
395 const poolSize = this._freeCount;
396 const poolCapacity = this._freeList.length;
397
398 // If the pool is small enough, it shouldn't really matter
399 if (poolSize < 128 && this._borrowRateAverage < 128 && poolCapacity < 128)
400 {
401 return;
402 }
403
404 // If pool is say, 2x, larger than borrowing rate on average (adjusted for variance/abs-dev), then downsize.
405 const threshold = Math.max(this._borrowRateAverage * (this._capacityRatio - 1), this._reserveCount);
406
407 if (this._freeCount > threshold + absDev)
408 {
409 const newCap = threshold + absDev;
410
411 this.capacity = Math.min(this._freeList.length, Math.ceil(newCap));
412 this._freeCount = this._freeList.length;
413 }
414 };}
415 }
416
417 var _class;
418 /**
419 * This stores existing object pools created for class-constructed objects.
420 *
421 * @ignore
422 */
423 const poolMap = new Map();
424
425 /**
426 * Factory for creating pools of objects with default constructors. It will store the pool of
427 * a given type and reuse it on further builds.
428 *
429 * @class
430 * @public
431 * @example
432 * ```js
433 * import { ObjectPool, ObjectPoolFactory } from '@pixi-essentials/object-pool';
434 *
435 * class AABB {}
436 *
437 * const opool: ObjectPool<AABB> = ObjectPoolFactory.build(AABB) as ObjectPool<AABB>;
438 *
439 * const temp = opool.borrowObject();
440 * // do something
441 * opool.returnObject(temp);
442 * ```
443 */
444 class ObjectPoolFactory
445 {
446 /**
447 * Builds an object-pool for objects constructed from the given class with a default constructor. If an
448 * object pool for that class was already created, an existing instance is returned.
449 *
450 * @param classConstructor
451 */
452 static build(Type)
453 {
454 let pool = poolMap.get(Type);
455
456 if (pool)
457 {
458 return pool;
459 }
460
461 pool = new (class DefaultObjectPool extends ObjectPool
462 {
463 create()
464 {
465 return new Type();
466 }
467 })();
468
469 poolMap.set(Type, pool);
470
471 return pool;
472 }
473
474 /**
475 * Builds an object-pool for objects built using a factory function. The factory function's context will be the
476 * object-pool.
477 *
478 * These types of pools are not cached and should only be used on internal data structures.
479 *
480 * @param factoryFunction
481 */
482 static buildFunctional(factoryFunction)
483 {
484 return new ( (_class =class DefaultObjectPool extends ObjectPool
485 {constructor(...args) { super(...args); _class.prototype.__init.call(this); }
486 __init() {this.create = factoryFunction;}
487 }, _class))();
488 }
489 }
490
491 exports.ObjectPool = ObjectPool;
492 exports.ObjectPoolFactory = ObjectPoolFactory;
493
494 Object.defineProperty(exports, '__esModule', { value: true });
495
496})));
497if (typeof _pixi_essentials_object_pool !== 'undefined') { Object.assign(this.PIXI, _pixi_essentials_object_pool); }
498//# sourceMappingURL=pixi-object-pool.js.map