| OLD | NEW |
| (Empty) | |
| 1 |
| 2 /** |
| 3 * Instances of [Node] represent nodes in a dependency graph. The |
| 4 * type parameter, [NodeType], is the derived type (this affords some |
| 5 * extra type safety by making it difficult to accidentally construct |
| 6 * bridges between unrelated dependency graphs). |
| 7 */ |
| 8 abstract class Node<NodeType> { |
| 9 /** |
| 10 * Index used by Tarjan's strongly connected components algorithm. |
| 11 * Zero means the node has not been visited yet; a nonzero value |
| 12 * counts the order in which the node was visited. |
| 13 */ |
| 14 int _index = 0; |
| 15 |
| 16 /** |
| 17 * Low link used by Tarjan's strongly connected components |
| 18 * algorithm. This represents the smallest [_index] of all the nodes |
| 19 * in the strongly connected component to which this node belongs. |
| 20 */ |
| 21 int _lowLink = 0; |
| 22 |
| 23 List<NodeType> _dependencies; |
| 24 |
| 25 /** |
| 26 * Retrieve the dependencies of this node. |
| 27 */ |
| 28 List<NodeType> get dependencies => _dependencies ??= computeDependencies(); |
| 29 |
| 30 /** |
| 31 * Indicates whether this node has been evaluated yet. |
| 32 */ |
| 33 bool get isEvaluated; |
| 34 |
| 35 /** |
| 36 * Compute the dependencies of this node. |
| 37 */ |
| 38 List<NodeType> computeDependencies(); |
| 39 } |
| 40 |
| 41 /** |
| 42 * An instance of [DependencyWalker] contains the core algorithms for |
| 43 * walking a dependency graph and evaluating nodes in a safe order. |
| 44 */ |
| 45 abstract class DependencyWalker<NodeType extends Node<NodeType>> { |
| 46 /** |
| 47 * Called by [walk] to evaluate a single non-cyclical node, after |
| 48 * all that node's dependencies have been evaluated. |
| 49 */ |
| 50 void evaluate(NodeType v); |
| 51 |
| 52 /** |
| 53 * Called by [walk] to evaluate a strongly connected component |
| 54 * containing one or more nodes. All dependencies of the strongly |
| 55 * connected component have been evaluated. |
| 56 */ |
| 57 void evaluateScc(List<NodeType> scc); |
| 58 |
| 59 /** |
| 60 * Walk the dependency graph starting at [startingPoint], finding |
| 61 * strongly connected components and evaluating them in a safe order |
| 62 * by calling [evaluate] and [evaluateScc]. |
| 63 * |
| 64 * This is an implementation of Tarjan's strongly connected |
| 65 * components algorithm |
| 66 * (https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_alg
orithm). |
| 67 */ |
| 68 void walk(NodeType startingPoint) { |
| 69 // TODO(paulberry): consider rewriting in a non-recursive way so |
| 70 // that long dependency chains don't cause stack overflow. |
| 71 |
| 72 // TODO(paulberry): in the event that an exception occurs during |
| 73 // the walk, restore the state of the [Node] data structures so |
| 74 // that further evaluation will be safe. |
| 75 |
| 76 // The index which will be assigned to the next node that is |
| 77 // freshly visited. |
| 78 int index = 1; |
| 79 |
| 80 // Stack of nodes which have been seen so far and whose strongly |
| 81 // connected component is still being determined. Nodes are only |
| 82 // popped off the stack when they are evaluated, so sometimes the |
| 83 // stack contains nodes that were visited after the current node. |
| 84 List<NodeType> stack = <NodeType>[]; |
| 85 |
| 86 void strongConnect(NodeType node) { |
| 87 bool hasTrivialCycle = false; |
| 88 |
| 89 // Assign the current node an index and add it to the stack. We |
| 90 // haven't seen any of its dependencies yet, so set its lowLink |
| 91 // to its index, indicating that so far it is the only node in |
| 92 // its strongly connected component. |
| 93 node._index = node._lowLink = index++; |
| 94 stack.add(node); |
| 95 |
| 96 // Consider the node's dependencies one at a time. |
| 97 for (NodeType dependency in node.dependencies) { |
| 98 // If the dependency has already been evaluated, it can't be |
| 99 // part of this node's strongly connected component, so we can |
| 100 // skip it. |
| 101 if (dependency.isEvaluated) { |
| 102 continue; |
| 103 } |
| 104 if (identical(node, dependency)) { |
| 105 // If a node includes itself as a dependency, there is no need to |
| 106 // explore the dependency further. |
| 107 hasTrivialCycle = true; |
| 108 } else if (dependency._index == 0) { |
| 109 // The dependency hasn't been seen yet, so recurse on it. |
| 110 strongConnect(dependency); |
| 111 // If the dependency's lowLink refers to a node that was |
| 112 // visited before the current node, that means that the |
| 113 // current node, the dependency, and the node referred to by |
| 114 // the dependency's lowLink are all part of the same |
| 115 // strongly connected component, so we need to update the |
| 116 // current node's lowLink accordingly. |
| 117 if (dependency._lowLink < node._lowLink) { |
| 118 node._lowLink = dependency._lowLink; |
| 119 } |
| 120 } else { |
| 121 // The dependency has already been seen, so it is part of |
| 122 // the current node's strongly connected component. If it |
| 123 // was visited earlier than the current node's lowLink, then |
| 124 // it is a new addition to the current node's strongly |
| 125 // connected component, so we need to update the current |
| 126 // node's lowLink accordingly. |
| 127 if (dependency._index < node._lowLink) { |
| 128 node._lowLink = dependency._index; |
| 129 } |
| 130 } |
| 131 } |
| 132 |
| 133 // If the current node's lowLink is the same as its index, then |
| 134 // we have finished visiting a strongly connected component, so |
| 135 // pop the stack and evaluate it before moving on. |
| 136 if (node._lowLink == node._index) { |
| 137 // The strongly connected component has only one node. If there is a |
| 138 // cycle, it's a trivial one. |
| 139 if (identical(stack.last, node)) { |
| 140 stack.removeLast(); |
| 141 if (hasTrivialCycle) { |
| 142 evaluateScc(<NodeType>[node]); |
| 143 } else { |
| 144 evaluate(node); |
| 145 } |
| 146 } else { |
| 147 // There are multiple nodes in the strongly connected |
| 148 // component. |
| 149 List<NodeType> scc = <NodeType>[]; |
| 150 while (true) { |
| 151 NodeType otherNode = stack.removeLast(); |
| 152 scc.add(otherNode); |
| 153 if (identical(otherNode, node)) { |
| 154 break; |
| 155 } |
| 156 } |
| 157 evaluateScc(scc); |
| 158 } |
| 159 } |
| 160 } |
| 161 |
| 162 // Kick off the algorithm starting with the starting point. |
| 163 strongConnect(startingPoint); |
| 164 } |
| 165 } |
| OLD | NEW |