| 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 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 84 /// soon as [isDirty] flips from `true` to `false`. | 84 /// soon as [isDirty] flips from `true` to `false`. |
| 85 Stream get onDone => _onDoneController.stream; | 85 Stream get onDone => _onDoneController.stream; |
| 86 final _onDoneController = new StreamController.broadcast(sync: true); | 86 final _onDoneController = new StreamController.broadcast(sync: true); |
| 87 | 87 |
| 88 /// A stream that emits any new assets emitted by [this]. | 88 /// A stream that emits any new assets emitted by [this]. |
| 89 /// | 89 /// |
| 90 /// Assets are emitted synchronously to ensure that any changes are thoroughly | 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 | 91 /// propagated as soon as they occur. Only a phase with no [next] phase will |
| 92 /// emit assets. | 92 /// emit assets. |
| 93 Stream<AssetNode> get onAsset => _onAssetController.stream; | 93 Stream<AssetNode> get onAsset => _onAssetController.stream; |
| 94 final _onAssetController = new StreamController<AssetNode>(sync: true); | 94 final _onAssetController = |
| 95 new StreamController<AssetNode>.broadcast(sync: true); |
| 95 | 96 |
| 96 /// Whether [this] is dirty and still has more processing to do. | 97 /// Whether [this] is dirty and still has more processing to do. |
| 97 /// | 98 /// |
| 98 /// A phase is considered dirty if any of the previous phases in the same | 99 /// A phase is considered dirty if any of the previous phases in the same |
| 99 /// cascade are dirty, since those phases could emit an asset that this phase | 100 /// cascade are dirty, since those phases could emit an asset that this phase |
| 100 /// will then need to process. | 101 /// will then need to process. |
| 101 bool get isDirty => (_previous != null && _previous.isDirty) || | 102 bool get isDirty => (_previous != null && _previous.isDirty) || |
| 102 _inputs.values.any((input) => input.isDirty) || | 103 _inputs.values.any((input) => input.isDirty) || |
| 103 _groups.values.any((group) => group.isDirty); | 104 _groups.values.any((group) => group.isDirty); |
| 104 | 105 |
| 105 /// A stream that emits an event whenever any transforms in this phase logs | 106 /// A stream that emits an event whenever any transforms in this phase logs |
| 106 /// an entry. | 107 /// an entry. |
| 107 Stream<LogEntry> get onLog => _onLogPool.stream; | 108 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 108 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 109 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 109 | 110 |
| 110 /// 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. |
| 111 final Phase _previous; | 112 final Phase _previous; |
| 112 | 113 |
| 113 /// The subscription to [_previous]'s [onDone] stream. | 114 /// The subscription to [_previous]'s [onDone] stream. |
| 114 StreamSubscription _previousOnDoneSubscription; | 115 StreamSubscription _previousOnDoneSubscription; |
| 115 | 116 |
| 116 /// The phase after this one. | 117 /// The subscription to [_previous]'s [onAsset] stream. |
| 117 /// | 118 StreamSubscription<AssetNode> _previousOnAssetSubscription; |
| 118 /// Outputs from this phase will be passed to it. | |
| 119 Phase get next => _next; | |
| 120 Phase _next; | |
| 121 | 119 |
| 122 /// A map of asset ids to completers for [getInput] requests. | 120 /// A map of asset ids to completers for [getInput] requests. |
| 123 /// | 121 /// |
| 124 /// 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 |
| 125 /// 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 |
| 126 /// completer should complete to `null`. | 124 /// completer should complete to `null`. |
| 127 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); | 125 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); |
| 128 | 126 |
| 129 /// Returns all currently-available output assets for this phase. | 127 /// Returns all currently-available output assets for this phase. |
| 130 Set<AssetNode> get availableOutputs { | 128 Set<AssetNode> get availableOutputs { |
| 131 return _outputs.values | 129 return _outputs.values |
| 132 .map((output) => output.output) | 130 .map((output) => output.output) |
| 133 .where((node) => node.state.isAvailable) | 131 .where((node) => node.state.isAvailable) |
| 134 .toSet(); | 132 .toSet(); |
| 135 } | 133 } |
| 136 | 134 |
| 137 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 135 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 138 // create an interface that just exposes [getInput]. Emit errors via | 136 // create an interface that just exposes [getInput]. Emit errors via |
| 139 // [AssetNode]s. | 137 // [AssetNode]s. |
| 140 Phase(AssetCascade cascade, String location) | 138 Phase(AssetCascade cascade, String location) |
| 141 : this._(cascade, location, 0); | 139 : this._(cascade, location, 0); |
| 142 | 140 |
| 143 Phase._(this.cascade, this._location, this._index, [this._previous]) { | 141 Phase._(this.cascade, this._location, this._index, [this._previous]) { |
| 144 if (_previous != null) { | 142 if (_previous != null) { |
| 143 _previousOnAssetSubscription = _previous.onAsset.listen(addInput); |
| 145 _previousOnDoneSubscription = _previous.onDone.listen((_) { | 144 _previousOnDoneSubscription = _previous.onDone.listen((_) { |
| 146 if (!isDirty) _onDoneController.add(null); | 145 if (!isDirty) _onDoneController.add(null); |
| 147 }); | 146 }); |
| 148 } | 147 } |
| 149 | 148 |
| 150 onDone.listen((_) { | 149 onDone.listen((_) { |
| 151 // All the previous phases have finished building. If anyone's still | 150 // All the previous phases have finished building. If anyone's still |
| 152 // waiting for outputs, cut off the wait; we won't be generating them, | 151 // waiting for outputs, cut off the wait; we won't be generating them, |
| 153 // at least until a source asset changes. | 152 // at least until a source asset changes. |
| 154 for (var completer in _pendingOutputRequests.values) { | 153 for (var completer in _pendingOutputRequests.values) { |
| (...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 314 | 313 |
| 315 for (var input in _inputs.values) { | 314 for (var input in _inputs.values) { |
| 316 input.forceAllTransforms(); | 315 input.forceAllTransforms(); |
| 317 } | 316 } |
| 318 } | 317 } |
| 319 | 318 |
| 320 /// Add a new phase after this one. | 319 /// Add a new phase after this one. |
| 321 /// | 320 /// |
| 322 /// This may only be called on a phase with no phase following it. | 321 /// This may only be called on a phase with no phase following it. |
| 323 Phase addPhase() { | 322 Phase addPhase() { |
| 324 assert(_next == null); | 323 var next = new Phase._(cascade, _location, _index + 1, this); |
| 325 _next = new Phase._(cascade, _location, _index + 1, this); | |
| 326 for (var output in _outputs.values.toList()) { | 324 for (var output in _outputs.values.toList()) { |
| 327 // Remove [output]'s listeners because now they should get the asset from | 325 // Remove [output]'s listeners because now they should get the asset from |
| 328 // [_next], rather than this phase. Any transforms consuming [output] will | 326 // [next], rather than this phase. Any transforms consuming [output] will |
| 329 // be re-run and will consume the output from the new final phase. | 327 // be re-run and will consume the output from the new final phase. |
| 330 output.removeListeners(); | 328 output.removeListeners(); |
| 331 } | 329 } |
| 332 return _next; | 330 return next; |
| 333 } | 331 } |
| 334 | 332 |
| 335 /// Mark this phase as removed. | 333 /// Mark this phase as removed. |
| 336 /// | 334 /// |
| 337 /// This will remove all the phase's outputs and all following phases. | 335 /// This will remove all the phase's outputs. |
| 338 void remove() { | 336 void remove() { |
| 339 if (_previous != null) _previous._next = null; | |
| 340 removeFollowing(); | |
| 341 for (var input in _inputs.values.toList()) { | 337 for (var input in _inputs.values.toList()) { |
| 342 input.remove(); | 338 input.remove(); |
| 343 } | 339 } |
| 344 for (var group in _groups.values) { | 340 for (var group in _groups.values) { |
| 345 group.remove(); | 341 group.remove(); |
| 346 } | 342 } |
| 347 _onAssetController.close(); | 343 _onAssetController.close(); |
| 348 _onLogPool.close(); | 344 _onLogPool.close(); |
| 349 if (_previousOnDoneSubscription != null) { | 345 if (_previousOnDoneSubscription != null) { |
| 350 _previousOnDoneSubscription.cancel(); | 346 _previousOnDoneSubscription.cancel(); |
| 351 } | 347 } |
| 352 } | 348 if (_previousOnAssetSubscription != null) { |
| 353 | 349 _previousOnAssetSubscription.cancel(); |
| 354 /// Remove all phases after this one. | 350 } |
| 355 void removeFollowing() { | |
| 356 if (_next == null) return; | |
| 357 _next.remove(); | |
| 358 _next = null; | |
| 359 } | 351 } |
| 360 | 352 |
| 361 /// Add [asset] as an output of this phase. | 353 /// Add [asset] as an output of this phase. |
| 362 void _handleOutput(AssetNode asset) { | 354 void _handleOutput(AssetNode asset) { |
| 363 if (_inputOrigins.contains(asset.origin)) { | 355 if (_inputOrigins.contains(asset.origin)) { |
| 364 _forwarders[asset.id].addIntermediateAsset(asset); | 356 _forwarders[asset.id].addIntermediateAsset(asset); |
| 365 } else { | 357 } else { |
| 366 _handleOutputWithoutForwarder(asset); | 358 _handleOutputWithoutForwarder(asset); |
| 367 } | 359 } |
| 368 } | 360 } |
| (...skipping 12 matching lines...) Expand all Loading... |
| 381 | 373 |
| 382 var exception = _outputs[asset.id].collisionException; | 374 var exception = _outputs[asset.id].collisionException; |
| 383 if (exception != null) cascade.reportError(exception); | 375 if (exception != null) cascade.reportError(exception); |
| 384 } | 376 } |
| 385 | 377 |
| 386 /// Emit [asset] as an output of this phase. | 378 /// Emit [asset] as an output of this phase. |
| 387 /// | 379 /// |
| 388 /// This should be called after [_handleOutput], so that collisions are | 380 /// This should be called after [_handleOutput], so that collisions are |
| 389 /// resolved. | 381 /// resolved. |
| 390 void _emit(AssetNode asset) { | 382 void _emit(AssetNode asset) { |
| 391 if (_next != null) { | 383 _onAssetController.add(asset); |
| 392 _next.addInput(asset); | |
| 393 } else { | |
| 394 _onAssetController.add(asset); | |
| 395 } | |
| 396 _providePendingAsset(asset); | 384 _providePendingAsset(asset); |
| 397 } | 385 } |
| 398 | 386 |
| 399 /// Provide an asset to a pending [getOutput] call. | 387 /// Provide an asset to a pending [getOutput] call. |
| 400 void _providePendingAsset(AssetNode asset) { | 388 void _providePendingAsset(AssetNode asset) { |
| 401 // If anyone's waiting for this asset, provide it to them. | 389 // If anyone's waiting for this asset, provide it to them. |
| 402 var request = _pendingOutputRequests.remove(asset.id); | 390 var request = _pendingOutputRequests.remove(asset.id); |
| 403 if (request == null) return; | 391 if (request == null) return; |
| 404 | 392 |
| 405 if (asset.state.isAvailable) { | 393 if (asset.state.isAvailable) { |
| 406 request.complete(asset); | 394 request.complete(asset); |
| 407 return; | 395 return; |
| 408 } | 396 } |
| 409 | 397 |
| 410 // A lazy asset may be emitted while still dirty. If so, we wait until it's | 398 // A lazy asset may be emitted while still dirty. If so, we wait until it's |
| 411 // either available or removed before trying again to access it. | 399 // either available or removed before trying again to access it. |
| 412 assert(asset.state.isDirty); | 400 assert(asset.state.isDirty); |
| 413 asset.force(); | 401 asset.force(); |
| 414 asset.whenStateChanges().then((state) { | 402 asset.whenStateChanges().then((state) { |
| 415 if (state.isRemoved) return getOutput(asset.id); | 403 if (state.isRemoved) return getOutput(asset.id); |
| 416 return asset; | 404 return asset; |
| 417 }).then(request.complete).catchError(request.completeError); | 405 }).then(request.complete).catchError(request.completeError); |
| 418 } | 406 } |
| 419 | 407 |
| 420 String toString() => "phase $_location.$_index"; | 408 String toString() => "phase $_location.$_index"; |
| 421 } | 409 } |
| OLD | NEW |