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