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