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.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'; |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 38 | 38 |
| 39 /// A string describing the location of [this] in the transformer graph. | 39 /// A string describing the location of [this] in the transformer graph. |
| 40 final String _location; | 40 final String _location; |
| 41 | 41 |
| 42 /// The index of [this] in its parent cascade or group. | 42 /// The index of [this] in its parent cascade or group. |
| 43 final int _index; | 43 final int _index; |
| 44 | 44 |
| 45 /// The transformers that can access [inputs]. | 45 /// The transformers that can access [inputs]. |
| 46 /// | 46 /// |
| 47 /// Their outputs will be available to the next phase. | 47 /// Their outputs will be available to the next phase. |
| 48 final Set<Transformer> _transformers; | 48 final _transformers = new Set<Transformer>(); |
| 49 | 49 |
| 50 /// The groups for this phase. | 50 /// The groups for this phase. |
| 51 final _groups = new Map<TransformerGroup, GroupRunner>(); | 51 final _groups = new Map<TransformerGroup, GroupRunner>(); |
| 52 | 52 |
| 53 /// The inputs for this phase. | 53 /// The inputs for this phase. |
| 54 /// | 54 /// |
| 55 /// For the first phase, these will be the source assets. For all other | 55 /// For the first phase, these will be the source assets. For all other |
| 56 /// phases, they will be the outputs from the previous phase. | 56 /// phases, they will be the outputs from the previous phase. |
| 57 final _inputs = new Map<AssetId, PhaseInput>(); | 57 final _inputs = new Map<AssetId, PhaseInput>(); |
| 58 | 58 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 70 /// that input isn't consumed by any transformers, it will be forwarded | 70 /// that input isn't consumed by any transformers, it will be forwarded |
| 71 /// through the PhaseInput. However, it's possible that it was consumed by a | 71 /// through the PhaseInput. However, it's possible that it was consumed by a |
| 72 /// group, and so shouldn't be forwarded through the phase as a whole. | 72 /// group, and so shouldn't be forwarded through the phase as a whole. |
| 73 /// | 73 /// |
| 74 /// 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 |
| 75 /// a PhaseInput, we must be able to distinguish it from other outputs with | 75 /// 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 | 76 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If |
| 77 /// so, it's been forwarded unmodified. | 77 /// so, it's been forwarded unmodified. |
| 78 final _inputOrigins = new Multiset<AssetNode>(); | 78 final _inputOrigins = new Multiset<AssetNode>(); |
| 79 | 79 |
| 80 /// A stream that emits an event whenever this phase becomes dirty and needs | 80 /// A stream that emits an event whenever [this] is no longer dirty. |
| 81 /// to be run. | |
| 82 /// | 81 /// |
| 83 /// This may emit events when the phase was already dirty or while processing | 82 /// This is synchronous in order to guarantee that it will emit an event as |
| 84 /// transforms. Events are emitted synchronously to ensure that the dirty | 83 /// soon as [isDirty] flips from `true` to `false`. |
| 85 /// state is thoroughly propagated as soon as any assets are changed. | 84 Stream get onDone => _onDoneController.stream; |
| 86 Stream get onDirty => _onDirtyPool.stream; | 85 final _onDoneController = new StreamController.broadcast(sync: true); |
| 87 final _onDirtyPool = new StreamPool.broadcast(); | |
| 88 | 86 |
| 89 /// A controller whose stream feeds into [_onDirtyPool]. | 87 /// A stream that emits any new assets emitted by [this]. |
| 90 /// | 88 /// |
| 91 /// This is used whenever an input is added or transforms are changed. | 89 /// Assets are emitted synchronously to ensure that any changes are thoroughly |
| 92 final _onDirtyController = new StreamController.broadcast(sync: true); | 90 /// propagated as soon as they occur. Only a phase with no [next] phase will |
| 91 /// emit assets. | |
| 92 Stream<AssetNode> get onAsset => _onAssetController.stream; | |
| 93 final _onAssetController = new StreamController<AssetNode>(sync: true); | |
| 93 | 94 |
| 94 /// Whether this phase is dirty and needs to be run. | 95 /// Whether [this] is dirty and still has more processing to do. |
| 95 bool get isDirty => _inputs.values.any((input) => input.isDirty) || | 96 bool get isDirty => _inputs.values.any((input) => input.isDirty) || |
| 96 _groups.values.any((group) => group.isDirty); | 97 _groups.values.any((group) => group.isDirty); |
| 97 | 98 |
| 98 /// A stream that emits an event whenever any transforms in this phase logs | 99 /// A stream that emits an event whenever any transforms in this phase logs |
| 99 /// an entry. | 100 /// an entry. |
| 100 Stream<LogEntry> get onLog => _onLogPool.stream; | 101 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 101 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 102 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 102 | 103 |
| 103 /// The phase after this one. | 104 /// The phase after this one. |
| 104 /// | 105 /// |
| 105 /// Outputs from this phase will be passed to it. | 106 /// Outputs from this phase will be passed to it. |
| 106 Phase get next => _next; | 107 Phase get next => _next; |
| 107 Phase _next; | 108 Phase _next; |
| 108 | 109 |
| 109 /// Returns all currently-available output assets for this phase. | 110 /// Returns all currently-available output assets for this phase. |
| 110 Set<AssetNode> get availableOutputs { | 111 Set<AssetNode> get availableOutputs { |
| 111 return _outputs.values | 112 return _outputs.values |
| 112 .map((output) => output.output) | 113 .map((output) => output.output) |
| 113 .where((node) => node.state.isAvailable) | 114 .where((node) => node.state.isAvailable) |
| 114 .toSet(); | 115 .toSet(); |
| 115 } | 116 } |
| 116 | 117 |
| 117 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 118 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 118 // create an interface that just exposes [getInput]. Emit errors via | 119 // create an interface that just exposes [getInput]. Emit errors via |
| 119 // [AssetNode]s. | 120 // [AssetNode]s. |
| 120 Phase(AssetCascade cascade, Iterable transformers, String location) | 121 Phase(AssetCascade cascade, String location) |
| 121 : this._(cascade, transformers, location, 0); | 122 : this._(cascade, location, 0); |
| 122 | 123 |
| 123 Phase._(this.cascade, Iterable transformers, this._location, this._index) | 124 Phase._(this.cascade, this._location, this._index); |
| 124 : _transformers = transformers.where((op) => op is Transformer).toSet() { | |
| 125 _onDirtyPool.add(_onDirtyController.stream); | |
| 126 | |
| 127 for (var group in transformers.where((op) => op is TransformerGroup)) { | |
| 128 var runner = new GroupRunner(cascade, group, "$_location.$_index"); | |
| 129 _groups[group] = runner; | |
| 130 _onDirtyPool.add(runner.onDirty); | |
| 131 _onLogPool.add(runner.onLog); | |
| 132 } | |
| 133 } | |
| 134 | 125 |
| 135 /// Adds a new asset as an input for this phase. | 126 /// Adds a new asset as an input for this phase. |
| 136 /// | 127 /// |
| 137 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 128 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 138 /// will automatically begin determining which transforms can consume it as a | 129 /// will automatically begin determining which transforms can consume it as a |
| 139 /// primary input. The transforms themselves won't be applied until [process] | 130 /// primary input. The transforms themselves won't be applied until [process] |
| 140 /// is called, however. | 131 /// is called, however. |
| 141 /// | 132 /// |
| 142 /// This should only be used for brand-new assets or assets that have been | 133 /// This should only be used for brand-new assets or assets that have been |
| 143 /// removed and re-created. The phase will automatically handle updated assets | 134 /// removed and re-created. The phase will automatically handle updated assets |
| 144 /// using the [AssetNode.onStateChange] stream. | 135 /// using the [AssetNode.onStateChange] stream. |
| 145 void addInput(AssetNode node) { | 136 void addInput(AssetNode node) { |
| 146 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); | 137 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); |
| 147 | 138 |
| 148 node.force(); | 139 node.force(); |
| 149 | 140 |
| 150 // Each group is one channel along which an asset may be forwarded. Then | 141 // Each group is one channel along which an asset may be forwarded. Then |
| 151 // there's one additional channel for the non-grouped transformers. | 142 // there's one additional channel for the non-grouped transformers. |
| 152 var forwarder = new PhaseForwarder(_groups.length + 1); | 143 var forwarder = new PhaseForwarder(_groups.length + 1); |
| 153 _forwarders[node.id] = forwarder; | 144 _forwarders[node.id] = forwarder; |
| 154 forwarder.onForwarding.listen((asset) { | 145 forwarder.onAsset.listen(_handleOutputWithoutForwarder); |
| 155 _addOutput(asset); | |
| 156 | |
| 157 var exception = _outputs[asset.id].collisionException; | |
| 158 if (exception != null) cascade.reportError(exception); | |
| 159 }); | |
| 160 | 146 |
| 161 _inputOrigins.add(node.origin); | 147 _inputOrigins.add(node.origin); |
| 162 var input = new PhaseInput(this, node, _transformers, "$_location.$_index"); | 148 var input = new PhaseInput(this, node, _transformers, "$_location.$_index"); |
| 163 _inputs[node.id] = input; | 149 _inputs[node.id] = input; |
| 164 input.input.whenRemoved(() { | 150 input.input.whenRemoved(() { |
| 165 _inputOrigins.remove(node.origin); | 151 _inputOrigins.remove(node.origin); |
| 166 _inputs.remove(node.id); | 152 _inputs.remove(node.id); |
| 167 _forwarders.remove(node.id).remove(); | 153 _forwarders.remove(node.id).remove(); |
| 154 if (!isDirty) _onDoneController.add(null); | |
| 168 }); | 155 }); |
| 169 _onDirtyPool.add(input.onDirty); | 156 input.onAsset.listen(_handleOutput); |
| 170 _onDirtyController.add(null); | |
| 171 _onLogPool.add(input.onLog); | 157 _onLogPool.add(input.onLog); |
| 158 input.onDone.listen((_) { | |
| 159 if (!isDirty) _onDoneController.add(isDirty); | |
|
Bob Nystrom
2014/03/05 22:13:25
.add(null);
nweiz
2014/03/06 00:29:08
Done.
| |
| 160 }); | |
| 172 | 161 |
| 173 for (var group in _groups.values) { | 162 for (var group in _groups.values) { |
| 174 group.addInput(node); | 163 group.addInput(node); |
| 175 } | 164 } |
| 176 } | 165 } |
| 177 | 166 |
| 178 /// Gets the asset node for an input [id]. | 167 /// Gets the asset node for an input [id]. |
| 179 /// | 168 /// |
| 180 /// If an input with that ID cannot be found, returns null. | 169 /// If an input with that ID cannot be found, returns null. |
| 181 Future<AssetNode> getInput(AssetId id) { | 170 Future<AssetNode> getInput(AssetId id) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 194 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 183 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 195 if (!_outputs.containsKey(id)) return null; | 184 if (!_outputs.containsKey(id)) return null; |
| 196 var output = _outputs[id].output; | 185 var output = _outputs[id].output; |
| 197 output.force(); | 186 output.force(); |
| 198 return output; | 187 return output; |
| 199 }); | 188 }); |
| 200 } | 189 } |
| 201 | 190 |
| 202 /// Set this phase's transformers to [transformers]. | 191 /// Set this phase's transformers to [transformers]. |
| 203 void updateTransformers(Iterable transformers) { | 192 void updateTransformers(Iterable transformers) { |
| 204 _onDirtyController.add(null); | |
| 205 | |
| 206 var actualTransformers = transformers.where((op) => op is Transformer); | 193 var actualTransformers = transformers.where((op) => op is Transformer); |
| 207 _transformers.clear(); | 194 _transformers.clear(); |
| 208 _transformers.addAll(actualTransformers); | 195 _transformers.addAll(actualTransformers); |
| 209 for (var input in _inputs.values) { | 196 for (var input in _inputs.values) { |
| 210 input.updateTransformers(actualTransformers); | 197 input.updateTransformers(actualTransformers); |
| 211 } | 198 } |
| 212 | 199 |
| 213 var newGroups = transformers.where((op) => op is TransformerGroup) | 200 var newGroups = transformers.where((op) => op is TransformerGroup) |
| 214 .toSet(); | 201 .toSet(); |
| 215 var oldGroups = _groups.keys.toSet(); | 202 var oldGroups = _groups.keys.toSet(); |
| 216 for (var removed in oldGroups.difference(newGroups)) { | 203 for (var removed in oldGroups.difference(newGroups)) { |
| 217 _groups.remove(removed).remove(); | 204 _groups.remove(removed).remove(); |
| 218 } | 205 } |
| 219 | 206 |
| 220 for (var added in newGroups.difference(oldGroups)) { | 207 for (var added in newGroups.difference(oldGroups)) { |
| 221 var runner = new GroupRunner(cascade, added, "$_location.$_index"); | 208 var runner = new GroupRunner(cascade, added, "$_location.$_index"); |
| 222 _groups[added] = runner; | 209 _groups[added] = runner; |
| 223 _onDirtyPool.add(runner.onDirty); | 210 runner.onAsset.listen(_handleOutput); |
| 224 _onLogPool.add(runner.onLog); | 211 _onLogPool.add(runner.onLog); |
| 212 runner.onDone.listen((_) { | |
| 213 if (!isDirty) _onDoneController.add(null); | |
| 214 }); | |
| 225 for (var input in _inputs.values) { | 215 for (var input in _inputs.values) { |
| 226 runner.addInput(input.input); | 216 runner.addInput(input.input); |
| 227 } | 217 } |
| 228 } | 218 } |
| 229 | 219 |
| 230 for (var forwarder in _forwarders.values) { | 220 for (var forwarder in _forwarders.values) { |
| 231 forwarder.numChannels = _groups.length + 1; | 221 forwarder.numChannels = _groups.length + 1; |
| 232 } | 222 } |
| 233 } | 223 } |
| 234 | 224 |
| 235 /// Force all [LazyTransformer]s' transforms in this phase to begin producing | 225 /// Force all [LazyTransformer]s' transforms in this phase to begin producing |
| 236 /// concrete assets. | 226 /// concrete assets. |
| 237 void forceAllTransforms() { | 227 void forceAllTransforms() { |
| 238 for (var group in _groups.values) { | 228 for (var group in _groups.values) { |
| 239 group.forceAllTransforms(); | 229 group.forceAllTransforms(); |
| 240 } | 230 } |
| 241 | 231 |
| 242 for (var input in _inputs.values) { | 232 for (var input in _inputs.values) { |
| 243 input.forceAllTransforms(); | 233 input.forceAllTransforms(); |
| 244 } | 234 } |
| 245 } | 235 } |
| 246 | 236 |
| 247 /// Add a new phase after this one with [transformers]. | 237 /// Add a new phase after this one. |
| 248 /// | 238 /// |
| 249 /// This may only be called on a phase with no phase following it. | 239 /// This may only be called on a phase with no phase following it. |
| 250 Phase addPhase(Iterable transformers) { | 240 Phase addPhase() { |
| 251 assert(_next == null); | 241 assert(_next == null); |
| 252 _next = new Phase._(cascade, transformers, _location, _index + 1); | 242 _next = new Phase._(cascade, _location, _index + 1); |
| 253 for (var output in _outputs.values.toList()) { | 243 for (var output in _outputs.values.toList()) { |
| 254 // Remove [output]'s listeners because now they should get the asset from | 244 // Remove [output]'s listeners because now they should get the asset from |
| 255 // [_next], rather than this phase. Any transforms consuming [output] will | 245 // [_next], rather than this phase. Any transforms consuming [output] will |
| 256 // be re-run and will consume the output from the new final phase. | 246 // be re-run and will consume the output from the new final phase. |
| 257 output.removeListeners(); | 247 output.removeListeners(); |
| 258 } | 248 } |
| 259 return _next; | 249 return _next; |
| 260 } | 250 } |
| 261 | 251 |
| 262 /// Mark this phase as removed. | 252 /// Mark this phase as removed. |
| 263 /// | 253 /// |
| 264 /// This will remove all the phase's outputs and all following phases. | 254 /// This will remove all the phase's outputs and all following phases. |
| 265 void remove() { | 255 void remove() { |
| 266 removeFollowing(); | 256 removeFollowing(); |
| 267 for (var input in _inputs.values.toList()) { | 257 for (var input in _inputs.values.toList()) { |
| 268 input.remove(); | 258 input.remove(); |
| 269 } | 259 } |
| 270 for (var group in _groups.values) { | 260 for (var group in _groups.values) { |
| 271 group.remove(); | 261 group.remove(); |
| 272 } | 262 } |
| 273 _onDirtyPool.close(); | 263 _onAssetController.close(); |
| 274 _onLogPool.close(); | 264 _onLogPool.close(); |
| 275 } | 265 } |
| 276 | 266 |
| 277 /// Remove all phases after this one. | 267 /// Remove all phases after this one. |
| 278 void removeFollowing() { | 268 void removeFollowing() { |
| 279 if (_next == null) return; | 269 if (_next == null) return; |
| 280 _next.remove(); | 270 _next.remove(); |
| 281 _next = null; | 271 _next = null; |
| 282 } | 272 } |
| 283 | 273 |
| 284 /// Processes this phase. | 274 /// Add [asset] as an output of this phase. |
| 285 /// | 275 void _handleOutput(AssetNode asset) { |
| 286 /// Returns a future that completes when processing is done. If there is | 276 if (_inputOrigins.contains(asset.origin)) { |
| 287 /// nothing to process, returns `null`. | 277 _forwarders[asset.id].addIntermediateAsset(asset); |
| 288 Future process() { | 278 } else { |
| 289 if (!isDirty) return null; | 279 _handleOutputWithoutForwarder(asset); |
| 290 | |
| 291 var outputIds = new Set<AssetId>(); | |
| 292 void _handleOutputs(Set<AssetNode> outputs) { | |
| 293 for (var asset in outputs) { | |
| 294 if (_inputOrigins.contains(asset.origin)) { | |
| 295 _forwarders[asset.id].addIntermediateAsset(asset); | |
| 296 continue; | |
| 297 } | |
| 298 | |
| 299 outputIds.add(asset.id); | |
| 300 _addOutput(asset); | |
| 301 } | |
| 302 } | 280 } |
| 303 | |
| 304 var outputFutures = []; | |
| 305 outputFutures.addAll(_inputs.values.map((input) { | |
| 306 if (!input.isDirty) return new Future.value(new Set()); | |
| 307 return input.process().then(_handleOutputs); | |
| 308 })); | |
| 309 outputFutures.addAll(_groups.values.map((group) { | |
| 310 if (!group.isDirty) return new Future.value(new Set()); | |
| 311 return group.process().then(_handleOutputs); | |
| 312 })); | |
| 313 | |
| 314 return Future.wait(outputFutures).then((_) { | |
| 315 // Report collisions in a deterministic order. | |
| 316 outputIds = outputIds.toList(); | |
| 317 outputIds.sort((a, b) => a.compareTo(b)); | |
| 318 for (var id in outputIds) { | |
| 319 // It's possible the output was removed before other transforms in this | |
| 320 // phase finished. | |
| 321 if (!_outputs.containsKey(id)) continue; | |
| 322 var exception = _outputs[id].collisionException; | |
| 323 if (exception != null) cascade.reportError(exception); | |
| 324 } | |
| 325 }); | |
| 326 } | 281 } |
| 327 | 282 |
| 328 /// Add [asset] as an output of this phase. | 283 /// Add [asset] as an output of this phase without checking if it's a |
| 329 void _addOutput(AssetNode asset) { | 284 /// forwarded asset. |
| 285 void _handleOutputWithoutForwarder(AssetNode asset) { | |
| 330 if (_outputs.containsKey(asset.id)) { | 286 if (_outputs.containsKey(asset.id)) { |
| 331 _outputs[asset.id].add(asset); | 287 _outputs[asset.id].add(asset); |
| 332 } else { | 288 } else { |
| 333 _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index"); | 289 _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index"); |
| 334 _outputs[asset.id].onAsset.listen((output) { | 290 _outputs[asset.id].onAsset.listen(_emit, |
| 335 if (_next != null) _next.addInput(output); | 291 onDone: () => _outputs.remove(asset.id)); |
| 336 }, onDone: () => _outputs.remove(asset.id)); | 292 _emit(_outputs[asset.id].output); |
| 337 if (_next != null) _next.addInput(_outputs[asset.id].output); | 293 } |
| 294 | |
| 295 var exception = _outputs[asset.id].collisionException; | |
| 296 if (exception != null) cascade.reportError(exception); | |
| 297 } | |
| 298 | |
| 299 /// Emit [asset] as an output of this phase. | |
| 300 /// | |
| 301 /// This should be called after [_handleOutput], so that collisions are | |
| 302 /// resolved. | |
| 303 void _emit(AssetNode asset) { | |
| 304 if (_next != null) { | |
| 305 _next.addInput(asset); | |
| 306 } else { | |
| 307 _onAssetController.add(asset); | |
| 338 } | 308 } |
| 339 } | 309 } |
| 340 | 310 |
| 341 String toString() => "phase $_location.$_index"; | 311 String toString() => "phase $_location.$_index"; |
| 342 } | 312 } |
| OLD | NEW |