| 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 | 8 |
| 9 import 'asset.dart'; | 9 import 'asset.dart'; |
| 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 'stream_pool.dart'; |
| 15 import 'transform_node.dart'; | 16 import 'transform_node.dart'; |
| 16 import 'transformer.dart'; | 17 import 'transformer.dart'; |
| 17 import 'utils.dart'; | 18 import 'utils.dart'; |
| 18 | 19 |
| 19 /// One phase in the ordered series of transformations in an [AssetCascade]. | 20 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 20 /// | 21 /// |
| 21 /// Each phase can access outputs from previous phases and can in turn pass | 22 /// Each phase can access outputs from previous phases and can in turn pass |
| 22 /// outputs to later phases. Phases are processed strictly serially. All | 23 /// outputs to later phases. Phases are processed strictly serially. All |
| 23 /// transforms in a phase will be complete before moving on to the next phase. | 24 /// transforms in a phase will be complete before moving on to the next phase. |
| 24 /// Within a single phase, all transforms will be run in parallel. | 25 /// Within a single phase, all transforms will be run in parallel. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 66 /// New asset nodes that were added while [_adjustTransformers] was still | 67 /// New asset nodes that were added while [_adjustTransformers] was still |
| 67 /// being run on an old version of that asset. | 68 /// being run on an old version of that asset. |
| 68 var _pendingNewInputs = new Map<AssetId, AssetNode>(); | 69 var _pendingNewInputs = new Map<AssetId, AssetNode>(); |
| 69 | 70 |
| 70 /// The ids of assets that are emitted by transforms in this phase. | 71 /// The ids of assets that are emitted by transforms in this phase. |
| 71 /// | 72 /// |
| 72 /// This is used to detect collisions where multiple transforms emit the same | 73 /// This is used to detect collisions where multiple transforms emit the same |
| 73 /// output. | 74 /// output. |
| 74 final _outputs = new Set<AssetId>(); | 75 final _outputs = new Set<AssetId>(); |
| 75 | 76 |
| 77 /// A stream that emits an event whenever this phase becomes dirty and needs |
| 78 /// to be run. |
| 79 /// |
| 80 /// This may emit events when the phase was already dirty or while processing |
| 81 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 82 /// state is thoroughly propagated as soon as any assets are changed. |
| 83 Stream get onDirty => _onDirtyPool.stream; |
| 84 final _onDirtyPool = new StreamPool.broadcast(); |
| 85 |
| 86 /// A controller whose stream feeds into [_onDirtyPool]. |
| 87 /// |
| 88 /// This is used whenever an input is added, changed, or removed. It's |
| 89 /// sometimes redundant with the events collected from [_transforms], but this |
| 90 /// stream is necessary for new and removed inputs, and the transform stream |
| 91 /// is necessary for modified secondary inputs. |
| 92 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 93 |
| 76 /// The phase after this one. | 94 /// The phase after this one. |
| 77 /// | 95 /// |
| 78 /// Outputs from this phase will be passed to it. | 96 /// Outputs from this phase will be passed to it. |
| 79 final Phase _next; | 97 final Phase _next; |
| 80 | 98 |
| 81 Phase(this.cascade, this._index, this._transformers, this._next); | 99 Phase(this.cascade, this._index, this._transformers, this._next) { |
| 100 _onDirtyPool.add(_onDirtyController.stream); |
| 101 } |
| 82 | 102 |
| 83 /// Adds a new asset as an input for this phase. | 103 /// Adds a new asset as an input for this phase. |
| 84 /// | 104 /// |
| 85 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 105 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 86 /// will automatically begin determining which transforms can consume it as a | 106 /// will automatically begin determining which transforms can consume it as a |
| 87 /// primary input. The transforms themselves won't be applied until [process] | 107 /// primary input. The transforms themselves won't be applied until [process] |
| 88 /// is called, however. | 108 /// is called, however. |
| 89 /// | 109 /// |
| 90 /// This should only be used for brand-new assets or assets that have been | 110 /// This should only be used for brand-new assets or assets that have been |
| 91 /// removed and re-created. The phase will automatically handle updated assets | 111 /// removed and re-created. The phase will automatically handle updated assets |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 161 return inputs[id]; | 181 return inputs[id]; |
| 162 } | 182 } |
| 163 | 183 |
| 164 /// Asynchronously determines which transformers can consume [node] as a | 184 /// Asynchronously determines which transformers can consume [node] as a |
| 165 /// primary input and creates transforms for them. | 185 /// primary input and creates transforms for them. |
| 166 /// | 186 /// |
| 167 /// This ensures that if [node] is modified or removed during or after the | 187 /// This ensures that if [node] is modified or removed during or after the |
| 168 /// time it takes to adjust its transformers, they're appropriately | 188 /// time it takes to adjust its transformers, they're appropriately |
| 169 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. | 189 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. |
| 170 void _adjustTransformers(AssetNode node) { | 190 void _adjustTransformers(AssetNode node) { |
| 191 // Mark the phase as dirty. This may not actually end up creating any new |
| 192 // transforms, but we want adding or removing a source asset to consistently |
| 193 // kick off a build, even if that build does nothing. |
| 194 _onDirtyController.add(null); |
| 195 |
| 171 // Once the input is available, hook up transformers for it. If it changes | 196 // Once the input is available, hook up transformers for it. If it changes |
| 172 // while that's happening, try again. | 197 // while that's happening, try again. |
| 173 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { | 198 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { |
| 174 var oldTransformers = _transforms[node.id] | 199 var oldTransformers = _transforms[node.id] |
| 175 .map((transform) => transform.transformer).toSet(); | 200 .map((transform) => transform.transformer).toSet(); |
| 176 | 201 |
| 177 return _removeStaleTransforms(asset) | 202 return _removeStaleTransforms(asset) |
| 178 .then((_) => _addFreshTransforms(node, oldTransformers)); | 203 .then((_) => _addFreshTransforms(node, oldTransformers)); |
| 179 }).then((_) { | 204 }).then((_) { |
| 180 // Now all the transforms are set up correctly and the asset is available | 205 // Now all the transforms are set up correctly and the asset is available |
| 181 // for the time being. Set up handlers for when the asset changes in the | 206 // for the time being. Set up handlers for when the asset changes in the |
| 182 // future. | 207 // future. |
| 183 node.onStateChange.first.then((state) { | 208 node.onStateChange.first.then((state) { |
| 184 if (state.isRemoved) { | 209 if (state.isRemoved) { |
| 210 _onDirtyController.add(null); |
| 185 _transforms.remove(node.id); | 211 _transforms.remove(node.id); |
| 186 } else { | 212 } else { |
| 187 _adjustTransformers(node); | 213 _adjustTransformers(node); |
| 188 } | 214 } |
| 189 }).catchError((e) { | 215 }).catchError((e) { |
| 190 _adjustTransformersFutures[node.id] = new Future.error(e); | 216 _adjustTransformersFutures[node.id] = new Future.error(e); |
| 191 }); | 217 }); |
| 192 }).catchError((error) { | 218 }).catchError((error) { |
| 193 if (error is! AssetNotFoundException || error.id != node.id) throw error; | 219 if (error is! AssetNotFoundException || error.id != node.id) throw error; |
| 194 | 220 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 207 | 233 |
| 208 // Remove any old transforms that used to have [asset] as a primary asset but | 234 // Remove any old transforms that used to have [asset] as a primary asset but |
| 209 // no longer apply to its new contents. | 235 // no longer apply to its new contents. |
| 210 Future _removeStaleTransforms(Asset asset) { | 236 Future _removeStaleTransforms(Asset asset) { |
| 211 return Future.wait(_transforms[asset.id].map((transform) { | 237 return Future.wait(_transforms[asset.id].map((transform) { |
| 212 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 238 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 213 // results. | 239 // results. |
| 214 return transform.transformer.isPrimary(asset).then((isPrimary) { | 240 return transform.transformer.isPrimary(asset).then((isPrimary) { |
| 215 if (isPrimary) return; | 241 if (isPrimary) return; |
| 216 _transforms[asset.id].remove(transform); | 242 _transforms[asset.id].remove(transform); |
| 243 _onDirtyPool.remove(transform.onDirty); |
| 217 transform.remove(); | 244 transform.remove(); |
| 218 }); | 245 }); |
| 219 })); | 246 })); |
| 220 } | 247 } |
| 221 | 248 |
| 222 // Add new transforms for transformers that consider [node]'s asset to be a | 249 // Add new transforms for transformers that consider [node]'s asset to be a |
| 223 // primary input. | 250 // primary input. |
| 224 // | 251 // |
| 225 // [oldTransformers] is the set of transformers that had [node] as a primary | 252 // [oldTransformers] is the set of transformers that had [node] as a primary |
| 226 // input prior to this. They don't need to be checked, since they were removed | 253 // input prior to this. They don't need to be checked, since they were removed |
| 227 // or preserved in [_removeStaleTransforms]. | 254 // or preserved in [_removeStaleTransforms]. |
| 228 Future _addFreshTransforms(AssetNode node, Set<Transformer> oldTransformers) { | 255 Future _addFreshTransforms(AssetNode node, Set<Transformer> oldTransformers) { |
| 229 return Future.wait(_transformers.map((transformer) { | 256 return Future.wait(_transformers.map((transformer) { |
| 230 if (oldTransformers.contains(transformer)) return new Future.value(); | 257 if (oldTransformers.contains(transformer)) return new Future.value(); |
| 231 | 258 |
| 232 // If the asset is unavailable, the results of this [_adjustTransformers] | 259 // If the asset is unavailable, the results of this [_adjustTransformers] |
| 233 // run will be discarded, so we can just short-circuit. | 260 // run will be discarded, so we can just short-circuit. |
| 234 if (node.asset == null) return new Future.value(); | 261 if (node.asset == null) return new Future.value(); |
| 235 | 262 |
| 236 // We can safely access [node.asset] here even though it might have | 263 // We can safely access [node.asset] here even though it might have |
| 237 // changed since (as above) if it has, [_adjustTransformers] will just be | 264 // changed since (as above) if it has, [_adjustTransformers] will just be |
| 238 // re-run. | 265 // re-run. |
| 239 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 266 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 240 // results. | 267 // results. |
| 241 return transformer.isPrimary(node.asset).then((isPrimary) { | 268 return transformer.isPrimary(node.asset).then((isPrimary) { |
| 242 if (!isPrimary) return; | 269 if (!isPrimary) return; |
| 243 _transforms[node.id].add(new TransformNode(this, transformer, node)); | 270 var transform = new TransformNode(this, transformer, node); |
| 271 _transforms[node.id].add(transform); |
| 272 _onDirtyPool.add(transform.onDirty); |
| 244 }); | 273 }); |
| 245 })); | 274 })); |
| 246 } | 275 } |
| 247 | 276 |
| 248 /// Processes this phase. | 277 /// Processes this phase. |
| 249 /// | 278 /// |
| 250 /// Returns a future that completes when processing is done. If there is | 279 /// Returns a future that completes when processing is done. If there is |
| 251 /// nothing to process, returns `null`. | 280 /// nothing to process, returns `null`. |
| 252 Future process() { | 281 Future process() { |
| 253 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); | 282 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 287 // Report collisions in a deterministic order. | 316 // Report collisions in a deterministic order. |
| 288 collisions = collisions.toList(); | 317 collisions = collisions.toList(); |
| 289 collisions.sort((a, b) => a.toString().compareTo(b.toString())); | 318 collisions.sort((a, b) => a.toString().compareTo(b.toString())); |
| 290 for (var collision in collisions) { | 319 for (var collision in collisions) { |
| 291 cascade.reportError(new AssetCollisionException(collision)); | 320 cascade.reportError(new AssetCollisionException(collision)); |
| 292 // TODO(rnystrom): Define what happens after a collision occurs. | 321 // TODO(rnystrom): Define what happens after a collision occurs. |
| 293 } | 322 } |
| 294 }); | 323 }); |
| 295 } | 324 } |
| 296 } | 325 } |
| OLD | NEW |