@parcel/graph 2.9.4-nightly.2999 → 2.9.4-nightly.3004

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.
@@ -354,6 +354,20 @@ class AdjacencyList {
354
354
  }
355
355
  return nodes;
356
356
  }
357
+ forEachNodeIdConnectedFromReverse(from, fn) {
358
+ let node = this.#nodes.head(from);
359
+ while (node !== null) {
360
+ let edge = this.#nodes.lastOut(node);
361
+ while (edge !== null) {
362
+ let to = this.#edges.to(edge);
363
+ if (fn(to)) {
364
+ return;
365
+ }
366
+ edge = this.#edges.prevOut(edge);
367
+ }
368
+ node = this.#nodes.next(node);
369
+ }
370
+ }
357
371
 
358
372
  /**
359
373
  * Get the list of nodes connected to this node.
package/lib/BitSet.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BitSet = void 0;
7
+ // Small wasm program that exposes the `ctz` instruction.
8
+ // https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Count_trailing_zeros
9
+ const wasmBuf = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0d, 0x01, 0x09, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x30, 0x00, 0x00, 0x0a, 0x07, 0x01, 0x05, 0x00, 0x20, 0x00, 0x68, 0x0b, 0x00, 0x0f, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02, 0x08, 0x01, 0x00, 0x01, 0x00, 0x03, 0x6e, 0x75, 0x6d]);
10
+
11
+ // eslint-disable-next-line
12
+ const {
13
+ trailing0
14
+ } = new WebAssembly.Instance(new WebAssembly.Module(wasmBuf)).exports;
15
+ class BitSet {
16
+ constructor(maxBits) {
17
+ this.bits = new Uint32Array(Math.ceil(maxBits / 32));
18
+ }
19
+ clone() {
20
+ let res = new BitSet(this.capacity);
21
+ res.bits.set(this.bits);
22
+ return res;
23
+ }
24
+ static union(a, b) {
25
+ let res = a.clone();
26
+ res.union(b);
27
+ return res;
28
+ }
29
+ get capacity() {
30
+ return this.bits.length * 32;
31
+ }
32
+ add(bit) {
33
+ this.bits[bit >>> 5] |= 1 << (bit & 31);
34
+ }
35
+ delete(bit) {
36
+ this.bits[bit >>> 5] &= ~(1 << (bit & 31));
37
+ }
38
+ has(bit) {
39
+ return Boolean(this.bits[bit >>> 5] & 1 << (bit & 31));
40
+ }
41
+ clear() {
42
+ this.bits.fill(0);
43
+ }
44
+ intersect(other) {
45
+ for (let i = 0; i < this.bits.length; i++) {
46
+ this.bits[i] &= other.bits[i];
47
+ }
48
+ }
49
+ union(other) {
50
+ for (let i = 0; i < this.bits.length; i++) {
51
+ this.bits[i] |= other.bits[i];
52
+ }
53
+ }
54
+ remove(other) {
55
+ for (let i = 0; i < this.bits.length; i++) {
56
+ this.bits[i] &= ~other.bits[i];
57
+ }
58
+ }
59
+ forEach(fn) {
60
+ // https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
61
+ let bits = this.bits;
62
+ for (let k = 0; k < bits.length; k++) {
63
+ let v = bits[k];
64
+ while (v !== 0) {
65
+ let t = (v & -v) >>> 0;
66
+ // $FlowFixMe
67
+ fn((k << 5) + trailing0(v));
68
+ v ^= t;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ exports.BitSet = BitSet;
package/lib/Graph.js CHANGED
@@ -7,13 +7,7 @@ exports.default = exports.ALL_EDGE_TYPES = void 0;
7
7
  exports.mapVisitor = mapVisitor;
8
8
  var _types = require("./types");
9
9
  var _AdjacencyList = _interopRequireDefault(require("./AdjacencyList"));
10
- function _assert() {
11
- const data = _interopRequireDefault(require("assert"));
12
- _assert = function () {
13
- return data;
14
- };
15
- return data;
16
- }
10
+ var _BitSet = require("./BitSet");
17
11
  function _nullthrows() {
18
12
  const data = _interopRequireDefault(require("nullthrows"));
19
13
  _nullthrows = function () {
@@ -26,7 +20,7 @@ const ALL_EDGE_TYPES = -1;
26
20
  exports.ALL_EDGE_TYPES = ALL_EDGE_TYPES;
27
21
  class Graph {
28
22
  constructor(opts) {
29
- this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) || new Map();
23
+ this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) || [];
30
24
  this.setRootNodeId(opts === null || opts === void 0 ? void 0 : opts.rootNodeId);
31
25
  let adjacencyList = opts === null || opts === void 0 ? void 0 : opts.adjacencyList;
32
26
  this.adjacencyList = adjacencyList ? _AdjacencyList.default.deserialize(adjacencyList) : new _AdjacencyList.default();
@@ -56,23 +50,23 @@ class Graph {
56
50
  }
57
51
  addNode(node) {
58
52
  let id = this.adjacencyList.addNode();
59
- this.nodes.set(id, node);
53
+ this.nodes.push(node);
60
54
  return id;
61
55
  }
62
56
  hasNode(id) {
63
- return this.nodes.has(id);
57
+ return this.nodes[id] != null;
64
58
  }
65
59
  getNode(id) {
66
- return this.nodes.get(id);
60
+ return this.nodes[id];
67
61
  }
68
62
  addEdge(from, to, type = 1) {
69
63
  if (Number(type) === 0) {
70
64
  throw new Error(`Edge type "${type}" not allowed`);
71
65
  }
72
- if (!this.getNode(from)) {
66
+ if (this.getNode(from) == null) {
73
67
  throw new Error(`"from" node '${(0, _types.fromNodeId)(from)}' not found`);
74
68
  }
75
- if (!this.getNode(to)) {
69
+ if (this.getNode(to) == null) {
76
70
  throw new Error(`"to" node '${(0, _types.fromNodeId)(to)}' not found`);
77
71
  }
78
72
  return this.adjacencyList.addEdge(from, to, type);
@@ -109,8 +103,7 @@ class Graph {
109
103
  } of this.adjacencyList.getOutboundEdgesByType(nodeId)) {
110
104
  this._removeEdge(nodeId, to, type);
111
105
  }
112
- let wasRemoved = this.nodes.delete(nodeId);
113
- (0, _assert().default)(wasRemoved);
106
+ this.nodes[nodeId] = null;
114
107
  }
115
108
  removeEdges(nodeId, type = 1) {
116
109
  if (!this.hasNode(nodeId)) {
@@ -164,7 +157,7 @@ class Graph {
164
157
  }
165
158
  updateNode(nodeId, node) {
166
159
  this._assertHasNodeId(nodeId);
167
- this.nodes.set(nodeId, node);
160
+ this.nodes[nodeId] = node;
168
161
  }
169
162
 
170
163
  // Update a node's downstream nodes making sure to prune any orphaned branches
@@ -183,11 +176,16 @@ class Graph {
183
176
  }
184
177
  }
185
178
  traverse(visit, startNodeId, type = 1) {
186
- return this.dfs({
187
- visit,
188
- startNodeId,
189
- getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type)
190
- });
179
+ let enter = typeof visit === 'function' ? visit : visit.enter;
180
+ if (type === ALL_EDGE_TYPES && enter && (typeof visit === 'function' || !visit.exit)) {
181
+ return this.dfsFast(enter, startNodeId);
182
+ } else {
183
+ return this.dfs({
184
+ visit,
185
+ startNodeId,
186
+ getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type)
187
+ });
188
+ }
191
189
  }
192
190
  filteredTraverse(filter, visit, startNodeId, type) {
193
191
  return this.traverse(mapVisitor(filter, visit), startNodeId, type);
@@ -199,6 +197,66 @@ class Graph {
199
197
  getChildren: nodeId => this.getNodeIdsConnectedTo(nodeId, type)
200
198
  });
201
199
  }
200
+ dfsFast(visit, startNodeId) {
201
+ let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
202
+ this._assertHasNodeId(traversalStartNode);
203
+ let visited;
204
+ if (!this._visited || this._visited.capacity < this.nodes.length) {
205
+ this._visited = new _BitSet.BitSet(this.nodes.length);
206
+ visited = this._visited;
207
+ } else {
208
+ visited = this._visited;
209
+ visited.clear();
210
+ }
211
+ // Take shared instance to avoid re-entrancy issues.
212
+ this._visited = null;
213
+ let stopped = false;
214
+ let skipped = false;
215
+ let actions = {
216
+ skipChildren() {
217
+ skipped = true;
218
+ },
219
+ stop() {
220
+ stopped = true;
221
+ }
222
+ };
223
+ let queue = [{
224
+ nodeId: traversalStartNode,
225
+ context: null
226
+ }];
227
+ while (queue.length !== 0) {
228
+ let {
229
+ nodeId,
230
+ context
231
+ } = queue.pop();
232
+ if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
233
+ visited.add(nodeId);
234
+ skipped = false;
235
+ let newContext = visit(nodeId, context, actions);
236
+ if (typeof newContext !== 'undefined') {
237
+ // $FlowFixMe[reassign-const]
238
+ context = newContext;
239
+ }
240
+ if (skipped) {
241
+ continue;
242
+ }
243
+ if (stopped) {
244
+ this._visited = visited;
245
+ return context;
246
+ }
247
+ this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
248
+ if (!visited.has(child)) {
249
+ queue.push({
250
+ nodeId: child,
251
+ context
252
+ });
253
+ }
254
+ return false;
255
+ });
256
+ }
257
+ this._visited = visited;
258
+ return null;
259
+ }
202
260
  dfs({
203
261
  visit,
204
262
  startNodeId,
@@ -206,7 +264,16 @@ class Graph {
206
264
  }) {
207
265
  let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
208
266
  this._assertHasNodeId(traversalStartNode);
209
- let visited = new Set();
267
+ let visited;
268
+ if (!this._visited || this._visited.capacity < this.nodes.length) {
269
+ this._visited = new _BitSet.BitSet(this.nodes.length);
270
+ visited = this._visited;
271
+ } else {
272
+ visited = this._visited;
273
+ visited.clear();
274
+ }
275
+ // Take shared instance to avoid re-entrancy issues.
276
+ this._visited = null;
210
277
  let stopped = false;
211
278
  let skipped = false;
212
279
  let actions = {
@@ -261,7 +328,9 @@ class Graph {
261
328
  return context;
262
329
  }
263
330
  };
264
- return walk(traversalStartNode);
331
+ let result = walk(traversalStartNode);
332
+ this._visited = visited;
333
+ return result;
265
334
  }
266
335
  bfs(visit) {
267
336
  let rootNodeId = (0, _nullthrows().default)(this.rootNodeId, 'A root node is required to traverse');
package/lib/index.js CHANGED
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "ALL_EDGE_TYPES", {
9
9
  return _Graph.ALL_EDGE_TYPES;
10
10
  }
11
11
  });
12
+ Object.defineProperty(exports, "BitSet", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _BitSet.BitSet;
16
+ }
17
+ });
12
18
  Object.defineProperty(exports, "ContentGraph", {
13
19
  enumerable: true,
14
20
  get: function () {
@@ -42,6 +48,7 @@ Object.defineProperty(exports, "toNodeId", {
42
48
  var _types = require("./types");
43
49
  var _Graph = _interopRequireWildcard(require("./Graph"));
44
50
  var _ContentGraph = _interopRequireDefault(require("./ContentGraph"));
51
+ var _BitSet = require("./BitSet");
45
52
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
46
53
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
47
54
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parcel/graph",
3
- "version": "2.9.4-nightly.2999+c182930c6",
3
+ "version": "2.9.4-nightly.3004+375a2e51e",
4
4
  "description": "Blazing fast, zero configuration web application bundler",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -20,7 +20,8 @@
20
20
  "node": ">= 12.0.0"
21
21
  },
22
22
  "dependencies": {
23
+ "@parcel/utils": "2.0.0-nightly.1381+375a2e51e",
23
24
  "nullthrows": "^1.1.1"
24
25
  },
25
- "gitHead": "c182930c631e3bf06034d5b350064152b57206ce"
26
+ "gitHead": "375a2e51e102e27d0d514781feae28b888cc4c08"
26
27
  }
@@ -467,6 +467,24 @@ export default class AdjacencyList<TEdgeType: number = 1> {
467
467
  return nodes;
468
468
  }
469
469
 
470
+ forEachNodeIdConnectedFromReverse(
471
+ from: NodeId,
472
+ fn: (nodeId: NodeId) => boolean,
473
+ ) {
474
+ let node = this.#nodes.head(from);
475
+ while (node !== null) {
476
+ let edge = this.#nodes.lastOut(node);
477
+ while (edge !== null) {
478
+ let to = this.#edges.to(edge);
479
+ if (fn(to)) {
480
+ return;
481
+ }
482
+ edge = this.#edges.prevOut(edge);
483
+ }
484
+ node = this.#nodes.next(node);
485
+ }
486
+ }
487
+
470
488
  /**
471
489
  * Get the list of nodes connected to this node.
472
490
  */
package/src/BitSet.js ADDED
@@ -0,0 +1,93 @@
1
+ // @flow strict-local
2
+
3
+ // Small wasm program that exposes the `ctz` instruction.
4
+ // https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Count_trailing_zeros
5
+ const wasmBuf = new Uint8Array([
6
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01,
7
+ 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0d, 0x01, 0x09, 0x74, 0x72,
8
+ 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x30, 0x00, 0x00, 0x0a, 0x07, 0x01, 0x05,
9
+ 0x00, 0x20, 0x00, 0x68, 0x0b, 0x00, 0x0f, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02,
10
+ 0x08, 0x01, 0x00, 0x01, 0x00, 0x03, 0x6e, 0x75, 0x6d,
11
+ ]);
12
+
13
+ // eslint-disable-next-line
14
+ const {trailing0} = new WebAssembly.Instance(new WebAssembly.Module(wasmBuf))
15
+ .exports;
16
+
17
+ export class BitSet {
18
+ bits: Uint32Array;
19
+
20
+ constructor(maxBits: number) {
21
+ this.bits = new Uint32Array(Math.ceil(maxBits / 32));
22
+ }
23
+
24
+ clone(): BitSet {
25
+ let res = new BitSet(this.capacity);
26
+ res.bits.set(this.bits);
27
+ return res;
28
+ }
29
+
30
+ static union(a: BitSet, b: BitSet): BitSet {
31
+ let res = a.clone();
32
+ res.union(b);
33
+ return res;
34
+ }
35
+
36
+ get capacity(): number {
37
+ return this.bits.length * 32;
38
+ }
39
+
40
+ add(bit: number) {
41
+ let i = bit >>> 5;
42
+ let b = bit & 31;
43
+ this.bits[i] |= 1 << b;
44
+ }
45
+
46
+ delete(bit: number) {
47
+ let i = bit >>> 5;
48
+ let b = bit & 31;
49
+ this.bits[i] &= ~(1 << b);
50
+ }
51
+
52
+ has(bit: number): boolean {
53
+ let i = bit >>> 5;
54
+ let b = bit & 31;
55
+ return Boolean(this.bits[i] & (1 << b));
56
+ }
57
+
58
+ clear() {
59
+ this.bits.fill(0);
60
+ }
61
+
62
+ intersect(other: BitSet) {
63
+ for (let i = 0; i < this.bits.length; i++) {
64
+ this.bits[i] &= other.bits[i];
65
+ }
66
+ }
67
+
68
+ union(other: BitSet) {
69
+ for (let i = 0; i < this.bits.length; i++) {
70
+ this.bits[i] |= other.bits[i];
71
+ }
72
+ }
73
+
74
+ remove(other: BitSet) {
75
+ for (let i = 0; i < this.bits.length; i++) {
76
+ this.bits[i] &= ~other.bits[i];
77
+ }
78
+ }
79
+
80
+ forEach(fn: (bit: number) => void) {
81
+ // https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
82
+ let bits = this.bits;
83
+ for (let k = 0; k < bits.length; k++) {
84
+ let v = bits[k];
85
+ while (v !== 0) {
86
+ let t = (v & -v) >>> 0;
87
+ // $FlowFixMe
88
+ fn((k << 5) + trailing0(v));
89
+ v ^= t;
90
+ }
91
+ }
92
+ }
93
+ }
package/src/Graph.js CHANGED
@@ -3,20 +3,24 @@
3
3
  import {fromNodeId} from './types';
4
4
  import AdjacencyList, {type SerializedAdjacencyList} from './AdjacencyList';
5
5
  import type {Edge, NodeId} from './types';
6
- import type {TraversalActions, GraphVisitor} from '@parcel/types';
6
+ import type {
7
+ TraversalActions,
8
+ GraphVisitor,
9
+ GraphTraversalCallback,
10
+ } from '@parcel/types';
11
+ import {BitSet} from './BitSet';
7
12
 
8
- import assert from 'assert';
9
13
  import nullthrows from 'nullthrows';
10
14
 
11
15
  export type NullEdgeType = 1;
12
16
  export type GraphOpts<TNode, TEdgeType: number = 1> = {|
13
- nodes?: Map<NodeId, TNode>,
17
+ nodes?: Array<TNode | null>,
14
18
  adjacencyList?: SerializedAdjacencyList<TEdgeType>,
15
19
  rootNodeId?: ?NodeId,
16
20
  |};
17
21
 
18
22
  export type SerializedGraph<TNode, TEdgeType: number = 1> = {|
19
- nodes: Map<NodeId, TNode>,
23
+ nodes: Array<TNode | null>,
20
24
  adjacencyList: SerializedAdjacencyList<TEdgeType>,
21
25
  rootNodeId: ?NodeId,
22
26
  |};
@@ -25,12 +29,13 @@ export type AllEdgeTypes = -1;
25
29
  export const ALL_EDGE_TYPES: AllEdgeTypes = -1;
26
30
 
27
31
  export default class Graph<TNode, TEdgeType: number = 1> {
28
- nodes: Map<NodeId, TNode>;
32
+ nodes: Array<TNode | null>;
29
33
  adjacencyList: AdjacencyList<TEdgeType>;
30
34
  rootNodeId: ?NodeId;
35
+ _visited: ?BitSet;
31
36
 
32
37
  constructor(opts: ?GraphOpts<TNode, TEdgeType>) {
33
- this.nodes = opts?.nodes || new Map();
38
+ this.nodes = opts?.nodes || [];
34
39
  this.setRootNodeId(opts?.rootNodeId);
35
40
 
36
41
  let adjacencyList = opts?.adjacencyList;
@@ -69,16 +74,16 @@ export default class Graph<TNode, TEdgeType: number = 1> {
69
74
 
70
75
  addNode(node: TNode): NodeId {
71
76
  let id = this.adjacencyList.addNode();
72
- this.nodes.set(id, node);
77
+ this.nodes.push(node);
73
78
  return id;
74
79
  }
75
80
 
76
81
  hasNode(id: NodeId): boolean {
77
- return this.nodes.has(id);
82
+ return this.nodes[id] != null;
78
83
  }
79
84
 
80
85
  getNode(id: NodeId): ?TNode {
81
- return this.nodes.get(id);
86
+ return this.nodes[id];
82
87
  }
83
88
 
84
89
  addEdge(
@@ -90,11 +95,11 @@ export default class Graph<TNode, TEdgeType: number = 1> {
90
95
  throw new Error(`Edge type "${type}" not allowed`);
91
96
  }
92
97
 
93
- if (!this.getNode(from)) {
98
+ if (this.getNode(from) == null) {
94
99
  throw new Error(`"from" node '${fromNodeId(from)}' not found`);
95
100
  }
96
101
 
97
- if (!this.getNode(to)) {
102
+ if (this.getNode(to) == null) {
98
103
  throw new Error(`"to" node '${fromNodeId(to)}' not found`);
99
104
  }
100
105
 
@@ -156,8 +161,7 @@ export default class Graph<TNode, TEdgeType: number = 1> {
156
161
  this._removeEdge(nodeId, to, type);
157
162
  }
158
163
 
159
- let wasRemoved = this.nodes.delete(nodeId);
160
- assert(wasRemoved);
164
+ this.nodes[nodeId] = null;
161
165
  }
162
166
 
163
167
  removeEdges(nodeId: NodeId, type: TEdgeType | NullEdgeType = 1) {
@@ -237,7 +241,7 @@ export default class Graph<TNode, TEdgeType: number = 1> {
237
241
 
238
242
  updateNode(nodeId: NodeId, node: TNode): void {
239
243
  this._assertHasNodeId(nodeId);
240
- this.nodes.set(nodeId, node);
244
+ this.nodes[nodeId] = node;
241
245
  }
242
246
 
243
247
  // Update a node's downstream nodes making sure to prune any orphaned branches
@@ -277,11 +281,20 @@ export default class Graph<TNode, TEdgeType: number = 1> {
277
281
  | Array<TEdgeType | NullEdgeType>
278
282
  | AllEdgeTypes = 1,
279
283
  ): ?TContext {
280
- return this.dfs({
281
- visit,
282
- startNodeId,
283
- getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type),
284
- });
284
+ let enter = typeof visit === 'function' ? visit : visit.enter;
285
+ if (
286
+ type === ALL_EDGE_TYPES &&
287
+ enter &&
288
+ (typeof visit === 'function' || !visit.exit)
289
+ ) {
290
+ return this.dfsFast(enter, startNodeId);
291
+ } else {
292
+ return this.dfs({
293
+ visit,
294
+ startNodeId,
295
+ getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type),
296
+ });
297
+ }
285
298
  }
286
299
 
287
300
  filteredTraverse<TValue, TContext>(
@@ -309,6 +322,72 @@ export default class Graph<TNode, TEdgeType: number = 1> {
309
322
  });
310
323
  }
311
324
 
325
+ dfsFast<TContext>(
326
+ visit: GraphTraversalCallback<NodeId, TContext>,
327
+ startNodeId: ?NodeId,
328
+ ): ?TContext {
329
+ let traversalStartNode = nullthrows(
330
+ startNodeId ?? this.rootNodeId,
331
+ 'A start node is required to traverse',
332
+ );
333
+ this._assertHasNodeId(traversalStartNode);
334
+
335
+ let visited;
336
+ if (!this._visited || this._visited.capacity < this.nodes.length) {
337
+ this._visited = new BitSet(this.nodes.length);
338
+ visited = this._visited;
339
+ } else {
340
+ visited = this._visited;
341
+ visited.clear();
342
+ }
343
+ // Take shared instance to avoid re-entrancy issues.
344
+ this._visited = null;
345
+
346
+ let stopped = false;
347
+ let skipped = false;
348
+ let actions: TraversalActions = {
349
+ skipChildren() {
350
+ skipped = true;
351
+ },
352
+ stop() {
353
+ stopped = true;
354
+ },
355
+ };
356
+
357
+ let queue = [{nodeId: traversalStartNode, context: null}];
358
+ while (queue.length !== 0) {
359
+ let {nodeId, context} = queue.pop();
360
+ if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
361
+ visited.add(nodeId);
362
+
363
+ skipped = false;
364
+ let newContext = visit(nodeId, context, actions);
365
+ if (typeof newContext !== 'undefined') {
366
+ // $FlowFixMe[reassign-const]
367
+ context = newContext;
368
+ }
369
+
370
+ if (skipped) {
371
+ continue;
372
+ }
373
+
374
+ if (stopped) {
375
+ this._visited = visited;
376
+ return context;
377
+ }
378
+
379
+ this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
380
+ if (!visited.has(child)) {
381
+ queue.push({nodeId: child, context});
382
+ }
383
+ return false;
384
+ });
385
+ }
386
+
387
+ this._visited = visited;
388
+ return null;
389
+ }
390
+
312
391
  dfs<TContext>({
313
392
  visit,
314
393
  startNodeId,
@@ -324,7 +403,17 @@ export default class Graph<TNode, TEdgeType: number = 1> {
324
403
  );
325
404
  this._assertHasNodeId(traversalStartNode);
326
405
 
327
- let visited = new Set<NodeId>();
406
+ let visited;
407
+ if (!this._visited || this._visited.capacity < this.nodes.length) {
408
+ this._visited = new BitSet(this.nodes.length);
409
+ visited = this._visited;
410
+ } else {
411
+ visited = this._visited;
412
+ visited.clear();
413
+ }
414
+ // Take shared instance to avoid re-entrancy issues.
415
+ this._visited = null;
416
+
328
417
  let stopped = false;
329
418
  let skipped = false;
330
419
  let actions: TraversalActions = {
@@ -392,7 +481,9 @@ export default class Graph<TNode, TEdgeType: number = 1> {
392
481
  }
393
482
  };
394
483
 
395
- return walk(traversalStartNode);
484
+ let result = walk(traversalStartNode);
485
+ this._visited = visited;
486
+ return result;
396
487
  }
397
488
 
398
489
  bfs(visit: (nodeId: NodeId) => ?boolean): ?NodeId {
package/src/index.js CHANGED
@@ -6,3 +6,4 @@ export type {ContentGraphOpts, SerializedContentGraph} from './ContentGraph';
6
6
  export {toNodeId, fromNodeId} from './types';
7
7
  export {default as Graph, ALL_EDGE_TYPES, mapVisitor} from './Graph';
8
8
  export {default as ContentGraph} from './ContentGraph';
9
+ export {BitSet} from './BitSet';
package/src/types.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @flow strict-local
2
2
 
3
3
  // forcing NodeId to be opaque as it should only be created once
4
- export opaque type NodeId = number;
4
+ export type NodeId = number;
5
5
  export function toNodeId(x: number): NodeId {
6
6
  return x;
7
7
  }
@@ -0,0 +1,98 @@
1
+ // @flow strict-local
2
+
3
+ import assert from 'assert';
4
+ import {BitSet} from '../src/BitSet';
5
+
6
+ function assertValues(set: BitSet, values: Array<number>) {
7
+ let setValues = [];
8
+ set.forEach(bit => {
9
+ setValues.push(bit);
10
+ });
11
+
12
+ for (let value of values) {
13
+ assert(set.has(value), 'Set.has returned false');
14
+ assert(
15
+ setValues.some(v => v === value),
16
+ 'Set values is missing value',
17
+ );
18
+ }
19
+
20
+ assert(
21
+ setValues.length === values.length,
22
+ `Expected ${values.length} values but got ${setValues.length}`,
23
+ );
24
+ }
25
+
26
+ describe('BitSet', () => {
27
+ it('clone should return a set with the same values', () => {
28
+ let set1 = new BitSet(5);
29
+ set1.add(1);
30
+ set1.add(3);
31
+
32
+ let set2 = set1.clone();
33
+
34
+ assertValues(set2, [1, 3]);
35
+ });
36
+
37
+ it('clear should remove all values from the set', () => {
38
+ let set1 = new BitSet(5);
39
+ set1.add(1);
40
+ set1.add(3);
41
+
42
+ set1.clear();
43
+
44
+ assertValues(set1, []);
45
+ });
46
+
47
+ it('delete should remove values from the set', () => {
48
+ let set1 = new BitSet(5);
49
+ set1.add(1);
50
+ set1.add(3);
51
+ set1.add(5);
52
+
53
+ set1.delete(3);
54
+
55
+ assertValues(set1, [1, 5]);
56
+ });
57
+
58
+ it('should intersect with another BitSet', () => {
59
+ let set1 = new BitSet(5);
60
+ set1.add(1);
61
+ set1.add(3);
62
+
63
+ let set2 = new BitSet(5);
64
+ set2.add(3);
65
+ set2.add(5);
66
+
67
+ set1.intersect(set2);
68
+ assertValues(set1, [3]);
69
+ });
70
+
71
+ it('should union with another BitSet', () => {
72
+ let set1 = new BitSet(5);
73
+ set1.add(1);
74
+ set1.add(3);
75
+
76
+ let set2 = new BitSet(5);
77
+ set2.add(3);
78
+ set2.add(5);
79
+
80
+ set1.union(set2);
81
+ assertValues(set1, [1, 3, 5]);
82
+ });
83
+
84
+ it('BitSet.union should create a new BitSet with the union', () => {
85
+ let set1 = new BitSet(5);
86
+ set1.add(1);
87
+ set1.add(3);
88
+
89
+ let set2 = new BitSet(5);
90
+ set2.add(3);
91
+ set2.add(5);
92
+
93
+ let set3 = BitSet.union(set1, set2);
94
+ assertValues(set1, [1, 3]);
95
+ assertValues(set2, [3, 5]);
96
+ assertValues(set3, [1, 3, 5]);
97
+ });
98
+ });
@@ -9,7 +9,7 @@ import {toNodeId} from '../src/types';
9
9
  describe('Graph', () => {
10
10
  it('constructor should initialize an empty graph', () => {
11
11
  let graph = new Graph();
12
- assert.deepEqual(graph.nodes, new Map());
12
+ assert.deepEqual(graph.nodes, []);
13
13
  assert.deepEqual([...graph.getAllEdges()], []);
14
14
  });
15
15
 
@@ -17,7 +17,7 @@ describe('Graph', () => {
17
17
  let graph = new Graph();
18
18
  let node = {};
19
19
  let id = graph.addNode(node);
20
- assert.equal(graph.nodes.get(id), node);
20
+ assert.equal(graph.getNode(id), node);
21
21
  });
22
22
 
23
23
  it('errors when traversing a graph with no root', () => {
@@ -117,10 +117,10 @@ describe('Graph', () => {
117
117
  graph.addEdge(nodeB, nodeD);
118
118
 
119
119
  graph.removeEdge(nodeA, nodeB);
120
- assert(graph.nodes.has(nodeA));
121
- assert(graph.nodes.has(nodeD));
122
- assert(!graph.nodes.has(nodeB));
123
- assert(!graph.nodes.has(nodeC));
120
+ assert(graph.hasNode(nodeA));
121
+ assert(graph.hasNode(nodeD));
122
+ assert(!graph.hasNode(nodeB));
123
+ assert(!graph.hasNode(nodeC));
124
124
  assert.deepEqual(
125
125
  [...graph.getAllEdges()],
126
126
  [{from: nodeA, to: nodeD, type: 1}],
@@ -164,7 +164,7 @@ describe('Graph', () => {
164
164
 
165
165
  graph.removeNode(nodeB);
166
166
 
167
- assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
167
+ assert.deepEqual(graph.nodes.filter(Boolean), ['a', 'c', 'f']);
168
168
  assert.deepEqual(Array.from(graph.getAllEdges()), [
169
169
  {from: nodeA, to: nodeC, type: 1},
170
170
  {from: nodeC, to: nodeF, type: 1},
@@ -209,7 +209,7 @@ describe('Graph', () => {
209
209
 
210
210
  graph.removeNode(nodeB);
211
211
 
212
- assert.deepEqual([...graph.nodes.keys()], [nodeA, nodeC, nodeF]);
212
+ assert.deepEqual(graph.nodes.filter(Boolean), ['a', 'c', 'f']);
213
213
  assert.deepEqual(Array.from(graph.getAllEdges()), [
214
214
  {from: nodeA, to: nodeC, type: 1},
215
215
  {from: nodeC, to: nodeF, type: 1},
@@ -337,7 +337,7 @@ describe('Graph', () => {
337
337
 
338
338
  graph.removeNode(node1);
339
339
 
340
- assert.strictEqual(graph.nodes.size, 1);
340
+ assert.deepEqual(graph.nodes.filter(Boolean), ['root']);
341
341
  assert.deepStrictEqual(Array.from(graph.getAllEdges()), []);
342
342
  });
343
343
  });