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