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