-
Notifications
You must be signed in to change notification settings - Fork 622
/
Copy pathcalculateCriticalPath.ts
118 lines (101 loc) · 3.84 KB
/
calculateCriticalPath.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
export interface ISortableOperation<T extends ISortableOperation<T>> {
operationName: string | undefined;
criticalPathLength?: number | undefined;
weight: number;
consumers: Set<T>;
}
/**
* For every operation in the input, computes the length of the longest chain of operations that depend on it.
* This value is stored as `operation.criticalPathLength`.
*/
export function calculateCriticalPathLengths<T extends ISortableOperation<T>>(operations: Iterable<T>): T[] {
// Clone the set of operations as an array, so that we can sort it.
const queue: T[] = Array.from(operations);
// Create a collection for detecting visited nodes
const cycleDetectorStack: Set<T> = new Set();
for (const operation of queue) {
calculateCriticalPathLength(operation, cycleDetectorStack);
}
return queue;
}
/**
* Calculates the shortest path from `startOperation` to `endOperation`.
* Used when printing out circular dependencies.
*/
export function calculateShortestPath<T extends ISortableOperation<T>>(
startOperation: T,
endOperation: T
): T[] {
// Map of each operation to the most optimal parent
const parents: Map<T, T | undefined> = new Map([[endOperation, undefined]]);
let finalParent: T | undefined;
// Run a breadth-first search to find the shortest path between the start and end operations
outer: for (const [operation] of parents) {
for (const consumer of operation.consumers) {
// Since this is a breadth-first traversal, the first encountered path to a given node
// will be tied for shortest, so only the first encountered path needs to be tracked
if (!parents.has(consumer)) {
parents.set(consumer, operation);
}
if (consumer === startOperation) {
finalParent = operation;
break outer;
}
}
}
if (!finalParent) {
throw new Error(
`Could not find a path from "${startOperation.operationName}" to "${endOperation.operationName}"`
);
}
// Walk back up the path from the end operation to the start operation
let currentOperation: T = finalParent;
const path: T[] = [startOperation];
while (currentOperation !== undefined) {
path.push(currentOperation);
currentOperation = parents.get(currentOperation)!;
}
return path;
}
/**
* Perform a depth-first search to find critical path length to the provided operation.
* Cycle detection comes at minimal additional cost.
*/
export function calculateCriticalPathLength<T extends ISortableOperation<T>>(
operation: T,
dependencyChain: Set<T>
): number {
if (dependencyChain.has(operation)) {
// Ensure we have the shortest path to the cycle
const shortestPath: T[] = calculateShortestPath(operation, operation);
throw new Error(
'A cyclic dependency was encountered:\n ' +
shortestPath.map((visitedTask) => visitedTask.operationName).join('\n -> ')
);
}
let { criticalPathLength } = operation;
if (criticalPathLength !== undefined) {
// This has been visited already
return criticalPathLength;
}
criticalPathLength = 0;
if (operation.consumers.size) {
dependencyChain.add(operation);
for (const consumer of operation.consumers) {
criticalPathLength = Math.max(
criticalPathLength,
calculateCriticalPathLength(consumer, dependencyChain)
);
}
dependencyChain.delete(operation);
}
// Include the contribution from the current operation
criticalPathLength += operation.weight ?? 1;
// Record result
operation.criticalPathLength = criticalPathLength;
// Directly writing operations to an output collection here would yield a topological sorted set
// However, we want a bit more fine-tuning of the output than just the raw topology
return criticalPathLength;
}