Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library barback.phase; | 5 library barback.phase; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'asset_cascade.dart'; | 10 import 'asset_cascade.dart'; |
| 11 import 'asset_id.dart'; | 11 import 'asset_id.dart'; |
| 12 import 'asset_node.dart'; | 12 import 'asset_node.dart'; |
| 13 import 'asset_set.dart'; | 13 import 'asset_set.dart'; |
| 14 import 'cluster_runner.dart'; | |
| 14 import 'errors.dart'; | 15 import 'errors.dart'; |
| 16 import 'operator.dart'; | |
| 17 import 'phase_forwarder.dart'; | |
| 15 import 'phase_input.dart'; | 18 import 'phase_input.dart'; |
| 16 import 'phase_output.dart'; | 19 import 'phase_output.dart'; |
| 17 import 'stream_pool.dart'; | 20 import 'stream_pool.dart'; |
| 18 import 'transformer.dart'; | 21 import 'transformer.dart'; |
| 22 import 'transformer_cluster.dart'; | |
| 19 import 'utils.dart'; | 23 import 'utils.dart'; |
| 20 | 24 |
| 21 /// One phase in the ordered series of transformations in an [AssetCascade]. | 25 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 22 /// | 26 /// |
| 23 /// Each phase can access outputs from previous phases and can in turn pass | 27 /// Each phase can access outputs from previous phases and can in turn pass |
| 24 /// outputs to later phases. Phases are processed strictly serially. All | 28 /// outputs to later phases. Phases are processed strictly serially. All |
| 25 /// transforms in a phase will be complete before moving on to the next phase. | 29 /// transforms in a phase will be complete before moving on to the next phase. |
| 26 /// Within a single phase, all transforms will be run in parallel. | 30 /// Within a single phase, all transforms will be run in parallel. |
| 27 /// | 31 /// |
| 28 /// Building can be interrupted between phases. For example, a source is added | 32 /// Building can be interrupted between phases. For example, a source is added |
| 29 /// which starts the background process. Sometime during, say, phase 2 (which | 33 /// which starts the background process. Sometime during, say, phase 2 (which |
| 30 /// is running asynchronously) that source is modified. When the process queue | 34 /// is running asynchronously) that source is modified. When the process queue |
| 31 /// goes to advance to phase 3, it will see that modification and start the | 35 /// goes to advance to phase 3, it will see that modification and start the |
| 32 /// waterfall from the beginning again. | 36 /// waterfall from the beginning again. |
| 33 class Phase { | 37 class Phase { |
| 34 /// The cascade that owns this phase. | 38 /// The cascade that owns this phase. |
| 35 final AssetCascade cascade; | 39 final AssetCascade cascade; |
| 36 | 40 |
| 37 /// The transformers that can access [inputs]. | 41 /// The transformers that can access [inputs]. |
| 38 /// | 42 /// |
| 39 /// Their outputs will be available to the next phase. | 43 /// Their outputs will be available to the next phase. |
| 40 final Set<Transformer> _transformers; | 44 final Set<Transformer> _transformers; |
| 41 | 45 |
| 46 /// The clusters for this phase. | |
| 47 final _clusters = new Map<TransformerCluster, ClusterRunner>(); | |
| 48 | |
| 42 /// The inputs for this phase. | 49 /// The inputs for this phase. |
| 43 /// | 50 /// |
| 44 /// For the first phase, these will be the source assets. For all other | 51 /// For the first phase, these will be the source assets. For all other |
| 45 /// phases, they will be the outputs from the previous phase. | 52 /// phases, they will be the outputs from the previous phase. |
| 46 final _inputs = new Map<AssetId, PhaseInput>(); | 53 final _inputs = new Map<AssetId, PhaseInput>(); |
| 47 | 54 |
| 55 /// The forwarders for this phase. | |
| 56 final _forwarders = new Map<AssetId, PhaseForwarder>(); | |
| 57 | |
| 48 /// The outputs for this phase. | 58 /// The outputs for this phase. |
| 49 final _outputs = new Map<AssetId, PhaseOutput>(); | 59 final _outputs = new Map<AssetId, PhaseOutput>(); |
| 50 | 60 |
| 61 // TODO(nweiz): don't re-calculate this on the fly all the time | |
|
Bob Nystrom
2013/10/04 21:47:50
"." and capitalize "Don't".
nweiz
2013/10/07 23:21:31
Done.
| |
| 62 /// The set of all [AssetNode.origin] properties of the input assets for this | |
| 63 /// phase. | |
| 64 /// | |
| 65 /// This is used to determine which assets have been passed unmodified through | |
| 66 /// [_inputs] or [_clusters]. Eeach input asset has a PhaseInput in [_inputs]. | |
|
Bob Nystrom
2013/10/04 21:47:50
"Eeach" -> "Each".
nweiz
2013/10/07 23:21:31
Done.
| |
| 67 /// If that input isn't consumed by any transformers, it will be forwarded | |
| 68 /// through the PhaseInput. However, it's possible that it was consumed by a | |
| 69 /// cluster, and so shouldn't be forwarded through the phase as a whole. | |
| 70 /// | |
| 71 /// In order to detect whether an output has been forwarded through a cluster | |
| 72 /// or a PhaseInput, we must be able to distinguish it from other outputs with | |
| 73 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If | |
| 74 /// so, it's been forwarded unmodified. | |
| 75 Set<AssetNode> get _inputOrigins => | |
| 76 _inputs.values.map((input) => input.input.origin).toSet(); | |
| 77 | |
| 51 /// A stream that emits an event whenever this phase becomes dirty and needs | 78 /// A stream that emits an event whenever this phase becomes dirty and needs |
| 52 /// to be run. | 79 /// to be run. |
| 53 /// | 80 /// |
| 54 /// This may emit events when the phase was already dirty or while processing | 81 /// This may emit events when the phase was already dirty or while processing |
| 55 /// transforms. Events are emitted synchronously to ensure that the dirty | 82 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 56 /// state is thoroughly propagated as soon as any assets are changed. | 83 /// state is thoroughly propagated as soon as any assets are changed. |
| 57 Stream get onDirty => _onDirtyPool.stream; | 84 Stream get onDirty => _onDirtyPool.stream; |
| 58 final _onDirtyPool = new StreamPool.broadcast(); | 85 final _onDirtyPool = new StreamPool.broadcast(); |
| 59 | 86 |
| 60 /// A controller whose stream feeds into [_onDirtyPool]. | 87 /// A controller whose stream feeds into [_onDirtyPool]. |
| 61 /// | 88 /// |
| 62 /// This is used whenever an input is added or transforms are changed. | 89 /// This is used whenever an input is added or transforms are changed. |
| 63 final _onDirtyController = new StreamController.broadcast(sync: true); | 90 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 64 | 91 |
| 92 /// Whether this phase is dirty and needs to be run. | |
| 93 bool get isDirty => _inputs.values.any((input) => input.isDirty) || | |
| 94 _clusters.values.any((cluster) => cluster.isDirty); | |
| 95 | |
| 65 /// The phase after this one. | 96 /// The phase after this one. |
| 66 /// | 97 /// |
| 67 /// Outputs from this phase will be passed to it. | 98 /// Outputs from this phase will be passed to it. |
| 68 Phase get next => _next; | 99 Phase get next => _next; |
| 69 Phase _next; | 100 Phase _next; |
| 70 | 101 |
| 71 /// Returns all currently-available output assets for this phase. | 102 /// Returns all currently-available output assets for this phase. |
| 72 AssetSet get availableOutputs { | 103 Set<AssetNode> get availableOutputs { |
| 73 return new AssetSet.from(_outputs.values | 104 return _outputs.values |
| 74 .map((output) => output.output) | 105 .map((output) => output.output) |
| 75 .where((node) => node.state.isAvailable) | 106 .where((node) => node.state.isAvailable) |
| 76 .map((node) => node.asset)); | 107 .toSet(); |
| 77 } | 108 } |
| 78 | 109 |
| 79 Phase(this.cascade, Iterable<Transformer> transformers) | 110 Phase(this.cascade, Iterable<Operator> operators) |
|
Bob Nystrom
2013/10/04 21:47:50
We could keep this API typed if the transformers a
nweiz
2013/10/07 23:21:31
I think that would result in more code duplication
| |
| 80 : _transformers = transformers.toSet() { | 111 : _transformers = operators.where((op) => op is Transformer).toSet() { |
| 81 _onDirtyPool.add(_onDirtyController.stream); | 112 _onDirtyPool.add(_onDirtyController.stream); |
| 113 | |
| 114 for (var cluster in operators.where((op) => op is TransformerCluster)) { | |
| 115 _clusters[cluster] = new ClusterRunner(cascade, cluster); | |
| 116 _onDirtyPool.add(_clusters[cluster].onDirty); | |
| 117 } | |
| 82 } | 118 } |
| 83 | 119 |
| 84 /// Adds a new asset as an input for this phase. | 120 /// Adds a new asset as an input for this phase. |
| 85 /// | 121 /// |
| 86 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 122 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 87 /// will automatically begin determining which transforms can consume it as a | 123 /// will automatically begin determining which transforms can consume it as a |
| 88 /// primary input. The transforms themselves won't be applied until [process] | 124 /// primary input. The transforms themselves won't be applied until [process] |
| 89 /// is called, however. | 125 /// is called, however. |
| 90 /// | 126 /// |
| 91 /// This should only be used for brand-new assets or assets that have been | 127 /// This should only be used for brand-new assets or assets that have been |
| 92 /// removed and re-created. The phase will automatically handle updated assets | 128 /// removed and re-created. The phase will automatically handle updated assets |
| 93 /// using the [AssetNode.onStateChange] stream. | 129 /// using the [AssetNode.onStateChange] stream. |
| 94 void addInput(AssetNode node) { | 130 void addInput(AssetNode node) { |
| 95 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); | 131 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); |
| 96 | 132 |
| 133 var forwarder = new PhaseForwarder(_clusters.length + 1); | |
|
Bob Nystrom
2013/10/04 21:47:50
Explain what the .length + 1 represents.
nweiz
2013/10/07 23:21:31
Done.
| |
| 134 _forwarders[node.id] = forwarder; | |
| 135 forwarder.onForwarding.listen((asset) { | |
| 136 _addOutput(asset); | |
| 137 | |
| 138 var exception = _outputs[asset.id].collisionException; | |
| 139 if (exception != null) cascade.reportError(exception); | |
| 140 }); | |
| 141 | |
| 97 var input = new PhaseInput(this, node, _transformers); | 142 var input = new PhaseInput(this, node, _transformers); |
| 98 _inputs[node.id] = input; | 143 _inputs[node.id] = input; |
| 99 input.input.whenRemoved.then((_) => _inputs.remove(node.id)); | 144 input.input.whenRemoved.then((_) { |
| 145 _inputs.remove(node.id); | |
| 146 _forwarders.remove(node.id).remove(); | |
| 147 }); | |
| 100 _onDirtyPool.add(input.onDirty); | 148 _onDirtyPool.add(input.onDirty); |
| 101 _onDirtyController.add(null); | 149 _onDirtyController.add(null); |
| 150 | |
| 151 for (var cluster in _clusters.values) { | |
| 152 cluster.addInput(node); | |
| 153 } | |
| 102 } | 154 } |
| 103 | 155 |
| 104 /// Gets the asset node for an input [id]. | 156 /// Gets the asset node for an input [id]. |
| 105 /// | 157 /// |
| 106 /// If an input with that ID cannot be found, returns null. | 158 /// If an input with that ID cannot be found, returns null. |
| 107 Future<AssetNode> getInput(AssetId id) { | 159 Future<AssetNode> getInput(AssetId id) { |
| 108 return newFuture(() { | 160 return newFuture(() { |
| 109 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 161 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 110 if (_inputs.containsKey(id)) return _inputs[id].input; | 162 if (_inputs.containsKey(id)) return _inputs[id].input; |
| 111 return null; | 163 return null; |
| 112 }); | 164 }); |
| 113 } | 165 } |
| 114 | 166 |
| 115 /// Gets the asset node for an output [id]. | 167 /// Gets the asset node for an output [id]. |
| 116 /// | 168 /// |
| 117 /// If an output with that ID cannot be found, returns null. | 169 /// If an output with that ID cannot be found, returns null. |
| 118 Future<AssetNode> getOutput(AssetId id) { | 170 Future<AssetNode> getOutput(AssetId id) { |
| 119 return newFuture(() { | 171 return newFuture(() { |
| 120 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 172 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 121 if (!_outputs.containsKey(id)) return null; | 173 if (!_outputs.containsKey(id)) return null; |
| 122 return _outputs[id].output; | 174 return _outputs[id].output; |
| 123 }); | 175 }); |
| 124 } | 176 } |
| 125 | 177 |
| 126 /// Set this phase's transformers to [transformers]. | 178 /// Set this phase's operators to [operators]. |
| 127 void updateTransformers(Iterable<Transformer> transformers) { | 179 void updateOperators(Iterable<Operator> operators) { |
| 128 _onDirtyController.add(null); | 180 _onDirtyController.add(null); |
| 181 | |
| 182 var transformers = operators.where((op) => op is Transformer); | |
| 129 _transformers.clear(); | 183 _transformers.clear(); |
| 130 _transformers.addAll(transformers); | 184 _transformers.addAll(transformers); |
| 131 for (var input in _inputs.values) { | 185 for (var input in _inputs.values) { |
| 132 input.updateTransformers(_transformers); | 186 input.updateTransformers(transformers); |
| 187 } | |
| 188 | |
| 189 var newClusters = operators.where((op) => op is TransformerCluster).toSet(); | |
| 190 var oldClusters = _clusters.keys.toSet(); | |
| 191 for (var removed in oldClusters.difference(newClusters)) { | |
| 192 _clusters.remove(removed).remove(); | |
| 193 } | |
|
Bob Nystrom
2013/10/04 21:47:50
Nit but add blank line after "}".
nweiz
2013/10/07 23:21:31
Done.
| |
| 194 for (var added in newClusters.difference(oldClusters)) { | |
| 195 var runner = new ClusterRunner(cascade, added); | |
| 196 _clusters[added] = runner; | |
| 197 _onDirtyPool.add(runner.onDirty); | |
| 198 for (var input in _inputs.values) { | |
| 199 runner.addInput(input.input); | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 for (var forwarder in _forwarders.values) { | |
| 204 forwarder.channels = _clusters.length + 1; | |
| 133 } | 205 } |
| 134 } | 206 } |
| 135 | 207 |
| 136 /// Add a new phase after this one with [transformers]. | 208 /// Add a new phase after this one with [operators]. |
| 137 /// | 209 /// |
| 138 /// This may only be called on a phase with no phase following it. | 210 /// This may only be called on a phase with no phase following it. |
| 139 Phase addPhase(Iterable<Transformer> transformers) { | 211 Phase addPhase(Iterable<Operators> operators) { |
| 140 assert(_next == null); | 212 assert(_next == null); |
| 141 _next = new Phase(cascade, transformers); | 213 _next = new Phase(cascade, operators); |
| 142 for (var output in _outputs.values.toList()) { | 214 for (var output in _outputs.values.toList()) { |
| 143 // Remove [output]'s listeners because now they should get the asset from | 215 // Remove [output]'s listeners because now they should get the asset from |
| 144 // [_next], rather than this phase. Any transforms consuming [output] will | 216 // [_next], rather than this phase. Any transforms consuming [output] will |
| 145 // be re-run and will consume the output from the new final phase. | 217 // be re-run and will consume the output from the new final phase. |
| 146 output.removeListeners(); | 218 output.removeListeners(); |
| 147 | 219 |
| 148 // Removing [output]'s listeners will cause it to be removed from | 220 // Removing [output]'s listeners will cause it to be removed from |
| 149 // [_outputs], so we have to put it back. | 221 // [_outputs], so we have to put it back. |
| 150 _outputs[output.output.id] = output; | 222 _outputs[output.output.id] = output; |
| 151 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id)); | 223 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id)); |
| 152 _next.addInput(output.output); | 224 _next.addInput(output.output); |
| 153 } | 225 } |
| 154 return _next; | 226 return _next; |
| 155 } | 227 } |
| 156 | 228 |
| 157 /// Mark this phase as removed. | 229 /// Mark this phase as removed. |
| 158 /// | 230 /// |
| 159 /// This will remove all the phase's outputs and all following phases. | 231 /// This will remove all the phase's outputs and all following phases. |
| 160 void remove() { | 232 void remove() { |
| 161 removeFollowing(); | 233 removeFollowing(); |
| 162 for (var input in _inputs.values.toList()) { | 234 for (var input in _inputs.values.toList()) { |
| 163 input.remove(); | 235 input.remove(); |
| 164 } | 236 } |
| 237 for (var cluster in _clusters.values) { | |
| 238 cluster.remove(); | |
| 239 } | |
| 165 _onDirtyPool.close(); | 240 _onDirtyPool.close(); |
| 166 } | 241 } |
| 167 | 242 |
| 168 /// Remove all phases after this one. | 243 /// Remove all phases after this one. |
| 169 void removeFollowing() { | 244 void removeFollowing() { |
| 170 if (_next == null) return; | 245 if (_next == null) return; |
| 171 _next.remove(); | 246 _next.remove(); |
| 172 _next = null; | 247 _next = null; |
| 173 } | 248 } |
| 174 | 249 |
| 175 /// Processes this phase. | 250 /// Processes this phase. |
| 176 /// | 251 /// |
| 177 /// Returns a future that completes when processing is done. If there is | 252 /// Returns a future that completes when processing is done. If there is |
| 178 /// nothing to process, returns `null`. | 253 /// nothing to process, returns `null`. |
| 179 Future process() { | 254 Future process() { |
| 180 if (!_inputs.values.any((input) => input.isDirty)) return null; | 255 if (!isDirty) return null; |
| 181 | 256 |
| 182 var outputIds = new Set<AssetId>(); | 257 var outputIds = new Set<AssetId>(); |
| 183 return Future.wait(_inputs.values.map((input) { | 258 void _handleOutputs(Set<AssetNode> outputs) { |
| 259 for (var asset in outputs) { | |
| 260 if (_inputOrigins.contains(asset.origin)) { | |
| 261 _forwarders[asset.id].add(asset); | |
| 262 continue; | |
| 263 } | |
| 264 | |
| 265 outputIds.add(asset.id); | |
| 266 _addOutput(asset); | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 var outputFutures = []..addAll(_inputs.values.map((input) { | |
| 184 if (!input.isDirty) return new Future.value(new Set()); | 271 if (!input.isDirty) return new Future.value(new Set()); |
| 185 return input.process().then((outputs) { | 272 return input.process().then(_handleOutputs); |
| 186 for (var asset in outputs) { | 273 }))..addAll(_clusters.values.map((input) { |
|
Bob Nystrom
2013/10/04 21:47:50
Maybe it's just me, but using ".." here feels a bi
nweiz
2013/10/07 23:21:31
Done.
| |
| 187 outputIds.add(asset.id); | 274 if (!input.isDirty) return new Future.value(new Set()); |
| 188 if (_outputs.containsKey(asset.id)) { | 275 return input.process().then(_handleOutputs); |
| 189 _outputs[asset.id].add(asset); | 276 })); |
| 190 } else { | 277 |
| 191 _outputs[asset.id] = new PhaseOutput(this, asset); | 278 // TODO(nweiz): handle pass-through. |
| 192 _outputs[asset.id].output.whenRemoved.then((_) { | 279 |
| 193 _outputs.remove(asset.id); | 280 return Future.wait(outputFutures).then((_) { |
| 194 }); | |
| 195 if (_next != null) _next.addInput(_outputs[asset.id].output); | |
| 196 } | |
| 197 } | |
| 198 }); | |
| 199 })).then((_) { | |
| 200 // Report collisions in a deterministic order. | 281 // Report collisions in a deterministic order. |
| 201 outputIds = outputIds.toList(); | 282 outputIds = outputIds.toList(); |
| 202 outputIds.sort((a, b) => a.compareTo(b)); | 283 outputIds.sort((a, b) => a.compareTo(b)); |
| 203 for (var id in outputIds) { | 284 for (var id in outputIds) { |
| 204 // It's possible the output was removed before other transforms in this | 285 // It's possible the output was removed before other transforms in this |
| 205 // phase finished. | 286 // phase finished. |
| 206 if (!_outputs.containsKey(id)) continue; | 287 if (!_outputs.containsKey(id)) continue; |
| 207 var exception = _outputs[id].collisionException; | 288 var exception = _outputs[id].collisionException; |
| 208 if (exception != null) cascade.reportError(exception); | 289 if (exception != null) cascade.reportError(exception); |
| 209 } | 290 } |
| 210 }); | 291 }); |
| 211 } | 292 } |
| 293 | |
| 294 /// Add [asset] as an output of this phase. | |
| 295 void _addOutput(AssetNode asset) { | |
| 296 if (_outputs.containsKey(asset.id)) { | |
| 297 _outputs[asset.id].add(asset); | |
| 298 } else { | |
| 299 _outputs[asset.id] = new PhaseOutput(this, asset); | |
| 300 _outputs[asset.id].output.whenRemoved.then((_) { | |
| 301 _outputs.remove(asset.id); | |
| 302 }); | |
| 303 if (_next != null) _next.addInput(_outputs[asset.id].output); | |
| 304 } | |
| 305 } | |
| 212 } | 306 } |
| OLD | NEW |