3
3
* @license MIT
4
4
*/
5
5
6
+ import { IdleTaskQueue } from 'common/TaskQueue' ;
7
+
6
8
// Work variables to avoid garbage collection.
7
9
let i = 0 ;
8
10
9
11
/**
10
- * A generic list that is maintained in sorted order and allows values with duplicate keys. This
11
- * list is based on binary search and as such locating a key will take O(log n) amortized, this
12
- * includes the by key iterator.
12
+ * A generic list that is maintained in sorted order and allows values with duplicate keys. Deferred
13
+ * batch insertion and deletion is used to significantly reduce the time it takes to insert and
14
+ * delete a large amount of items in succession. This list is based on binary search and as such
15
+ * locating a key will take O(log n) amortized, this includes the by key iterator.
13
16
*/
14
17
export class SortedList < T > {
15
- private readonly _array : T [ ] = [ ] ;
18
+ private _array : T [ ] = [ ] ;
19
+
20
+ private readonly _insertedValues : T [ ] = [ ] ;
21
+ private readonly _flushInsertedTask = new IdleTaskQueue ( ) ;
22
+ private _isFlushingInserted = false ;
23
+
24
+ private readonly _deletedIndices : number [ ] = [ ] ;
25
+ private readonly _flushDeletedTask = new IdleTaskQueue ( ) ;
26
+ private _isFlushingDeleted = false ;
16
27
17
28
constructor (
18
29
private readonly _getKey : ( value : T ) => number
@@ -21,18 +32,50 @@ export class SortedList<T> {
21
32
22
33
public clear ( ) : void {
23
34
this . _array . length = 0 ;
35
+ this . _insertedValues . length = 0 ;
36
+ this . _flushInsertedTask . clear ( ) ;
37
+ this . _isFlushingInserted = false ;
38
+ this . _deletedIndices . length = 0 ;
39
+ this . _flushDeletedTask . clear ( ) ;
40
+ this . _isFlushingDeleted = false ;
24
41
}
25
42
26
43
public insert ( value : T ) : void {
27
- if ( this . _array . length === 0 ) {
28
- this . _array . push ( value ) ;
29
- return ;
44
+ this . _flushCleanupDeleted ( ) ;
45
+ if ( this . _insertedValues . length === 0 ) {
46
+ this . _flushInsertedTask . enqueue ( ( ) => this . _flushInserted ( ) ) ;
47
+ }
48
+ this . _insertedValues . push ( value ) ;
49
+ }
50
+
51
+ private _flushInserted ( ) : void {
52
+ const sortedAddedValues = this . _insertedValues . sort ( ( a , b ) => this . _getKey ( a ) - this . _getKey ( b ) ) ;
53
+ let sortedAddedValuesIndex = 0 ;
54
+ let arrayIndex = 0 ;
55
+
56
+ const newArray = new Array ( this . _array . length + this . _insertedValues . length ) ;
57
+
58
+ for ( let newArrayIndex = 0 ; newArrayIndex < newArray . length ; newArrayIndex ++ ) {
59
+ if ( arrayIndex >= this . _array . length || this . _getKey ( sortedAddedValues [ sortedAddedValuesIndex ] ) <= this . _getKey ( this . _array [ arrayIndex ] ) ) {
60
+ newArray [ newArrayIndex ] = sortedAddedValues [ sortedAddedValuesIndex ] ;
61
+ sortedAddedValuesIndex ++ ;
62
+ } else {
63
+ newArray [ newArrayIndex ] = this . _array [ arrayIndex ++ ] ;
64
+ }
65
+ }
66
+
67
+ this . _array = newArray ;
68
+ this . _insertedValues . length = 0 ;
69
+ }
70
+
71
+ private _flushCleanupInserted ( ) : void {
72
+ if ( ! this . _isFlushingInserted && this . _insertedValues . length > 0 ) {
73
+ this . _flushInsertedTask . flush ( ) ;
30
74
}
31
- i = this . _search ( this . _getKey ( value ) ) ;
32
- this . _array . splice ( i , 0 , value ) ;
33
75
}
34
76
35
77
public delete ( value : T ) : boolean {
78
+ this . _flushCleanupInserted ( ) ;
36
79
if ( this . _array . length === 0 ) {
37
80
return false ;
38
81
}
@@ -49,14 +92,43 @@ export class SortedList<T> {
49
92
}
50
93
do {
51
94
if ( this . _array [ i ] === value ) {
52
- this . _array . splice ( i , 1 ) ;
95
+ if ( this . _deletedIndices . length === 0 ) {
96
+ this . _flushDeletedTask . enqueue ( ( ) => this . _flushDeleted ( ) ) ;
97
+ }
98
+ this . _deletedIndices . push ( i ) ;
53
99
return true ;
54
100
}
55
101
} while ( ++ i < this . _array . length && this . _getKey ( this . _array [ i ] ) === key ) ;
56
102
return false ;
57
103
}
58
104
105
+ private _flushDeleted ( ) : void {
106
+ this . _isFlushingDeleted = true ;
107
+ const sortedDeletedIndices = this . _deletedIndices . sort ( ( a , b ) => a - b ) ;
108
+ let sortedDeletedIndicesIndex = 0 ;
109
+ const newArray = new Array ( this . _array . length - sortedDeletedIndices . length ) ;
110
+ let newArrayIndex = 0 ;
111
+ for ( let i = 0 ; i < this . _array . length ; i ++ ) {
112
+ if ( sortedDeletedIndices [ sortedDeletedIndicesIndex ] === i ) {
113
+ sortedDeletedIndicesIndex ++ ;
114
+ } else {
115
+ newArray [ newArrayIndex ++ ] = this . _array [ i ] ;
116
+ }
117
+ }
118
+ this . _array = newArray ;
119
+ this . _deletedIndices . length = 0 ;
120
+ this . _isFlushingDeleted = false ;
121
+ }
122
+
123
+ private _flushCleanupDeleted ( ) : void {
124
+ if ( ! this . _isFlushingDeleted && this . _deletedIndices . length > 0 ) {
125
+ this . _flushDeletedTask . flush ( ) ;
126
+ }
127
+ }
128
+
59
129
public * getKeyIterator ( key : number ) : IterableIterator < T > {
130
+ this . _flushCleanupInserted ( ) ;
131
+ this . _flushCleanupDeleted ( ) ;
60
132
if ( this . _array . length === 0 ) {
61
133
return ;
62
134
}
@@ -73,6 +145,8 @@ export class SortedList<T> {
73
145
}
74
146
75
147
public forEachByKey ( key : number , callback : ( value : T ) => void ) : void {
148
+ this . _flushCleanupInserted ( ) ;
149
+ this . _flushCleanupDeleted ( ) ;
76
150
if ( this . _array . length === 0 ) {
77
151
return ;
78
152
}
@@ -89,6 +163,8 @@ export class SortedList<T> {
89
163
}
90
164
91
165
public values ( ) : IterableIterator < T > {
166
+ this . _flushCleanupInserted ( ) ;
167
+ this . _flushCleanupDeleted ( ) ;
92
168
// Duplicate the array to avoid issues when _array changes while iterating
93
169
return [ ...this . _array ] . values ( ) ;
94
170
}
0 commit comments