@parcel/graph 3.1.1-nightly.3121 → 3.1.1-nightly.3124

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.
@@ -26,20 +26,77 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
26
26
  /** The address of the edge in the edges map. */
27
27
  // eslint-disable-next-line no-unused-vars
28
28
  // eslint-disable-next-line no-unused-vars
29
- /** The upper bound above which capacity should be increased. */
30
- const LOAD_FACTOR = 0.7;
31
- /** The lower bound below which capacity should be decreased. */
32
- const UNLOAD_FACTOR = 0.3;
33
- /** The max amount by which to grow the capacity. */
34
- const MAX_GROW_FACTOR = 8;
35
- /** The min amount by which to grow the capacity. */
36
- const MIN_GROW_FACTOR = 2;
37
- /** The amount by which to shrink the capacity. */
38
- const SHRINK_FACTOR = 0.5;
29
+ const DEFAULT_PARAMS = {
30
+ initialCapacity: 2,
31
+ unloadFactor: 0.3,
32
+ maxGrowFactor: 8,
33
+ minGrowFactor: 2,
34
+ peakCapacity: 2 ** 18,
35
+ shrinkFactor: 0.5
36
+ };
37
+
38
+ /**
39
+ * An Enum representing the result of a call to `link`.
40
+ *
41
+ * `EdgeAdded` = `0`: the edge was successfully linked
42
+ * `EdgeExists` = `1`: the edge already exists
43
+ * `EdgesOverloaded` = `2`: the edge map is overloaded
44
+ * `TooManyDeletes` = `3`: the edge map has too many deleted edges
45
+ * `NodesOverloaded` = `4`: the node map is overloaded
46
+ */
47
+ const LinkResult = {
48
+ EdgeAdded: 0,
49
+ EdgeExists: 1,
50
+ EdgesOverloaded: 2,
51
+ TooManyDeletes: 3,
52
+ NodesOverloaded: 4
53
+ };
54
+
55
+ /**
56
+ * Allow 3 attempts to link an edge before erroring.
57
+ *
58
+ * The three attempts correspond to the three possible inconclusive link results:
59
+ * - `LinkResult.EdgesOverloaded`
60
+ * - `LinkResult.TooManyDeletes`
61
+ * - `LinkResult.NodesOverloaded`
62
+ *
63
+ * If after 3 tries, the link result is still one of these,
64
+ * this is considered an error.
65
+ */
66
+ const MAX_LINK_TRIES = 3;
67
+
68
+ /**
69
+ * `AdjacencyList` maps nodes to lists of their adjacent nodes.
70
+ *
71
+ * It is implemented as a hashmap of nodes, where each node has
72
+ * doubly linked lists of edges of each unique edge type.
73
+ * The edges are stored in a separate hashmap, where each edge has
74
+ * a pointer to the originating node, the terminating node, and
75
+ * the next and previous edges to and from adjacent nodes.
76
+ *
77
+ * The hash maps are each stored in a `Uint32Array` backed
78
+ * by a `SharedArrayBuffer`. See `SharedTypeMap` for more details.
79
+ *
80
+ * It's primary interface is through the `getNodeIdsConnectedFrom`
81
+ * and `getNodeIdsConnectedTo` methods, which return the list of
82
+ * nodes connected from or to a given node, respectively.
83
+ *
84
+ * It is also possible to get the lists of edges connected from or to
85
+ * a given node, using the `getOutboundEdgesByType` and
86
+ * `getInboundEdgesByType` methods.
87
+ *
88
+ */
39
89
  class AdjacencyList {
40
90
  #nodes /*: NodeTypeMap<TEdgeType | NullEdgeType> */;
41
91
  #edges /*: EdgeTypeMap<TEdgeType | NullEdgeType> */;
42
92
 
93
+ #params /*: AdjacencyListParams */;
94
+
95
+ /**
96
+ * Create a new `AdjacencyList` in one of two ways:
97
+ * - with specified options, or
98
+ * - with data serialized from a previous `AdjacencyList`.
99
+ */
43
100
  constructor(opts) {
44
101
  let nodes;
45
102
  let edges;
@@ -50,27 +107,40 @@ class AdjacencyList {
50
107
  } = opts);
51
108
  this.#nodes = new NodeTypeMap(nodes);
52
109
  this.#edges = new EdgeTypeMap(edges);
110
+ this.#params = {
111
+ ...DEFAULT_PARAMS,
112
+ initialCapacity: this.#edges.capacity
113
+ };
53
114
  } else {
115
+ this.#params = {
116
+ ...DEFAULT_PARAMS,
117
+ ...opts
118
+ };
54
119
  let {
55
- nodeCapacity = NodeTypeMap.MIN_CAPACITY,
56
- edgeCapacity = EdgeTypeMap.MIN_CAPACITY
57
- } = opts !== null && opts !== void 0 ? opts : {};
58
- (0, _assert().default)(nodeCapacity <= NodeTypeMap.MAX_CAPACITY, 'Node capacity overflow!');
59
- (0, _assert().default)(edgeCapacity <= EdgeTypeMap.MAX_CAPACITY, 'Edge capacity overflow!');
60
- this.#nodes = new NodeTypeMap(nodeCapacity);
61
- this.#edges = new EdgeTypeMap(edgeCapacity);
120
+ initialCapacity
121
+ } = this.#params;
122
+
123
+ // TODO: Find a heuristic for right-sizing nodes.
124
+ // e.g., given an average ratio of `e` edges for every `n` nodes,
125
+ // init nodes with `capacity * n / e`.
126
+ let initialNodeCapacity = 2;
127
+ NodeTypeMap.assertMaxCapacity(initialNodeCapacity);
128
+ EdgeTypeMap.assertMaxCapacity(initialCapacity);
129
+ this.#nodes = new NodeTypeMap(initialNodeCapacity);
130
+ this.#edges = new EdgeTypeMap(initialCapacity);
62
131
  }
63
132
  }
64
133
 
65
134
  /**
66
- * Create a new `AdjacencyList` from the given options.
135
+ * Create a new `AdjacencyList` with data serialized
136
+ * from another `AdjacencyList`.
67
137
  */
68
138
  static deserialize(opts) {
69
139
  return new AdjacencyList(opts);
70
140
  }
71
141
 
72
142
  /**
73
- * Returns a serializable object of the nodes and edges in the graph.
143
+ * Returns a serializable object of the nodes and edges in the AdjacencyList.
74
144
  */
75
145
  serialize() {
76
146
  return {
@@ -78,7 +148,10 @@ class AdjacencyList {
78
148
  edges: this.#edges.data
79
149
  };
80
150
  }
151
+
152
+ /** Statistics about the current state of the `AdjacencyList`. */
81
153
  get stats() {
154
+ let edgeTypes = new Set();
82
155
  let buckets = new Map();
83
156
  for (let {
84
157
  from,
@@ -91,40 +164,46 @@ class AdjacencyList {
91
164
  (0, _assert().default)(!bucket.has(key), `Duplicate node detected: ${key}`);
92
165
  bucket.add(key);
93
166
  buckets.set(hash, bucket);
167
+ edgeTypes.add(type);
94
168
  }
95
169
  let maxCollisions = 0;
96
170
  let collisions = 0;
97
171
  let distribution = 0;
172
+ /**
173
+ * The expected distribution of hashes across available hash buckets.
174
+ *
175
+ * See: https://en.wikipedia.org/wiki/Hash_function#Testing_and_measurement
176
+ */
177
+ let uniformDistribution = this.#edges.count / (2 * this.#edges.capacity) * (this.#edges.count + 2 * this.#edges.capacity - 1);
98
178
  for (let bucket of buckets.values()) {
99
179
  maxCollisions = Math.max(maxCollisions, bucket.size - 1);
100
180
  collisions += bucket.size - 1;
101
181
  distribution += bucket.size * (bucket.size + 1) / 2;
102
182
  }
103
- let uniformity = distribution / (this.#edges.count / (2 * this.#edges.capacity) * (this.#edges.count + 2 * this.#edges.capacity - 1));
104
183
  return {
184
+ capacity: this.#edges.capacity,
105
185
  nodes: (0, _types.fromNodeId)(this.#nodes.nextId),
106
186
  nodeEdgeTypes: this.#nodes.count,
107
- nodeCapacity: this.#nodes.capacity,
108
187
  nodeLoad: `${Math.round(this.#nodes.load * 100)}%`,
109
188
  nodeBufferSize: this.#nodes.bufferSize,
110
189
  edges: this.#edges.count,
111
190
  deleted: this.#edges.deletes,
112
- edgeCapacity: this.#edges.capacity,
191
+ edgeTypes: edgeTypes.size,
113
192
  edgeLoad: `${Math.round(this.#edges.load * 100)}%`,
114
193
  edgeLoadWithDeletes: `${Math.round(this.#edges.getLoad(this.#edges.count + this.#edges.deletes) * 100)}%`,
115
194
  edgeBufferSize: this.#edges.bufferSize,
116
195
  collisions,
117
196
  maxCollisions,
118
- avgCollisions: Math.round(collisions / buckets.size * 100) / 100 || 0,
119
- uniformity: Math.round(uniformity * 100) / 100 || 0
197
+ avgCollisions: Math.round(collisions / this.#edges.count * 100) / 100 || 0,
198
+ uniformity: Math.round(distribution / uniformDistribution * 100) / 100 || 0
120
199
  };
121
200
  }
122
201
 
123
202
  /**
124
203
  * Resize the internal nodes array.
125
204
  *
126
- * This is used in `addNode` when the `numNodes` meets or exceeds
127
- * the allocated size of the `nodes` array.
205
+ * This is used in `addNode` and in `addEdge` when
206
+ * the `nodes` array is at capacity,
128
207
  */
129
208
  resizeNodes(size) {
130
209
  let nodes = this.#nodes;
@@ -137,38 +216,39 @@ class AdjacencyList {
137
216
  /**
138
217
  * Resize the internal edges array.
139
218
  *
140
- * This is used in `addEdge` when the `numEdges` meets or exceeds
141
- * the allocated size of the `edges` array.
219
+ * This is used in `addEdge` when the `edges` array is at capacity.
142
220
  */
143
221
  resizeEdges(size) {
144
222
  // Allocate the required space for new `nodes` and `edges` maps.
145
- let copy = new AdjacencyList({
146
- nodeCapacity: this.#nodes.capacity,
147
- edgeCapacity: size
148
- });
223
+ let edges = new EdgeTypeMap(size);
224
+ let nodes = new NodeTypeMap(this.#nodes.capacity);
149
225
 
150
226
  // Copy the existing edges into the new array.
151
- copy.#nodes.nextId = this.#nodes.nextId;
152
- this.#edges.forEach(edge => void copy.addEdge(this.#edges.from(edge), this.#edges.to(edge), this.#edges.typeOf(edge)));
227
+ nodes.nextId = this.#nodes.nextId;
228
+ this.#edges.forEach(edge => void link(this.#edges.from(edge), this.#edges.to(edge), this.#edges.typeOf(edge), edges, nodes, this.#params.unloadFactor));
153
229
 
154
230
  // We expect to preserve the same number of edges.
155
- (0, _assert().default)(this.#edges.count === copy.#edges.count, `Edge mismatch! ${this.#edges.count} does not match ${copy.#edges.count}.`);
231
+ (0, _assert().default)(this.#edges.count === edges.count, `Edge mismatch! ${this.#edges.count} does not match ${edges.count}.`);
156
232
 
157
233
  // Finally, copy the new data arrays over to this graph.
158
- this.#nodes = copy.#nodes;
159
- this.#edges = copy.#edges;
234
+ this.#nodes = nodes;
235
+ this.#edges = edges;
160
236
  }
161
237
 
162
238
  /**
163
239
  * Adds a node to the graph.
164
240
  *
241
+ * Note that this method does not increment the node count
242
+ * (that only happens in `addEdge`), it _may_ preemptively resize
243
+ * the nodes array if it is at capacity, under the asumption that
244
+ * at least 1 edge to or from this new node will be added.
245
+ *
165
246
  * Returns the id of the added node.
166
247
  */
167
248
  addNode() {
168
249
  let id = this.#nodes.getId();
169
- // If we're in danger of overflowing the `nodes` array, resize it.
170
- if (this.#nodes.load > LOAD_FACTOR) {
171
- this.resizeNodes(increaseNodeCapacity(this.#nodes.capacity));
250
+ if (this.#nodes.getLoad() >= 1) {
251
+ this.resizeNodes(increaseNodeCapacity(this.#nodes.capacity, this.#params));
172
252
  }
173
253
  return id;
174
254
  }
@@ -176,64 +256,54 @@ class AdjacencyList {
176
256
  /**
177
257
  * Adds an edge to the graph.
178
258
  *
259
+ * This method will increment the edge count, and it _may_
260
+ * also increment the node count, if the originating or
261
+ * terminating node does not yet have any edges of the given type.
262
+ *
263
+ * If either the `nodes` or `edges` arrays are at capacity,
264
+ * this method will resize them before adding.
265
+ *
266
+ * Furthermore, if the `edges` array has a high number of
267
+ * deleted edges, it may reclaim the space before adding.
268
+ *
179
269
  * Returns `true` if the edge was added,
180
270
  * or `false` if the edge already exists.
181
271
  */
182
272
  addEdge(from, to, type = 1) {
273
+ (0, _assert().default)(from < this.#nodes.nextId, `Node ${from} does not exist.`);
274
+ (0, _assert().default)(to < this.#nodes.nextId, `Node ${to} does not exist.`);
183
275
  (0, _assert().default)(type > 0, `Unsupported edge type ${type}`);
184
- let hash = this.#edges.hash(from, to, type);
185
- let edge = this.#edges.addressOf(hash, from, to, type);
276
+ let result;
277
+ let tries = 0;
278
+ do {
279
+ (0, _assert().default)(tries++ < MAX_LINK_TRIES, 'Failed to addEdge too many times!');
280
+ result = link(from, to, type, this.#edges, this.#nodes, this.#params.unloadFactor);
186
281
 
187
- // The edge is already in the graph; do nothing.
188
- if (edge !== null) return false;
189
- let capacity = this.#edges.capacity;
190
- // We add 1 to account for the edge we are adding.
191
- let count = this.#edges.count + 1;
192
- // Since the space occupied by deleted edges isn't reclaimed,
193
- // we include them in our count to avoid overflowing the `edges` array.
194
- let deletes = this.#edges.deletes;
195
- let total = count + deletes;
196
- // If we have enough space to keep adding edges, we can
197
- // put off reclaiming the deleted space until the next resize.
198
- if (this.#edges.getLoad(total) > LOAD_FACTOR) {
199
- if (this.#edges.getLoad(deletes) > UNLOAD_FACTOR) {
200
- // If we have a significant number of deletes, we compute our new
201
- // capacity based on the current count, even though we decided to
202
- // resize based on the sum total of count and deletes.
203
- // In this case, resizing is more like a compaction.
204
- this.resizeEdges(getNextEdgeCapacity(capacity, count, this.#edges.getLoad(count)));
205
- } else {
206
- this.resizeEdges(getNextEdgeCapacity(capacity, total, this.#edges.getLoad(total)));
207
- }
208
- // We must rehash because the capacity has changed.
209
- hash = this.#edges.hash(from, to, type);
210
- }
211
- let toNode = this.#nodes.addressOf(to, type);
212
- let fromNode = this.#nodes.addressOf(from, type);
213
- if (toNode === null || fromNode === null) {
214
- // If we're in danger of overflowing the `nodes` array, resize it.
215
- if (this.#nodes.load >= LOAD_FACTOR) {
216
- this.resizeNodes(increaseNodeCapacity(this.#nodes.capacity));
217
- // We need to update our indices since the `nodes` array has changed.
218
- toNode = this.#nodes.addressOf(to, type);
219
- fromNode = this.#nodes.addressOf(from, type);
282
+ // Sometimes we need to resize before we can add.
283
+ switch (result) {
284
+ case LinkResult.NodesOverloaded:
285
+ {
286
+ this.resizeNodes(increaseNodeCapacity(this.#nodes.capacity, this.#params));
287
+ break;
288
+ }
289
+ case LinkResult.EdgesOverloaded:
290
+ {
291
+ this.resizeEdges(increaseEdgeCapacity(this.#edges.capacity, this.#params));
292
+ break;
293
+ }
294
+ case LinkResult.TooManyDeletes:
295
+ {
296
+ this.resizeEdges(decreaseEdgeCapacity(this.#edges.capacity, this.#params));
297
+ break;
298
+ }
220
299
  }
221
- }
222
- if (toNode === null) toNode = this.#nodes.add(to, type);
223
- if (fromNode === null) fromNode = this.#nodes.add(from, type);
224
-
225
- // Add our new edge to its hash bucket.
226
- edge = this.#edges.add(hash, from, to, type);
227
-
228
- // Link this edge to the node's list of incoming edges.
229
- let prevIn = this.#nodes.linkIn(toNode, edge);
230
- if (prevIn !== null) this.#edges.linkIn(prevIn, edge);
231
-
232
- // Link this edge to the node's list of outgoing edges.
233
- let prevOut = this.#nodes.linkOut(fromNode, edge);
234
- if (prevOut !== null) this.#edges.linkOut(prevOut, edge);
235
- return true;
300
+ } while (result > LinkResult.EdgeExists);
301
+ return result === LinkResult.EdgeAdded;
236
302
  }
303
+
304
+ /**
305
+ * Iterate over all edges in insertion order.
306
+ */
237
307
  *getAllEdges() {
238
308
  for (let edge of this.#edges) {
239
309
  yield {
@@ -259,7 +329,12 @@ class AdjacencyList {
259
329
  }
260
330
 
261
331
  /**
332
+ * Remove an edge connecting the `from` and `to` nodes.
333
+ *
334
+ * Note that space for the deleted edge is not reclaimed
335
+ * until the `edges` array is resized.
262
336
  *
337
+ * This method will increment the edge delete count.
263
338
  */
264
339
  removeEdge(from, to, type = 1) {
265
340
  let hash = this.#edges.hash(from, to, type);
@@ -285,6 +360,14 @@ class AdjacencyList {
285
360
  // Finally, delete the edge.
286
361
  this.#edges.delete(edge);
287
362
  }
363
+
364
+ /**
365
+ * Check if the given node has any edges incoming from other nodes.
366
+ *
367
+ * Essentially, this is an orphan check. If a node has no incoming edges,
368
+ * it (and its entire subgraph) is completely disconnected from the
369
+ * rest of the graph.
370
+ */
288
371
  hasInboundEdges(to) {
289
372
  let node = this.#nodes.head(to);
290
373
  while (node !== null) {
@@ -293,6 +376,11 @@ class AdjacencyList {
293
376
  }
294
377
  return false;
295
378
  }
379
+
380
+ /**
381
+ * Get a list of every node (labeled `from`) connecting _to_
382
+ * the given `to` node, along with the edge `type` connecting them.
383
+ */
296
384
  getInboundEdgesByType(to) {
297
385
  let edges = [];
298
386
  let node = this.#nodes.head(to);
@@ -311,6 +399,11 @@ class AdjacencyList {
311
399
  }
312
400
  return edges;
313
401
  }
402
+
403
+ /**
404
+ * Get a list of every node (labeled `to`) connected _from_
405
+ * the given `from` node, along with the edge `type` connecting them.
406
+ */
314
407
  getOutboundEdgesByType(from) {
315
408
  let edges = [];
316
409
  let node = this.#nodes.head(from);
@@ -331,7 +424,11 @@ class AdjacencyList {
331
424
  }
332
425
 
333
426
  /**
334
- * Get the list of nodes connected from this node.
427
+ * Get the list of node ids connected from this node.
428
+ *
429
+ * If `type` is specified, only return nodes connected by edges of that type.
430
+ * If `type` is an array, return nodes connected by edges of any of those types.
431
+ * If `type` is `AllEdgeTypes` (`-1`), return nodes connected by edges of any type.
335
432
  */
336
433
  getNodeIdsConnectedFrom(from, type = 1) {
337
434
  let matches = node => type === _Graph.ALL_EDGE_TYPES || (Array.isArray(type) ? type.includes(this.#nodes.typeOf(node)) : type === this.#nodes.typeOf(node));
@@ -370,7 +467,11 @@ class AdjacencyList {
370
467
  }
371
468
 
372
469
  /**
373
- * Get the list of nodes connected to this node.
470
+ * Get the list of node ids connected to this node.
471
+ *
472
+ * If `type` is specified, only return nodes connected by edges of that type.
473
+ * If `type` is an array, return nodes connected by edges of any of those types.
474
+ * If `type` is `AllEdgeTypes` (`-1`), return nodes connected by edges of any type.
374
475
  */
375
476
  getNodeIdsConnectedTo(to, type = 1) {
376
477
  let matches = node => type === _Graph.ALL_EDGE_TYPES || (Array.isArray(type) ? type.includes(this.#nodes.typeOf(node)) : type === this.#nodes.typeOf(node));
@@ -420,7 +521,7 @@ class AdjacencyList {
420
521
  * └──┴──┴──┴───────┴──┴──┴──┴───────┴──┴──┘
421
522
  * └──┬──┘ └─────────┬─────────┘
422
523
  * header items
423
- * (HEADER_SIZE) (capacity * ITEM_SIZE * BUCKET_SIZE)
524
+ * (HEADER_SIZE) (capacity * ITEM_SIZE)
424
525
  *
425
526
  *
426
527
  * An item is added with a hash key that fits within the range of the hash
@@ -478,29 +579,55 @@ class SharedTypeMap {
478
579
  /** The offset at which an item's type is stored. */
479
580
  static #TYPE = 1;
480
581
 
481
- /** The number of items to accommodate per hash bucket. */
482
- static BUCKET_SIZE = 2;
582
+ /** The largest possible capacity. */
583
+ static get MAX_CAPACITY() {
584
+ return Math.floor(
585
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length#what_went_wrong
586
+ (2 ** 31 - 1 - this.HEADER_SIZE) / this.ITEM_SIZE);
587
+ }
588
+
589
+ /** Assert that the given `capacity` does not exceed `MAX_CAPACITY`. */
590
+ static assertMaxCapacity(capacity) {
591
+ (0, _assert().default)(capacity <= this.MAX_CAPACITY, `${this.name} capacity overflow!`);
592
+ }
593
+ /** The total number of items that can fit in the map. */
483
594
  get capacity() {
484
595
  return this.data[SharedTypeMap.#CAPACITY];
485
596
  }
597
+
598
+ /** The number of items in the map. */
486
599
  get count() {
487
600
  return this.data[SharedTypeMap.#COUNT];
488
601
  }
602
+
603
+ /** The ratio of the count to the capacity. */
489
604
  get load() {
490
605
  return this.getLoad();
491
606
  }
607
+
608
+ /** The total length of the map, in bytes. */
492
609
  get length() {
493
610
  return this.getLength();
494
611
  }
612
+
613
+ /** The address of the first item in the map. */
495
614
  get addressableLimit() {
496
615
  return this.constructor.HEADER_SIZE + this.capacity;
497
616
  }
617
+
618
+ /** The size of the map in mb, as a localized string. */
498
619
  get bufferSize() {
499
620
  return `${(this.data.byteLength / 1024 / 1024).toLocaleString(undefined, {
500
621
  minimumFractionDigits: 2,
501
622
  maximumFractionDigits: 2
502
623
  })} mb`;
503
624
  }
625
+
626
+ /**
627
+ * Create a new `SharedTypeMap` in one of two ways:
628
+ * - with a capacity of `capacityOrData` if it is a number,
629
+ * - or with `capacityOrData` as its data, if it is a `Uint32Array`.
630
+ */
504
631
  constructor(capacityOrData) {
505
632
  if (typeof capacityOrData === 'number') {
506
633
  let {
@@ -515,6 +642,14 @@ class SharedTypeMap {
515
642
  (0, _assert().default)(this.getLength() === this.data.length, 'Data appears corrupt.');
516
643
  }
517
644
  }
645
+
646
+ /**
647
+ * Overwrite the data in this map with the given `data`.
648
+ *
649
+ * The `data` is expected to conform to the same
650
+ * partitioning and schema as the data in this map,
651
+ * and is expected to be of equal or smaller capacity to this map.
652
+ */
518
653
  set(data) {
519
654
  let {
520
655
  HEADER_SIZE,
@@ -547,19 +682,30 @@ class SharedTypeMap {
547
682
  if (toItems[i + NEXT]) toItems[i + NEXT] += delta;
548
683
  }
549
684
  }
685
+
686
+ /**
687
+ * Given a `count` (defaulting to `this.count`),
688
+ * get the load on the map.
689
+ *
690
+ * The load is the ratio of the `count` the capacity of the map.
691
+ *
692
+ * If the load is `1`, it means the map is at capacity, and needs
693
+ * to be resized before adding more items.
694
+ */
550
695
  getLoad(count = this.count) {
551
- let {
552
- BUCKET_SIZE
553
- } = this.constructor;
554
- return count / (this.capacity * BUCKET_SIZE);
696
+ return count / this.capacity;
555
697
  }
698
+
699
+ /**
700
+ * Given a `capacity` (defaulting to `this.capacity`),
701
+ * get the length of the map, in bytes.
702
+ */
556
703
  getLength(capacity = this.capacity) {
557
704
  let {
558
705
  HEADER_SIZE,
559
- ITEM_SIZE,
560
- BUCKET_SIZE
706
+ ITEM_SIZE
561
707
  } = this.constructor;
562
- return capacity + HEADER_SIZE + ITEM_SIZE * BUCKET_SIZE * capacity;
708
+ return capacity + HEADER_SIZE + ITEM_SIZE * capacity;
563
709
  }
564
710
 
565
711
  /** Get the next available address in the map. */
@@ -584,9 +730,16 @@ class SharedTypeMap {
584
730
  let NEXT = SharedTypeMap.#NEXT;
585
731
  return this.data[item + NEXT] || null;
586
732
  }
733
+
734
+ /** Get the type of the item at the given `item` address. */
587
735
  typeOf(item) {
588
736
  return this.data[item + SharedTypeMap.#TYPE];
589
737
  }
738
+
739
+ /**
740
+ * Store an item of `type` at the `item` address and
741
+ * link the address to the `hash` bucket.
742
+ */
590
743
  link(hash, item, type) {
591
744
  let COUNT = SharedTypeMap.#COUNT;
592
745
  let NEXT = SharedTypeMap.#NEXT;
@@ -609,6 +762,10 @@ class SharedTypeMap {
609
762
  }
610
763
  this.data[COUNT]++;
611
764
  }
765
+
766
+ /**
767
+ * Remove the link to the `item` address from the `hash` bucket.
768
+ */
612
769
  unlink(hash, item) {
613
770
  let COUNT = SharedTypeMap.#COUNT;
614
771
  let NEXT = SharedTypeMap.#NEXT;
@@ -673,16 +830,13 @@ class SharedTypeMap {
673
830
  }
674
831
  inspect() {
675
832
  const {
676
- HEADER_SIZE,
677
- ITEM_SIZE,
678
- BUCKET_SIZE
833
+ HEADER_SIZE
679
834
  } = this.constructor;
680
- let min = HEADER_SIZE + this.capacity;
681
- let max = min + this.capacity * BUCKET_SIZE * ITEM_SIZE;
835
+ let min = this.addressableLimit;
682
836
  return {
683
837
  header: this.data.subarray(0, HEADER_SIZE),
684
838
  table: this.data.subarray(HEADER_SIZE, min),
685
- data: this.data.subarray(min, max)
839
+ data: this.data.subarray(min)
686
840
  };
687
841
  }
688
842
  }
@@ -690,7 +844,17 @@ class SharedTypeMap {
690
844
  /**
691
845
  * Nodes are stored in a `SharedTypeMap`, keyed on node id plus an edge type.
692
846
  * This means that for any given unique node id, there may be `e` nodes in the
693
- * map, where `e` is the number of possible edge types in the graph.
847
+ * map, where `e` is the number of unique edge types in the graph.
848
+ *
849
+ * The _hash_ for a node is simply the node id (as issued by `getId`),
850
+ * and forms the head of linked list of unique _edge types_ connected
851
+ * to or from the same node id.
852
+ *
853
+ * In addition to a unique edge type, each Node contains the heads and tails
854
+ * of doubly linked lists of incoming and outgoing edges of the same type.
855
+ *
856
+ * Note that the links in the doubly linked lists are Edges (not Nodes),
857
+ * which are stored in a corresponding `EdgeTypeMap`.
694
858
  */
695
859
  exports.SharedTypeMap = SharedTypeMap;
696
860
  class NodeTypeMap extends SharedTypeMap {
@@ -707,6 +871,15 @@ class NodeTypeMap extends SharedTypeMap {
707
871
  * ┌──────────┬───────┬─────────┐
708
872
  * │ CAPACITY │ COUNT │ NEXT_ID │
709
873
  * └──────────┴───────┴─────────┘
874
+ *
875
+ * The `nextId` is a count of the number of times `getId` has been called.
876
+ * This is distinct concept from the `count`, which tracks the number of times
877
+ * `add` has been called.
878
+ *
879
+ * The reason for this distinction is that `getId` is called once per node
880
+ * (to issue a _unique_ id) and will _always increment_ the `nextId` counter,
881
+ * whereas `add` is called once per edge, and will only increment the `count`
882
+ * if the _type_ of edge is new for the given node.
710
883
  */
711
884
  static HEADER_SIZE = 3;
712
885
  /** The offset from the header where the next available node id is stored. */
@@ -728,6 +901,9 @@ class NodeTypeMap extends SharedTypeMap {
728
901
  * ┌──────┬──────┬──────────┬───────────┬─────────┬──────────┐
729
902
  * │ NEXT │ TYPE │ FIRST_IN │ FIRST_OUT │ LAST_IN │ LAST_OUT │
730
903
  * └──────┴──────┴──────────┴───────────┴─────────┴──────────┘
904
+ *
905
+ * The `Node` implicitly maps a node id (the hash the node was added with)
906
+ * to the first and last incoming and outgoing edges of the same _edge type_.
731
907
  */
732
908
  static ITEM_SIZE = 6;
733
909
  /** The offset at which a node's first incoming edge of this type is stored. */
@@ -738,13 +914,6 @@ class NodeTypeMap extends SharedTypeMap {
738
914
  static #LAST_IN = 4;
739
915
  /** The offset at which a node's last outgoing edge of this type is stored. */
740
916
  static #LAST_OUT = 5;
741
-
742
- /** The smallest functional node map capacity. */
743
- static MIN_CAPACITY = 2;
744
- /** The largest possible node map capacity. */
745
- static MAX_CAPACITY = Math.floor(
746
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length#what_went_wrong
747
- (2 ** 31 - 1 - NodeTypeMap.HEADER_SIZE) / NodeTypeMap.ITEM_SIZE / NodeTypeMap.BUCKET_SIZE);
748
917
  get nextId() {
749
918
  return (0, _types.toNodeId)(this.data[NodeTypeMap.#NEXT_ID]);
750
919
  }
@@ -752,13 +921,28 @@ class NodeTypeMap extends SharedTypeMap {
752
921
  this.data[NodeTypeMap.#NEXT_ID] = (0, _types.fromNodeId)(nextId);
753
922
  }
754
923
 
755
- /** Get a unique node id. */
924
+ /**
925
+ * Get the load on the node map.
926
+ *
927
+ * The load is the greater of either:
928
+ * - the ratio of the number of node ids to the capacity of the map,
929
+ * - or the ratio of the `count` to the capacity of the map.
930
+ *
931
+ * if `count` is not provided, the default is the number of items
932
+ * currently added to the map.
933
+ */
934
+ getLoad(count) {
935
+ return Math.max((0, _types.fromNodeId)(this.nextId) / this.capacity, super.getLoad(count));
936
+ }
937
+
938
+ /** Increment the node counter to get a unique node id. */
756
939
  getId() {
757
940
  return (0, _types.toNodeId)(this.data[NodeTypeMap.#NEXT_ID]++);
758
941
  }
759
- getLoad(count = this.count) {
760
- return Math.max((0, _types.fromNodeId)(this.nextId) / this.capacity, super.getLoad(count));
761
- }
942
+
943
+ /**
944
+ * Add new lists of edges of the given `type` to and from the given `node`.
945
+ */
762
946
  add(node, type) {
763
947
  let index = (0, _types.fromNodeId)(node);
764
948
  (0, _assert().default)(index >= 0 && index < this.data[NodeTypeMap.#NEXT_ID], `Invalid node id ${String(node)} (${this.data[NodeTypeMap.#NEXT_ID]})`);
@@ -766,6 +950,11 @@ class NodeTypeMap extends SharedTypeMap {
766
950
  this.link(node, address, type);
767
951
  return address;
768
952
  }
953
+
954
+ /**
955
+ * Get the address of the lists edges of the given `type`
956
+ * to and from the given `node`.
957
+ */
769
958
  addressOf(node, type) {
770
959
  let address = this.head(node);
771
960
  while (address !== null) {
@@ -776,18 +965,46 @@ class NodeTypeMap extends SharedTypeMap {
776
965
  }
777
966
  return null;
778
967
  }
968
+
969
+ /**
970
+ * Given a `node` address, get the _head_ of the linked list
971
+ * of incoming edges of the same type to the same node.
972
+ */
779
973
  firstIn(node) {
780
974
  return this.data[node + NodeTypeMap.#FIRST_IN] || null;
781
975
  }
976
+
977
+ /**
978
+ * Given a `node` address, get the _head_ of the linked list
979
+ * of outgoing edges of the same type from the same node.
980
+ */
782
981
  firstOut(node) {
783
982
  return this.data[node + NodeTypeMap.#FIRST_OUT] || null;
784
983
  }
984
+
985
+ /**
986
+ * Given a `node` address, get the _tail_ of the linked list
987
+ * of incoming edges of the same type to the same node.
988
+ */
785
989
  lastIn(node) {
786
990
  return this.data[node + NodeTypeMap.#LAST_IN] || null;
787
991
  }
992
+
993
+ /**
994
+ * Given a `node` address, get the _tail_ of the linked list
995
+ * of outgoing edges of the same type from the same node.
996
+ */
788
997
  lastOut(node) {
789
998
  return this.data[node + NodeTypeMap.#LAST_OUT] || null;
790
999
  }
1000
+
1001
+ /**
1002
+ * Set `edge` as the last incoming edge to `node`.
1003
+ * If `node` has no incoming edges, set `edge`
1004
+ * as the first incoming edge, as well.
1005
+ *
1006
+ * Returns the address of the old last incoming edge, if any.
1007
+ */
791
1008
  linkIn(node, edge) {
792
1009
  let first = this.firstIn(node);
793
1010
  let last = this.lastIn(node);
@@ -795,6 +1012,14 @@ class NodeTypeMap extends SharedTypeMap {
795
1012
  this.data[node + NodeTypeMap.#LAST_IN] = edge;
796
1013
  return last;
797
1014
  }
1015
+
1016
+ /**
1017
+ * If `edge` is the last incoming edge to `node`,
1018
+ * update the node's last incoming edge to `prev`.
1019
+ *
1020
+ * If `edge` is the first incoming edge to `node`,
1021
+ * update the node's first incoming edge to `next`.
1022
+ */
798
1023
  unlinkIn(node, edge, prev, next) {
799
1024
  let first = this.firstIn(node);
800
1025
  let last = this.lastIn(node);
@@ -805,6 +1030,14 @@ class NodeTypeMap extends SharedTypeMap {
805
1030
  this.data[node + NodeTypeMap.#FIRST_IN] = next === null ? 0 : next;
806
1031
  }
807
1032
  }
1033
+
1034
+ /**
1035
+ * Set `edge` as the last outgoing edge from `node`.
1036
+ * If `node` has no outgoing edges, set `edge`
1037
+ * as the first outgoing edge, as well.
1038
+ *
1039
+ * Returns the address of the old last outgoing edge, if any.
1040
+ */
808
1041
  linkOut(node, edge) {
809
1042
  let first = this.firstOut(node);
810
1043
  let last = this.lastOut(node);
@@ -812,6 +1045,14 @@ class NodeTypeMap extends SharedTypeMap {
812
1045
  this.data[node + NodeTypeMap.#LAST_OUT] = edge;
813
1046
  return last;
814
1047
  }
1048
+
1049
+ /**
1050
+ * If `edge` is the last outgoing edge from `node`,
1051
+ * update the node's last outgoing edge to `prev`.
1052
+ *
1053
+ * If `edge` is the first outgoing edge from `node`,
1054
+ * update the node's first outgoing edge to `next`.
1055
+ */
815
1056
  unlinkOut(node, edge, prev, next) {
816
1057
  let first = this.firstOut(node);
817
1058
  let last = this.lastOut(node);
@@ -827,6 +1068,14 @@ class NodeTypeMap extends SharedTypeMap {
827
1068
  /**
828
1069
  * Edges are stored in a `SharedTypeMap`,
829
1070
  * keyed on the 'from' and 'to' node ids, and the edge type.
1071
+ *
1072
+ * The _hash_ for an edge is a hash of the edge's `from`, `to`, and `type` values,
1073
+ * and forms the head of linked list of edges with the same hash.
1074
+ *
1075
+ * In addition to the `from`, `to` and `type` values, each Edge contains
1076
+ * the next and previous links of doubly linked lists of the _adjacent_ edges
1077
+ * of the same type, both incoming to the `to` node, and outgoing from
1078
+ * the `from` node.
830
1079
  */
831
1080
  exports.NodeTypeMap = NodeTypeMap;
832
1081
  class EdgeTypeMap extends SharedTypeMap {
@@ -843,6 +1092,13 @@ class EdgeTypeMap extends SharedTypeMap {
843
1092
  * ┌──────────┬───────┬─────────┐
844
1093
  * │ CAPACITY │ COUNT │ DELETES │
845
1094
  * └──────────┴───────┴─────────┘
1095
+ *
1096
+ * Since new edges are always appended, the space for deleted edges
1097
+ * is not reused. Instead, the `deletes` count is incremented when an
1098
+ * edge is deleted. The next available address is calculated by
1099
+ * adding the `count` and `deletes` values to the header size.
1100
+ *
1101
+ * The only way to reclaim the space used by deleted edges is to resize the map.
846
1102
  */
847
1103
  static HEADER_SIZE = 3;
848
1104
  /** The offset from the header where the delete count is stored. */
@@ -866,6 +1122,10 @@ class EdgeTypeMap extends SharedTypeMap {
866
1122
  * ┌──────┬──────┬──────┬────┬─────────┬─────────┬──────────┬──────────┐
867
1123
  * │ NEXT │ TYPE │ FROM │ TO │ NEXT_IN │ PREV_IN │ NEXT_OUT │ PREV_OUT │
868
1124
  * └──────┴──────┴──────┴────┴─────────┴─────────┴──────────┴──────────┘
1125
+ *
1126
+ * The `Edge` implicitly maps an edge hash (the hash of the edge's `FROM`,
1127
+ * `TO`, and `TYPE` values) to the next and previous adjacent edges of the
1128
+ * same _edge type_.
869
1129
  */
870
1130
  static ITEM_SIZE = 8;
871
1131
  /** The offset at which an edge's 'from' node id is stored. */
@@ -881,23 +1141,23 @@ class EdgeTypeMap extends SharedTypeMap {
881
1141
  /** The offset at which the 'from' node's previous outgoing edge is stored. */
882
1142
  static #PREV_OUT = 7;
883
1143
 
884
- /** The smallest functional edge map capacity. */
885
- static MIN_CAPACITY = 2;
886
- /** The largest possible edge map capacity. */
887
- static MAX_CAPACITY = Math.floor(
888
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length#what_went_wrong
889
- (2 ** 31 - 1 - EdgeTypeMap.HEADER_SIZE) / EdgeTypeMap.ITEM_SIZE / EdgeTypeMap.BUCKET_SIZE);
890
- /** The size after which to grow the capacity by the minimum factor. */
891
- static PEAK_CAPACITY = 2 ** 18;
1144
+ /** The number of deleted edges currently occupying space in the map. */
892
1145
  get deletes() {
893
1146
  return this.data[EdgeTypeMap.#DELETES];
894
1147
  }
1148
+
1149
+ /** Get the next available address in the map. */
895
1150
  getNextAddress() {
896
1151
  let {
897
1152
  ITEM_SIZE
898
1153
  } = this.constructor;
899
1154
  return this.addressableLimit + (this.count + this.deletes) * ITEM_SIZE;
900
1155
  }
1156
+
1157
+ /**
1158
+ * Add an edge of the given `type` between the `to` and `from` nodes
1159
+ * and link the address to the `hash` bucket.
1160
+ */
901
1161
  add(hash, from, to, type) {
902
1162
  (0, _assert().default)(hash >= 0 && hash < this.capacity, `Invalid edge hash ${String(hash)}`);
903
1163
  // Use the next available edge address.
@@ -908,11 +1168,21 @@ class EdgeTypeMap extends SharedTypeMap {
908
1168
  this.data[edge + EdgeTypeMap.#TO] = (0, _types.fromNodeId)(to);
909
1169
  return edge;
910
1170
  }
1171
+
1172
+ /**
1173
+ * Remove the `to` and `from` nodes for the given `edge` address
1174
+ * and increment the `deletes` counter.
1175
+ */
911
1176
  delete(edge) {
912
1177
  this.data[edge + EdgeTypeMap.#FROM] = 0;
913
1178
  this.data[edge + EdgeTypeMap.#TO] = 0;
914
1179
  this.data[EdgeTypeMap.#DELETES]++;
915
1180
  }
1181
+
1182
+ /**
1183
+ * Get the address of the edge with the given `hash`, `from` and `to` nodes,
1184
+ * and edge `type`.
1185
+ */
916
1186
  addressOf(hash, from, to, type) {
917
1187
  let address = this.head(hash);
918
1188
  while (address !== null) {
@@ -923,22 +1193,45 @@ class EdgeTypeMap extends SharedTypeMap {
923
1193
  }
924
1194
  return null;
925
1195
  }
1196
+
1197
+ /** Get the id of the 'from' node for the given `edge` address. */
926
1198
  from(edge) {
927
1199
  return (0, _types.toNodeId)(this.data[edge + EdgeTypeMap.#FROM]);
928
1200
  }
1201
+
1202
+ /** Get the id of the 'to' node for the given `edge` address. */
929
1203
  to(edge) {
930
1204
  return (0, _types.toNodeId)(this.data[edge + EdgeTypeMap.#TO]);
931
1205
  }
1206
+
1207
+ /**
1208
+ * Get the address of the next edge _of the same type_
1209
+ * incoming _to the same node_ as the edge at the given address.
1210
+ */
932
1211
  nextIn(edge) {
933
1212
  return this.data[edge + EdgeTypeMap.#NEXT_IN] || null;
934
1213
  }
1214
+
1215
+ /**
1216
+ * Get the address of the previous edge _of the same type_
1217
+ * incoming _to the same node_ as the edge at the given address.
1218
+ */
935
1219
  prevIn(edge) {
936
1220
  return this.data[edge + EdgeTypeMap.#PREV_IN] || null;
937
1221
  }
1222
+
1223
+ /** Link two adjacent edges of the same type incoming to the same node. */
938
1224
  linkIn(edge, next) {
1225
+ (0, _assert().default)(this.typeOf(edge) === this.typeOf(next), 'Edge types must match.');
1226
+ (0, _assert().default)(this.to(edge) === this.to(next), 'To nodes must match.');
939
1227
  this.data[edge + EdgeTypeMap.#NEXT_IN] = next;
940
1228
  this.data[next + EdgeTypeMap.#PREV_IN] = edge;
941
1229
  }
1230
+
1231
+ /**
1232
+ * Unlink an edge from the doubly linked list of incoming edges
1233
+ * to the same node.
1234
+ */
942
1235
  unlinkIn(edge) {
943
1236
  let next = this.nextIn(edge);
944
1237
  let prev = this.prevIn(edge);
@@ -953,16 +1246,35 @@ class EdgeTypeMap extends SharedTypeMap {
953
1246
  this.data[prev + EdgeTypeMap.#NEXT_IN] = 0;
954
1247
  }
955
1248
  }
1249
+
1250
+ /**
1251
+ * Get the address of the next edge _of the same type_
1252
+ * outgoing _from the same node_ as the edge at the given address.
1253
+ */
956
1254
  nextOut(edge) {
957
1255
  return this.data[edge + EdgeTypeMap.#NEXT_OUT] || null;
958
1256
  }
1257
+
1258
+ /**
1259
+ * Get the address of the previous edge _of the same type_
1260
+ * outgoing _from the same node_ as the edge at the given address.
1261
+ */
959
1262
  prevOut(edge) {
960
1263
  return this.data[edge + EdgeTypeMap.#PREV_OUT] || null;
961
1264
  }
1265
+
1266
+ /** Link two adjacent edges of the same type outgoing from the same node. */
962
1267
  linkOut(edge, next) {
1268
+ (0, _assert().default)(this.typeOf(edge) === this.typeOf(next), 'Edge types must match.');
1269
+ (0, _assert().default)(this.from(edge) === this.from(next), 'From nodes must match.');
963
1270
  this.data[edge + EdgeTypeMap.#NEXT_OUT] = next;
964
1271
  this.data[next + EdgeTypeMap.#PREV_OUT] = edge;
965
1272
  }
1273
+
1274
+ /**
1275
+ * Unlink an edge from the doubly linked list of outgoing edges
1276
+ * of the same type from the same node.
1277
+ */
966
1278
  unlinkOut(edge) {
967
1279
  let next = this.nextOut(edge);
968
1280
  let prev = this.prevOut(edge);
@@ -994,8 +1306,64 @@ class EdgeTypeMap extends SharedTypeMap {
994
1306
  }
995
1307
  }
996
1308
 
997
- // From https://gist.github.com/badboy/6267743#32-bit-mix-functions
1309
+ /**
1310
+ * Links a node to another node with an edge of the given type.
1311
+ *
1312
+ * Returns one of the following numeric status codes:
1313
+ * - `0` EdgeAdded: the edge was added
1314
+ * - `1` EdgeExists: the edge already exists
1315
+ * - `2` EdgesOverloaded: the edge map is overloaded
1316
+ * - `3` TooManyDeletes: the edge map has too many deleted edges
1317
+ * - `4` NodesOverloaded: the node map is overloaded
1318
+ */
998
1319
  exports.EdgeTypeMap = EdgeTypeMap;
1320
+ function link(from, to, type, edges, nodes, unloadFactor = DEFAULT_PARAMS.unloadFactor) {
1321
+ let hash = edges.hash(from, to, type);
1322
+ let edge = edges.addressOf(hash, from, to, type);
1323
+
1324
+ // The edge is already in the graph; do nothing.
1325
+ if (edge !== null) return LinkResult.EdgeExists;
1326
+ let toNode = nodes.addressOf(to, type);
1327
+ let fromNode = nodes.addressOf(from, type);
1328
+ let nodeCount = nodes.count;
1329
+ // add one for each node we must add.
1330
+ if (toNode === null) nodeCount++;
1331
+ if (fromNode === null) nodeCount++;
1332
+ // If we're in danger of overflowing the `nodes` array, resize it.
1333
+ if (nodes.getLoad(nodeCount) >= 1) {
1334
+ return LinkResult.NodesOverloaded;
1335
+ }
1336
+
1337
+ // We add 1 to account for the edge we are adding.
1338
+ let count = edges.count + 1;
1339
+ // Since the space occupied by deleted edges isn't reclaimed,
1340
+ // we include them in our count to avoid overflowing the `edges` array.
1341
+ let deletes = edges.deletes;
1342
+ if (edges.getLoad(count + deletes) >= 1) {
1343
+ if (edges.getLoad(deletes) >= unloadFactor && edges.getLoad(count) < unloadFactor) {
1344
+ // If we have a significant number of deletes, reclaim the space.
1345
+ return LinkResult.TooManyDeletes;
1346
+ } else {
1347
+ return LinkResult.EdgesOverloaded;
1348
+ }
1349
+ }
1350
+ if (toNode === null) toNode = nodes.add(to, type);
1351
+ if (fromNode === null) fromNode = nodes.add(from, type);
1352
+
1353
+ // Add our new edge to its hash bucket.
1354
+ edge = edges.add(hash, from, to, type);
1355
+
1356
+ // Link this edge to the node's list of incoming edges.
1357
+ let prevIn = nodes.linkIn(toNode, edge);
1358
+ if (prevIn !== null) edges.linkIn(prevIn, edge);
1359
+
1360
+ // Link this edge to the node's list of outgoing edges.
1361
+ let prevOut = nodes.linkOut(fromNode, edge);
1362
+ if (prevOut !== null) edges.linkOut(prevOut, edge);
1363
+ return LinkResult.EdgeAdded;
1364
+ }
1365
+
1366
+ // From https://gist.github.com/badboy/6267743#32-bit-mix-functions
999
1367
  function hash32shift(key) {
1000
1368
  key = ~key + (key << 15); // key = (key << 15) - key - 1;
1001
1369
  key = key ^ key >> 12;
@@ -1008,35 +1376,40 @@ function hash32shift(key) {
1008
1376
  function interpolate(x, y, t) {
1009
1377
  return x + (y - x) * Math.min(1, Math.max(0, t));
1010
1378
  }
1011
- function increaseNodeCapacity(nodeCapacity) {
1012
- let {
1013
- MIN_CAPACITY,
1014
- MAX_CAPACITY
1015
- } = NodeTypeMap;
1016
- let newCapacity = Math.round(nodeCapacity * MIN_GROW_FACTOR);
1017
- (0, _assert().default)(newCapacity <= MAX_CAPACITY, 'Node capacity overflow!');
1018
- return Math.max(MIN_CAPACITY, newCapacity);
1379
+ function increaseNodeCapacity(currentCapacity, params) {
1380
+ let newCapacity = Math.max(
1381
+ // Make sure we have room for at least 2 more nodes.
1382
+ currentCapacity + 2, Math.ceil(currentCapacity * params.minGrowFactor));
1383
+ if (newCapacity >= NodeTypeMap.MAX_CAPACITY) {
1384
+ if (currentCapacity > NodeTypeMap.MAX_CAPACITY - 2) {
1385
+ throw new Error('Node capacity overflow!');
1386
+ }
1387
+ return NodeTypeMap.MAX_CAPACITY;
1388
+ }
1389
+ return newCapacity;
1390
+ }
1391
+ function increaseEdgeCapacity(currentCapacity, params) {
1392
+ // This is intended to strike a balance between growing the edge capacity
1393
+ // in too small increments, which causes a lot of resizing, and growing
1394
+ // the edge capacity in too large increments, which results in a lot of
1395
+ // wasted memory.
1396
+ let pct = currentCapacity / params.peakCapacity;
1397
+ let growFactor = interpolate(params.maxGrowFactor, params.minGrowFactor, pct);
1398
+ let newCapacity = Math.max(
1399
+ // Make sure we have room for at least one more edge.
1400
+ currentCapacity + 1, Math.ceil(currentCapacity * growFactor));
1401
+ if (newCapacity >= EdgeTypeMap.MAX_CAPACITY) {
1402
+ if (currentCapacity > EdgeTypeMap.MAX_CAPACITY - 1) {
1403
+ throw new Error('Edge capacity overflow!');
1404
+ }
1405
+ return EdgeTypeMap.MAX_CAPACITY;
1406
+ }
1407
+ return newCapacity;
1019
1408
  }
1020
- function getNextEdgeCapacity(capacity, count, load) {
1021
- let {
1022
- MIN_CAPACITY,
1023
- MAX_CAPACITY,
1024
- PEAK_CAPACITY
1025
- } = EdgeTypeMap;
1026
- let newCapacity = capacity;
1027
- if (load > LOAD_FACTOR) {
1028
- // This is intended to strike a balance between growing the edge capacity
1029
- // in too small increments, which causes a lot of resizing, and growing
1030
- // the edge capacity in too large increments, which results in a lot of
1031
- // wasted memory.
1032
-
1033
- let growFactor = interpolate(MAX_GROW_FACTOR, MIN_GROW_FACTOR, capacity / PEAK_CAPACITY);
1034
- newCapacity = Math.round(capacity * growFactor);
1035
- } else if (load < UNLOAD_FACTOR) {
1036
- // In some cases, it may be possible to shrink the edge capacity,
1037
- // but this is only likely to occur when a lot of edges have been removed.
1038
- newCapacity = Math.round(capacity * SHRINK_FACTOR);
1039
- }
1040
- (0, _assert().default)(newCapacity <= MAX_CAPACITY, 'Edge capacity overflow!');
1041
- return Math.max(MIN_CAPACITY, newCapacity);
1409
+ function decreaseEdgeCapacity(currentCapacity, params) {
1410
+ return Math.max(
1411
+ // Make sure we don't shrink the capacity _below_ 2.
1412
+ 2, Math.min(
1413
+ // Make sure we shrink the capacity by at least 1.
1414
+ currentCapacity - 1, Math.ceil(currentCapacity * params.shrinkFactor)));
1042
1415
  }