| 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 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 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 = new StreamController<AssetNode>(sync: true); |
| 95 | 95 |
| 96 /// Whether [this] is dirty and still has more processing to do. | 96 /// Whether [this] is dirty and still has more processing to do. |
| 97 bool get isDirty => _inputs.values.any((input) => input.isDirty) || | 97 /// |
| 98 /// 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 /// will then need to process. |
| 101 bool get isDirty => (_previous != null && _previous.isDirty) || |
| 102 _inputs.values.any((input) => input.isDirty) || |
| 98 _groups.values.any((group) => group.isDirty); | 103 _groups.values.any((group) => group.isDirty); |
| 99 | 104 |
| 100 /// Whether [this] or any previous phase is dirty. | |
| 101 bool get _isTransitivelyDirty => isDirty || | |
| 102 (_previous != null && _previous._isTransitivelyDirty); | |
| 103 | |
| 104 /// A stream that emits an event whenever any transforms in this phase logs | 105 /// A stream that emits an event whenever any transforms in this phase logs |
| 105 /// an entry. | 106 /// an entry. |
| 106 Stream<LogEntry> get onLog => _onLogPool.stream; | 107 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 107 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 108 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 108 | 109 |
| 109 /// The previous phase in the cascade, or null if this is the first phase. | 110 /// The previous phase in the cascade, or null if this is the first phase. |
| 110 final Phase _previous; | 111 final Phase _previous; |
| 111 | 112 |
| 113 /// The subscription to [_previous]'s [onDone] stream. |
| 114 StreamSubscription _previousOnDoneSubscription; |
| 115 |
| 112 /// The phase after this one. | 116 /// The phase after this one. |
| 113 /// | 117 /// |
| 114 /// Outputs from this phase will be passed to it. | 118 /// Outputs from this phase will be passed to it. |
| 115 Phase get next => _next; | 119 Phase get next => _next; |
| 116 Phase _next; | 120 Phase _next; |
| 117 | 121 |
| 118 /// A map of asset ids to completers for [getInput] requests. | 122 /// A map of asset ids to completers for [getInput] requests. |
| 119 /// | 123 /// |
| 120 /// If an asset node is requested before it's available, we put a completer in | 124 /// If an asset node is requested before it's available, we put a completer in |
| 121 /// this map to wait for the asset to be generated. If it's not generated, the | 125 /// this map to wait for the asset to be generated. If it's not generated, the |
| 122 /// completer should complete to `null`. | 126 /// completer should complete to `null`. |
| 123 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); | 127 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); |
| 124 | 128 |
| 125 /// Returns all currently-available output assets for this phase. | 129 /// Returns all currently-available output assets for this phase. |
| 126 Set<AssetNode> get availableOutputs { | 130 Set<AssetNode> get availableOutputs { |
| 127 return _outputs.values | 131 return _outputs.values |
| 128 .map((output) => output.output) | 132 .map((output) => output.output) |
| 129 .where((node) => node.state.isAvailable) | 133 .where((node) => node.state.isAvailable) |
| 130 .toSet(); | 134 .toSet(); |
| 131 } | 135 } |
| 132 | 136 |
| 133 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 137 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 134 // create an interface that just exposes [getInput]. Emit errors via | 138 // create an interface that just exposes [getInput]. Emit errors via |
| 135 // [AssetNode]s. | 139 // [AssetNode]s. |
| 136 Phase(AssetCascade cascade, String location) | 140 Phase(AssetCascade cascade, String location) |
| 137 : this._(cascade, location, 0); | 141 : this._(cascade, location, 0); |
| 138 | 142 |
| 139 Phase._(this.cascade, this._location, this._index, [this._previous]) { | 143 Phase._(this.cascade, this._location, this._index, [this._previous]) { |
| 140 // TODO(nweiz): This does O(n^2) work whenever a phase emits an [onDone] | 144 if (_previous != null) { |
| 141 // event, since each phase after it has to check each phase before. Find a | 145 _previousOnDoneSubscription = _previous.onDone.listen((_) { |
| 142 // better way to do this. | 146 if (!isDirty) _onDoneController.add(null); |
| 143 for (var phase = this; phase != null; phase = phase._previous) { | |
| 144 phase.onDone.listen((_) { | |
| 145 if (_isTransitivelyDirty) return; | |
| 146 | |
| 147 // All the previous phases have finished building. If anyone's still | |
| 148 // waiting for outputs, cut off the wait; we won't be generating them, | |
| 149 // at least until a source asset changes. | |
| 150 for (var completer in _pendingOutputRequests.values) { | |
| 151 completer.complete(null); | |
| 152 } | |
| 153 _pendingOutputRequests.clear(); | |
| 154 }); | 147 }); |
| 155 } | 148 } |
| 149 |
| 150 onDone.listen((_) { |
| 151 // 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, |
| 153 // at least until a source asset changes. |
| 154 for (var completer in _pendingOutputRequests.values) { |
| 155 completer.complete(null); |
| 156 } |
| 157 _pendingOutputRequests.clear(); |
| 158 }); |
| 156 } | 159 } |
| 157 | 160 |
| 158 /// Adds a new asset as an input for this phase. | 161 /// Adds a new asset as an input for this phase. |
| 159 /// | 162 /// |
| 160 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 163 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 161 /// will automatically begin determining which transforms can consume it as a | 164 /// will automatically begin determining which transforms can consume it as a |
| 162 /// primary input. The transforms themselves won't be applied until [process] | 165 /// primary input. The transforms themselves won't be applied until [process] |
| 163 /// is called, however. | 166 /// is called, however. |
| 164 /// | 167 /// |
| 165 /// This should only be used for brand-new assets or assets that have been | 168 /// This should only be used for brand-new assets or assets that have been |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 243 // try again, since it could be generated again. | 246 // try again, since it could be generated again. |
| 244 output.force(); | 247 output.force(); |
| 245 return output.whenAvailable((_) => output).catchError((error) { | 248 return output.whenAvailable((_) => output).catchError((error) { |
| 246 if (error is! AssetNotFoundException) throw error; | 249 if (error is! AssetNotFoundException) throw error; |
| 247 return getOutput(id); | 250 return getOutput(id); |
| 248 }); | 251 }); |
| 249 } | 252 } |
| 250 | 253 |
| 251 // If neither this phase nor the previous phases are dirty, the requested | 254 // If neither this phase nor the previous phases are dirty, the requested |
| 252 // output won't be generated and we can safely return null. | 255 // output won't be generated and we can safely return null. |
| 253 if (!_isTransitivelyDirty) return null; | 256 if (!isDirty) return null; |
| 254 | 257 |
| 255 // Otherwise, store a completer for the asset node. If it's generated in | 258 // Otherwise, store a completer for the asset node. If it's generated in |
| 256 // the future, we'll complete this completer. | 259 // the future, we'll complete this completer. |
| 257 var completer = _pendingOutputRequests.putIfAbsent(id, | 260 var completer = _pendingOutputRequests.putIfAbsent(id, |
| 258 () => new Completer.sync()); | 261 () => new Completer.sync()); |
| 259 return completer.future; | 262 return completer.future; |
| 260 }); | 263 }); |
| 261 } | 264 } |
| 262 | 265 |
| 263 /// Set this phase's transformers to [transformers]. | 266 /// Set this phase's transformers to [transformers]. |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 328 if (_previous != null) _previous._next = null; | 331 if (_previous != null) _previous._next = null; |
| 329 removeFollowing(); | 332 removeFollowing(); |
| 330 for (var input in _inputs.values.toList()) { | 333 for (var input in _inputs.values.toList()) { |
| 331 input.remove(); | 334 input.remove(); |
| 332 } | 335 } |
| 333 for (var group in _groups.values) { | 336 for (var group in _groups.values) { |
| 334 group.remove(); | 337 group.remove(); |
| 335 } | 338 } |
| 336 _onAssetController.close(); | 339 _onAssetController.close(); |
| 337 _onLogPool.close(); | 340 _onLogPool.close(); |
| 341 _previousOnDoneSubscription.cancel(); |
| 338 } | 342 } |
| 339 | 343 |
| 340 /// Remove all phases after this one. | 344 /// Remove all phases after this one. |
| 341 void removeFollowing() { | 345 void removeFollowing() { |
| 342 if (_next == null) return; | 346 if (_next == null) return; |
| 343 _next.remove(); | 347 _next.remove(); |
| 344 _next = null; | 348 _next = null; |
| 345 } | 349 } |
| 346 | 350 |
| 347 /// Add [asset] as an output of this phase. | 351 /// Add [asset] as an output of this phase. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 398 assert(asset.state.isDirty); | 402 assert(asset.state.isDirty); |
| 399 asset.force(); | 403 asset.force(); |
| 400 asset.whenStateChanges().then((state) { | 404 asset.whenStateChanges().then((state) { |
| 401 if (state.isRemoved) return getOutput(asset.id); | 405 if (state.isRemoved) return getOutput(asset.id); |
| 402 return asset; | 406 return asset; |
| 403 }).then(request.complete).catchError(request.completeError); | 407 }).then(request.complete).catchError(request.completeError); |
| 404 } | 408 } |
| 405 | 409 |
| 406 String toString() => "phase $_location.$_index"; | 410 String toString() => "phase $_location.$_index"; |
| 407 } | 411 } |
| OLD | NEW |