| 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 'errors.dart'; | 12 import 'errors.dart'; |
| 13 import 'group_runner.dart'; | 13 import 'group_runner.dart'; |
| 14 import 'log.dart'; | 14 import 'log.dart'; |
| 15 import 'multiset.dart'; | 15 import 'multiset.dart'; |
| 16 import 'node_status.dart'; |
| 16 import 'node_streams.dart'; | 17 import 'node_streams.dart'; |
| 17 import 'phase_forwarder.dart'; | 18 import 'phase_forwarder.dart'; |
| 18 import 'phase_input.dart'; | 19 import 'phase_input.dart'; |
| 19 import 'phase_output.dart'; | 20 import 'phase_output.dart'; |
| 20 import 'transformer.dart'; | 21 import 'transformer.dart'; |
| 21 import 'transformer_group.dart'; | 22 import 'transformer_group.dart'; |
| 22 import 'utils.dart'; | 23 import 'utils.dart'; |
| 23 | 24 |
| 24 /// One phase in the ordered series of transformations in an [AssetCascade]. | 25 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 25 /// | 26 /// |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 /// group, and so shouldn't be forwarded through the phase as a whole. | 74 /// group, and so shouldn't be forwarded through the phase as a whole. |
| 74 /// | 75 /// |
| 75 /// In order to detect whether an output has been forwarded through a group or | 76 /// In order to detect whether an output has been forwarded through a group or |
| 76 /// a PhaseInput, we must be able to distinguish it from other outputs with | 77 /// a PhaseInput, we must be able to distinguish it from other outputs with |
| 77 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If | 78 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If |
| 78 /// so, it's been forwarded unmodified. | 79 /// so, it's been forwarded unmodified. |
| 79 final _inputOrigins = new Multiset<AssetNode>(); | 80 final _inputOrigins = new Multiset<AssetNode>(); |
| 80 | 81 |
| 81 /// The streams exposed by this phase. | 82 /// The streams exposed by this phase. |
| 82 final _streams = new NodeStreams(); | 83 final _streams = new NodeStreams(); |
| 83 Stream get onDone => _streams.onDone; | 84 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; |
| 84 Stream<AssetNode> get onAsset => _streams.onAsset; | 85 Stream<AssetNode> get onAsset => _streams.onAsset; |
| 85 Stream<LogEntry> get onLog => _streams.onLog; | 86 Stream<LogEntry> get onLog => _streams.onLog; |
| 86 | 87 |
| 87 /// Whether [this] is dirty and still has more processing to do. | 88 /// How far along [this] is in processing its assets. |
| 88 /// | 89 NodeStatus get status { |
| 89 /// A phase is considered dirty if any of the previous phases in the same | 90 var inputStatus = NodeStatus.dirtiest( |
| 90 /// cascade are dirty, since those phases could emit an asset that this phase | 91 _inputs.values.map((input) => input.status)); |
| 91 /// will then need to process. | 92 var groupStatus = NodeStatus.dirtiest( |
| 92 bool get isDirty => (previous != null && previous.isDirty) || | 93 _groups.values.map((group) => group.status)); |
| 93 _inputs.values.any((input) => input.isDirty) || | 94 return (previous == null ? NodeStatus.IDLE : previous.status) |
| 94 _groups.values.any((group) => group.isDirty); | 95 .dirtier(inputStatus) |
| 96 .dirtier(groupStatus); |
| 97 } |
| 95 | 98 |
| 96 /// The previous phase in the cascade, or null if this is the first phase. | 99 /// The previous phase in the cascade, or null if this is the first phase. |
| 97 final Phase previous; | 100 final Phase previous; |
| 98 | 101 |
| 99 /// The subscription to [previous]'s [onDone] stream. | 102 /// The subscription to [previous]'s [onStatusChange] stream. |
| 100 StreamSubscription _previousOnDoneSubscription; | 103 StreamSubscription _previousStatusSubscription; |
| 101 | 104 |
| 102 /// The subscription to [previous]'s [onAsset] stream. | 105 /// The subscription to [previous]'s [onAsset] stream. |
| 103 StreamSubscription<AssetNode> _previousOnAssetSubscription; | 106 StreamSubscription<AssetNode> _previousOnAssetSubscription; |
| 104 | 107 |
| 105 /// A map of asset ids to completers for [getInput] requests. | 108 /// A map of asset ids to completers for [getInput] requests. |
| 106 /// | 109 /// |
| 107 /// If an asset node is requested before it's available, we put a completer in | 110 /// If an asset node is requested before it's available, we put a completer in |
| 108 /// this map to wait for the asset to be generated. If it's not generated, the | 111 /// this map to wait for the asset to be generated. If it's not generated, the |
| 109 /// completer should complete to `null`. | 112 /// completer should complete to `null`. |
| 110 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); | 113 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); |
| 111 | 114 |
| 112 /// Returns all currently-available output assets for this phase. | 115 /// Returns all currently-available output assets for this phase. |
| 113 Set<AssetNode> get availableOutputs { | 116 Set<AssetNode> get availableOutputs { |
| 114 return _outputs.values | 117 return _outputs.values |
| 115 .map((output) => output.output) | 118 .map((output) => output.output) |
| 116 .where((node) => node.state.isAvailable) | 119 .where((node) => node.state.isAvailable) |
| 117 .toSet(); | 120 .toSet(); |
| 118 } | 121 } |
| 119 | 122 |
| 120 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 123 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 121 // create an interface that just exposes [getInput]. Emit errors via | 124 // create an interface that just exposes [getInput]. Emit errors via |
| 122 // [AssetNode]s. | 125 // [AssetNode]s. |
| 123 Phase(AssetCascade cascade, String location) | 126 Phase(AssetCascade cascade, String location) |
| 124 : this._(cascade, location, 0); | 127 : this._(cascade, location, 0); |
| 125 | 128 |
| 126 Phase._(this.cascade, this._location, this._index, [this.previous]) { | 129 Phase._(this.cascade, this._location, this._index, [this.previous]) { |
| 127 if (previous != null) { | 130 if (previous != null) { |
| 128 _previousOnAssetSubscription = previous.onAsset.listen(addInput); | 131 _previousOnAssetSubscription = previous.onAsset.listen(addInput); |
| 129 _previousOnDoneSubscription = previous.onDone.listen((_) { | 132 _previousStatusSubscription = previous.onStatusChange |
| 130 if (!isDirty) _streams.onDoneController.add(null); | 133 .listen((_) => _streams.changeStatus(status)); |
| 131 }); | |
| 132 } | 134 } |
| 133 | 135 |
| 134 onDone.listen((_) { | 136 onStatusChange.listen((status) { |
| 135 // All the previous phases have finished building. If anyone's still | 137 if (status == NodeStatus.RUNNING) return; |
| 136 // waiting for outputs, cut off the wait; we won't be generating them, | 138 |
| 137 // at least until a source asset changes. | 139 // All the previous phases have finished declaring or producing their |
| 140 // outputs. If anyone's still waiting for outputs, cut off the wait; we |
| 141 // won't be generating them, at least until a source asset changes. |
| 138 for (var completer in _pendingOutputRequests.values) { | 142 for (var completer in _pendingOutputRequests.values) { |
| 139 completer.complete(null); | 143 completer.complete(null); |
| 140 } | 144 } |
| 141 _pendingOutputRequests.clear(); | 145 _pendingOutputRequests.clear(); |
| 142 }); | 146 }); |
| 143 } | 147 } |
| 144 | 148 |
| 145 /// Adds a new asset as an input for this phase. | 149 /// Adds a new asset as an input for this phase. |
| 146 /// | 150 /// |
| 147 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 151 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| (...skipping 17 matching lines...) Expand all Loading... |
| 165 _handleOutputWithoutForwarder(forwarder.output); | 169 _handleOutputWithoutForwarder(forwarder.output); |
| 166 } | 170 } |
| 167 | 171 |
| 168 _inputOrigins.add(node.origin); | 172 _inputOrigins.add(node.origin); |
| 169 var input = new PhaseInput(this, node, "$_location.$_index"); | 173 var input = new PhaseInput(this, node, "$_location.$_index"); |
| 170 _inputs[node.id] = input; | 174 _inputs[node.id] = input; |
| 171 input.input.whenRemoved(() { | 175 input.input.whenRemoved(() { |
| 172 _inputOrigins.remove(node.origin); | 176 _inputOrigins.remove(node.origin); |
| 173 _inputs.remove(node.id); | 177 _inputs.remove(node.id); |
| 174 _forwarders.remove(node.id).remove(); | 178 _forwarders.remove(node.id).remove(); |
| 175 if (!isDirty) _streams.onDoneController.add(null); | 179 _streams.changeStatus(status); |
| 176 }); | 180 }); |
| 177 input.onAsset.listen(_handleOutput); | 181 input.onAsset.listen(_handleOutput); |
| 178 _streams.onLogPool.add(input.onLog); | 182 _streams.onLogPool.add(input.onLog); |
| 179 input.onDone.listen((_) { | 183 input.onStatusChange.listen((_) => _streams.changeStatus(status)); |
| 180 if (!isDirty) _streams.onDoneController.add(null); | |
| 181 }); | |
| 182 | 184 |
| 183 input.updateTransformers(_transformers); | 185 input.updateTransformers(_transformers); |
| 184 | 186 |
| 185 for (var group in _groups.values) { | 187 for (var group in _groups.values) { |
| 186 group.addInput(node); | 188 group.addInput(node); |
| 187 } | 189 } |
| 188 } | 190 } |
| 189 | 191 |
| 190 // TODO(nweiz): If the output is available when this is called, it's | 192 // 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 | 193 // theoretically possible for it to become unavailable between the call and |
| (...skipping 20 matching lines...) Expand all Loading... |
| 212 // try again, since it could be generated again. | 214 // try again, since it could be generated again. |
| 213 output.force(); | 215 output.force(); |
| 214 return output.whenAvailable((_) { | 216 return output.whenAvailable((_) { |
| 215 return output; | 217 return output; |
| 216 }).catchError((error) { | 218 }).catchError((error) { |
| 217 if (error is! AssetNotFoundException) throw error; | 219 if (error is! AssetNotFoundException) throw error; |
| 218 return getOutput(id); | 220 return getOutput(id); |
| 219 }); | 221 }); |
| 220 } | 222 } |
| 221 | 223 |
| 222 // If neither this phase nor the previous phases are dirty, the requested | 224 // If this phase and the previous phases are fully declared or done, the |
| 223 // output won't be generated and we can safely return null. | 225 // requested output won't be generated and we can safely return null. |
| 224 if (!isDirty) return null; | 226 if (status != NodeStatus.RUNNING) return null; |
| 225 | 227 |
| 226 // Otherwise, store a completer for the asset node. If it's generated in | 228 // Otherwise, store a completer for the asset node. If it's generated in |
| 227 // the future, we'll complete this completer. | 229 // the future, we'll complete this completer. |
| 228 var completer = _pendingOutputRequests.putIfAbsent(id, | 230 var completer = _pendingOutputRequests.putIfAbsent(id, |
| 229 () => new Completer.sync()); | 231 () => new Completer.sync()); |
| 230 return completer.future; | 232 return completer.future; |
| 231 }); | 233 }); |
| 232 } | 234 } |
| 233 | 235 |
| 234 /// Set this phase's transformers to [transformers]. | 236 /// Set this phase's transformers to [transformers]. |
| (...skipping 10 matching lines...) Expand all Loading... |
| 245 var oldGroups = _groups.keys.toSet(); | 247 var oldGroups = _groups.keys.toSet(); |
| 246 for (var removed in oldGroups.difference(newGroups)) { | 248 for (var removed in oldGroups.difference(newGroups)) { |
| 247 _groups.remove(removed).remove(); | 249 _groups.remove(removed).remove(); |
| 248 } | 250 } |
| 249 | 251 |
| 250 for (var added in newGroups.difference(oldGroups)) { | 252 for (var added in newGroups.difference(oldGroups)) { |
| 251 var runner = new GroupRunner(cascade, added, "$_location.$_index"); | 253 var runner = new GroupRunner(cascade, added, "$_location.$_index"); |
| 252 _groups[added] = runner; | 254 _groups[added] = runner; |
| 253 runner.onAsset.listen(_handleOutput); | 255 runner.onAsset.listen(_handleOutput); |
| 254 _streams.onLogPool.add(runner.onLog); | 256 _streams.onLogPool.add(runner.onLog); |
| 255 runner.onDone.listen((_) { | 257 runner.onStatusChange.listen((_) => _streams.changeStatus(status)); |
| 256 if (!isDirty) _streams.onDoneController.add(null); | |
| 257 }); | |
| 258 for (var input in _inputs.values) { | 258 for (var input in _inputs.values) { |
| 259 runner.addInput(input.input); | 259 runner.addInput(input.input); |
| 260 } | 260 } |
| 261 } | 261 } |
| 262 | 262 |
| 263 for (var forwarder in _forwarders.values) { | 263 for (var forwarder in _forwarders.values) { |
| 264 forwarder.updateTransformers(_transformers.length, _groups.length); | 264 forwarder.updateTransformers(_transformers.length, _groups.length); |
| 265 } | 265 } |
| 266 } | 266 } |
| 267 | 267 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 295 /// | 295 /// |
| 296 /// This will remove all the phase's outputs. | 296 /// This will remove all the phase's outputs. |
| 297 void remove() { | 297 void remove() { |
| 298 for (var input in _inputs.values.toList()) { | 298 for (var input in _inputs.values.toList()) { |
| 299 input.remove(); | 299 input.remove(); |
| 300 } | 300 } |
| 301 for (var group in _groups.values) { | 301 for (var group in _groups.values) { |
| 302 group.remove(); | 302 group.remove(); |
| 303 } | 303 } |
| 304 _streams.close(); | 304 _streams.close(); |
| 305 if (_previousOnDoneSubscription != null) { | 305 if (_previousStatusSubscription != null) { |
| 306 _previousOnDoneSubscription.cancel(); | 306 _previousStatusSubscription.cancel(); |
| 307 } | 307 } |
| 308 if (_previousOnAssetSubscription != null) { | 308 if (_previousOnAssetSubscription != null) { |
| 309 _previousOnAssetSubscription.cancel(); | 309 _previousOnAssetSubscription.cancel(); |
| 310 } | 310 } |
| 311 } | 311 } |
| 312 | 312 |
| 313 /// Add [asset] as an output of this phase. | 313 /// Add [asset] as an output of this phase. |
| 314 void _handleOutput(AssetNode asset) { | 314 void _handleOutput(AssetNode asset) { |
| 315 if (_inputOrigins.contains(asset.origin)) { | 315 if (_inputOrigins.contains(asset.origin)) { |
| 316 _forwarders[asset.id].addIntermediateAsset(asset); | 316 _forwarders[asset.id].addIntermediateAsset(asset); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 360 assert(asset.state.isDirty); | 360 assert(asset.state.isDirty); |
| 361 asset.force(); | 361 asset.force(); |
| 362 asset.whenStateChanges().then((state) { | 362 asset.whenStateChanges().then((state) { |
| 363 if (state.isRemoved) return getOutput(asset.id); | 363 if (state.isRemoved) return getOutput(asset.id); |
| 364 return asset; | 364 return asset; |
| 365 }).then(request.complete).catchError(request.completeError); | 365 }).then(request.complete).catchError(request.completeError); |
| 366 } | 366 } |
| 367 | 367 |
| 368 String toString() => "phase $_location.$_index"; | 368 String toString() => "phase $_location.$_index"; |
| 369 } | 369 } |
| OLD | NEW |