| 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 'errors.dart'; | 14 import 'errors.dart'; |
| 15 import 'phase_input.dart'; | 15 import 'phase_input.dart'; |
| 16 import 'phase_output.dart'; |
| 16 import 'stream_pool.dart'; | 17 import 'stream_pool.dart'; |
| 17 import 'transformer.dart'; | 18 import 'transformer.dart'; |
| 18 import 'utils.dart'; | 19 import 'utils.dart'; |
| 19 | 20 |
| 20 /// One phase in the ordered series of transformations in an [AssetCascade]. | 21 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 21 /// | 22 /// |
| 22 /// Each phase can access outputs from previous phases and can in turn pass | 23 /// Each phase can access outputs from previous phases and can in turn pass |
| 23 /// outputs to later phases. Phases are processed strictly serially. All | 24 /// outputs to later phases. Phases are processed strictly serially. All |
| 24 /// transforms in a phase will be complete before moving on to the next phase. | 25 /// transforms in a phase will be complete before moving on to the next phase. |
| 25 /// Within a single phase, all transforms will be run in parallel. | 26 /// Within a single phase, all transforms will be run in parallel. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 37 /// | 38 /// |
| 38 /// Their outputs will be available to the next phase. | 39 /// Their outputs will be available to the next phase. |
| 39 final Set<Transformer> _transformers; | 40 final Set<Transformer> _transformers; |
| 40 | 41 |
| 41 /// The inputs for this phase. | 42 /// The inputs for this phase. |
| 42 /// | 43 /// |
| 43 /// For the first phase, these will be the source assets. For all other | 44 /// For the first phase, these will be the source assets. For all other |
| 44 /// phases, they will be the outputs from the previous phase. | 45 /// phases, they will be the outputs from the previous phase. |
| 45 final _inputs = new Map<AssetId, PhaseInput>(); | 46 final _inputs = new Map<AssetId, PhaseInput>(); |
| 46 | 47 |
| 47 /// A map of output ids to the asset node outputs for those ids and the | 48 /// The outputs for this phase. |
| 48 /// transforms that produced those asset nodes. | 49 final _outputs = new Map<AssetId, PhaseOutput>(); |
| 49 /// | |
| 50 /// Usually there's only one node for a given output id. However, it's | |
| 51 /// possible for multiple transformers to output an asset with the same id. In | |
| 52 /// that case, the chronologically first output emitted is passed forward. We | |
| 53 /// keep track of the other nodes so that if that output is removed, we know | |
| 54 /// which asset to replace it with. | |
| 55 final _outputs = new Map<AssetId, Queue<AssetNode>>(); | |
| 56 | 50 |
| 57 /// A stream that emits an event whenever this phase becomes dirty and needs | 51 /// A stream that emits an event whenever this phase becomes dirty and needs |
| 58 /// to be run. | 52 /// to be run. |
| 59 /// | 53 /// |
| 60 /// This may emit events when the phase was already dirty or while processing | 54 /// This may emit events when the phase was already dirty or while processing |
| 61 /// transforms. Events are emitted synchronously to ensure that the dirty | 55 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 62 /// state is thoroughly propagated as soon as any assets are changed. | 56 /// state is thoroughly propagated as soon as any assets are changed. |
| 63 Stream get onDirty => _onDirtyPool.stream; | 57 Stream get onDirty => _onDirtyPool.stream; |
| 64 final _onDirtyPool = new StreamPool.broadcast(); | 58 final _onDirtyPool = new StreamPool.broadcast(); |
| 65 | 59 |
| 66 /// A controller whose stream feeds into [_onDirtyPool]. | 60 /// A controller whose stream feeds into [_onDirtyPool]. |
| 67 /// | 61 /// |
| 68 /// This is used whenever an input is added or transforms are changed. | 62 /// This is used whenever an input is added or transforms are changed. |
| 69 final _onDirtyController = new StreamController.broadcast(sync: true); | 63 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 70 | 64 |
| 71 /// The phase after this one. | 65 /// The phase after this one. |
| 72 /// | 66 /// |
| 73 /// Outputs from this phase will be passed to it. | 67 /// Outputs from this phase will be passed to it. |
| 74 Phase get next => _next; | 68 Phase get next => _next; |
| 75 Phase _next; | 69 Phase _next; |
| 76 | 70 |
| 77 /// Returns all currently-available output assets for this phase. | 71 /// Returns all currently-available output assets for this phase. |
| 78 AssetSet get availableOutputs { | 72 AssetSet get availableOutputs { |
| 79 return new AssetSet.from(_outputs.values | 73 return new AssetSet.from(_outputs.values |
| 80 .map((queue) => queue.first) | 74 .map((output) => output.output) |
| 81 .where((node) => node.state.isAvailable) | 75 .where((node) => node.state.isAvailable) |
| 82 .map((node) => node.asset)); | 76 .map((node) => node.asset)); |
| 83 } | 77 } |
| 84 | 78 |
| 85 Phase(this.cascade, Iterable<Transformer> transformers) | 79 Phase(this.cascade, Iterable<Transformer> transformers) |
| 86 : _transformers = transformers.toSet() { | 80 : _transformers = transformers.toSet() { |
| 87 _onDirtyPool.add(_onDirtyController.stream); | 81 _onDirtyPool.add(_onDirtyController.stream); |
| 88 } | 82 } |
| 89 | 83 |
| 90 /// Adds a new asset as an input for this phase. | 84 /// Adds a new asset as an input for this phase. |
| (...skipping 27 matching lines...) Expand all Loading... |
| 118 }); | 112 }); |
| 119 } | 113 } |
| 120 | 114 |
| 121 /// Gets the asset node for an output [id]. | 115 /// Gets the asset node for an output [id]. |
| 122 /// | 116 /// |
| 123 /// If an output with that ID cannot be found, returns null. | 117 /// If an output with that ID cannot be found, returns null. |
| 124 Future<AssetNode> getOutput(AssetId id) { | 118 Future<AssetNode> getOutput(AssetId id) { |
| 125 return newFuture(() { | 119 return newFuture(() { |
| 126 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 120 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 127 if (!_outputs.containsKey(id)) return null; | 121 if (!_outputs.containsKey(id)) return null; |
| 128 return _outputs[id].first; | 122 return _outputs[id].output; |
| 129 }); | 123 }); |
| 130 } | 124 } |
| 131 | 125 |
| 132 /// Set this phase's transformers to [transformers]. | 126 /// Set this phase's transformers to [transformers]. |
| 133 void updateTransformers(Iterable<Transformer> transformers) { | 127 void updateTransformers(Iterable<Transformer> transformers) { |
| 134 _onDirtyController.add(null); | 128 _onDirtyController.add(null); |
| 135 _transformers.clear(); | 129 _transformers.clear(); |
| 136 _transformers.addAll(transformers); | 130 _transformers.addAll(transformers); |
| 137 for (var input in _inputs.values) { | 131 for (var input in _inputs.values) { |
| 138 input.updateTransformers(_transformers); | 132 input.updateTransformers(_transformers); |
| 139 } | 133 } |
| 140 } | 134 } |
| 141 | 135 |
| 142 /// Add a new phase after this one with [transformers]. | 136 /// Add a new phase after this one with [transformers]. |
| 143 /// | 137 /// |
| 144 /// This may only be called on a phase with no phase following it. | 138 /// This may only be called on a phase with no phase following it. |
| 145 Phase addPhase(Iterable<Transformer> transformers) { | 139 Phase addPhase(Iterable<Transformer> transformers) { |
| 146 assert(_next == null); | 140 assert(_next == null); |
| 147 _next = new Phase(cascade, transformers); | 141 _next = new Phase(cascade, transformers); |
| 148 for (var outputs in _outputs.values) { | 142 for (var outputs in _outputs.values) { |
| 149 _next.addInput(outputs.first); | 143 _next.addInput(outputs.output); |
| 150 } | 144 } |
| 151 return _next; | 145 return _next; |
| 152 } | 146 } |
| 153 | 147 |
| 154 /// Processes this phase. | 148 /// Processes this phase. |
| 155 /// | 149 /// |
| 156 /// Returns a future that completes when processing is done. If there is | 150 /// Returns a future that completes when processing is done. If there is |
| 157 /// nothing to process, returns `null`. | 151 /// nothing to process, returns `null`. |
| 158 Future process() { | 152 Future process() { |
| 159 if (!_inputs.values.any((input) => input.isDirty)) return null; | 153 if (!_inputs.values.any((input) => input.isDirty)) return null; |
| 160 | 154 |
| 155 var outputIds = new Set<AssetId>(); |
| 161 return Future.wait(_inputs.values.map((input) { | 156 return Future.wait(_inputs.values.map((input) { |
| 162 if (!input.isDirty) return new Future.value(new Set()); | 157 if (!input.isDirty) return new Future.value(new Set()); |
| 163 return input.process().then((outputs) { | 158 return input.process().then((outputs) { |
| 164 return outputs.where(_addOutput).map((output) => output.id).toSet(); | 159 for (var asset in outputs) { |
| 160 outputIds.add(asset.id); |
| 161 if (_outputs.containsKey(asset.id)) { |
| 162 _outputs[asset.id].add(asset); |
| 163 } else { |
| 164 _outputs[asset.id] = new PhaseOutput(this, asset); |
| 165 _outputs[asset.id].output.whenRemoved |
| 166 .then((_) => _outputs.remove(asset.id)); |
| 167 if (_next != null) _next.addInput(_outputs[asset.id].output); |
| 168 } |
| 169 } |
| 165 }); | 170 }); |
| 166 })).then((collisionsList) { | 171 })).then((_) { |
| 167 // Report collisions in a deterministic order. | 172 // Report collisions in a deterministic order. |
| 168 var collisions = unionAll(collisionsList).toList(); | 173 outputIds = outputIds.toList(); |
| 169 collisions.sort((a, b) => a.compareTo(b)); | 174 outputIds.sort((a, b) => a.compareTo(b)); |
| 170 for (var collision in collisions) { | 175 for (var id in outputIds) { |
| 171 // Ensure that there's still a collision. It's possible it was resolved | 176 // It's possible the output was removed before other transforms in this |
| 172 // while another transform was running. | 177 // phase finished. |
| 173 if (_outputs[collision].length <= 1) continue; | 178 if (!_outputs.containsKey(id)) continue; |
| 174 cascade.reportError(new AssetCollisionException( | 179 var exception = _outputs[id].collisionException; |
| 175 _outputs[collision].where((asset) => asset.transform != null) | 180 if (exception != null) cascade.reportError(exception); |
| 176 .map((asset) => asset.transform.info), | |
| 177 collision)); | |
| 178 } | 181 } |
| 179 }); | 182 }); |
| 180 } | 183 } |
| 181 | |
| 182 /// Add [output] as an output of this phase, forwarding it to the next phase | |
| 183 /// if necessary. | |
| 184 /// | |
| 185 /// Returns whether or not [output] collides with another pre-existing output. | |
| 186 bool _addOutput(AssetNode output) { | |
| 187 _handleOutputRemoval(output); | |
| 188 | |
| 189 if (_outputs.containsKey(output.id)) { | |
| 190 _outputs[output.id].add(output); | |
| 191 return true; | |
| 192 } | |
| 193 | |
| 194 _outputs[output.id] = new Queue<AssetNode>.from([output]); | |
| 195 if (_next != null) _next.addInput(output); | |
| 196 return false; | |
| 197 } | |
| 198 | |
| 199 /// Properly resolve collisions when [output] is removed. | |
| 200 void _handleOutputRemoval(AssetNode output) { | |
| 201 output.whenRemoved.then((_) { | |
| 202 var assets = _outputs[output.id]; | |
| 203 if (assets.length == 1) { | |
| 204 assert(assets.single == output); | |
| 205 _outputs.remove(output.id); | |
| 206 return; | |
| 207 } | |
| 208 | |
| 209 // If there was more than one asset, we're resolving a collision -- | |
| 210 // possibly partially. | |
| 211 var wasFirst = assets.first == output; | |
| 212 assets.remove(output); | |
| 213 | |
| 214 // If this was the first asset, we need to pass the next asset | |
| 215 // (chronologically) to the next phase. Pump the event queue first to give | |
| 216 // [_next] a chance to handle the removal of its input before getting a | |
| 217 // new input. | |
| 218 if (wasFirst && _next != null) { | |
| 219 newFuture(() => _next.addInput(assets.first)); | |
| 220 } | |
| 221 | |
| 222 // If there's still a collision, report it. This lets the user know | |
| 223 // if they've successfully resolved the collision or not. | |
| 224 if (assets.length > 1) { | |
| 225 // Pump the event queue to ensure that the removal of the input triggers | |
| 226 // a new build to which we can attach the error. | |
| 227 newFuture(() => cascade.reportError(new AssetCollisionException( | |
| 228 assets.where((asset) => asset.transform != null) | |
| 229 .map((asset) => asset.transform.info), | |
| 230 output.id))); | |
| 231 } | |
| 232 }); | |
| 233 } | |
| 234 } | 184 } |
| OLD | NEW |