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.transform_node; | 5 library barback.transform_node; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'asset.dart'; | 9 import 'asset.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'asset_set.dart'; | 12 import 'asset_set.dart'; |
| 13 import 'errors.dart'; | 13 import 'errors.dart'; |
| 14 import 'phase.dart'; | 14 import 'phase.dart'; |
| 15 import 'transform.dart'; | 15 import 'transform.dart'; |
| 16 import 'transformer.dart'; | 16 import 'transformer.dart'; |
| 17 | 17 |
| 18 /// Describes a transform on a set of assets and its relationship to the build | 18 /// Describes a transform on a set of assets and its relationship to the build |
| 19 /// dependency graph. | 19 /// dependency graph. |
| 20 /// | 20 /// |
| 21 /// Keeps track of whether it's dirty and needs to be run and which assets it | 21 /// Keeps track of whether it's dirty and needs to be run and which assets it |
| 22 /// depends on. | 22 /// depends on. |
| 23 class TransformNode { | 23 class TransformNode { |
| 24 /// The [Phase] that this transform runs in. | 24 /// The [Phase] that this transform runs in. |
| 25 final Phase phase; | 25 final Phase phase; |
| 26 | 26 |
| 27 /// The [Transformer] to apply to this node's inputs. | 27 /// The [Transformer] to apply to this node's inputs. |
| 28 final Transformer _transformer; | 28 final Transformer transformer; |
| 29 | 29 |
| 30 /// The node for the primary asset this transform depends on. | 30 /// The node for the primary asset this transform depends on. |
| 31 final AssetNode primary; | 31 final AssetNode primary; |
| 32 | 32 |
| 33 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | |
| 34 StreamSubscription _primarySubscription; | |
| 35 | |
| 33 /// True if an input has been modified since the last time this transform | 36 /// True if an input has been modified since the last time this transform |
| 34 /// was run. | 37 /// began running. |
| 35 bool get isDirty => _isDirty; | 38 bool get isDirty => _isDirty; |
| 36 var _isDirty = true; | 39 var _isDirty = true; |
| 37 | 40 |
| 38 /// The inputs read by this transform the last time it was run. | 41 /// The inputs read by this transform the last time it was run. |
| 39 /// | 42 /// |
| 40 /// Used to tell if an input was removed in a later run. | 43 /// Used to tell if an input was added or removed in a later run. |
| 41 var _inputs = new Set<AssetNode>(); | 44 var _inputs = new Set<AssetNode>(); |
| 42 | 45 |
| 43 /// The outputs created by this transform the last time it was run. | 46 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
| 47 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | |
| 48 | |
| 49 /// The controllers for the asset nodes emitted by this node. | |
| 50 var _outputControllers = new Map<AssetId, AssetNodeController>(); | |
| 51 | |
| 52 TransformNode(this.phase, this.transformer, this.primary) { | |
| 53 _primarySubscription = primary.onStateChange.listen((state) { | |
| 54 if (state.isRemoved) { | |
| 55 remove(); | |
| 56 } else { | |
| 57 _dirty(); | |
| 58 } | |
| 59 }); | |
| 60 } | |
| 61 | |
| 62 /// Marks this transform as removed. | |
| 44 /// | 63 /// |
| 45 /// Used to tell if an output was removed in a later run. | 64 /// This causes all of the transform's outputs to be marked as removed as |
| 46 Set<AssetId> get outputs => _outputs; | 65 /// well. Normally this will be automatically done internally based on events |
| 47 var _outputs = new Set<AssetId>(); | 66 /// from the primary input, but it's possible for a transform to no longer be |
| 67 /// valid even if its primary input still exists. | |
| 68 void remove() { | |
| 69 _isDirty = true; | |
| 70 _primarySubscription.cancel(); | |
| 71 for (var subscription in _inputSubscriptions.values) { | |
| 72 subscription.cancel(); | |
| 73 } | |
| 74 for (var controller in _outputControllers.values) { | |
| 75 controller.setRemoved(); | |
| 76 } | |
| 77 } | |
| 48 | 78 |
| 49 TransformNode(this.phase, this._transformer, this.primary); | 79 /// Marks this transform as dirty. |
| 50 | 80 /// |
| 51 /// Marks this transform as needing to be run. | 81 /// This causes all of the transform's outputs to be marked as dirty as well. |
| 52 void dirty() { | 82 void _dirty() { |
| 53 _isDirty = true; | 83 _isDirty = true; |
| 84 for (var controller in _outputControllers.values) { | |
| 85 controller.setDirty(); | |
| 86 } | |
| 54 } | 87 } |
| 55 | 88 |
| 56 /// Applies this transform. | 89 /// Applies this transform. |
| 57 /// | 90 /// |
| 58 /// Returns a [TransformOutputs] describing the resulting outputs compared to | 91 /// Returns a set of asset nodes representing the outputs from this transform |
| 59 /// previous runs. | 92 /// that weren't emitted last time it was run. |
| 60 Future<TransformOutputs> apply() { | 93 Future<Set<AssetNode>> apply() { |
| 61 var newInputs = new Set<AssetNode>(); | 94 var newInputs = new Set<AssetNode>(); |
| 62 var newOutputs = new AssetSet(); | 95 var newOutputs = new AssetSet(); |
| 63 var transform = createTransform(this, newInputs, newOutputs); | 96 var transform = createTransform(this, newInputs, newOutputs); |
| 64 return _transformer.apply(transform).catchError((error) { | 97 _isDirty = false; |
| 65 // Catch all transformer errors and pipe them to the results stream. This | 98 return transformer.apply(transform).catchError((error) { |
| 66 // is so a broken transformer doesn't take down the whole graph. | 99 // If the transform became dirty while processing, ignore any errors from |
| 100 // it. | |
| 101 if (_isDirty) return; | |
| 102 | |
| 103 // Catch all transformer errors and pipe them to the results stream. | |
| 104 // This is so a broken transformer doesn't take down the whole graph. | |
| 67 phase.cascade.reportError(error); | 105 phase.cascade.reportError(error); |
| 68 | 106 |
| 69 // Don't allow partial results from a failed transform. | 107 // Don't allow partial results from a failed transform. |
| 70 newOutputs.clear(); | 108 newOutputs.clear(); |
| 71 }).then((_) { | 109 }).then((_) { |
| 72 _isDirty = false; | 110 if (_isDirty) return []; |
| 73 | 111 |
| 74 // Stop watching any inputs that were removed. | 112 _adjustInputs(newInputs); |
| 75 for (var oldInput in _inputs) { | 113 return _adjustOutputs(newOutputs); |
| 76 oldInput.consumers.remove(this); | |
| 77 } | |
| 78 | |
| 79 // Watch any new inputs so this transform will be re-processed when an | |
| 80 // input is modified. | |
| 81 for (var newInput in newInputs) { | |
| 82 newInput.consumers.add(this); | |
| 83 } | |
| 84 | |
| 85 _inputs = newInputs; | |
| 86 | |
| 87 // See which outputs are missing from the last run. | |
| 88 var outputIds = newOutputs.map((asset) => asset.id).toSet(); | |
| 89 var invalidIds = outputIds | |
| 90 .where((id) => id.package != phase.cascade.package).toSet(); | |
| 91 outputIds.removeAll(invalidIds); | |
| 92 | |
| 93 for (var id in invalidIds) { | |
| 94 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 95 phase.cascade.reportError( | |
| 96 new InvalidOutputException(phase.cascade.package, id)); | |
| 97 } | |
| 98 | |
| 99 var removed = _outputs.difference(outputIds); | |
| 100 _outputs = outputIds; | |
| 101 | |
| 102 return new TransformOutputs(newOutputs, removed); | |
| 103 }); | 114 }); |
| 104 } | 115 } |
| 116 | |
| 117 /// Adjusts the inputs of the transform to reflect the inputs consumed on its | |
| 118 /// most recent run. | |
| 119 void _adjustInputs(Set<AssetNode> newInputs) { | |
| 120 // Stop watching any inputs that were removed. | |
| 121 for (var oldInput in _inputs.difference(newInputs)) { | |
| 122 _inputSubscriptions.remove(oldInput.id).cancel(); | |
| 123 } | |
| 124 | |
| 125 // Watch any new inputs so this transform will be re-processed when an | |
| 126 // input is modified. | |
| 127 for (var newInput in newInputs.difference(_inputs)) { | |
| 128 if (newInput.id == primary.id) continue; | |
|
Bob Nystrom
2013/07/31 20:05:41
How can this case get hit?
nweiz
2013/07/31 22:47:53
This case is hit whenever the transform loads the
| |
| 129 // TODO(nweiz): support the case where a new secondary input changes | |
| 130 // after it's been loaded by the transform but before the transform has | |
| 131 // finished running. | |
| 132 _inputSubscriptions[newInput.id] = newInput.onStateChange | |
| 133 .listen((_) => _dirty()); | |
| 134 } | |
| 135 | |
| 136 _inputs = newInputs; | |
| 137 } | |
| 138 | |
| 139 /// Adjusts the outputs of the transform to reflect the outputs emitted on its | |
| 140 /// most recent run. | |
| 141 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { | |
| 142 // Any ids that are for the wrong package are invalid. | |
|
Bob Nystrom
2013/07/31 20:05:41
"for the wrong" -> "to a different"
nweiz
2013/07/31 22:47:53
Done.
| |
| 143 var invalidIds = newOutputs | |
| 144 .map((asset) => asset.id) | |
| 145 .where((id) => id.package != phase.cascade.package) | |
| 146 .toSet(); | |
| 147 for (var id in invalidIds) { | |
| 148 newOutputs.removeId(id); | |
| 149 // TODO(nweiz): report this as a warning rather than a failing error. | |
| 150 phase.cascade.reportError( | |
| 151 new InvalidOutputException(phase.cascade.package, id)); | |
| 152 } | |
| 153 | |
| 154 // Remove outputs that used to exist but don't anymore. | |
| 155 for (var id in _outputControllers.keys.toList()) { | |
|
Bob Nystrom
2013/07/31 20:05:41
If we make AssetSet implement Set, you could do:
nweiz
2013/07/31 22:47:53
Eh, I think this is clear enough.
| |
| 156 if (newOutputs.containsId(id)) continue; | |
| 157 _outputControllers.remove(id).setRemoved(); | |
| 158 } | |
| 159 | |
| 160 var brandNewOutputs = new Set<AssetNode>(); | |
| 161 // Store any new outputs or new contents for existing outputs. | |
| 162 for (var asset in newOutputs) { | |
| 163 var controller = _outputControllers[asset.id]; | |
|
Bob Nystrom
2013/07/31 20:05:41
Use putIfAbsent() here?
nweiz
2013/07/31 22:47:53
That doesn't work well when we want to call [setAv
Bob Nystrom
2013/07/31 23:20:15
You could just call that from within the pubIfAbse
| |
| 164 if (controller != null) { | |
| 165 controller.setAvailable(asset); | |
| 166 } else { | |
| 167 var controller = new AssetNodeController.available(asset); | |
| 168 _outputControllers[asset.id] = controller; | |
| 169 brandNewOutputs.add(controller.node); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 return brandNewOutputs; | |
| 174 } | |
| 105 } | 175 } |
| 106 | |
| 107 /// The result of running a [Transform], compared to the previous time it was | |
| 108 /// applied. | |
| 109 class TransformOutputs { | |
| 110 /// The outputs that are new or were modified since the last run. | |
| 111 final AssetSet updated; | |
| 112 | |
| 113 /// The outputs that were created by the previous run but were not generated | |
| 114 /// by the most recent run. | |
| 115 final Set<AssetId> removed; | |
| 116 | |
| 117 TransformOutputs(this.updated, this.removed); | |
| 118 } | |
| OLD | NEW |