| 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.graph.phase; | 5 library barback.phase; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import '../asset/asset_id.dart'; | |
| 10 import '../asset/asset_node.dart'; | |
| 11 import '../asset/asset_node_set.dart'; | |
| 12 import '../errors.dart'; | |
| 13 import '../log.dart'; | |
| 14 import '../transformer/transformer.dart'; | |
| 15 import '../transformer/transformer_group.dart'; | |
| 16 import '../utils.dart'; | |
| 17 import '../utils/multiset.dart'; | |
| 18 import 'asset_cascade.dart'; | 9 import 'asset_cascade.dart'; |
| 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; |
| 12 import 'errors.dart'; |
| 19 import 'group_runner.dart'; | 13 import 'group_runner.dart'; |
| 20 import 'node_status.dart'; | 14 import 'log.dart'; |
| 21 import 'node_streams.dart'; | 15 import 'multiset.dart'; |
| 22 import 'phase_forwarder.dart'; | 16 import 'phase_forwarder.dart'; |
| 17 import 'phase_input.dart'; |
| 23 import 'phase_output.dart'; | 18 import 'phase_output.dart'; |
| 24 import 'transformer_classifier.dart'; | 19 import 'stream_pool.dart'; |
| 20 import 'transformer.dart'; |
| 21 import 'transformer_group.dart'; |
| 22 import 'utils.dart'; |
| 25 | 23 |
| 26 /// One phase in the ordered series of transformations in an [AssetCascade]. | 24 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 27 /// | 25 /// |
| 28 /// Each phase can access outputs from previous phases and can in turn pass | 26 /// Each phase can access outputs from previous phases and can in turn pass |
| 29 /// outputs to later phases. Phases are processed strictly serially. All | 27 /// outputs to later phases. Phases are processed strictly serially. All |
| 30 /// transforms in a phase will be complete before moving on to the next phase. | 28 /// transforms in a phase will be complete before moving on to the next phase. |
| 31 /// Within a single phase, all transforms will be run in parallel. | 29 /// Within a single phase, all transforms will be run in parallel. |
| 32 /// | 30 /// |
| 33 /// Building can be interrupted between phases. For example, a source is added | 31 /// Building can be interrupted between phases. For example, a source is added |
| 34 /// which starts the background process. Sometime during, say, phase 2 (which | 32 /// which starts the background process. Sometime during, say, phase 2 (which |
| 35 /// is running asynchronously) that source is modified. When the process queue | 33 /// is running asynchronously) that source is modified. When the process queue |
| 36 /// goes to advance to phase 3, it will see that modification and start the | 34 /// goes to advance to phase 3, it will see that modification and start the |
| 37 /// waterfall from the beginning again. | 35 /// waterfall from the beginning again. |
| 38 class Phase { | 36 class Phase { |
| 39 /// The cascade that owns this phase. | 37 /// The cascade that owns this phase. |
| 40 final AssetCascade cascade; | 38 final AssetCascade cascade; |
| 41 | 39 |
| 42 /// A string describing the location of [this] in the transformer graph. | 40 /// A string describing the location of [this] in the transformer graph. |
| 43 final String _location; | 41 final String _location; |
| 44 | 42 |
| 45 /// The index of [this] in its parent cascade or group. | 43 /// The index of [this] in its parent cascade or group. |
| 46 final int _index; | 44 final int _index; |
| 47 | 45 |
| 46 /// The transformers that can access [inputs]. |
| 47 /// |
| 48 /// Their outputs will be available to the next phase. |
| 49 final _transformers = new Set<Transformer>(); |
| 50 |
| 48 /// The groups for this phase. | 51 /// The groups for this phase. |
| 49 final _groups = new Map<TransformerGroup, GroupRunner>(); | 52 final _groups = new Map<TransformerGroup, GroupRunner>(); |
| 50 | 53 |
| 51 /// The inputs for this phase. | 54 /// The inputs for this phase. |
| 52 /// | 55 /// |
| 53 /// For the first phase, these will be the source assets. For all other | 56 /// For the first phase, these will be the source assets. For all other |
| 54 /// phases, they will be the outputs from the previous phase. | 57 /// phases, they will be the outputs from the previous phase. |
| 55 final _inputs = new AssetNodeSet(); | 58 final _inputs = new Map<AssetId, PhaseInput>(); |
| 56 | |
| 57 /// The transformer classifiers for this phase. | |
| 58 final _classifiers = new Map<Transformer, TransformerClassifier>(); | |
| 59 | 59 |
| 60 /// The forwarders for this phase. | 60 /// The forwarders for this phase. |
| 61 final _forwarders = new Map<AssetId, PhaseForwarder>(); | 61 final _forwarders = new Map<AssetId, PhaseForwarder>(); |
| 62 | 62 |
| 63 /// The outputs for this phase. | 63 /// The outputs for this phase. |
| 64 final _outputs = new Map<AssetId, PhaseOutput>(); | 64 final _outputs = new Map<AssetId, PhaseOutput>(); |
| 65 | 65 |
| 66 /// 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 |
| 67 /// phase. | 67 /// phase. |
| 68 /// | 68 /// |
| 69 /// 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 |
| 70 /// [_classifiers] or [_groups]. It's possible that a given asset was consumed | 70 /// [_inputs] or [_groups]. Each input asset has a PhaseInput in [_inputs]. If |
| 71 /// by a group and not an individual transformer, and so shouldn't be | 71 /// that input isn't consumed by any transformers, it will be forwarded |
| 72 /// forwarded through the phase as a whole. | 72 /// through the PhaseInput. However, it's possible that it was consumed by a |
| 73 /// group, and so shouldn't be forwarded through the phase as a whole. |
| 73 /// | 74 /// |
| 74 /// In order to detect whether an output has been forwarded through a group or | 75 /// In order to detect whether an output has been forwarded through a group or |
| 75 /// a classifier, we must be able to distinguish it from other outputs with | 76 /// a PhaseInput, we must be able to distinguish it from other outputs with |
| 76 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If | 77 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If |
| 77 /// so, it's been forwarded unmodified. | 78 /// so, it's been forwarded unmodified. |
| 78 final _inputOrigins = new Multiset<AssetNode>(); | 79 final _inputOrigins = new Multiset<AssetNode>(); |
| 79 | 80 |
| 80 /// The streams exposed by this phase. | 81 /// A stream that emits an event whenever [this] is no longer dirty. |
| 81 final _streams = new NodeStreams(); | 82 /// |
| 82 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | 83 /// This is synchronous in order to guarantee that it will emit an event as |
| 83 Stream<AssetNode> get onAsset => _streams.onAsset; | 84 /// soon as [isDirty] flips from `true` to `false`. |
| 84 Stream<LogEntry> get onLog => _streams.onLog; | 85 Stream get onDone => _onDoneController.stream; |
| 86 final _onDoneController = new StreamController.broadcast(sync: true); |
| 85 | 87 |
| 86 /// How far along [this] is in processing its assets. | 88 /// A stream that emits any new assets emitted by [this]. |
| 87 NodeStatus get status { | 89 /// |
| 88 // Before any transformers are added, the phase should be dirty if and only | 90 /// Assets are emitted synchronously to ensure that any changes are thoroughly |
| 89 // if any input is dirty. | 91 /// propagated as soon as they occur. Only a phase with no [next] phase will |
| 90 if (_classifiers.isEmpty && _groups.isEmpty && previous == null) { | 92 /// emit assets. |
| 91 return _inputs.any((input) => input.state.isDirty) ? | 93 Stream<AssetNode> get onAsset => _onAssetController.stream; |
| 92 NodeStatus.RUNNING : NodeStatus.IDLE; | 94 final _onAssetController = |
| 93 } | 95 new StreamController<AssetNode>.broadcast(sync: true); |
| 94 | 96 |
| 95 var classifierStatus = NodeStatus.dirtiest( | 97 /// Whether [this] is dirty and still has more processing to do. |
| 96 _classifiers.values.map((classifier) => classifier.status)); | 98 /// |
| 97 var groupStatus = NodeStatus.dirtiest( | 99 /// A phase is considered dirty if any of the previous phases in the same |
| 98 _groups.values.map((group) => group.status)); | 100 /// cascade are dirty, since those phases could emit an asset that this phase |
| 99 return (previous == null ? NodeStatus.IDLE : previous.status) | 101 /// will then need to process. |
| 100 .dirtier(classifierStatus) | 102 bool get isDirty => (previous != null && previous.isDirty) || |
| 101 .dirtier(groupStatus); | 103 _inputs.values.any((input) => input.isDirty) || |
| 102 } | 104 _groups.values.any((group) => group.isDirty); |
| 105 |
| 106 /// A stream that emits an event whenever any transforms in this phase logs |
| 107 /// an entry. |
| 108 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 109 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 103 | 110 |
| 104 /// The previous phase in the cascade, or null if this is the first phase. | 111 /// The previous phase in the cascade, or null if this is the first phase. |
| 105 final Phase previous; | 112 final Phase previous; |
| 106 | 113 |
| 107 /// The subscription to [previous]'s [onStatusChange] stream. | 114 /// The subscription to [previous]'s [onDone] stream. |
| 108 StreamSubscription _previousStatusSubscription; | 115 StreamSubscription _previousOnDoneSubscription; |
| 109 | 116 |
| 110 /// The subscription to [previous]'s [onAsset] stream. | 117 /// The subscription to [previous]'s [onAsset] stream. |
| 111 StreamSubscription<AssetNode> _previousOnAssetSubscription; | 118 StreamSubscription<AssetNode> _previousOnAssetSubscription; |
| 112 | 119 |
| 113 /// A map of asset ids to completers for [getInput] requests. | 120 /// A map of asset ids to completers for [getInput] requests. |
| 114 /// | 121 /// |
| 115 /// If an asset node is requested before it's available, we put a completer in | 122 /// If an asset node is requested before it's available, we put a completer in |
| 116 /// this map to wait for the asset to be generated. If it's not generated, the | 123 /// this map to wait for the asset to be generated. If it's not generated, the |
| 117 /// completer should complete to `null`. | 124 /// completer should complete to `null`. |
| 118 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); | 125 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); |
| 119 | 126 |
| 120 /// Returns all currently-available output assets for this phase. | 127 /// Returns all currently-available output assets for this phase. |
| 121 Set<AssetNode> get availableOutputs { | 128 Set<AssetNode> get availableOutputs { |
| 122 return _outputs.values | 129 return _outputs.values |
| 123 .map((output) => output.output) | 130 .map((output) => output.output) |
| 124 .where((node) => node.state.isAvailable) | 131 .where((node) => node.state.isAvailable) |
| 125 .toSet(); | 132 .toSet(); |
| 126 } | 133 } |
| 127 | 134 |
| 128 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 135 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 129 // create an interface that just exposes [getInput]. Emit errors via | 136 // create an interface that just exposes [getInput]. Emit errors via |
| 130 // [AssetNode]s. | 137 // [AssetNode]s. |
| 131 Phase(AssetCascade cascade, String location) | 138 Phase(AssetCascade cascade, String location) |
| 132 : this._(cascade, location, 0); | 139 : this._(cascade, location, 0); |
| 133 | 140 |
| 134 Phase._(this.cascade, this._location, this._index, [this.previous]) { | 141 Phase._(this.cascade, this._location, this._index, [this.previous]) { |
| 135 if (previous != null) { | 142 if (previous != null) { |
| 136 _previousOnAssetSubscription = previous.onAsset.listen(addInput); | 143 _previousOnAssetSubscription = previous.onAsset.listen(addInput); |
| 137 _previousStatusSubscription = previous.onStatusChange | 144 _previousOnDoneSubscription = previous.onDone.listen((_) { |
| 138 .listen((_) => _streams.changeStatus(status)); | 145 if (!isDirty) _onDoneController.add(null); |
| 146 }); |
| 139 } | 147 } |
| 140 | 148 |
| 141 onStatusChange.listen((status) { | 149 onDone.listen((_) { |
| 142 if (status == NodeStatus.RUNNING) return; | 150 // All the previous phases have finished building. If anyone's still |
| 143 | 151 // waiting for outputs, cut off the wait; we won't be generating them, |
| 144 // All the previous phases have finished declaring or producing their | 152 // at least until a source asset changes. |
| 145 // outputs. If anyone's still waiting for outputs, cut off the wait; we | |
| 146 // won't be generating them, at least until a source asset changes. | |
| 147 for (var completer in _pendingOutputRequests.values) { | 153 for (var completer in _pendingOutputRequests.values) { |
| 148 completer.complete(null); | 154 completer.complete(null); |
| 149 } | 155 } |
| 150 _pendingOutputRequests.clear(); | 156 _pendingOutputRequests.clear(); |
| 151 }); | 157 }); |
| 152 } | 158 } |
| 153 | 159 |
| 154 /// Adds a new asset as an input for this phase. | 160 /// Adds a new asset as an input for this phase. |
| 155 /// | 161 /// |
| 156 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 162 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 157 /// will automatically begin determining which transforms can consume it as a | 163 /// will automatically begin determining which transforms can consume it as a |
| 158 /// primary input. The transforms themselves won't be applied until [process] | 164 /// primary input. The transforms themselves won't be applied until [process] |
| 159 /// is called, however. | 165 /// is called, however. |
| 160 /// | 166 /// |
| 161 /// This should only be used for brand-new assets or assets that have been | 167 /// This should only be used for brand-new assets or assets that have been |
| 162 /// removed and re-created. The phase will automatically handle updated assets | 168 /// removed and re-created. The phase will automatically handle updated assets |
| 163 /// using the [AssetNode.onStateChange] stream. | 169 /// using the [AssetNode.onStateChange] stream. |
| 164 void addInput(AssetNode node) { | 170 void addInput(AssetNode node) { |
| 171 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); |
| 172 |
| 173 node.force(); |
| 174 |
| 165 // Each group is one channel along which an asset may be forwarded, as is | 175 // Each group is one channel along which an asset may be forwarded, as is |
| 166 // each transformer. | 176 // each transformer. |
| 167 var forwarder = new PhaseForwarder( | 177 var forwarder = new PhaseForwarder( |
| 168 node, _classifiers.length, _groups.length); | 178 node, _transformers.length, _groups.length); |
| 169 _forwarders[node.id] = forwarder; | 179 _forwarders[node.id] = forwarder; |
| 170 forwarder.onAsset.listen(_handleOutputWithoutForwarder); | 180 forwarder.onAsset.listen(_handleOutputWithoutForwarder); |
| 171 if (forwarder.output != null) { | 181 if (forwarder.output != null) { |
| 172 _handleOutputWithoutForwarder(forwarder.output); | 182 _handleOutputWithoutForwarder(forwarder.output); |
| 173 } | 183 } |
| 174 | 184 |
| 175 _inputOrigins.add(node.origin); | 185 _inputOrigins.add(node.origin); |
| 176 _inputs.add(node); | 186 var input = new PhaseInput(this, node, "$_location.$_index"); |
| 177 node.onStateChange.listen((state) { | 187 _inputs[node.id] = input; |
| 178 if (state.isRemoved) { | 188 input.input.whenRemoved(() { |
| 179 _inputOrigins.remove(node.origin); | 189 _inputOrigins.remove(node.origin); |
| 180 _forwarders.remove(node.id).remove(); | 190 _inputs.remove(node.id); |
| 181 } | 191 _forwarders.remove(node.id).remove(); |
| 182 _streams.changeStatus(status); | 192 if (!isDirty) _onDoneController.add(null); |
| 193 }); |
| 194 input.onAsset.listen(_handleOutput); |
| 195 _onLogPool.add(input.onLog); |
| 196 input.onDone.listen((_) { |
| 197 if (!isDirty) _onDoneController.add(null); |
| 183 }); | 198 }); |
| 184 | 199 |
| 185 for (var classifier in _classifiers.values) { | 200 input.updateTransformers(_transformers); |
| 186 classifier.addInput(node); | 201 |
| 202 for (var group in _groups.values) { |
| 203 group.addInput(node); |
| 187 } | 204 } |
| 188 } | 205 } |
| 189 | 206 |
| 190 // TODO(nweiz): If the output is available when this is called, it's | 207 // TODO(nweiz): If the output is available when this is called, it's |
| 191 // theoretically possible for it to become unavailable between the call and | 208 // theoretically possible for it to become unavailable between the call and |
| 192 // the return. If it does so, it won't trigger the rebuilding process. To | 209 // the return. If it does so, it won't trigger the rebuilding process. To |
| 193 // avoid this, we should have this and the methods it calls take explicit | 210 // avoid this, we should have this and the methods it calls take explicit |
| 194 // callbacks, as in [AssetNode.whenAvailable]. | 211 // callbacks, as in [AssetNode.whenAvailable]. |
| 195 /// Gets the asset node for an output [id]. | 212 /// Gets the asset node for an output [id]. |
| 196 /// | 213 /// |
| (...skipping 15 matching lines...) Expand all Loading... |
| 212 // try again, since it could be generated again. | 229 // try again, since it could be generated again. |
| 213 output.force(); | 230 output.force(); |
| 214 return output.whenAvailable((_) { | 231 return output.whenAvailable((_) { |
| 215 return output; | 232 return output; |
| 216 }).catchError((error) { | 233 }).catchError((error) { |
| 217 if (error is! AssetNotFoundException) throw error; | 234 if (error is! AssetNotFoundException) throw error; |
| 218 return getOutput(id); | 235 return getOutput(id); |
| 219 }); | 236 }); |
| 220 } | 237 } |
| 221 | 238 |
| 222 // If this phase and the previous phases are fully declared or done, the | 239 // If neither this phase nor the previous phases are dirty, the requested |
| 223 // requested output won't be generated and we can safely return null. | 240 // output won't be generated and we can safely return null. |
| 224 if (status != NodeStatus.RUNNING) return null; | 241 if (!isDirty) return null; |
| 225 | 242 |
| 226 // Otherwise, store a completer for the asset node. If it's generated in | 243 // Otherwise, store a completer for the asset node. If it's generated in |
| 227 // the future, we'll complete this completer. | 244 // the future, we'll complete this completer. |
| 228 var completer = _pendingOutputRequests.putIfAbsent(id, | 245 var completer = _pendingOutputRequests.putIfAbsent(id, |
| 229 () => new Completer.sync()); | 246 () => new Completer.sync()); |
| 230 return completer.future; | 247 return completer.future; |
| 231 }); | 248 }); |
| 232 } | 249 } |
| 233 | 250 |
| 234 /// Set this phase's transformers to [transformers]. | 251 /// Set this phase's transformers to [transformers]. |
| 235 void updateTransformers(Iterable transformers) { | 252 void updateTransformers(Iterable transformers) { |
| 236 var newTransformers = transformers.where((op) => op is Transformer) | 253 var actualTransformers = transformers.where((op) => op is Transformer); |
| 237 .toSet(); | 254 _transformers.clear(); |
| 238 var oldTransformers = _classifiers.keys.toSet(); | 255 _transformers.addAll(actualTransformers); |
| 239 for (var removed in oldTransformers.difference(newTransformers)) { | 256 for (var input in _inputs.values) { |
| 240 _classifiers.remove(removed).remove(); | 257 input.updateTransformers(actualTransformers); |
| 241 } | |
| 242 | |
| 243 for (var transformer in newTransformers.difference(oldTransformers)) { | |
| 244 var classifier = new TransformerClassifier( | |
| 245 this, transformer, "$_location.$_index"); | |
| 246 _classifiers[transformer] = classifier; | |
| 247 classifier.onAsset.listen(_handleOutput); | |
| 248 _streams.onLogPool.add(classifier.onLog); | |
| 249 classifier.onStatusChange.listen((_) => _streams.changeStatus(status)); | |
| 250 for (var input in _inputs) { | |
| 251 classifier.addInput(input); | |
| 252 } | |
| 253 } | 258 } |
| 254 | 259 |
| 255 var newGroups = transformers.where((op) => op is TransformerGroup) | 260 var newGroups = transformers.where((op) => op is TransformerGroup) |
| 256 .toSet(); | 261 .toSet(); |
| 257 var oldGroups = _groups.keys.toSet(); | 262 var oldGroups = _groups.keys.toSet(); |
| 258 for (var removed in oldGroups.difference(newGroups)) { | 263 for (var removed in oldGroups.difference(newGroups)) { |
| 259 _groups.remove(removed).remove(); | 264 _groups.remove(removed).remove(); |
| 260 } | 265 } |
| 261 | 266 |
| 262 for (var added in newGroups.difference(oldGroups)) { | 267 for (var added in newGroups.difference(oldGroups)) { |
| 263 var runner = new GroupRunner(previous, added, "$_location.$_index"); | 268 var runner = new GroupRunner(cascade, added, "$_location.$_index"); |
| 264 _groups[added] = runner; | 269 _groups[added] = runner; |
| 265 runner.onAsset.listen(_handleOutput); | 270 runner.onAsset.listen(_handleOutput); |
| 266 _streams.onLogPool.add(runner.onLog); | 271 _onLogPool.add(runner.onLog); |
| 267 runner.onStatusChange.listen((_) => _streams.changeStatus(status)); | 272 runner.onDone.listen((_) { |
| 273 if (!isDirty) _onDoneController.add(null); |
| 274 }); |
| 275 for (var input in _inputs.values) { |
| 276 runner.addInput(input.input); |
| 277 } |
| 268 } | 278 } |
| 269 | 279 |
| 270 for (var forwarder in _forwarders.values) { | 280 for (var forwarder in _forwarders.values) { |
| 271 forwarder.updateTransformers(_classifiers.length, _groups.length); | 281 forwarder.updateTransformers(_transformers.length, _groups.length); |
| 272 } | 282 } |
| 273 | |
| 274 _streams.changeStatus(status); | |
| 275 } | 283 } |
| 276 | 284 |
| 277 /// Force all [LazyTransformer]s' transforms in this phase to begin producing | 285 /// Force all [LazyTransformer]s' transforms in this phase to begin producing |
| 278 /// concrete assets. | 286 /// concrete assets. |
| 279 void forceAllTransforms() { | 287 void forceAllTransforms() { |
| 280 for (var classifier in _classifiers.values) { | 288 for (var group in _groups.values) { |
| 281 classifier.forceAllTransforms(); | 289 group.forceAllTransforms(); |
| 282 } | 290 } |
| 283 | 291 |
| 284 for (var group in _groups.values) { | 292 for (var input in _inputs.values) { |
| 285 group.forceAllTransforms(); | 293 input.forceAllTransforms(); |
| 286 } | 294 } |
| 287 } | 295 } |
| 288 | 296 |
| 289 /// Add a new phase after this one. | 297 /// Add a new phase after this one. |
| 290 /// | 298 /// |
| 291 /// The new phase will have a location annotation describing its place in the | 299 /// This may only be called on a phase with no phase following it. |
| 292 /// package graph. By default, this annotation will describe it as being | 300 Phase addPhase() { |
| 293 /// directly after [this]. If [location] is passed, though, it's described as | 301 var next = new Phase._(cascade, _location, _index + 1, this); |
| 294 /// being the first phase in that location. | |
| 295 Phase addPhase([String location]) { | |
| 296 var index = 0; | |
| 297 if (location == null) { | |
| 298 location = _location; | |
| 299 index = _index + 1; | |
| 300 } | |
| 301 | |
| 302 var next = new Phase._(cascade, location, index, this); | |
| 303 for (var output in _outputs.values.toList()) { | 302 for (var output in _outputs.values.toList()) { |
| 304 // Remove [output]'s listeners because now they should get the asset from | 303 // Remove [output]'s listeners because now they should get the asset from |
| 305 // [next], rather than this phase. Any transforms consuming [output] will | 304 // [next], rather than this phase. Any transforms consuming [output] will |
| 306 // be re-run and will consume the output from the new final phase. | 305 // be re-run and will consume the output from the new final phase. |
| 307 output.removeListeners(); | 306 output.removeListeners(); |
| 308 } | 307 } |
| 309 return next; | 308 return next; |
| 310 } | 309 } |
| 311 | 310 |
| 312 /// Mark this phase as removed. | 311 /// Mark this phase as removed. |
| 313 /// | 312 /// |
| 314 /// This will remove all the phase's outputs. | 313 /// This will remove all the phase's outputs. |
| 315 void remove() { | 314 void remove() { |
| 316 for (var classifier in _classifiers.values.toList()) { | 315 for (var input in _inputs.values.toList()) { |
| 317 classifier.remove(); | 316 input.remove(); |
| 318 } | 317 } |
| 319 for (var group in _groups.values) { | 318 for (var group in _groups.values) { |
| 320 group.remove(); | 319 group.remove(); |
| 321 } | 320 } |
| 322 _streams.close(); | 321 _onAssetController.close(); |
| 323 if (_previousStatusSubscription != null) { | 322 _onLogPool.close(); |
| 324 _previousStatusSubscription.cancel(); | 323 if (_previousOnDoneSubscription != null) { |
| 324 _previousOnDoneSubscription.cancel(); |
| 325 } | 325 } |
| 326 if (_previousOnAssetSubscription != null) { | 326 if (_previousOnAssetSubscription != null) { |
| 327 _previousOnAssetSubscription.cancel(); | 327 _previousOnAssetSubscription.cancel(); |
| 328 } | 328 } |
| 329 } | 329 } |
| 330 | 330 |
| 331 /// Add [asset] as an output of this phase. | 331 /// Add [asset] as an output of this phase. |
| 332 void _handleOutput(AssetNode asset) { | 332 void _handleOutput(AssetNode asset) { |
| 333 if (_inputOrigins.contains(asset.origin)) { | 333 if (_inputOrigins.contains(asset.origin)) { |
| 334 _forwarders[asset.id].addIntermediateAsset(asset); | 334 _forwarders[asset.id].addIntermediateAsset(asset); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 351 | 351 |
| 352 var exception = _outputs[asset.id].collisionException; | 352 var exception = _outputs[asset.id].collisionException; |
| 353 if (exception != null) cascade.reportError(exception); | 353 if (exception != null) cascade.reportError(exception); |
| 354 } | 354 } |
| 355 | 355 |
| 356 /// Emit [asset] as an output of this phase. | 356 /// Emit [asset] as an output of this phase. |
| 357 /// | 357 /// |
| 358 /// This should be called after [_handleOutput], so that collisions are | 358 /// This should be called after [_handleOutput], so that collisions are |
| 359 /// resolved. | 359 /// resolved. |
| 360 void _emit(AssetNode asset) { | 360 void _emit(AssetNode asset) { |
| 361 _streams.onAssetController.add(asset); | 361 _onAssetController.add(asset); |
| 362 _providePendingAsset(asset); | 362 _providePendingAsset(asset); |
| 363 } | 363 } |
| 364 | 364 |
| 365 /// Provide an asset to a pending [getOutput] call. | 365 /// Provide an asset to a pending [getOutput] call. |
| 366 void _providePendingAsset(AssetNode asset) { | 366 void _providePendingAsset(AssetNode asset) { |
| 367 // If anyone's waiting for this asset, provide it to them. | 367 // If anyone's waiting for this asset, provide it to them. |
| 368 var request = _pendingOutputRequests.remove(asset.id); | 368 var request = _pendingOutputRequests.remove(asset.id); |
| 369 if (request == null) return; | 369 if (request == null) return; |
| 370 | 370 |
| 371 if (asset.state.isAvailable) { | 371 if (asset.state.isAvailable) { |
| 372 request.complete(asset); | 372 request.complete(asset); |
| 373 return; | 373 return; |
| 374 } | 374 } |
| 375 | 375 |
| 376 // A lazy asset may be emitted while still dirty. If so, we wait until it's | 376 // A lazy asset may be emitted while still dirty. If so, we wait until it's |
| 377 // either available or removed before trying again to access it. | 377 // either available or removed before trying again to access it. |
| 378 assert(asset.state.isDirty); | 378 assert(asset.state.isDirty); |
| 379 asset.force(); | 379 asset.force(); |
| 380 asset.whenStateChanges().then((state) { | 380 asset.whenStateChanges().then((state) { |
| 381 if (state.isRemoved) return getOutput(asset.id); | 381 if (state.isRemoved) return getOutput(asset.id); |
| 382 return asset; | 382 return asset; |
| 383 }).then(request.complete).catchError(request.completeError); | 383 }).then(request.complete).catchError(request.completeError); |
| 384 } | 384 } |
| 385 | 385 |
| 386 String toString() => "phase $_location.$_index"; | 386 String toString() => "phase $_location.$_index"; |
| 387 } | 387 } |
| OLD | NEW |