| 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_input; | 5 library barback.phase_input; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'asset.dart'; | 10 import 'asset.dart'; |
| 11 import 'asset_forwarder.dart'; | 11 import 'asset_forwarder.dart'; |
| 12 import 'asset_node.dart'; | 12 import 'asset_node.dart'; |
| 13 import 'asset_node_set.dart'; |
| 13 import 'errors.dart'; | 14 import 'errors.dart'; |
| 14 import 'log.dart'; | 15 import 'log.dart'; |
| 15 import 'phase.dart'; | 16 import 'phase.dart'; |
| 16 import 'stream_pool.dart'; | 17 import 'stream_pool.dart'; |
| 17 import 'transform_node.dart'; | 18 import 'transform_node.dart'; |
| 18 import 'transformer.dart'; | 19 import 'transformer.dart'; |
| 19 import 'utils.dart'; | 20 import 'utils.dart'; |
| 20 | 21 |
| 21 /// A class for watching a single [AssetNode] and running any transforms that | 22 /// A class for watching a single [AssetNode] and running any transforms that |
| 22 /// take that node as a primary input. | 23 /// take that node as a primary input. |
| (...skipping 15 matching lines...) Expand all Loading... |
| 38 final _transforms = new Set<TransformNode>(); | 39 final _transforms = new Set<TransformNode>(); |
| 39 | 40 |
| 40 /// A forwarder for the input [AssetNode] for this phase. | 41 /// A forwarder for the input [AssetNode] for this phase. |
| 41 /// | 42 /// |
| 42 /// This is used to mark the node as removed should the input ever be removed. | 43 /// This is used to mark the node as removed should the input ever be removed. |
| 43 final AssetForwarder _inputForwarder; | 44 final AssetForwarder _inputForwarder; |
| 44 | 45 |
| 45 /// The asset node for this input. | 46 /// The asset node for this input. |
| 46 AssetNode get input => _inputForwarder.node; | 47 AssetNode get input => _inputForwarder.node; |
| 47 | 48 |
| 48 /// The controller that's used for the output node if [input] isn't consumed | 49 /// The controller that's used for the output node if [input] isn't |
| 49 /// by any transformers. | 50 /// overwritten by any transformers. |
| 50 /// | 51 /// |
| 51 /// This needs an intervening controller to ensure that the output can be | 52 /// This needs an intervening controller to ensure that the output can be |
| 52 /// marked dirty when determining whether transforms apply, and removed if | 53 /// marked dirty when determining whether transforms will overwrite it, and be |
| 53 /// they do. It's null if the asset is not being passed through. | 54 /// marked removed if they do. It's null if the asset is not being passed |
| 55 /// through. |
| 54 AssetNodeController _passThroughController; | 56 AssetNodeController _passThroughController; |
| 55 | 57 |
| 56 /// Whether [_passThroughController] has been newly created since [process] | 58 /// Whether [_passThroughController] has been newly created since [process] |
| 57 /// last completed. | 59 /// last completed. |
| 58 bool _newPassThrough = false; | 60 bool _newPassThrough = false; |
| 59 | 61 |
| 62 final _outputs = new AssetNodeSet(); |
| 63 |
| 60 /// A Future that will complete once the transformers that consume [input] are | 64 /// A Future that will complete once the transformers that consume [input] are |
| 61 /// determined. | 65 /// determined. |
| 62 Future _adjustTransformersFuture; | 66 Future _adjustTransformersFuture; |
| 63 | 67 |
| 64 /// A stream that emits an event whenever this input becomes dirty and needs | 68 /// A stream that emits an event whenever this input becomes dirty and needs |
| 65 /// [process] to be called. | 69 /// [process] to be called. |
| 66 /// | 70 /// |
| 67 /// This may emit events when the input was already dirty or while processing | 71 /// This may emit events when the input was already dirty or while processing |
| 68 /// transforms. Events are emitted synchronously to ensure that the dirty | 72 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 69 /// state is thoroughly propagated as soon as any assets are changed. | 73 /// state is thoroughly propagated as soon as any assets are changed. |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 130 // automatically pick up on [removedTransformer] being gone. | 134 // automatically pick up on [removedTransformer] being gone. |
| 131 if (_adjustTransformersFuture != null) continue; | 135 if (_adjustTransformersFuture != null) continue; |
| 132 | 136 |
| 133 _transforms.removeWhere((transform) { | 137 _transforms.removeWhere((transform) { |
| 134 if (transform.transformer != removedTransformer) return false; | 138 if (transform.transformer != removedTransformer) return false; |
| 135 transform.remove(); | 139 transform.remove(); |
| 136 return true; | 140 return true; |
| 137 }); | 141 }); |
| 138 } | 142 } |
| 139 | 143 |
| 140 if (_transforms.isEmpty && _adjustTransformersFuture == null && | |
| 141 _passThroughController == null) { | |
| 142 _passThroughController = new AssetNodeController.from(input); | |
| 143 _newPassThrough = true; | |
| 144 } | |
| 145 | |
| 146 var brandNewTransformers = newTransformers.difference(oldTransformers); | 144 var brandNewTransformers = newTransformers.difference(oldTransformers); |
| 147 if (brandNewTransformers.isEmpty) return; | 145 if (brandNewTransformers.isEmpty) return; |
| 148 | 146 |
| 149 brandNewTransformers.forEach(_transformers.add); | 147 brandNewTransformers.forEach(_transformers.add); |
| 150 if (_adjustTransformersFuture == null) _adjustTransformers(); | 148 if (_adjustTransformersFuture == null) _adjustTransformers(); |
| 151 } | 149 } |
| 152 | 150 |
| 153 /// Force all [LazyTransformer]s' transforms in this input to begin producing | 151 /// Force all [LazyTransformer]s' transforms in this input to begin producing |
| 154 /// concrete assets. | 152 /// concrete assets. |
| 155 void forceAllTransforms() { | 153 void forceAllTransforms() { |
| 156 for (var transform in _transforms) { | 154 for (var transform in _transforms) { |
| 157 transform.force(); | 155 transform.force(); |
| 158 } | 156 } |
| 159 } | 157 } |
| 160 | 158 |
| 161 /// Asynchronously determines which transformers can consume [input] as a | 159 /// Asynchronously determines which transformers can consume [input] as a |
| 162 /// primary input and creates transforms for them. | 160 /// primary input and creates transforms for them. |
| 163 /// | 161 /// |
| 164 /// This ensures that if [input] is modified or removed during or after the | 162 /// This ensures that if [input] is modified or removed during or after the |
| 165 /// time it takes to adjust its transformers, they're appropriately | 163 /// time it takes to adjust its transformers, they're appropriately |
| 166 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. | 164 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. |
| 167 void _adjustTransformers() { | 165 void _adjustTransformers() { |
| 168 // Mark the input as dirty. This may not actually end up creating any new | 166 // Mark the input as dirty. This may not actually end up creating any new |
| 169 // transforms, but we want adding or removing a source asset to consistently | 167 // transforms, but we want adding or removing a source asset to consistently |
| 170 // kick off a build, even if that build does nothing. | 168 // kick off a build, even if that build does nothing. |
| 171 _onDirtyController.add(null); | 169 _onDirtyController.add(null); |
| 172 | 170 |
| 173 // If there's a pass-through for this input, mark it dirty while we figure | 171 // If there's a pass-through for this input, mark it dirty until we figure |
| 174 // out whether we need to add any transforms for it. | 172 // out if a transformer will emit an asset with that id. |
| 175 if (_passThroughController != null) _passThroughController.setDirty(); | 173 if (_passThroughController != null) _passThroughController.setDirty(); |
| 176 | 174 |
| 177 // Once the input is available, hook up transformers for it. If it changes | 175 // Once the input is available, hook up transformers for it. If it changes |
| 178 // while that's happening, try again. | 176 // while that's happening, try again. |
| 179 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { | 177 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { |
| 180 var oldTransformers = | 178 var oldTransformers = |
| 181 _transforms.map((transform) => transform.transformer).toSet(); | 179 _transforms.map((transform) => transform.transformer).toSet(); |
| 182 | 180 |
| 183 return _removeStaleTransforms(asset, transformers).then((_) => | 181 return _removeStaleTransforms(asset, transformers).then((_) => |
| 184 _addFreshTransforms(transformers, oldTransformers)); | 182 _addFreshTransforms(transformers, oldTransformers)); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 236 // We can safely access [input.asset] here even though it might have | 234 // We can safely access [input.asset] here even though it might have |
| 237 // changed since (as above) if it has, [_adjustTransformers] will just be | 235 // changed since (as above) if it has, [_adjustTransformers] will just be |
| 238 // re-run. | 236 // re-run. |
| 239 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 237 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
| 240 // results. | 238 // results. |
| 241 return transformer.isPrimary(input.asset).then((isPrimary) { | 239 return transformer.isPrimary(input.asset).then((isPrimary) { |
| 242 if (!isPrimary) return; | 240 if (!isPrimary) return; |
| 243 var transform = new TransformNode( | 241 var transform = new TransformNode( |
| 244 _phase, transformer, input, _location); | 242 _phase, transformer, input, _location); |
| 245 _transforms.add(transform); | 243 _transforms.add(transform); |
| 244 transform.onDirty.listen((_) { |
| 245 if (_passThroughController != null) _passThroughController.setDirty(); |
| 246 }); |
| 246 _onDirtyPool.add(transform.onDirty); | 247 _onDirtyPool.add(transform.onDirty); |
| 247 _onLogPool.add(transform.onLog); | 248 _onLogPool.add(transform.onLog); |
| 248 }); | 249 }); |
| 249 })); | 250 })); |
| 250 } | 251 } |
| 251 | 252 |
| 252 /// Adjust whether [input] is passed through the phase unmodified, based on | |
| 253 /// whether it's consumed by other transforms in this phase. | |
| 254 /// | |
| 255 /// If [input] was already passed-through, this will update the passed-through | |
| 256 /// value. | |
| 257 void _adjustPassThrough() { | |
| 258 assert(input.state.isAvailable); | |
| 259 | |
| 260 if (_transforms.isEmpty) { | |
| 261 if (_passThroughController != null) { | |
| 262 _passThroughController.setAvailable(input.asset); | |
| 263 } else { | |
| 264 _passThroughController = new AssetNodeController.from(input); | |
| 265 _newPassThrough = true; | |
| 266 } | |
| 267 } else if (_passThroughController != null) { | |
| 268 _passThroughController.setRemoved(); | |
| 269 _passThroughController = null; | |
| 270 _newPassThrough = false; | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this | 253 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this |
| 275 /// phase's transformers are modified. | 254 /// phase's transformers are modified. |
| 276 Future _tryUntilStable( | 255 Future _tryUntilStable( |
| 277 Future callback(Asset asset, Set<Transformer> transformers)) { | 256 Future callback(Asset asset, Set<Transformer> transformers)) { |
| 278 var oldTransformers; | 257 var oldTransformers; |
| 279 return input.tryUntilStable((asset) { | 258 return input.tryUntilStable((asset) { |
| 280 oldTransformers = _transformers.toSet(); | 259 oldTransformers = _transformers.toSet(); |
| 281 return callback(asset, _transformers); | 260 return callback(asset, _transformers); |
| 282 }).then((result) { | 261 }).then((result) { |
| 283 if (setEquals(oldTransformers, _transformers)) return result; | 262 if (setEquals(oldTransformers, _transformers)) return result; |
| 284 return _tryUntilStable(callback); | 263 return _tryUntilStable(callback); |
| 285 }); | 264 }); |
| 286 } | 265 } |
| 287 | 266 |
| 288 /// Processes the transforms for this input. | 267 /// Processes the transforms for this input. |
| 289 /// | 268 /// |
| 290 /// Returns the set of newly-created asset nodes that transforms have emitted | 269 /// Returns the set of newly-created asset nodes that transforms have emitted |
| 291 /// for this input. The assets returned this way are guaranteed not to be | 270 /// for this input. The assets returned this way are guaranteed not to be |
| 292 /// [AssetState.REMOVED]. | 271 /// [AssetState.REMOVED]. |
| 293 Future<Set<AssetNode>> process() { | 272 Future<Set<AssetNode>> process() { |
| 294 return _waitForTransformers(() => _processTransforms()).then((outputs) { | 273 return _waitForTransformers(() { |
| 274 if (input.state.isRemoved) return new Future.value(new Set()); |
| 275 |
| 276 return Future.wait(_transforms.map((transform) { |
| 277 if (!transform.isDirty) return new Future.value(new Set()); |
| 278 return transform.apply(); |
| 279 })).then((outputs) => unionAll(outputs)); |
| 280 }).then((outputs) { |
| 295 if (input.state.isRemoved) return new Set(); | 281 if (input.state.isRemoved) return new Set(); |
| 282 |
| 283 for (var output in outputs) { |
| 284 assert(!output.state.isRemoved); |
| 285 _outputs.add(output); |
| 286 output.whenRemoved(_adjustPassThrough); |
| 287 } |
| 288 |
| 289 _adjustPassThrough(); |
| 290 if (_newPassThrough) { |
| 291 outputs.add(_passThroughController.node); |
| 292 _newPassThrough = false; |
| 293 } |
| 296 return outputs; | 294 return outputs; |
| 297 }); | 295 }); |
| 298 } | 296 } |
| 299 | 297 |
| 300 /// Runs [callback] once all the transformers are adjusted correctly and the | 298 /// Runs [callback] once all the transformers are adjusted correctly and the |
| 301 /// input is ready to be processed. | 299 /// input is ready to be processed. |
| 302 /// | 300 /// |
| 303 /// If the transformers are already properly adjusted, [callback] is called | 301 /// If the transformers are already properly adjusted, [callback] is called |
| 304 /// synchronously to ensure that [_adjustTransformers] isn't called before the | 302 /// synchronously to ensure that [_adjustTransformers] isn't called before the |
| 305 /// callback. | 303 /// callback. |
| 306 Future _waitForTransformers(callback()) { | 304 Future _waitForTransformers(callback()) { |
| 307 if (_adjustTransformersFuture == null) return syncFuture(callback); | 305 if (_adjustTransformersFuture == null) return syncFuture(callback); |
| 308 return _adjustTransformersFuture.then( | 306 return _adjustTransformersFuture.then( |
| 309 (_) => _waitForTransformers(callback)); | 307 (_) => _waitForTransformers(callback)); |
| 310 } | 308 } |
| 311 | 309 |
| 312 /// Applies all currently wired up and dirty transforms. | 310 /// Adjust whether [input] is passed through the phase unmodified, based on |
| 313 Future<Set<AssetNode>> _processTransforms() { | 311 /// whether it's overwritten by other transforms in this phase. |
| 314 if (input.state.isRemoved) return new Future.value(new Set()); | 312 /// |
| 313 /// If [input] was already passed-through, this will update the passed-through |
| 314 /// value. |
| 315 void _adjustPassThrough() { |
| 316 if (!input.state.isAvailable) return; |
| 315 | 317 |
| 316 if (_passThroughController != null) { | 318 // If there's an output with the same id as the primary input, that |
| 317 if (!_newPassThrough) return new Future.value(new Set()); | 319 // overwrites the input so it doesn't get passed through. Otherwise, |
| 318 _newPassThrough = false; | 320 // create a pass-through controller if none exists, or set the existing |
| 319 return new Future.value( | 321 // one available. |
| 320 new Set<AssetNode>.from([_passThroughController.node])); | 322 if (_outputs.any((output) => output.id == input.id)) { |
| 323 if (_passThroughController != null) { |
| 324 _passThroughController.setRemoved(); |
| 325 _passThroughController = null; |
| 326 _newPassThrough = false; |
| 327 } |
| 328 } else if (_passThroughController == null) { |
| 329 _passThroughController = new AssetNodeController.from(input); |
| 330 _newPassThrough = true; |
| 331 } else if (_passThroughController.node.state.isDirty) { |
| 332 _passThroughController.setAvailable(input.asset); |
| 321 } | 333 } |
| 322 | |
| 323 return Future.wait(_transforms.map((transform) { | |
| 324 if (!transform.isDirty) return new Future.value(new Set()); | |
| 325 return transform.apply(); | |
| 326 })).then((outputs) => unionAll(outputs)); | |
| 327 } | 334 } |
| 328 | 335 |
| 329 String toString() => "phase input in $_location for $input"; | 336 String toString() => "phase input in $_location for $input"; |
| 330 } | 337 } |
| OLD | NEW |