| 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_streams.dart'; |
| 16 import 'phase_forwarder.dart'; | 17 import 'phase_forwarder.dart'; |
| 17 import 'phase_input.dart'; | 18 import 'phase_input.dart'; |
| 18 import 'phase_output.dart'; | 19 import 'phase_output.dart'; |
| 19 import 'stream_pool.dart'; | |
| 20 import 'transformer.dart'; | 20 import 'transformer.dart'; |
| 21 import 'transformer_group.dart'; | 21 import 'transformer_group.dart'; |
| 22 import 'utils.dart'; | 22 import 'utils.dart'; |
| 23 | 23 |
| 24 /// One phase in the ordered series of transformations in an [AssetCascade]. | 24 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 25 /// | 25 /// |
| 26 /// 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 |
| 27 /// outputs to later phases. Phases are processed strictly serially. All | 27 /// outputs to later phases. Phases are processed strictly serially. All |
| 28 /// 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. |
| 29 /// Within a single phase, all transforms will be run in parallel. | 29 /// Within a single phase, all transforms will be run in parallel. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 71 /// that input isn't consumed by any transformers, it will be forwarded | 71 /// that input isn't consumed by any transformers, it will be forwarded |
| 72 /// through the PhaseInput. However, it's possible that it was consumed by a | 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 /// group, and so shouldn't be forwarded through the phase as a whole. |
| 74 /// | 74 /// |
| 75 /// 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 |
| 76 /// a PhaseInput, 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 |
| 77 /// 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 |
| 78 /// so, it's been forwarded unmodified. | 78 /// so, it's been forwarded unmodified. |
| 79 final _inputOrigins = new Multiset<AssetNode>(); | 79 final _inputOrigins = new Multiset<AssetNode>(); |
| 80 | 80 |
| 81 /// A stream that emits an event whenever [this] is no longer dirty. | 81 /// The streams exposed by this phase. |
| 82 /// | 82 final _streams = new NodeStreams(); |
| 83 /// This is synchronous in order to guarantee that it will emit an event as | 83 Stream get onDone => _streams.onDone; |
| 84 /// soon as [isDirty] flips from `true` to `false`. | 84 Stream<AssetNode> get onAsset => _streams.onAsset; |
| 85 Stream get onDone => _onDoneController.stream; | 85 Stream<LogEntry> get onLog => _streams.onLog; |
| 86 final _onDoneController = new StreamController.broadcast(sync: true); | |
| 87 | |
| 88 /// A stream that emits any new assets emitted by [this]. | |
| 89 /// | |
| 90 /// Assets are emitted synchronously to ensure that any changes are thoroughly | |
| 91 /// propagated as soon as they occur. Only a phase with no [next] phase will | |
| 92 /// emit assets. | |
| 93 Stream<AssetNode> get onAsset => _onAssetController.stream; | |
| 94 final _onAssetController = | |
| 95 new StreamController<AssetNode>.broadcast(sync: true); | |
| 96 | 86 |
| 97 /// Whether [this] is dirty and still has more processing to do. | 87 /// Whether [this] is dirty and still has more processing to do. |
| 98 /// | 88 /// |
| 99 /// A phase is considered dirty if any of the previous phases in the same | 89 /// A phase is considered dirty if any of the previous phases in the same |
| 100 /// cascade are dirty, since those phases could emit an asset that this phase | 90 /// cascade are dirty, since those phases could emit an asset that this phase |
| 101 /// will then need to process. | 91 /// will then need to process. |
| 102 bool get isDirty => (previous != null && previous.isDirty) || | 92 bool get isDirty => (previous != null && previous.isDirty) || |
| 103 _inputs.values.any((input) => input.isDirty) || | 93 _inputs.values.any((input) => input.isDirty) || |
| 104 _groups.values.any((group) => group.isDirty); | 94 _groups.values.any((group) => group.isDirty); |
| 105 | 95 |
| 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(); | |
| 110 | |
| 111 /// The previous phase in the cascade, or null if this is the first phase. | 96 /// The previous phase in the cascade, or null if this is the first phase. |
| 112 final Phase previous; | 97 final Phase previous; |
| 113 | 98 |
| 114 /// The subscription to [previous]'s [onDone] stream. | 99 /// The subscription to [previous]'s [onDone] stream. |
| 115 StreamSubscription _previousOnDoneSubscription; | 100 StreamSubscription _previousOnDoneSubscription; |
| 116 | 101 |
| 117 /// The subscription to [previous]'s [onAsset] stream. | 102 /// The subscription to [previous]'s [onAsset] stream. |
| 118 StreamSubscription<AssetNode> _previousOnAssetSubscription; | 103 StreamSubscription<AssetNode> _previousOnAssetSubscription; |
| 119 | 104 |
| 120 /// A map of asset ids to completers for [getInput] requests. | 105 /// A map of asset ids to completers for [getInput] requests. |
| (...skipping 14 matching lines...) Expand all Loading... |
| 135 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 120 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 136 // create an interface that just exposes [getInput]. Emit errors via | 121 // create an interface that just exposes [getInput]. Emit errors via |
| 137 // [AssetNode]s. | 122 // [AssetNode]s. |
| 138 Phase(AssetCascade cascade, String location) | 123 Phase(AssetCascade cascade, String location) |
| 139 : this._(cascade, location, 0); | 124 : this._(cascade, location, 0); |
| 140 | 125 |
| 141 Phase._(this.cascade, this._location, this._index, [this.previous]) { | 126 Phase._(this.cascade, this._location, this._index, [this.previous]) { |
| 142 if (previous != null) { | 127 if (previous != null) { |
| 143 _previousOnAssetSubscription = previous.onAsset.listen(addInput); | 128 _previousOnAssetSubscription = previous.onAsset.listen(addInput); |
| 144 _previousOnDoneSubscription = previous.onDone.listen((_) { | 129 _previousOnDoneSubscription = previous.onDone.listen((_) { |
| 145 if (!isDirty) _onDoneController.add(null); | 130 if (!isDirty) _streams.onDoneController.add(null); |
| 146 }); | 131 }); |
| 147 } | 132 } |
| 148 | 133 |
| 149 onDone.listen((_) { | 134 onDone.listen((_) { |
| 150 // All the previous phases have finished building. If anyone's still | 135 // All the previous phases have finished building. If anyone's still |
| 151 // waiting for outputs, cut off the wait; we won't be generating them, | 136 // waiting for outputs, cut off the wait; we won't be generating them, |
| 152 // at least until a source asset changes. | 137 // at least until a source asset changes. |
| 153 for (var completer in _pendingOutputRequests.values) { | 138 for (var completer in _pendingOutputRequests.values) { |
| 154 completer.complete(null); | 139 completer.complete(null); |
| 155 } | 140 } |
| (...skipping 24 matching lines...) Expand all Loading... |
| 180 _handleOutputWithoutForwarder(forwarder.output); | 165 _handleOutputWithoutForwarder(forwarder.output); |
| 181 } | 166 } |
| 182 | 167 |
| 183 _inputOrigins.add(node.origin); | 168 _inputOrigins.add(node.origin); |
| 184 var input = new PhaseInput(this, node, "$_location.$_index"); | 169 var input = new PhaseInput(this, node, "$_location.$_index"); |
| 185 _inputs[node.id] = input; | 170 _inputs[node.id] = input; |
| 186 input.input.whenRemoved(() { | 171 input.input.whenRemoved(() { |
| 187 _inputOrigins.remove(node.origin); | 172 _inputOrigins.remove(node.origin); |
| 188 _inputs.remove(node.id); | 173 _inputs.remove(node.id); |
| 189 _forwarders.remove(node.id).remove(); | 174 _forwarders.remove(node.id).remove(); |
| 190 if (!isDirty) _onDoneController.add(null); | 175 if (!isDirty) _streams.onDoneController.add(null); |
| 191 }); | 176 }); |
| 192 input.onAsset.listen(_handleOutput); | 177 input.onAsset.listen(_handleOutput); |
| 193 _onLogPool.add(input.onLog); | 178 _streams.onLogPool.add(input.onLog); |
| 194 input.onDone.listen((_) { | 179 input.onDone.listen((_) { |
| 195 if (!isDirty) _onDoneController.add(null); | 180 if (!isDirty) _streams.onDoneController.add(null); |
| 196 }); | 181 }); |
| 197 | 182 |
| 198 input.updateTransformers(_transformers); | 183 input.updateTransformers(_transformers); |
| 199 | 184 |
| 200 for (var group in _groups.values) { | 185 for (var group in _groups.values) { |
| 201 group.addInput(node); | 186 group.addInput(node); |
| 202 } | 187 } |
| 203 } | 188 } |
| 204 | 189 |
| 205 // TODO(nweiz): If the output is available when this is called, it's | 190 // TODO(nweiz): If the output is available when this is called, it's |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 259 .toSet(); | 244 .toSet(); |
| 260 var oldGroups = _groups.keys.toSet(); | 245 var oldGroups = _groups.keys.toSet(); |
| 261 for (var removed in oldGroups.difference(newGroups)) { | 246 for (var removed in oldGroups.difference(newGroups)) { |
| 262 _groups.remove(removed).remove(); | 247 _groups.remove(removed).remove(); |
| 263 } | 248 } |
| 264 | 249 |
| 265 for (var added in newGroups.difference(oldGroups)) { | 250 for (var added in newGroups.difference(oldGroups)) { |
| 266 var runner = new GroupRunner(cascade, added, "$_location.$_index"); | 251 var runner = new GroupRunner(cascade, added, "$_location.$_index"); |
| 267 _groups[added] = runner; | 252 _groups[added] = runner; |
| 268 runner.onAsset.listen(_handleOutput); | 253 runner.onAsset.listen(_handleOutput); |
| 269 _onLogPool.add(runner.onLog); | 254 _streams.onLogPool.add(runner.onLog); |
| 270 runner.onDone.listen((_) { | 255 runner.onDone.listen((_) { |
| 271 if (!isDirty) _onDoneController.add(null); | 256 if (!isDirty) _streams.onDoneController.add(null); |
| 272 }); | 257 }); |
| 273 for (var input in _inputs.values) { | 258 for (var input in _inputs.values) { |
| 274 runner.addInput(input.input); | 259 runner.addInput(input.input); |
| 275 } | 260 } |
| 276 } | 261 } |
| 277 | 262 |
| 278 for (var forwarder in _forwarders.values) { | 263 for (var forwarder in _forwarders.values) { |
| 279 forwarder.updateTransformers(_transformers.length, _groups.length); | 264 forwarder.updateTransformers(_transformers.length, _groups.length); |
| 280 } | 265 } |
| 281 } | 266 } |
| (...skipping 27 matching lines...) Expand all Loading... |
| 309 /// Mark this phase as removed. | 294 /// Mark this phase as removed. |
| 310 /// | 295 /// |
| 311 /// This will remove all the phase's outputs. | 296 /// This will remove all the phase's outputs. |
| 312 void remove() { | 297 void remove() { |
| 313 for (var input in _inputs.values.toList()) { | 298 for (var input in _inputs.values.toList()) { |
| 314 input.remove(); | 299 input.remove(); |
| 315 } | 300 } |
| 316 for (var group in _groups.values) { | 301 for (var group in _groups.values) { |
| 317 group.remove(); | 302 group.remove(); |
| 318 } | 303 } |
| 319 _onAssetController.close(); | 304 _streams.close(); |
| 320 _onLogPool.close(); | |
| 321 if (_previousOnDoneSubscription != null) { | 305 if (_previousOnDoneSubscription != null) { |
| 322 _previousOnDoneSubscription.cancel(); | 306 _previousOnDoneSubscription.cancel(); |
| 323 } | 307 } |
| 324 if (_previousOnAssetSubscription != null) { | 308 if (_previousOnAssetSubscription != null) { |
| 325 _previousOnAssetSubscription.cancel(); | 309 _previousOnAssetSubscription.cancel(); |
| 326 } | 310 } |
| 327 } | 311 } |
| 328 | 312 |
| 329 /// Add [asset] as an output of this phase. | 313 /// Add [asset] as an output of this phase. |
| 330 void _handleOutput(AssetNode asset) { | 314 void _handleOutput(AssetNode asset) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 349 | 333 |
| 350 var exception = _outputs[asset.id].collisionException; | 334 var exception = _outputs[asset.id].collisionException; |
| 351 if (exception != null) cascade.reportError(exception); | 335 if (exception != null) cascade.reportError(exception); |
| 352 } | 336 } |
| 353 | 337 |
| 354 /// Emit [asset] as an output of this phase. | 338 /// Emit [asset] as an output of this phase. |
| 355 /// | 339 /// |
| 356 /// This should be called after [_handleOutput], so that collisions are | 340 /// This should be called after [_handleOutput], so that collisions are |
| 357 /// resolved. | 341 /// resolved. |
| 358 void _emit(AssetNode asset) { | 342 void _emit(AssetNode asset) { |
| 359 _onAssetController.add(asset); | 343 _streams.onAssetController.add(asset); |
| 360 _providePendingAsset(asset); | 344 _providePendingAsset(asset); |
| 361 } | 345 } |
| 362 | 346 |
| 363 /// Provide an asset to a pending [getOutput] call. | 347 /// Provide an asset to a pending [getOutput] call. |
| 364 void _providePendingAsset(AssetNode asset) { | 348 void _providePendingAsset(AssetNode asset) { |
| 365 // If anyone's waiting for this asset, provide it to them. | 349 // If anyone's waiting for this asset, provide it to them. |
| 366 var request = _pendingOutputRequests.remove(asset.id); | 350 var request = _pendingOutputRequests.remove(asset.id); |
| 367 if (request == null) return; | 351 if (request == null) return; |
| 368 | 352 |
| 369 if (asset.state.isAvailable) { | 353 if (asset.state.isAvailable) { |
| 370 request.complete(asset); | 354 request.complete(asset); |
| 371 return; | 355 return; |
| 372 } | 356 } |
| 373 | 357 |
| 374 // A lazy asset may be emitted while still dirty. If so, we wait until it's | 358 // A lazy asset may be emitted while still dirty. If so, we wait until it's |
| 375 // either available or removed before trying again to access it. | 359 // either available or removed before trying again to access it. |
| 376 assert(asset.state.isDirty); | 360 assert(asset.state.isDirty); |
| 377 asset.force(); | 361 asset.force(); |
| 378 asset.whenStateChanges().then((state) { | 362 asset.whenStateChanges().then((state) { |
| 379 if (state.isRemoved) return getOutput(asset.id); | 363 if (state.isRemoved) return getOutput(asset.id); |
| 380 return asset; | 364 return asset; |
| 381 }).then(request.complete).catchError(request.completeError); | 365 }).then(request.complete).catchError(request.completeError); |
| 382 } | 366 } |
| 383 | 367 |
| 384 String toString() => "phase $_location.$_index"; | 368 String toString() => "phase $_location.$_index"; |
| 385 } | 369 } |
| OLD | NEW |