UNPKG

11.8 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 */
12import { 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 */
23class 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 */
114class 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
412var _class;
413/**
414 * This stores existing object pools created for class-constructed objects.
415 *
416 * @ignore
417 */
418const 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 */
439class 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
486export { ObjectPool, ObjectPoolFactory };
487//# sourceMappingURL=pixi-object-pool.es.js.map