@x-oasis/integer-buffer-set 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/index.d.ts +59 -23
- package/dist/integer-buffer-set.cjs.development.js +442 -79
- package/dist/integer-buffer-set.cjs.development.js.map +1 -1
- package/dist/integer-buffer-set.cjs.production.min.js +1 -1
- package/dist/integer-buffer-set.cjs.production.min.js.map +1 -1
- package/dist/integer-buffer-set.esm.js +442 -79
- package/dist/integer-buffer-set.esm.js.map +1 -1
- package/dist/types.d.ts +24 -0
- package/package.json +6 -2
- package/src/index.ts +665 -131
- package/src/types.ts +36 -0
package/src/index.ts
CHANGED
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
// import invariant from 'invariant';
|
|
2
1
|
import Heap from '@x-oasis/heap';
|
|
2
|
+
import isClamped from '@x-oasis/is-clamped';
|
|
3
|
+
import invariant from '@x-oasis/invariant';
|
|
4
|
+
import returnHook, { ReturnHook } from '@x-oasis/return-hook';
|
|
5
|
+
import {
|
|
6
|
+
HeapItem,
|
|
7
|
+
SafeRange,
|
|
8
|
+
MetaExtractor,
|
|
9
|
+
IndexExtractor,
|
|
10
|
+
IntegerBufferSetProps,
|
|
11
|
+
MetaToIndexMap,
|
|
12
|
+
MetaToPositionMap,
|
|
13
|
+
IndexToMetaMap,
|
|
14
|
+
} from './types';
|
|
3
15
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
lowValue: number;
|
|
12
|
-
highValue: number;
|
|
13
|
-
};
|
|
14
|
-
bufferSetRange: {
|
|
15
|
-
maxValue: number;
|
|
16
|
-
minValue: number;
|
|
17
|
-
};
|
|
18
|
-
currentIndex: number;
|
|
19
|
-
}) => {
|
|
20
|
-
const { safeRange, bufferSetRange } = options;
|
|
21
|
-
const { lowValue, highValue } = safeRange;
|
|
22
|
-
const { maxValue, minValue } = bufferSetRange;
|
|
23
|
-
return lowValue - minValue > maxValue - highValue;
|
|
24
|
-
};
|
|
16
|
+
const defaultMetaExtractor = (value) => value;
|
|
17
|
+
export const defaultBufferSize = 10;
|
|
18
|
+
|
|
19
|
+
// !!!!! should do meta validation...meta should has an index...
|
|
20
|
+
// value: original data `index` value
|
|
21
|
+
// value(index) => meta => position
|
|
22
|
+
// `index to getIndices, meta to find index`
|
|
25
23
|
|
|
26
24
|
// Data structure that allows to store values and assign positions to them
|
|
27
25
|
// in a way to minimize changing positions of stored values when new ones are
|
|
@@ -34,64 +32,156 @@ const defaultUseMinValueFn = (options: {
|
|
|
34
32
|
// and get it's position back
|
|
35
33
|
// All operations take amortized log(n) time where n is number of elements in
|
|
36
34
|
// the set.
|
|
37
|
-
|
|
35
|
+
// feature: add / delete / update item will also in consider..
|
|
36
|
+
class IntegerBufferSet<Meta = any> {
|
|
38
37
|
private _size: number;
|
|
39
|
-
private
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
private _name: string;
|
|
39
|
+
private _bufferSize: number;
|
|
40
|
+
// private _positionToValueObject: ValueToPositionObject;
|
|
41
|
+
|
|
42
|
+
private _indexToMetaMap: IndexToMetaMap<Meta>;
|
|
43
|
+
private _metaToPositionMap: MetaToPositionMap<Meta>;
|
|
44
|
+
private _positionToMetaList: Array<Meta>;
|
|
45
|
+
private _metaToIndexMap: MetaToIndexMap<Meta>;
|
|
46
|
+
|
|
42
47
|
private _smallValues: Heap<HeapItem>;
|
|
43
48
|
private _largeValues: Heap<HeapItem>;
|
|
44
|
-
private
|
|
49
|
+
private _metaExtractor: MetaExtractor<Meta>;
|
|
50
|
+
private _indexExtractor: IndexExtractor<Meta>;
|
|
51
|
+
|
|
52
|
+
private _onTheFlyIndices: Array<Meta>;
|
|
53
|
+
|
|
54
|
+
private _isOnTheFlyFull: boolean;
|
|
55
|
+
private _isOnTheFlyFullReturnHook: ReturnHook;
|
|
56
|
+
|
|
57
|
+
private _loopMS: number;
|
|
58
|
+
private _lastUpdatedMS: number;
|
|
59
|
+
|
|
60
|
+
constructor(props: IntegerBufferSetProps<Meta> = {}) {
|
|
61
|
+
const {
|
|
62
|
+
name = 'default_buffer',
|
|
63
|
+
indexExtractor,
|
|
64
|
+
bufferSize = defaultBufferSize,
|
|
65
|
+
metaExtractor = defaultMetaExtractor,
|
|
66
|
+
} = props;
|
|
67
|
+
this._metaExtractor = metaExtractor;
|
|
68
|
+
this._indexExtractor = indexExtractor;
|
|
69
|
+
|
|
70
|
+
this._name = name;
|
|
71
|
+
// this._positionToValueObject = {};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* this._indexToMetaMap is used to find the prev meta when finding a position for index.
|
|
75
|
+
*/
|
|
76
|
+
this._indexToMetaMap = new Map();
|
|
77
|
+
this._metaToPositionMap = new Map();
|
|
78
|
+
this._positionToMetaList = [];
|
|
79
|
+
this._metaToIndexMap = new Map();
|
|
80
|
+
this._onTheFlyIndices = [];
|
|
45
81
|
|
|
46
|
-
constructor() {
|
|
47
|
-
this._valueToPositionMap = {};
|
|
48
82
|
this._size = 0;
|
|
83
|
+
this._bufferSize = bufferSize;
|
|
84
|
+
|
|
49
85
|
this._smallValues = new Heap([], this._smallerComparator);
|
|
50
86
|
this._largeValues = new Heap([], this._greaterComparator);
|
|
51
87
|
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
88
|
+
this.getNewPositionForIndex = this.getNewPositionForIndex.bind(this);
|
|
89
|
+
this.getIndexPosition = this.getIndexPosition.bind(this);
|
|
54
90
|
this.getSize = this.getSize.bind(this);
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
91
|
+
this.replacePositionInFliedIndices =
|
|
92
|
+
this.replacePositionInFliedIndices.bind(this);
|
|
93
|
+
this.replaceFurthestIndexPosition =
|
|
94
|
+
this.replaceFurthestIndexPosition.bind(this);
|
|
95
|
+
this._isOnTheFlyFullReturnHook = returnHook(
|
|
96
|
+
this.setIsOnTheFlyFull.bind(this)
|
|
97
|
+
);
|
|
57
98
|
|
|
58
|
-
this.
|
|
99
|
+
this._loopMS = Date.now();
|
|
100
|
+
this._lastUpdatedMS = this._loopMS;
|
|
59
101
|
}
|
|
60
102
|
|
|
61
103
|
getSize() {
|
|
62
104
|
return this._size;
|
|
63
105
|
}
|
|
64
106
|
|
|
65
|
-
get
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
107
|
+
get bufferSize() {
|
|
108
|
+
return this._bufferSize;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setIsOnTheFlyFull(val: any) {
|
|
112
|
+
if (val != null) {
|
|
113
|
+
const data = this._onTheFlyIndices.filter((v) => v);
|
|
114
|
+
this._isOnTheFlyFull = data.length === this._bufferSize;
|
|
70
115
|
}
|
|
71
|
-
return indices;
|
|
72
116
|
}
|
|
73
117
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
118
|
+
get isBufferFull() {
|
|
119
|
+
return this._positionToMetaList.length >= this._bufferSize;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getOnTheFlyUncriticalPosition(safeRange: SafeRange) {
|
|
123
|
+
const { startIndex, endIndex } = safeRange;
|
|
124
|
+
for (let idx = 0; idx < this._onTheFlyIndices.length; idx++) {
|
|
125
|
+
const meta = this._onTheFlyIndices[idx];
|
|
126
|
+
const metaIndex = this._metaToIndexMap.get(meta);
|
|
127
|
+
if (!isClamped(startIndex, metaIndex, endIndex)) {
|
|
128
|
+
return idx;
|
|
129
|
+
}
|
|
77
130
|
}
|
|
78
|
-
return
|
|
131
|
+
return null;
|
|
79
132
|
}
|
|
80
133
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
initialize() {
|
|
135
|
+
return {
|
|
136
|
+
smallValues: new Heap([], this._smallerComparator),
|
|
137
|
+
largeValues: new Heap([], this._greaterComparator),
|
|
138
|
+
valueToPositionObject: {},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getIndexMeta(index: number) {
|
|
143
|
+
return this._metaExtractor(index);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getMetaIndex(meta: Meta) {
|
|
147
|
+
if (this._indexExtractor) return this._indexExtractor(meta);
|
|
148
|
+
return this._metaToIndexMap.get(meta);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setMetaIndex(meta: Meta, index: number) {
|
|
152
|
+
if (!this._indexExtractor) {
|
|
153
|
+
return this._metaToIndexMap.set(meta, index);
|
|
86
154
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
deleteMetaIndex(meta: Meta) {
|
|
159
|
+
return this._metaToIndexMap.delete(meta);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
replaceMetaToIndexMap(newMetaToIndexMap: MetaToIndexMap<Meta>) {
|
|
163
|
+
if (!this._indexExtractor) {
|
|
164
|
+
return (this._metaToIndexMap = newMetaToIndexMap);
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getIndexPosition(index: number): undefined | number {
|
|
170
|
+
return this.getMetaIndex(this.getIndexMeta(index));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
getNewPositionForIndex(index: number) {
|
|
174
|
+
const meta = this.getIndexMeta(index);
|
|
175
|
+
invariant(
|
|
176
|
+
this._metaToPositionMap.get(meta) === undefined,
|
|
177
|
+
"Shouldn't try to find new position for value already stored in BufferSet"
|
|
178
|
+
);
|
|
179
|
+
const newPosition = this._positionToMetaList.length;
|
|
180
|
+
|
|
181
|
+
this._pushToHeaps(newPosition, index);
|
|
182
|
+
this._setMetaIndex(meta, index);
|
|
183
|
+
this._setMetaPosition(meta, newPosition);
|
|
184
|
+
|
|
95
185
|
return newPosition;
|
|
96
186
|
}
|
|
97
187
|
|
|
@@ -103,110 +193,483 @@ class IntegerBufferSet {
|
|
|
103
193
|
return this._largeValues.peek()?.value;
|
|
104
194
|
}
|
|
105
195
|
|
|
106
|
-
|
|
107
|
-
|
|
196
|
+
/**
|
|
197
|
+
* values actually is the position of original data.
|
|
198
|
+
*/
|
|
199
|
+
setValuePosition(value: number, position: number) {}
|
|
200
|
+
|
|
201
|
+
findPositionMeta(position: number) {
|
|
202
|
+
for (const [meta, pos] of this._metaToPositionMap) {
|
|
203
|
+
if (pos === position) return meta;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
rebuildHeapsWithMeta(metaToPositionMap: MetaToPositionMap<Meta>) {
|
|
209
|
+
const { smallValues, largeValues } = this.initialize();
|
|
210
|
+
|
|
211
|
+
for (const [meta, position] of metaToPositionMap) {
|
|
212
|
+
const index = this.getMetaIndex(meta);
|
|
213
|
+
const token = { index, position };
|
|
214
|
+
smallValues.push(token);
|
|
215
|
+
largeValues.push(token);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this._smallValues = smallValues;
|
|
219
|
+
this._largeValues = largeValues;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
*
|
|
224
|
+
* @param position
|
|
225
|
+
* @param value
|
|
226
|
+
*
|
|
227
|
+
*
|
|
228
|
+
*/
|
|
229
|
+
setPositionIndex(position: number, index: number) {
|
|
230
|
+
const meta = this._metaExtractor(index);
|
|
231
|
+
const originalPosition = this._metaToPositionMap.get(meta);
|
|
232
|
+
|
|
233
|
+
// current index has a position
|
|
108
234
|
if (originalPosition !== undefined) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
);
|
|
112
|
-
if (index === -1) this._vacantPositions.push(originalPosition);
|
|
113
|
-
delete this._valueToPositionMap[value];
|
|
114
|
-
this._valueToPositionMap[value] = position;
|
|
235
|
+
if (originalPosition === position) return true;
|
|
236
|
+
this.deleteMetaIndex(meta);
|
|
115
237
|
}
|
|
238
|
+
|
|
239
|
+
const metaToReplace = this.findPositionMeta(position);
|
|
240
|
+
if (metaToReplace) this._metaToPositionMap.delete(metaToReplace);
|
|
241
|
+
this._metaToPositionMap.set(meta, position);
|
|
242
|
+
|
|
243
|
+
this.rebuildHeapsWithMeta(this._metaToPositionMap);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getMetaPosition(meta: Meta) {
|
|
248
|
+
return this._metaToPositionMap.get(meta);
|
|
116
249
|
}
|
|
117
250
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
251
|
+
// performRangeUpdate(
|
|
252
|
+
// startIndex: number,
|
|
253
|
+
// endIndex: number,
|
|
254
|
+
// safeRange: {
|
|
255
|
+
// startIndex: number;
|
|
256
|
+
// endIndex: number;
|
|
257
|
+
// }
|
|
258
|
+
// ) {
|
|
259
|
+
// const _start = Math.max(startIndex, safeRange.startIndex);
|
|
260
|
+
// const _end = Math.min(endIndex, safeRange.endIndex);
|
|
261
|
+
// const primaryMetaList = [];
|
|
262
|
+
// const secondaryMetaList = [];
|
|
263
|
+
// const locationStartIndex = startIndex;
|
|
264
|
+
// const targetIndices = new Array(this._bufferSize);
|
|
265
|
+
|
|
266
|
+
// const _valueToPositionObject = {};
|
|
267
|
+
// const _positionToValueObject = {};
|
|
268
|
+
|
|
269
|
+
// const _valueToMetaObject = {};
|
|
270
|
+
// const _metaToIndexMap = new Map();
|
|
271
|
+
|
|
272
|
+
// for (let value = startIndex; value <= endIndex; value++) {
|
|
273
|
+
// const meta = this._metaExtractor(value);
|
|
274
|
+
// if (meta) {
|
|
275
|
+
// const _i = value - locationStartIndex;
|
|
276
|
+
// if (isClamped(value, safeRange.startIndex, safeRange.endIndex)) {
|
|
277
|
+
// primaryMetaList[_i] = meta;
|
|
278
|
+
// const targetIndex = this.getMetaPosition(meta);
|
|
279
|
+
// if (isNumber(targetIndex)) {
|
|
280
|
+
// targetIndices[targetIndex] = value;
|
|
281
|
+
// _valueToPositionObject[value] = targetIndex;
|
|
282
|
+
// _valueToMetaObject[value] = meta;
|
|
283
|
+
// _metaToIndexMap.set(meta, value);
|
|
284
|
+
// _positionToValueObject[targetIndex] = value;
|
|
285
|
+
// }
|
|
286
|
+
// } else {
|
|
287
|
+
// secondaryMetaList[_i] = meta;
|
|
288
|
+
// }
|
|
289
|
+
// }
|
|
290
|
+
// }
|
|
291
|
+
|
|
292
|
+
// for (let idx = _start; idx <= _end; idx++) {
|
|
293
|
+
// const meta = this._metaExtractor(idx);
|
|
294
|
+
// if (_metaToIndexMap.get(meta) !== undefined) continue;
|
|
295
|
+
// let p;
|
|
296
|
+
// while (
|
|
297
|
+
// (p =
|
|
298
|
+
// targetIndices[
|
|
299
|
+
// this.resolvePosition(safeRange.startIndex, safeRange.endIndex, idx)
|
|
300
|
+
// ]) === undefined
|
|
301
|
+
// ) {
|
|
302
|
+
// targetIndices[p] = idx;
|
|
303
|
+
// }
|
|
304
|
+
// }
|
|
305
|
+
// }
|
|
306
|
+
|
|
307
|
+
replacePositionInFliedIndices(newIndex: number, safeRange: SafeRange) {
|
|
308
|
+
const { startIndex, endIndex } = safeRange;
|
|
309
|
+
|
|
310
|
+
if (this._isOnTheFlyFull) {
|
|
311
|
+
// newIndex is not critical index, do nothing
|
|
312
|
+
if (!isClamped(startIndex, newIndex, endIndex)) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
// if `newIndex` is critical index, replace an un-committed
|
|
316
|
+
// index value from _onTheFlyIndices.
|
|
317
|
+
const pos = this.getOnTheFlyUncriticalPosition(safeRange);
|
|
318
|
+
if (pos != null) return pos;
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
getFliedPosition(newIndex: number, safeRange: SafeRange) {
|
|
324
|
+
const pos = this.replacePositionInFliedIndices(newIndex, safeRange);
|
|
325
|
+
if (pos != null) {
|
|
326
|
+
const meta = this.getIndexMeta(newIndex);
|
|
327
|
+
this._onTheFlyIndices[pos] = meta;
|
|
328
|
+
this._setMetaIndex(meta, newIndex);
|
|
329
|
+
return this._isOnTheFlyFullReturnHook(pos);
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
*
|
|
336
|
+
* @param newIndex
|
|
337
|
+
* @param safeRange
|
|
338
|
+
* @returns
|
|
339
|
+
*
|
|
340
|
+
*
|
|
341
|
+
* _positionToMetaList maybe undefined on next loop
|
|
342
|
+
*/
|
|
343
|
+
getPosition(newIndex: number, safeRange?: SafeRange) {
|
|
344
|
+
this.prepare();
|
|
345
|
+
const meta = this.getIndexMeta(newIndex);
|
|
346
|
+
const prevMetaPosition = this._metaToPositionMap.get(meta);
|
|
347
|
+
|
|
348
|
+
if (prevMetaPosition !== undefined) {
|
|
349
|
+
const onTheFlyPositionMeta = this._onTheFlyIndices[prevMetaPosition];
|
|
350
|
+
// the occupied meta should change position
|
|
351
|
+
if (onTheFlyPositionMeta) {
|
|
352
|
+
// such as place item 11 twice...
|
|
353
|
+
if (onTheFlyPositionMeta === meta) {
|
|
354
|
+
return prevMetaPosition;
|
|
355
|
+
}
|
|
356
|
+
let positionToReplace = this._replaceFurthestIndexPosition(
|
|
357
|
+
newIndex,
|
|
358
|
+
safeRange
|
|
359
|
+
);
|
|
360
|
+
if (this._isOnTheFlyFull)
|
|
361
|
+
return this.getFliedPosition(newIndex, safeRange);
|
|
362
|
+
|
|
363
|
+
while (this._onTheFlyIndices[positionToReplace]) {
|
|
364
|
+
positionToReplace = this._replaceFurthestIndexPosition(
|
|
365
|
+
newIndex,
|
|
366
|
+
safeRange
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (positionToReplace != null) {
|
|
371
|
+
this._setMetaIndex(meta, newIndex);
|
|
372
|
+
this._onTheFlyIndices[positionToReplace] = onTheFlyPositionMeta;
|
|
373
|
+
return this._isOnTheFlyFullReturnHook(positionToReplace);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
this._onTheFlyIndices[prevMetaPosition] = meta;
|
|
377
|
+
return this._isOnTheFlyFullReturnHook(prevMetaPosition);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// placed on new buffered position
|
|
381
|
+
if (!this.isBufferFull)
|
|
382
|
+
return this._isOnTheFlyFullReturnHook(
|
|
383
|
+
this.getNewPositionForIndex(newIndex)
|
|
138
384
|
);
|
|
385
|
+
|
|
386
|
+
// console.log('this. fly ', this._isOnTheFlyFull)
|
|
387
|
+
if (this._isOnTheFlyFull) return this.getFliedPosition(newIndex, safeRange);
|
|
388
|
+
|
|
389
|
+
let positionToReplace;
|
|
390
|
+
const prevIndexMeta = this._indexToMetaMap.get(newIndex);
|
|
391
|
+
// console.log('this. is ', this.isBufferFull, prevIndexMeta);
|
|
392
|
+
|
|
393
|
+
// Index has already been stored, but we cant use its old position directly...
|
|
394
|
+
// 1:index -> meta, meta may be reused later
|
|
395
|
+
|
|
396
|
+
// 2: temp use index -> meta -> position, this issue should exist for follows...
|
|
397
|
+
if (!prevIndexMeta) {
|
|
398
|
+
this._cleanHeaps();
|
|
399
|
+
positionToReplace = this._replaceFurthestIndexPosition(
|
|
400
|
+
newIndex,
|
|
401
|
+
safeRange
|
|
402
|
+
);
|
|
403
|
+
} else {
|
|
404
|
+
positionToReplace = this._metaToPositionMap.get(prevIndexMeta);
|
|
139
405
|
}
|
|
140
|
-
// invariant(
|
|
141
|
-
// this._valueToPositionMap[newValue] === undefined,
|
|
142
|
-
// "Shouldn't try to replace values with value already stored value in " +
|
|
143
|
-
// 'BufferSet'
|
|
144
|
-
// );
|
|
145
406
|
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
407
|
+
this._onTheFlyIndices[positionToReplace] = meta;
|
|
408
|
+
this._setMetaIndex(meta, newIndex);
|
|
409
|
+
this._setMetaPosition(meta, positionToReplace);
|
|
410
|
+
// should not push to heap, pop only
|
|
411
|
+
// this._pushToHeaps(positionToReplace, newIndex)
|
|
412
|
+
|
|
413
|
+
return this._isOnTheFlyFullReturnHook(positionToReplace);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
replaceFurthestIndexPosition(
|
|
417
|
+
newIndex: number,
|
|
418
|
+
safeRange?: {
|
|
419
|
+
startIndex: number;
|
|
420
|
+
endIndex: number;
|
|
421
|
+
}
|
|
422
|
+
) {
|
|
423
|
+
if (!this.isBufferFull) {
|
|
424
|
+
return this._isOnTheFlyFullReturnHook(
|
|
425
|
+
this.getNewPositionForIndex(newIndex)
|
|
426
|
+
);
|
|
151
427
|
}
|
|
152
428
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
429
|
+
return this._replaceFurthestIndexPosition(newIndex, safeRange);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
_replaceFurthestIndexPosition(
|
|
433
|
+
newIndex: number,
|
|
434
|
+
safeRange?: {
|
|
435
|
+
startIndex: number;
|
|
436
|
+
endIndex: number;
|
|
437
|
+
}
|
|
438
|
+
) {
|
|
439
|
+
if (this._largeValues.empty() || this._smallValues.empty()) {
|
|
440
|
+
return this._isOnTheFlyFullReturnHook(
|
|
441
|
+
this.getNewPositionForIndex(newIndex)
|
|
442
|
+
);
|
|
158
443
|
}
|
|
159
444
|
|
|
160
445
|
const minValue = this._smallValues.peek()!.value;
|
|
161
446
|
const maxValue = this._largeValues.peek()!.value;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
447
|
+
|
|
448
|
+
// console.log('mxa ', maxValue, minValue);
|
|
449
|
+
let indexToReplace;
|
|
450
|
+
|
|
451
|
+
if (!safeRange) {
|
|
452
|
+
// far from min
|
|
453
|
+
if (Math.abs(newIndex - minValue) > Math.abs(newIndex - maxValue)) {
|
|
454
|
+
indexToReplace = minValue;
|
|
455
|
+
this._smallValues.pop();
|
|
456
|
+
} else {
|
|
457
|
+
indexToReplace = maxValue;
|
|
458
|
+
this._largeValues.pop();
|
|
459
|
+
}
|
|
460
|
+
const replacedMeta = this._indexToMetaMap.get(indexToReplace);
|
|
461
|
+
const position = this._metaToPositionMap.get(replacedMeta);
|
|
462
|
+
|
|
463
|
+
return position;
|
|
165
464
|
}
|
|
166
465
|
|
|
167
|
-
|
|
466
|
+
const { startIndex: lowValue, endIndex: highValue } = safeRange;
|
|
467
|
+
|
|
468
|
+
// All values currently stored are necessary, we can't reuse any of them.
|
|
168
469
|
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
maxValue,
|
|
177
|
-
},
|
|
178
|
-
currentIndex: newValue,
|
|
179
|
-
})
|
|
470
|
+
isClamped(lowValue, minValue, highValue) &&
|
|
471
|
+
isClamped(lowValue, maxValue, highValue)
|
|
472
|
+
) {
|
|
473
|
+
return null;
|
|
474
|
+
} else if (
|
|
475
|
+
isClamped(lowValue, minValue, highValue) &&
|
|
476
|
+
!isClamped(lowValue, maxValue, highValue)
|
|
180
477
|
) {
|
|
181
|
-
|
|
478
|
+
indexToReplace = maxValue;
|
|
479
|
+
this._largeValues.pop();
|
|
480
|
+
} else if (
|
|
481
|
+
!isClamped(lowValue, minValue, highValue) &&
|
|
482
|
+
isClamped(lowValue, maxValue, highValue)
|
|
483
|
+
) {
|
|
484
|
+
indexToReplace = minValue;
|
|
485
|
+
this._smallValues.pop();
|
|
486
|
+
} else if (lowValue - minValue > maxValue - highValue) {
|
|
182
487
|
// minValue is further from provided range. We will reuse it's position.
|
|
183
|
-
|
|
488
|
+
indexToReplace = minValue;
|
|
184
489
|
this._smallValues.pop();
|
|
185
490
|
} else {
|
|
186
|
-
|
|
491
|
+
indexToReplace = maxValue;
|
|
187
492
|
this._largeValues.pop();
|
|
188
493
|
}
|
|
189
|
-
const position = this._valueToPositionMap[valueToReplace];
|
|
190
|
-
delete this._valueToPositionMap[valueToReplace];
|
|
191
|
-
this._valueToPositionMap[newValue] = position;
|
|
192
|
-
this._pushToHeaps(position, newValue);
|
|
193
494
|
|
|
194
|
-
const
|
|
195
|
-
|
|
495
|
+
const replacedMeta = this._indexToMetaMap.get(indexToReplace);
|
|
496
|
+
const position = this._metaToPositionMap.get(replacedMeta);
|
|
196
497
|
|
|
197
498
|
return position;
|
|
198
499
|
}
|
|
199
500
|
|
|
501
|
+
shuffle() {
|
|
502
|
+
const indices = new Array(this.bufferSize);
|
|
503
|
+
for (let idx = 0; idx < indices.length; idx++) {
|
|
504
|
+
const meta = this._onTheFlyIndices[idx] || this._positionToMetaList[idx];
|
|
505
|
+
const targetIndex = this.getMetaIndex(meta);
|
|
506
|
+
indices[idx] = targetIndex;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// console.log(
|
|
510
|
+
// 'position xxx ',
|
|
511
|
+
// this._positionToMetaList,
|
|
512
|
+
// this._onTheFlyIndices
|
|
513
|
+
// );
|
|
514
|
+
|
|
515
|
+
const _arr = new Array(indices.length);
|
|
516
|
+
const _available = [];
|
|
517
|
+
const indexToMetaMap = new Map();
|
|
518
|
+
const metaToIndexMap = new Map();
|
|
519
|
+
const metaToPositionMap = new Map();
|
|
520
|
+
for (let idx = 0; idx < indices.length; idx++) {
|
|
521
|
+
const currentIndex = indices[idx];
|
|
522
|
+
const currentMeta = this._metaExtractor(currentIndex);
|
|
523
|
+
if (currentMeta == null) continue;
|
|
524
|
+
|
|
525
|
+
indexToMetaMap.set(currentIndex, currentMeta);
|
|
526
|
+
metaToIndexMap.set(currentMeta, currentIndex);
|
|
527
|
+
|
|
528
|
+
if (currentMeta === this._positionToMetaList[idx]) {
|
|
529
|
+
_arr[idx] = currentMeta;
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const _i = this._positionToMetaList.findIndex((v) => v === currentMeta);
|
|
533
|
+
if (_i !== -1) {
|
|
534
|
+
_arr[_i] = currentMeta;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
_available.push(currentMeta);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const { smallValues, largeValues } = this.initialize();
|
|
542
|
+
const positionToMetaList = [];
|
|
543
|
+
|
|
544
|
+
for (let position = 0; position < indices.length; position++) {
|
|
545
|
+
const value = indices[position];
|
|
546
|
+
if (_arr[position] != null) {
|
|
547
|
+
positionToMetaList[position] = _arr[position];
|
|
548
|
+
metaToPositionMap.set(_arr[position], position);
|
|
549
|
+
const element = { position, value };
|
|
550
|
+
smallValues.push(element);
|
|
551
|
+
largeValues.push(element);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const meta = _available.shift();
|
|
555
|
+
if (meta != null) {
|
|
556
|
+
positionToMetaList[position] = meta;
|
|
557
|
+
metaToPositionMap.set(meta, position);
|
|
558
|
+
|
|
559
|
+
const element = { position, value };
|
|
560
|
+
smallValues.push(element);
|
|
561
|
+
largeValues.push(element);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// console.log('position ', positionToMetaList, largeValues.peek().value);
|
|
566
|
+
|
|
567
|
+
this._positionToMetaList = positionToMetaList;
|
|
568
|
+
this._smallValues = smallValues;
|
|
569
|
+
this._largeValues = largeValues;
|
|
570
|
+
this._indexToMetaMap = indexToMetaMap;
|
|
571
|
+
this.replaceMetaToIndexMap(metaToIndexMap);
|
|
572
|
+
this._metaToPositionMap = metaToPositionMap;
|
|
573
|
+
this._onTheFlyIndices = [];
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const indices = new Array(this.bufferSize);
|
|
577
|
+
for (let idx = 0; idx < indices.length; idx++) {
|
|
578
|
+
const meta =
|
|
579
|
+
this._onTheFlyIndices[idx] || this._positionToMetaList[idx];
|
|
580
|
+
const targetIndex = this.getMetaIndex(meta);
|
|
581
|
+
if (meta != null) {
|
|
582
|
+
indices[idx] = {
|
|
583
|
+
meta,
|
|
584
|
+
targetIndex,
|
|
585
|
+
recyclerKey: `${this._name}_${idx}`,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return indices;
|
|
590
|
+
} catch (err) {
|
|
591
|
+
this.readyToStartNextLoop();
|
|
592
|
+
return this._positionToMetaList;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// key point: `meta` should be preserved..
|
|
597
|
+
getIndices() {
|
|
598
|
+
try {
|
|
599
|
+
const indices = new Array(this.bufferSize);
|
|
600
|
+
for (let idx = 0; idx < indices.length; idx++) {
|
|
601
|
+
const meta =
|
|
602
|
+
this._onTheFlyIndices[idx] || this._positionToMetaList[idx];
|
|
603
|
+
const targetIndex = this.getMetaIndex(meta);
|
|
604
|
+
// which means source data has changed. such as one element has been deleted
|
|
605
|
+
if (meta !== this.getIndexMeta(targetIndex)) {
|
|
606
|
+
return this.shuffle();
|
|
607
|
+
}
|
|
608
|
+
if (meta != null) {
|
|
609
|
+
indices[idx] = {
|
|
610
|
+
meta,
|
|
611
|
+
targetIndex,
|
|
612
|
+
recyclerKey: `${this._name}_${idx}`,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// clear on the fly indices after return indices.
|
|
617
|
+
this._onTheFlyIndices = [];
|
|
618
|
+
|
|
619
|
+
return indices;
|
|
620
|
+
} catch (err) {
|
|
621
|
+
this.readyToStartNextLoop();
|
|
622
|
+
return this._positionToMetaList;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
200
626
|
_pushToHeaps(position: number, value: number) {
|
|
201
|
-
const element = {
|
|
202
|
-
position,
|
|
203
|
-
value,
|
|
204
|
-
};
|
|
627
|
+
const element = { position, value };
|
|
205
628
|
// We can reuse the same object in both heaps, because we don't mutate them
|
|
206
629
|
this._smallValues.push(element);
|
|
207
630
|
this._largeValues.push(element);
|
|
208
631
|
}
|
|
209
632
|
|
|
633
|
+
_setMetaPosition(meta: Meta, position: number) {
|
|
634
|
+
const prevMetaOnPosition = this._positionToMetaList[position];
|
|
635
|
+
if (prevMetaOnPosition) this._metaToPositionMap.delete(prevMetaOnPosition);
|
|
636
|
+
this._positionToMetaList[position] = meta;
|
|
637
|
+
this._metaToPositionMap.set(meta, position);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
*
|
|
642
|
+
* @param meta
|
|
643
|
+
* @param index
|
|
644
|
+
* @returns true means index not changed
|
|
645
|
+
*/
|
|
646
|
+
_setMetaIndex(meta: Meta, index: number) {
|
|
647
|
+
const prevMetaIndex = this.getMetaIndex(meta);
|
|
648
|
+
if (prevMetaIndex !== undefined) {
|
|
649
|
+
// no need to set
|
|
650
|
+
// if (prevMetaIndex === index) return true;
|
|
651
|
+
this._indexToMetaMap.delete(prevMetaIndex);
|
|
652
|
+
}
|
|
653
|
+
this.setMetaIndex(meta, index);
|
|
654
|
+
this._indexToMetaMap.set(index, meta);
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
readyToStartNextLoop() {
|
|
659
|
+
this._lastUpdatedMS = Date.now();
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
prepare() {
|
|
663
|
+
if (this._loopMS === this._lastUpdatedMS) return;
|
|
664
|
+
this._loopMS = this._lastUpdatedMS;
|
|
665
|
+
|
|
666
|
+
this._onTheFlyIndices = [];
|
|
667
|
+
this._isOnTheFlyFull = false;
|
|
668
|
+
const len = this._positionToMetaList.length;
|
|
669
|
+
|
|
670
|
+
for (let index = 0; index < len; index++) {}
|
|
671
|
+
}
|
|
672
|
+
|
|
210
673
|
_cleanHeaps() {
|
|
211
674
|
// We not usually only remove object from one heap while moving value.
|
|
212
675
|
// Here we make sure that there is no stale data on top of heaps.
|
|
@@ -227,6 +690,72 @@ class IntegerBufferSet {
|
|
|
227
690
|
}
|
|
228
691
|
}
|
|
229
692
|
|
|
693
|
+
rebuildHeapsWithValues(
|
|
694
|
+
arr: Array<{
|
|
695
|
+
position: number;
|
|
696
|
+
value: number;
|
|
697
|
+
}>
|
|
698
|
+
) {
|
|
699
|
+
const valueToPositionObject = {};
|
|
700
|
+
const newSmallValues = new Heap<HeapItem>([], this._smallerComparator);
|
|
701
|
+
const newLargeValues = new Heap<HeapItem>([], this._greaterComparator);
|
|
702
|
+
|
|
703
|
+
arr.forEach((element) => {
|
|
704
|
+
const { position, value } = element;
|
|
705
|
+
if (value !== undefined) {
|
|
706
|
+
const element = {
|
|
707
|
+
position,
|
|
708
|
+
value,
|
|
709
|
+
};
|
|
710
|
+
newSmallValues.push(element);
|
|
711
|
+
newLargeValues.push(element);
|
|
712
|
+
valueToPositionObject[value] = position;
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
const _arr = new Array(this._bufferSize).fill(2);
|
|
716
|
+
Object.keys(valueToPositionObject).map(
|
|
717
|
+
(key) => (_arr[valueToPositionObject[key]] = 1)
|
|
718
|
+
);
|
|
719
|
+
_arr.forEach((_i, position) => {
|
|
720
|
+
if (_i === 2) {
|
|
721
|
+
const value = Number.MAX_SAFE_INTEGER - position;
|
|
722
|
+
const element = {
|
|
723
|
+
position,
|
|
724
|
+
value,
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
newSmallValues.push(element);
|
|
728
|
+
newLargeValues.push(element);
|
|
729
|
+
valueToPositionObject[value] = position;
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
this._smallValues = newSmallValues;
|
|
733
|
+
this._largeValues = newLargeValues;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// rebuildHeaps() {
|
|
737
|
+
// const valueToPositionObject = {};
|
|
738
|
+
// const newSmallValues = new Heap<HeapItem>([], this._smallerComparator);
|
|
739
|
+
// const newLargeValues = new Heap<HeapItem>([], this._greaterComparator);
|
|
740
|
+
|
|
741
|
+
// const keys = Object.keys(this._positionToValueObject);
|
|
742
|
+
// for (let position = 0; position < keys.length; position++) {
|
|
743
|
+
// const value = this._positionToValueObject[position];
|
|
744
|
+
// if (value !== undefined) {
|
|
745
|
+
// const element = {
|
|
746
|
+
// position,
|
|
747
|
+
// value,
|
|
748
|
+
// };
|
|
749
|
+
// valueToPositionObject[value] = position;
|
|
750
|
+
// newSmallValues.push(element);
|
|
751
|
+
// newLargeValues.push(element);
|
|
752
|
+
// }
|
|
753
|
+
// }
|
|
754
|
+
|
|
755
|
+
// this._smallValues = newSmallValues;
|
|
756
|
+
// this._largeValues = newLargeValues;
|
|
757
|
+
// }
|
|
758
|
+
|
|
230
759
|
_recreateHeaps() {
|
|
231
760
|
const sourceHeap =
|
|
232
761
|
this._smallValues.size() < this._largeValues.size()
|
|
@@ -242,8 +771,11 @@ class IntegerBufferSet {
|
|
|
242
771
|
);
|
|
243
772
|
while (!sourceHeap.empty()) {
|
|
244
773
|
const element = sourceHeap.pop()!;
|
|
245
|
-
// Push all
|
|
246
|
-
if (
|
|
774
|
+
// Push all still valid elements to new heaps
|
|
775
|
+
if (
|
|
776
|
+
this._metaToPositionMap.get(this._indexToMetaMap.get(element.value)) !=
|
|
777
|
+
null
|
|
778
|
+
) {
|
|
247
779
|
newSmallValues.push(element);
|
|
248
780
|
newLargeValues.push(element);
|
|
249
781
|
}
|
|
@@ -255,7 +787,9 @@ class IntegerBufferSet {
|
|
|
255
787
|
_cleanHeap(heap: Heap<HeapItem>) {
|
|
256
788
|
while (
|
|
257
789
|
!heap.empty() &&
|
|
258
|
-
this.
|
|
790
|
+
this._metaToPositionMap.get(
|
|
791
|
+
this._indexToMetaMap.get(heap.peek()!.value)
|
|
792
|
+
) == null
|
|
259
793
|
) {
|
|
260
794
|
heap.pop();
|
|
261
795
|
}
|