UNPKG

12 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 */
12'use strict';
13
14Object.defineProperty(exports, '__esModule', { value: true });
15
16var 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 */
27class 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 */
118class 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
416var _class;
417/**
418 * This stores existing object pools created for class-constructed objects.
419 *
420 * @ignore
421 */
422const 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 */
443class 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
490exports.ObjectPool = ObjectPool;
491exports.ObjectPoolFactory = ObjectPoolFactory;
492//# sourceMappingURL=pixi-object-pool.js.map