-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathObjectPool.ts
221 lines (181 loc) · 5.51 KB
/
ObjectPool.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import LinkedList, { LNode } from './LinkedList';
import { describe } from './decorators';
type InitArgs<T extends PoolObject> = T['onInit'] extends (...args: infer A) => any ? A : never;
export type PoolState<T extends PoolObject> = {
pool?: ObjectPool<T>;
inUse: boolean;
readonly body: T;
head?: LNode<T>;
tail?: LNode<T>;
};
export abstract class PoolObject {
@describe({ writable: false, enumerable: false })
public readonly poolState = {
pool: undefined,
inUse: false,
body: this,
head: undefined,
tail: undefined,
} as PoolState<this>;
/** Constructor must take no arguments */
public constructor() {}
/** Initializer for when object is spawned by the pool */
public abstract onInit(...args: any[]): void;
/** Deinitializer called when object is put back into the pool or freed from memory */
public abstract onClean(): void;
}
export class ObjectPool<O extends PoolObject> {
/** Objects within the pool must inherit from ObjectPool.Object */
public static Object = PoolObject;
@describe({ enumerable: false, writable: false })
protected readonly _list = new LinkedList<O, PoolState<O>>();
@describe({ enumerable: false, writable: false })
protected readonly _Class: new () => O;
@describe({ enumerable: false })
private _max = 0;
@describe({ enumerable: false })
private _active = 0;
/** The current (max) size of the object pool */
public get size() {
return this._max;
}
/** The current count of active objects in the pool */
public get count() {
return this._active;
}
public constructor(
PoolObjectClass: new () => O,
allocSize = 0,
/** @hidden **/
additionalProps?: Record<any, any>
) {
this._Class = PoolObjectClass;
if (additionalProps) Object.assign(this, additionalProps);
if (allocSize > 0) this.alloc(allocSize);
}
public *items() {
for (const item of this._list) {
if (item.poolState.inUse) yield item;
else break;
}
}
public [Symbol.iterator]() {
return this.items();
}
/** Spawns and initializes object from pool, or create new object and increase pool size if full */
public forceSpawn(...args: InitArgs<O>): O {
let item = this._list.tail;
if (!item || item.poolState.inUse) {
item = this.create();
item.onInit(...args);
item.poolState.inUse = true;
++this._max;
this.insert(item);
} else {
item.onInit(...args);
item.poolState.inUse = true;
this._list.headNode(item.poolState);
}
++this._active;
return item;
}
/** Spawns and initializes object from pool (if any are free) */
public spawn(...args: InitArgs<O>): O | undefined {
const item = this._list.tail;
if (item && !item.poolState.inUse) {
this._list.headNode(item.poolState);
item.poolState.inUse = true;
item.onInit(...args);
++this._active;
return item;
}
return undefined;
}
/** Returns object back to pool to be re-used later */
public free(item: O) {
this._list.tailNode(item.poolState);
this.deallocateObject(item, false);
return this;
}
/** Increases pool size and allocates new objects to fill it */
public alloc(size = 1) {
if (size > 0) {
let i = -1;
this._max += size;
while (++i < size) this.add(this.create());
}
return this;
}
/** Overrides max pool size, deallocating any overflowing objects */
public reallocUnsafe(size = this._max || 1) {
if (size > this._max) {
let i = this._max - 1;
while (++i < size) this.add(this.create());
} else {
let i = this._max;
while (--i >= size) this.deallocateObject(this._list.pop(), false);
}
this._max = size;
return this;
}
/** Sets max pool size, but ignores call if provided size is smaller than current max */
public realloc(size = this._max || 1) {
if (size > this._max) {
let i = this._max - 1;
this._max = size;
while (++i < size) this.add(this.create());
}
return this;
}
/** Completely clear the pool, freeing all objects from memory */
public clear() {
return this.dealloc(this._max);
}
/** Downsize pool by specified amount, deallocating overflowing objects */
public dealloc(size = 1) {
if (size > 0) {
const m = Math.min(size, this._max);
let i = -1;
while (++i < m) this.deallocateObject(this._list.pop(), false);
this._max -= m;
}
return this;
}
/** Create and return new PoolObject */
protected create() {
const obj = new this._Class() as O;
obj.poolState.pool = this;
return obj;
}
/** Append new object to end of the pool queue */
protected add(obj: O) {
obj.poolState.head = this._list.tail?.poolState;
this._list.addNode(obj.poolState);
}
/** Insert new object into front of the pool queue */
protected insert(obj: O) {
obj.poolState.tail = this._list.head?.poolState;
this._list.insertNode(obj.poolState);
}
/** Cleans and removes object from pool entirely */
protected deallocateObject(obj?: O, del = true) {
if (obj && (!del || this._list.deleteNode(obj.poolState))) {
obj.onClean();
obj.poolState.inUse = false;
obj.poolState.pool = undefined;
obj.poolState.tail = undefined;
obj.poolState.head = undefined;
--this._active;
return true;
}
return false;
}
/** @hidden */
get [Symbol.toStringTag]() {
return `${this.constructor.name}<${this._Class.name}>(${this._max})`;
}
public toJSON() {
return [...this.items()];
}
}
export default ObjectPool;