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 | */
|
12 | this.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 | })));
|
497 | if (typeof _pixi_essentials_object_pool !== 'undefined') { Object.assign(this.PIXI, _pixi_essentials_object_pool); }
|
498 | //# sourceMappingURL=pixi-object-pool.js.map
|