@x-oasis/integer-buffer-set 0.1.18 → 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/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
- type HeapItem = {
5
- position: number;
6
- value: number;
7
- };
8
-
9
- const defaultUseMinValueFn = (options: {
10
- safeRange: {
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,61 +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
- class IntegerBufferSet {
35
+ // feature: add / delete / update item will also in consider..
36
+ class IntegerBufferSet<Meta = any> {
38
37
  private _size: number;
39
- private _valueToPositionMap: {
40
- [key: string]: number;
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>;
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 = [];
44
81
 
45
- constructor() {
46
- this._valueToPositionMap = {};
47
82
  this._size = 0;
83
+ this._bufferSize = bufferSize;
84
+
48
85
  this._smallValues = new Heap([], this._smallerComparator);
49
86
  this._largeValues = new Heap([], this._greaterComparator);
50
87
 
51
- this.getNewPositionForValue = this.getNewPositionForValue.bind(this);
52
- this.getValuePosition = this.getValuePosition.bind(this);
88
+ this.getNewPositionForIndex = this.getNewPositionForIndex.bind(this);
89
+ this.getIndexPosition = this.getIndexPosition.bind(this);
53
90
  this.getSize = this.getSize.bind(this);
54
- this.replaceFurthestValuePosition =
55
- this.replaceFurthestValuePosition.bind(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
+ );
98
+
99
+ this._loopMS = Date.now();
100
+ this._lastUpdatedMS = this._loopMS;
56
101
  }
57
102
 
58
103
  getSize() {
59
104
  return this._size;
60
105
  }
61
106
 
62
- get indices() {
63
- const indices = [];
64
- for (const key in this._valueToPositionMap) {
65
- const value = this._valueToPositionMap[key];
66
- indices[value] = key;
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;
67
115
  }
68
- return indices;
69
116
  }
70
117
 
71
- getValuePosition(value: number): null | number {
72
- if (this._valueToPositionMap[value] === undefined) {
73
- return null;
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
+ }
74
130
  }
75
- return this._valueToPositionMap[value];
131
+ return null;
76
132
  }
77
133
 
78
- getNewPositionForValue(value: number) {
79
- if (this._valueToPositionMap[value] !== undefined) {
80
- console.warn(
81
- "Shouldn't try to find new position for value already stored in BufferSet"
82
- );
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);
83
154
  }
84
- // invariant(
85
- // this._valueToPositionMap[value] === undefined,
86
- // "Shouldn't try to find new position for value already stored in BufferSet"
87
- // );
88
- const newPosition = this._size;
89
- this._size++;
90
- this._pushToHeaps(newPosition, value);
91
- this._valueToPositionMap[value] = newPosition;
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
+
92
185
  return newPosition;
93
186
  }
94
187
 
@@ -100,88 +193,483 @@ class IntegerBufferSet {
100
193
  return this._largeValues.peek()?.value;
101
194
  }
102
195
 
103
- replaceFurthestValuePosition(
104
- lowValue: number,
105
- highValue: number,
106
- newValue: number,
107
- useMinValueFn: (options: {
108
- safeRange: {
109
- lowValue: number;
110
- highValue: number;
111
- };
112
- bufferSetRange: {
113
- maxValue: number;
114
- minValue: number;
115
- };
116
- currentIndex: number;
117
- }) => boolean = defaultUseMinValueFn
118
- ): null | number {
119
- if (this._valueToPositionMap[newValue] !== undefined) {
120
- console.warn(
121
- "Shouldn't try to replace values with value already stored value in " +
122
- 'BufferSet'
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
234
+ if (originalPosition !== undefined) {
235
+ if (originalPosition === position) return true;
236
+ this.deleteMetaIndex(meta);
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);
249
+ }
250
+
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)
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);
405
+ }
406
+
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)
123
426
  );
124
427
  }
125
- // invariant(
126
- // this._valueToPositionMap[newValue] === undefined,
127
- // "Shouldn't try to replace values with value already stored value in " +
128
- // 'BufferSet'
129
- // );
130
428
 
131
- this._cleanHeaps();
132
- if (this._smallValues.empty() || this._largeValues.empty()) {
133
- // Threre are currently no values stored. We will have to create new
134
- // position for this value.
135
- return null;
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
+ );
136
443
  }
137
444
 
138
445
  const minValue = this._smallValues.peek()!.value;
139
446
  const maxValue = this._largeValues.peek()!.value;
140
- if (minValue >= lowValue && maxValue <= highValue) {
141
- // All values currently stored are necessary, we can't reuse any of them.
142
- return null;
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;
143
464
  }
144
465
 
145
- let valueToReplace;
466
+ const { startIndex: lowValue, endIndex: highValue } = safeRange;
467
+
468
+ // All values currently stored are necessary, we can't reuse any of them.
146
469
  if (
147
- useMinValueFn({
148
- safeRange: {
149
- lowValue,
150
- highValue,
151
- },
152
- bufferSetRange: {
153
- minValue,
154
- maxValue,
155
- },
156
- currentIndex: newValue,
157
- })
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)
158
477
  ) {
159
- // if (lowValue - minValue > maxValue - highValue) {
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) {
160
487
  // minValue is further from provided range. We will reuse it's position.
161
- valueToReplace = minValue;
488
+ indexToReplace = minValue;
162
489
  this._smallValues.pop();
163
490
  } else {
164
- valueToReplace = maxValue;
491
+ indexToReplace = maxValue;
165
492
  this._largeValues.pop();
166
493
  }
167
- const position = this._valueToPositionMap[valueToReplace];
168
- delete this._valueToPositionMap[valueToReplace];
169
- this._valueToPositionMap[newValue] = position;
170
- this._pushToHeaps(position, newValue);
494
+
495
+ const replacedMeta = this._indexToMetaMap.get(indexToReplace);
496
+ const position = this._metaToPositionMap.get(replacedMeta);
171
497
 
172
498
  return position;
173
499
  }
174
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
+
175
626
  _pushToHeaps(position: number, value: number) {
176
- const element = {
177
- position,
178
- value,
179
- };
627
+ const element = { position, value };
180
628
  // We can reuse the same object in both heaps, because we don't mutate them
181
629
  this._smallValues.push(element);
182
630
  this._largeValues.push(element);
183
631
  }
184
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
+
185
673
  _cleanHeaps() {
186
674
  // We not usually only remove object from one heap while moving value.
187
675
  // Here we make sure that there is no stale data on top of heaps.
@@ -202,6 +690,72 @@ class IntegerBufferSet {
202
690
  }
203
691
  }
204
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
+
205
759
  _recreateHeaps() {
206
760
  const sourceHeap =
207
761
  this._smallValues.size() < this._largeValues.size()
@@ -217,8 +771,11 @@ class IntegerBufferSet {
217
771
  );
218
772
  while (!sourceHeap.empty()) {
219
773
  const element = sourceHeap.pop()!;
220
- // Push all stil valid elements to new heaps
221
- if (this._valueToPositionMap[element.value] !== undefined) {
774
+ // Push all still valid elements to new heaps
775
+ if (
776
+ this._metaToPositionMap.get(this._indexToMetaMap.get(element.value)) !=
777
+ null
778
+ ) {
222
779
  newSmallValues.push(element);
223
780
  newLargeValues.push(element);
224
781
  }
@@ -230,7 +787,9 @@ class IntegerBufferSet {
230
787
  _cleanHeap(heap: Heap<HeapItem>) {
231
788
  while (
232
789
  !heap.empty() &&
233
- this._valueToPositionMap[heap.peek()!.value] === undefined
790
+ this._metaToPositionMap.get(
791
+ this._indexToMetaMap.get(heap.peek()!.value)
792
+ ) == null
234
793
  ) {
235
794
  heap.pop();
236
795
  }