| 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_cascade.dart'; | 9 import 'asset_cascade.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'asset_node_set.dart'; |
| 12 import 'errors.dart'; | 13 import 'errors.dart'; |
| 13 import 'group_runner.dart'; | 14 import 'group_runner.dart'; |
| 14 import 'log.dart'; | 15 import 'log.dart'; |
| 15 import 'multiset.dart'; | 16 import 'multiset.dart'; |
| 16 import 'node_status.dart'; | 17 import 'node_status.dart'; |
| 17 import 'node_streams.dart'; | 18 import 'node_streams.dart'; |
| 18 import 'phase_forwarder.dart'; | 19 import 'phase_forwarder.dart'; |
| 19 import 'phase_input.dart'; | |
| 20 import 'phase_output.dart'; | 20 import 'phase_output.dart'; |
| 21 import 'transformer.dart'; | 21 import 'transformer.dart'; |
| 22 import 'transformer_classifier.dart'; |
| 22 import 'transformer_group.dart'; | 23 import 'transformer_group.dart'; |
| 23 import 'utils.dart'; | 24 import 'utils.dart'; |
| 24 | 25 |
| 25 /// One phase in the ordered series of transformations in an [AssetCascade]. | 26 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 26 /// | 27 /// |
| 27 /// Each phase can access outputs from previous phases and can in turn pass | 28 /// Each phase can access outputs from previous phases and can in turn pass |
| 28 /// outputs to later phases. Phases are processed strictly serially. All | 29 /// outputs to later phases. Phases are processed strictly serially. All |
| 29 /// transforms in a phase will be complete before moving on to the next phase. | 30 /// transforms in a phase will be complete before moving on to the next phase. |
| 30 /// Within a single phase, all transforms will be run in parallel. | 31 /// Within a single phase, all transforms will be run in parallel. |
| 31 /// | 32 /// |
| 32 /// Building can be interrupted between phases. For example, a source is added | 33 /// Building can be interrupted between phases. For example, a source is added |
| 33 /// which starts the background process. Sometime during, say, phase 2 (which | 34 /// which starts the background process. Sometime during, say, phase 2 (which |
| 34 /// is running asynchronously) that source is modified. When the process queue | 35 /// is running asynchronously) that source is modified. When the process queue |
| 35 /// goes to advance to phase 3, it will see that modification and start the | 36 /// goes to advance to phase 3, it will see that modification and start the |
| 36 /// waterfall from the beginning again. | 37 /// waterfall from the beginning again. |
| 37 class Phase { | 38 class Phase { |
| 38 /// The cascade that owns this phase. | 39 /// The cascade that owns this phase. |
| 39 final AssetCascade cascade; | 40 final AssetCascade cascade; |
| 40 | 41 |
| 41 /// A string describing the location of [this] in the transformer graph. | 42 /// A string describing the location of [this] in the transformer graph. |
| 42 final String _location; | 43 final String _location; |
| 43 | 44 |
| 44 /// The index of [this] in its parent cascade or group. | 45 /// The index of [this] in its parent cascade or group. |
| 45 final int _index; | 46 final int _index; |
| 46 | 47 |
| 47 /// The transformers that can access [inputs]. | |
| 48 /// | |
| 49 /// Their outputs will be available to the next phase. | |
| 50 final _transformers = new Set<Transformer>(); | |
| 51 | |
| 52 /// The groups for this phase. | 48 /// The groups for this phase. |
| 53 final _groups = new Map<TransformerGroup, GroupRunner>(); | 49 final _groups = new Map<TransformerGroup, GroupRunner>(); |
| 54 | 50 |
| 55 /// The inputs for this phase. | 51 /// The inputs for this phase. |
| 56 /// | 52 /// |
| 57 /// For the first phase, these will be the source assets. For all other | 53 /// For the first phase, these will be the source assets. For all other |
| 58 /// phases, they will be the outputs from the previous phase. | 54 /// phases, they will be the outputs from the previous phase. |
| 59 final _inputs = new Map<AssetId, PhaseInput>(); | 55 final _inputs = new AssetNodeSet(); |
| 56 |
| 57 /// The transformer classifiers for this phase. |
| 58 final _classifiers = new Map<Transformer, TransformerClassifier>(); |
| 60 | 59 |
| 61 /// The forwarders for this phase. | 60 /// The forwarders for this phase. |
| 62 final _forwarders = new Map<AssetId, PhaseForwarder>(); | 61 final _forwarders = new Map<AssetId, PhaseForwarder>(); |
| 63 | 62 |
| 64 /// The outputs for this phase. | 63 /// The outputs for this phase. |
| 65 final _outputs = new Map<AssetId, PhaseOutput>(); | 64 final _outputs = new Map<AssetId, PhaseOutput>(); |
| 66 | 65 |
| 67 /// The set of all [AssetNode.origin] properties of the input assets for this | 66 /// The set of all [AssetNode.origin] properties of the input assets for this |
| 68 /// phase. | 67 /// phase. |
| 69 /// | 68 /// |
| 70 /// This is used to determine which assets have been passed unmodified through | 69 /// This is used to determine which assets have been passed unmodified through |
| 71 /// [_inputs] or [_groups]. Each input asset has a PhaseInput in [_inputs]. If | 70 /// [_classifiers] or [_groups]. It's possible that a given asset was consumed |
| 72 /// that input isn't consumed by any transformers, it will be forwarded | 71 /// by a group and not an individual transformer, and so shouldn't be |
| 73 /// through the PhaseInput. However, it's possible that it was consumed by a | 72 /// forwarded through the phase as a whole. |
| 74 /// group, and so shouldn't be forwarded through the phase as a whole. | |
| 75 /// | 73 /// |
| 76 /// In order to detect whether an output has been forwarded through a group or | 74 /// In order to detect whether an output has been forwarded through a group or |
| 77 /// a PhaseInput, we must be able to distinguish it from other outputs with | 75 /// a classifier, we must be able to distinguish it from other outputs with |
| 78 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If | 76 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If |
| 79 /// so, it's been forwarded unmodified. | 77 /// so, it's been forwarded unmodified. |
| 80 final _inputOrigins = new Multiset<AssetNode>(); | 78 final _inputOrigins = new Multiset<AssetNode>(); |
| 81 | 79 |
| 82 /// The streams exposed by this phase. | 80 /// The streams exposed by this phase. |
| 83 final _streams = new NodeStreams(); | 81 final _streams = new NodeStreams(); |
| 84 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | 82 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; |
| 85 Stream<AssetNode> get onAsset => _streams.onAsset; | 83 Stream<AssetNode> get onAsset => _streams.onAsset; |
| 86 Stream<LogEntry> get onLog => _streams.onLog; | 84 Stream<LogEntry> get onLog => _streams.onLog; |
| 87 | 85 |
| 88 /// How far along [this] is in processing its assets. | 86 /// How far along [this] is in processing its assets. |
| 89 NodeStatus get status { | 87 NodeStatus get status { |
| 90 var inputStatus = NodeStatus.dirtiest( | 88 // Before any transformers are added, the phase should be dirty if and only |
| 91 _inputs.values.map((input) => input.status)); | 89 // if any input is dirty. |
| 90 if (_classifiers.isEmpty && _groups.isEmpty) { |
| 91 return _inputs.any((input) => input.state.isDirty) ? |
| 92 NodeStatus.RUNNING : NodeStatus.IDLE; |
| 93 } |
| 94 |
| 95 var classifierStatus = NodeStatus.dirtiest( |
| 96 _classifiers.values.map((classifier) => classifier.status)); |
| 92 var groupStatus = NodeStatus.dirtiest( | 97 var groupStatus = NodeStatus.dirtiest( |
| 93 _groups.values.map((group) => group.status)); | 98 _groups.values.map((group) => group.status)); |
| 94 return (previous == null ? NodeStatus.IDLE : previous.status) | 99 return (previous == null ? NodeStatus.IDLE : previous.status) |
| 95 .dirtier(inputStatus) | 100 .dirtier(classifierStatus) |
| 96 .dirtier(groupStatus); | 101 .dirtier(groupStatus); |
| 97 } | 102 } |
| 98 | 103 |
| 99 /// The previous phase in the cascade, or null if this is the first phase. | 104 /// The previous phase in the cascade, or null if this is the first phase. |
| 100 final Phase previous; | 105 final Phase previous; |
| 101 | 106 |
| 102 /// The subscription to [previous]'s [onStatusChange] stream. | 107 /// The subscription to [previous]'s [onStatusChange] stream. |
| 103 StreamSubscription _previousStatusSubscription; | 108 StreamSubscription _previousStatusSubscription; |
| 104 | 109 |
| 105 /// The subscription to [previous]'s [onAsset] stream. | 110 /// The subscription to [previous]'s [onAsset] stream. |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 150 /// | 155 /// |
| 151 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 156 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 152 /// will automatically begin determining which transforms can consume it as a | 157 /// will automatically begin determining which transforms can consume it as a |
| 153 /// primary input. The transforms themselves won't be applied until [process] | 158 /// primary input. The transforms themselves won't be applied until [process] |
| 154 /// is called, however. | 159 /// is called, however. |
| 155 /// | 160 /// |
| 156 /// This should only be used for brand-new assets or assets that have been | 161 /// This should only be used for brand-new assets or assets that have been |
| 157 /// removed and re-created. The phase will automatically handle updated assets | 162 /// removed and re-created. The phase will automatically handle updated assets |
| 158 /// using the [AssetNode.onStateChange] stream. | 163 /// using the [AssetNode.onStateChange] stream. |
| 159 void addInput(AssetNode node) { | 164 void addInput(AssetNode node) { |
| 160 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); | |
| 161 | |
| 162 // Each group is one channel along which an asset may be forwarded, as is | 165 // Each group is one channel along which an asset may be forwarded, as is |
| 163 // each transformer. | 166 // each transformer. |
| 164 var forwarder = new PhaseForwarder( | 167 var forwarder = new PhaseForwarder( |
| 165 node, _transformers.length, _groups.length); | 168 node, _classifiers.length, _groups.length); |
| 166 _forwarders[node.id] = forwarder; | 169 _forwarders[node.id] = forwarder; |
| 167 forwarder.onAsset.listen(_handleOutputWithoutForwarder); | 170 forwarder.onAsset.listen(_handleOutputWithoutForwarder); |
| 168 if (forwarder.output != null) { | 171 if (forwarder.output != null) { |
| 169 _handleOutputWithoutForwarder(forwarder.output); | 172 _handleOutputWithoutForwarder(forwarder.output); |
| 170 } | 173 } |
| 171 | 174 |
| 172 _inputOrigins.add(node.origin); | 175 _inputOrigins.add(node.origin); |
| 173 var input = new PhaseInput(this, node, "$_location.$_index"); | 176 _inputs.add(node); |
| 174 _inputs[node.id] = input; | 177 node.onStateChange.listen((state) { |
| 175 input.input.whenRemoved(() { | 178 if (state.isRemoved) { |
| 176 _inputOrigins.remove(node.origin); | 179 _inputOrigins.remove(node.origin); |
| 177 _inputs.remove(node.id); | 180 _forwarders.remove(node.id).remove(); |
| 178 _forwarders.remove(node.id).remove(); | 181 } |
| 179 _streams.changeStatus(status); | 182 _streams.changeStatus(status); |
| 180 }); | 183 }); |
| 181 input.onAsset.listen(_handleOutput); | |
| 182 _streams.onLogPool.add(input.onLog); | |
| 183 input.onStatusChange.listen((_) => _streams.changeStatus(status)); | |
| 184 | 184 |
| 185 input.updateTransformers(_transformers); | 185 for (var classifier in _classifiers.values) { |
| 186 | 186 classifier.addInput(node); |
| 187 } |
| 187 for (var group in _groups.values) { | 188 for (var group in _groups.values) { |
| 188 group.addInput(node); | 189 group.addInput(node); |
| 189 } | 190 } |
| 190 } | 191 } |
| 191 | 192 |
| 192 // TODO(nweiz): If the output is available when this is called, it's | 193 // TODO(nweiz): If the output is available when this is called, it's |
| 193 // theoretically possible for it to become unavailable between the call and | 194 // theoretically possible for it to become unavailable between the call and |
| 194 // the return. If it does so, it won't trigger the rebuilding process. To | 195 // the return. If it does so, it won't trigger the rebuilding process. To |
| 195 // avoid this, we should have this and the methods it calls take explicit | 196 // avoid this, we should have this and the methods it calls take explicit |
| 196 // callbacks, as in [AssetNode.whenAvailable]. | 197 // callbacks, as in [AssetNode.whenAvailable]. |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 228 // Otherwise, store a completer for the asset node. If it's generated in | 229 // Otherwise, store a completer for the asset node. If it's generated in |
| 229 // the future, we'll complete this completer. | 230 // the future, we'll complete this completer. |
| 230 var completer = _pendingOutputRequests.putIfAbsent(id, | 231 var completer = _pendingOutputRequests.putIfAbsent(id, |
| 231 () => new Completer.sync()); | 232 () => new Completer.sync()); |
| 232 return completer.future; | 233 return completer.future; |
| 233 }); | 234 }); |
| 234 } | 235 } |
| 235 | 236 |
| 236 /// Set this phase's transformers to [transformers]. | 237 /// Set this phase's transformers to [transformers]. |
| 237 void updateTransformers(Iterable transformers) { | 238 void updateTransformers(Iterable transformers) { |
| 238 var actualTransformers = transformers.where((op) => op is Transformer); | 239 var newTransformers = transformers.where((op) => op is Transformer) |
| 239 _transformers.clear(); | 240 .toSet(); |
| 240 _transformers.addAll(actualTransformers); | 241 var oldTransformers = _classifiers.keys.toSet(); |
| 241 for (var input in _inputs.values) { | 242 for (var removed in oldTransformers.difference(newTransformers)) { |
| 242 input.updateTransformers(actualTransformers); | 243 _classifiers.remove(removed).remove(); |
| 244 } |
| 245 |
| 246 for (var transformer in newTransformers.difference(oldTransformers)) { |
| 247 var classifier = new TransformerClassifier( |
| 248 this, transformer, "$_location.$_index"); |
| 249 _classifiers[transformer] = classifier; |
| 250 classifier.onAsset.listen(_handleOutput); |
| 251 _streams.onLogPool.add(classifier.onLog); |
| 252 classifier.onStatusChange.listen((_) => _streams.changeStatus(status)); |
| 253 for (var input in _inputs) { |
| 254 classifier.addInput(input); |
| 255 } |
| 243 } | 256 } |
| 244 | 257 |
| 245 var newGroups = transformers.where((op) => op is TransformerGroup) | 258 var newGroups = transformers.where((op) => op is TransformerGroup) |
| 246 .toSet(); | 259 .toSet(); |
| 247 var oldGroups = _groups.keys.toSet(); | 260 var oldGroups = _groups.keys.toSet(); |
| 248 for (var removed in oldGroups.difference(newGroups)) { | 261 for (var removed in oldGroups.difference(newGroups)) { |
| 249 _groups.remove(removed).remove(); | 262 _groups.remove(removed).remove(); |
| 250 } | 263 } |
| 251 | 264 |
| 252 for (var added in newGroups.difference(oldGroups)) { | 265 for (var added in newGroups.difference(oldGroups)) { |
| 253 var runner = new GroupRunner(cascade, added, "$_location.$_index"); | 266 var runner = new GroupRunner(cascade, added, "$_location.$_index"); |
| 254 _groups[added] = runner; | 267 _groups[added] = runner; |
| 255 runner.onAsset.listen(_handleOutput); | 268 runner.onAsset.listen(_handleOutput); |
| 256 _streams.onLogPool.add(runner.onLog); | 269 _streams.onLogPool.add(runner.onLog); |
| 257 runner.onStatusChange.listen((_) => _streams.changeStatus(status)); | 270 runner.onStatusChange.listen((_) => _streams.changeStatus(status)); |
| 258 for (var input in _inputs.values) { | 271 for (var input in _inputs) { |
| 259 runner.addInput(input.input); | 272 runner.addInput(input); |
| 260 } | 273 } |
| 261 } | 274 } |
| 262 | 275 |
| 263 for (var forwarder in _forwarders.values) { | 276 for (var forwarder in _forwarders.values) { |
| 264 forwarder.updateTransformers(_transformers.length, _groups.length); | 277 forwarder.updateTransformers(_classifiers.length, _groups.length); |
| 265 } | 278 } |
| 279 |
| 280 _streams.changeStatus(status); |
| 266 } | 281 } |
| 267 | 282 |
| 268 /// Force all [LazyTransformer]s' transforms in this phase to begin producing | 283 /// Force all [LazyTransformer]s' transforms in this phase to begin producing |
| 269 /// concrete assets. | 284 /// concrete assets. |
| 270 void forceAllTransforms() { | 285 void forceAllTransforms() { |
| 286 for (var classifier in _classifiers.values) { |
| 287 classifier.forceAllTransforms(); |
| 288 } |
| 289 |
| 271 for (var group in _groups.values) { | 290 for (var group in _groups.values) { |
| 272 group.forceAllTransforms(); | 291 group.forceAllTransforms(); |
| 273 } | 292 } |
| 274 | |
| 275 for (var input in _inputs.values) { | |
| 276 input.forceAllTransforms(); | |
| 277 } | |
| 278 } | 293 } |
| 279 | 294 |
| 280 /// Add a new phase after this one. | 295 /// Add a new phase after this one. |
| 281 /// | 296 /// |
| 282 /// This may only be called on a phase with no phase following it. | 297 /// This may only be called on a phase with no phase following it. |
| 283 Phase addPhase() { | 298 Phase addPhase() { |
| 284 var next = new Phase._(cascade, _location, _index + 1, this); | 299 var next = new Phase._(cascade, _location, _index + 1, this); |
| 285 for (var output in _outputs.values.toList()) { | 300 for (var output in _outputs.values.toList()) { |
| 286 // Remove [output]'s listeners because now they should get the asset from | 301 // Remove [output]'s listeners because now they should get the asset from |
| 287 // [next], rather than this phase. Any transforms consuming [output] will | 302 // [next], rather than this phase. Any transforms consuming [output] will |
| 288 // be re-run and will consume the output from the new final phase. | 303 // be re-run and will consume the output from the new final phase. |
| 289 output.removeListeners(); | 304 output.removeListeners(); |
| 290 } | 305 } |
| 291 return next; | 306 return next; |
| 292 } | 307 } |
| 293 | 308 |
| 294 /// Mark this phase as removed. | 309 /// Mark this phase as removed. |
| 295 /// | 310 /// |
| 296 /// This will remove all the phase's outputs. | 311 /// This will remove all the phase's outputs. |
| 297 void remove() { | 312 void remove() { |
| 298 for (var input in _inputs.values.toList()) { | 313 for (var classifier in _classifiers.values.toList()) { |
| 299 input.remove(); | 314 classifier.remove(); |
| 300 } | 315 } |
| 301 for (var group in _groups.values) { | 316 for (var group in _groups.values) { |
| 302 group.remove(); | 317 group.remove(); |
| 303 } | 318 } |
| 304 _streams.close(); | 319 _streams.close(); |
| 305 if (_previousStatusSubscription != null) { | 320 if (_previousStatusSubscription != null) { |
| 306 _previousStatusSubscription.cancel(); | 321 _previousStatusSubscription.cancel(); |
| 307 } | 322 } |
| 308 if (_previousOnAssetSubscription != null) { | 323 if (_previousOnAssetSubscription != null) { |
| 309 _previousOnAssetSubscription.cancel(); | 324 _previousOnAssetSubscription.cancel(); |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 360 assert(asset.state.isDirty); | 375 assert(asset.state.isDirty); |
| 361 asset.force(); | 376 asset.force(); |
| 362 asset.whenStateChanges().then((state) { | 377 asset.whenStateChanges().then((state) { |
| 363 if (state.isRemoved) return getOutput(asset.id); | 378 if (state.isRemoved) return getOutput(asset.id); |
| 364 return asset; | 379 return asset; |
| 365 }).then(request.complete).catchError(request.completeError); | 380 }).then(request.complete).catchError(request.completeError); |
| 366 } | 381 } |
| 367 | 382 |
| 368 String toString() => "phase $_location.$_index"; | 383 String toString() => "phase $_location.$_index"; |
| 369 } | 384 } |
| OLD | NEW |