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.transform_node; | 5 library barback.transform_node; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'asset.dart'; | 9 import 'asset.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'asset_set.dart'; | |
| 13 import 'declaring_transform.dart'; | 12 import 'declaring_transform.dart'; |
| 14 import 'errors.dart'; | 13 import 'errors.dart'; |
| 15 import 'lazy_transformer.dart'; | 14 import 'lazy_transformer.dart'; |
| 16 import 'log.dart'; | 15 import 'log.dart'; |
| 17 import 'phase.dart'; | 16 import 'phase.dart'; |
| 18 import 'stream_pool.dart'; | 17 import 'stream_pool.dart'; |
| 19 import 'transform.dart'; | 18 import 'transform.dart'; |
| 20 import 'transformer.dart'; | 19 import 'transformer.dart'; |
| 21 import 'utils.dart'; | 20 import 'utils.dart'; |
| 22 | 21 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 34 | 33 |
| 35 /// The node for the primary asset this transform depends on. | 34 /// The node for the primary asset this transform depends on. |
| 36 final AssetNode primary; | 35 final AssetNode primary; |
| 37 | 36 |
| 38 /// A string describing the location of [this] in the transformer graph. | 37 /// A string describing the location of [this] in the transformer graph. |
| 39 final String _location; | 38 final String _location; |
| 40 | 39 |
| 41 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. | 40 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. |
| 42 StreamSubscription _primarySubscription; | 41 StreamSubscription _primarySubscription; |
| 43 | 42 |
| 44 // TODO(nweiz): Remove this and move isPrimary computation into TransformNode. | |
| 45 /// Whether the parent [PhaseInput] is currently computing whether its input | |
| 46 /// is primary for [this]. | |
| 47 bool _pendingIsPrimary = false; | |
| 48 | |
| 49 /// Whether [this] is dirty and still has more processing to do. | 43 /// Whether [this] is dirty and still has more processing to do. |
| 50 bool get isDirty => _pendingIsPrimary || _isApplying; | 44 bool get isDirty => !_state.isDone; |
| 51 | |
| 52 /// Whether any input has become dirty since [_apply] last started running. | |
| 53 var _hasBecomeDirty = false; | |
| 54 | |
| 55 /// Whether [_apply] is currently running. | |
| 56 var _isApplying = false; | |
| 57 | |
| 58 /// Whether the most recent run of this transform has declared that it | |
| 59 /// consumes the primary input. | |
| 60 /// | |
| 61 /// Defaults to `false`. | |
| 62 bool get consumePrimary => _consumePrimary; | |
| 63 bool _consumePrimary = false; | |
| 64 | 45 |
| 65 /// Whether [transformer] is lazy and this transform has yet to be forced. | 46 /// Whether [transformer] is lazy and this transform has yet to be forced. |
| 66 bool _isLazy; | 47 bool _isLazy; |
| 67 | 48 |
| 68 /// The subscriptions to each input's [AssetNode.onStateChange] stream. | 49 /// The subscriptions to each input's [AssetNode.onStateChange] stream. |
| 69 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); | 50 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); |
| 70 | 51 |
| 71 /// The controllers for the asset nodes emitted by this node. | 52 /// The controllers for the asset nodes emitted by this node. |
| 72 var _outputControllers = new Map<AssetId, AssetNodeController>(); | 53 var _outputControllers = new Map<AssetId, AssetNodeController>(); |
| 73 | 54 |
| 74 // TODO(nweiz): It's weird that this is different than the [onDone] stream the | 55 /// The controller that's used to pass [primary] through [this] if it's not |
| 75 // other nodes emit. See if we can make that more consistent. | 56 /// consumed or overwritten. |
| 76 /// A stream that emits an event whenever [onDirty] changes its value. | 57 /// |
| 58 /// This needs an intervening controller to ensure that the output can be | |
| 59 /// marked dirty when determining whether [this] will consume or overwrite it, | |
| 60 /// and be marked removed if it does. [_passThroughController] will be null | |
| 61 /// null if the asset is not being passed through. | |
|
Bob Nystrom
2014/03/12 17:42:58
"null null" -> "null"
nweiz
2014/03/12 21:44:47
Done.
| |
| 62 AssetNodeController _passThroughController; | |
| 63 | |
| 64 /// A stream that emits an event whenever [this] is no longer dirty. | |
| 77 /// | 65 /// |
| 78 /// This is synchronous in order to guarantee that it will emit an event as | 66 /// This is synchronous in order to guarantee that it will emit an event as |
| 79 /// soon as [isDirty] changes. It's possible for this to emit multiple events | 67 /// soon as [isDirty] flips from `true` to `false`. |
| 80 /// while [isDirty] is `true`. However, it will only emit a single event each | 68 Stream get onDone => _onDoneController.stream; |
| 81 /// time [isDirty] becomes `false`. | 69 final _onDoneController = new StreamController.broadcast(sync: true); |
| 82 Stream get onStateChange => _onStateChangeController.stream; | |
| 83 final _onStateChangeController = new StreamController.broadcast(sync: true); | |
| 84 | 70 |
| 85 /// A stream that emits any new assets emitted by [this]. | 71 /// A stream that emits any new assets emitted by [this]. |
| 86 /// | 72 /// |
| 87 /// Assets are emitted synchronously to ensure that any changes are thoroughly | 73 /// Assets are emitted synchronously to ensure that any changes are thoroughly |
| 88 /// propagated as soon as they occur. | 74 /// propagated as soon as they occur. |
| 89 Stream<AssetNode> get onAsset => _onAssetController.stream; | 75 Stream<AssetNode> get onAsset => _onAssetController.stream; |
| 90 final _onAssetController = new StreamController<AssetNode>(sync: true); | 76 final _onAssetController = new StreamController<AssetNode>(sync: true); |
| 91 | 77 |
| 92 /// A stream that emits an event whenever this transform logs an entry. | 78 /// A stream that emits an event whenever this transform logs an entry. |
| 93 /// | 79 /// |
| 94 /// This is synchronous because error logs can cause the transform to fail, so | 80 /// This is synchronous because error logs can cause the transform to fail, so |
| 95 /// we need to ensure that their processing isn't delayed until after the | 81 /// we need to ensure that their processing isn't delayed until after the |
| 96 /// transform or build has finished. | 82 /// transform or build has finished. |
| 97 Stream<LogEntry> get onLog => _onLogPool.stream; | 83 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 98 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 84 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 99 | 85 |
| 86 /// The current state of [this]. | |
| 87 var _state = _TransformNodeState.PROCESSING; | |
| 88 | |
| 89 /// Whether [this] has been marked as removed. | |
| 90 bool get _isRemoved => _onAssetController.isClosed; | |
| 91 | |
| 92 /// Whether the most recent run of this transform has declared that it | |
| 93 /// consumes the primary input. | |
| 94 /// | |
| 95 /// Defaults to `false`. This is not meaningful unless [_state] is | |
| 96 /// [_TransformNodeState.APPLIED]. | |
| 97 bool _consumePrimary = false; | |
| 98 | |
| 100 TransformNode(this.phase, Transformer transformer, this.primary, | 99 TransformNode(this.phase, Transformer transformer, this.primary, |
| 101 this._location) | 100 this._location) |
| 102 : transformer = transformer, | 101 : transformer = transformer, |
| 103 _isLazy = transformer is LazyTransformer { | 102 _isLazy = transformer is LazyTransformer { |
| 104 _primarySubscription = primary.onStateChange.listen((state) { | 103 _primarySubscription = primary.onStateChange.listen((state) { |
| 105 if (state.isRemoved) { | 104 if (state.isRemoved) { |
| 106 remove(); | 105 remove(); |
| 107 } else { | 106 } else { |
| 108 if (state.isDirty) _pendingIsPrimary = true; | 107 _dirty(primaryChanged: true); |
| 109 _dirty(); | |
| 110 } | 108 } |
| 111 }); | 109 }); |
| 112 | 110 |
| 113 _apply(); | 111 _process(); |
| 114 } | 112 } |
| 115 | 113 |
| 116 /// The [TransformInfo] describing this node. | 114 /// The [TransformInfo] describing this node. |
| 117 /// | 115 /// |
| 118 /// [TransformInfo] is the publicly-visible representation of a transform | 116 /// [TransformInfo] is the publicly-visible representation of a transform |
| 119 /// node. | 117 /// node. |
| 120 TransformInfo get info => new TransformInfo(transformer, primary.id); | 118 TransformInfo get info => new TransformInfo(transformer, primary.id); |
| 121 | 119 |
| 122 /// Marks this transform as removed. | 120 /// Marks this transform as removed. |
| 123 /// | 121 /// |
| 124 /// This causes all of the transform's outputs to be marked as removed as | 122 /// This causes all of the transform's outputs to be marked as removed as |
| 125 /// well. Normally this will be automatically done internally based on events | 123 /// well. Normally this will be automatically done internally based on events |
| 126 /// from the primary input, but it's possible for a transform to no longer be | 124 /// from the primary input, but it's possible for a transform to no longer be |
| 127 /// valid even if its primary input still exists. | 125 /// valid even if its primary input still exists. |
| 128 void remove() { | 126 void remove() { |
| 129 _hasBecomeDirty = false; | |
| 130 _onAssetController.close(); | 127 _onAssetController.close(); |
| 131 _onStateChangeController.close(); | 128 _onDoneController.close(); |
| 132 _primarySubscription.cancel(); | 129 _primarySubscription.cancel(); |
| 133 for (var subscription in _inputSubscriptions.values) { | 130 _clearInputSubscriptions(); |
| 134 subscription.cancel(); | |
| 135 } | |
| 136 for (var controller in _outputControllers.values) { | 131 for (var controller in _outputControllers.values) { |
| 137 controller.setRemoved(); | 132 controller.setRemoved(); |
| 138 } | 133 } |
| 134 if (_passThroughController != null) { | |
| 135 _passThroughController.setRemoved(); | |
| 136 _passThroughController = null; | |
| 137 } | |
| 139 } | 138 } |
| 140 | 139 |
| 141 /// If [transformer] is lazy, ensures that its concrete outputs will be | 140 /// If [transformer] is lazy, ensures that its concrete outputs will be |
| 142 /// generated. | 141 /// generated. |
| 143 void force() { | 142 void force() { |
| 144 // TODO(nweiz): we might want to have a timeout after which, if the | 143 // TODO(nweiz): we might want to have a timeout after which, if the |
| 145 // transform's outputs have gone unused, we switch it back to lazy mode. | 144 // transform's outputs have gone unused, we switch it back to lazy mode. |
| 146 if (!_isLazy) return; | 145 if (!_isLazy) return; |
| 147 _isLazy = false; | 146 _isLazy = false; |
| 148 _dirty(); | 147 _dirty(primaryChanged: false); |
| 149 } | |
| 150 | |
| 151 // TODO(nweiz): remove this and move isPrimary computation into TransformNode. | |
| 152 /// Mark that the parent [PhaseInput] has determined that its input is indeed | |
| 153 /// primary for [this]. | |
| 154 void markPrimary() { | |
| 155 if (!_pendingIsPrimary) return; | |
| 156 _pendingIsPrimary = false; | |
| 157 if (!_isApplying) _apply(); | |
| 158 } | 148 } |
| 159 | 149 |
| 160 /// Marks this transform as dirty. | 150 /// Marks this transform as dirty. |
| 161 /// | 151 /// |
| 162 /// This causes all of the transform's outputs to be marked as dirty as well. | 152 /// This causes all of the transform's outputs to be marked as dirty as well. |
| 163 void _dirty() { | 153 /// [primaryChanged] should be true if and only if [this] was set dirty |
| 154 /// because [primary] changed. | |
| 155 void _dirty({bool primaryChanged: false}) { | |
| 156 if (!primaryChanged && _state.isNotPrimary) return; | |
| 157 | |
| 158 if (_passThroughController != null) _passThroughController.setDirty(); | |
| 164 for (var controller in _outputControllers.values) { | 159 for (var controller in _outputControllers.values) { |
| 165 controller.setDirty(); | 160 controller.setDirty(); |
| 166 } | 161 } |
| 167 | 162 |
| 168 _hasBecomeDirty = true; | 163 if (_state.isDone) { |
| 169 _onStateChangeController.add(null); | 164 if (primaryChanged) { |
| 170 if (!_isApplying && !_pendingIsPrimary) _apply(); | 165 _process(); |
| 166 } else { | |
| 167 _apply(); | |
| 168 } | |
| 169 } else if (primaryChanged) { | |
| 170 _state = _TransformNodeState.NEEDS_IS_PRIMARY; | |
| 171 } else if (!_state.needsIsPrimary) { | |
| 172 _state = _TransformNodeState.NEEDS_APPLY; | |
| 173 } | |
|
Bob Nystrom
2014/03/12 17:42:58
It might be worth moving this conditional logic in
nweiz
2014/03/12 21:44:47
I tried this out, but I ended up finding it more c
| |
| 174 } | |
| 175 | |
| 176 /// Determines whether [primary] is primary for [transformer], and if so runs | |
| 177 /// [transformer.apply]. | |
| 178 void _process() { | |
| 179 // Clear all the old input subscriptions. If an input is re-used, we'll | |
| 180 // re-subscribe. | |
| 181 _clearInputSubscriptions(); | |
| 182 _state = _TransformNodeState.PROCESSING; | |
| 183 primary.whenAvailable((_) { | |
| 184 _state = _TransformNodeState.PROCESSING; | |
| 185 return transformer.isPrimary(primary.asset); | |
| 186 }).catchError((error, stackTrace) { | |
| 187 // If the transform became dirty while processing, ignore any errors from | |
| 188 // it. | |
| 189 if (_state.needsIsPrimary || _isRemoved) return false; | |
| 190 | |
| 191 if (error is! MissingInputException) { | |
| 192 error = new TransformerException(info, error, stackTrace); | |
| 193 } | |
| 194 | |
| 195 // Catch all transformer errors and pipe them to the results stream. This | |
| 196 // is so a broken transformer doesn't take down the whole graph. | |
| 197 phase.cascade.reportError(error); | |
| 198 | |
| 199 return false; | |
| 200 }).then((isPrimary) { | |
| 201 if (_isRemoved) return; | |
| 202 if (_state.needsIsPrimary) { | |
| 203 _process(); | |
| 204 } else if (isPrimary) { | |
| 205 _apply(); | |
| 206 } else { | |
| 207 _doesNotApply(); | |
| 208 _state = _TransformNodeState.NOT_PRIMARY; | |
| 209 _onDoneController.add(null); | |
| 210 } | |
|
Bob Nystrom
2014/03/12 17:42:58
This might be another chunk of conditional logic y
| |
| 211 }); | |
| 171 } | 212 } |
| 172 | 213 |
| 173 /// Applies this transform. | 214 /// Applies this transform. |
| 174 void _apply() { | 215 void _apply() { |
| 175 assert(!_onAssetController.isClosed); | 216 assert(!_onAssetController.isClosed); |
| 176 | 217 |
| 177 // Clear all the old input subscriptions. If an input is re-used, we'll | 218 // Clear input subscriptions here as well as in [_process] because [_apply] |
| 178 // re-subscribe. | 219 // may be restarted independently if only a secondary input changes. |
| 179 for (var subscription in _inputSubscriptions.values) { | 220 _clearInputSubscriptions(); |
| 180 subscription.cancel(); | 221 _state = _TransformNodeState.PROCESSING; |
| 181 } | |
| 182 _inputSubscriptions.clear(); | |
| 183 | |
| 184 _isApplying = true; | |
| 185 _onStateChangeController.add(null); | |
| 186 primary.whenAvailable((_) { | 222 primary.whenAvailable((_) { |
| 187 _hasBecomeDirty = false; | 223 if (_state.needsApply) _state = _TransformNodeState.PROCESSING; |
| 188 | |
| 189 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a | 224 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a |
| 190 // [LazyTransformer], we can get some mileage out of doing a declarative | 225 // [LazyTransformer], we can get some mileage out of doing a declarative |
| 191 // first so we know how to hook up the assets. | 226 // first so we know how to hook up the assets. |
| 192 if (_isLazy) return _declareLazy(); | 227 if (_isLazy) return _declareLazy(); |
| 193 return _applyImmediate(); | 228 return _applyImmediate(); |
| 194 }).catchError((error, stackTrace) { | 229 }).catchError((error, stackTrace) { |
| 195 // If the transform became dirty while processing, ignore any errors from | 230 // If the transform became dirty while processing, ignore any errors from |
| 196 // it. | 231 // it. |
| 197 if (_hasBecomeDirty || _onAssetController.isClosed) return; | 232 if (!_state.isProcessing || _isRemoved) return; |
| 198 | 233 |
| 199 if (error is! MissingInputException) { | 234 if (error is! MissingInputException) { |
| 200 error = new TransformerException(info, error, stackTrace); | 235 error = new TransformerException(info, error, stackTrace); |
| 201 } | 236 } |
| 202 | 237 |
| 203 // Catch all transformer errors and pipe them to the results stream. This | 238 // Catch all transformer errors and pipe them to the results stream. This |
| 204 // is so a broken transformer doesn't take down the whole graph. | 239 // is so a broken transformer doesn't take down the whole graph. |
| 205 phase.cascade.reportError(error); | 240 phase.cascade.reportError(error); |
| 206 | 241 |
| 207 // Remove all the previously-emitted assets. | 242 _doesNotApply(); |
| 208 for (var controller in _outputControllers.values) { | |
| 209 controller.setRemoved(); | |
| 210 } | |
| 211 _outputControllers.clear(); | |
| 212 }).then((_) { | 243 }).then((_) { |
| 213 if (_onAssetController.isClosed) return; | 244 if (_isRemoved) return; |
| 214 | 245 |
| 215 _isApplying = false; | 246 if (_state.needsIsPrimary) { |
| 216 if (_hasBecomeDirty) { | 247 _process(); |
| 217 // Re-apply the transform if it became dirty while applying. | 248 } else if (_state.needsApply) { |
| 218 if (!_pendingIsPrimary) _apply(); | 249 _apply(); |
| 219 } else { | 250 } else { |
| 220 assert(!isDirty); | 251 assert(_state.isProcessing); |
| 221 // Otherwise, notify the parent nodes that it's no longer dirty. | 252 _state = _TransformNodeState.APPLIED; |
| 222 _onStateChangeController.add(null); | 253 _onDoneController.add(null); |
| 223 } | 254 } |
|
Bob Nystrom
2014/03/12 17:42:58
Ditto.
| |
| 224 }); | 255 }); |
| 225 } | 256 } |
| 226 | 257 |
| 227 /// Gets the asset for an input [id]. | 258 /// Gets the asset for an input [id]. |
| 228 /// | 259 /// |
| 229 /// If an input with that ID cannot be found, throws an | 260 /// If an input with that ID cannot be found, throws an |
| 230 /// [AssetNotFoundException]. | 261 /// [AssetNotFoundException]. |
| 231 Future<Asset> getInput(AssetId id) { | 262 Future<Asset> getInput(AssetId id) { |
| 232 return phase.getInput(id).then((node) { | 263 return phase.getInput(id).then((node) { |
| 233 // Throw if the input isn't found. This ensures the transformer's apply | 264 // Throw if the input isn't found. This ensures the transformer's apply |
| 234 // is exited. We'll then catch this and report it through the proper | 265 // is exited. We'll then catch this and report it through the proper |
| 235 // results stream. | 266 // results stream. |
| 236 if (node == null) throw new MissingInputException(info, id); | 267 if (node == null) throw new MissingInputException(info, id); |
| 237 | 268 |
| 238 _inputSubscriptions.putIfAbsent(node.id, | 269 _inputSubscriptions.putIfAbsent(node.id, () { |
| 239 () => node.onStateChange.listen((_) => _dirty())); | 270 return node.onStateChange.listen((_) => _dirty(primaryChanged: false)); |
| 271 }); | |
| 240 | 272 |
| 241 return node.asset; | 273 return node.asset; |
| 242 }); | 274 }); |
| 243 } | 275 } |
| 244 | 276 |
| 245 /// Applies the transform so that it produces concrete (as opposed to lazy) | 277 /// Applies the transform so that it produces concrete (as opposed to lazy) |
| 246 /// outputs. | 278 /// outputs. |
| 247 Future _applyImmediate() { | 279 Future _applyImmediate() { |
| 248 var transformController = new TransformController(this); | 280 var transformController = new TransformController(this); |
| 249 _onLogPool.add(transformController.onLog); | 281 _onLogPool.add(transformController.onLog); |
| 250 | 282 |
| 251 return syncFuture(() { | 283 return syncFuture(() { |
| 252 return transformer.apply(transformController.transform); | 284 return transformer.apply(transformController.transform); |
| 253 }).then((_) { | 285 }).then((_) { |
| 254 if (_hasBecomeDirty || _onAssetController.isClosed) return; | 286 if (!_state.isProcessing || _onAssetController.isClosed) return; |
| 255 | 287 |
| 256 _consumePrimary = transformController.consumePrimary; | 288 _consumePrimary = transformController.consumePrimary; |
| 257 | 289 |
| 258 var newOutputs = transformController.outputs; | 290 var newOutputs = transformController.outputs; |
| 259 // Any ids that are for a different package are invalid. | 291 // Any ids that are for a different package are invalid. |
| 260 var invalidIds = newOutputs | 292 var invalidIds = newOutputs |
| 261 .map((asset) => asset.id) | 293 .map((asset) => asset.id) |
| 262 .where((id) => id.package != phase.cascade.package) | 294 .where((id) => id.package != phase.cascade.package) |
| 263 .toSet(); | 295 .toSet(); |
| 264 for (var id in invalidIds) { | 296 for (var id in invalidIds) { |
| 265 newOutputs.removeId(id); | 297 newOutputs.removeId(id); |
| 266 // TODO(nweiz): report this as a warning rather than a failing error. | 298 // TODO(nweiz): report this as a warning rather than a failing error. |
| 267 phase.cascade.reportError(new InvalidOutputException(info, id)); | 299 phase.cascade.reportError(new InvalidOutputException(info, id)); |
| 268 } | 300 } |
| 269 | 301 |
| 270 // Remove outputs that used to exist but don't anymore. | 302 // Remove outputs that used to exist but don't anymore. |
| 271 for (var id in _outputControllers.keys.toList()) { | 303 for (var id in _outputControllers.keys.toList()) { |
| 272 if (newOutputs.containsId(id)) continue; | 304 if (newOutputs.containsId(id)) continue; |
| 273 _outputControllers.remove(id).setRemoved(); | 305 _outputControllers.remove(id).setRemoved(); |
| 274 } | 306 } |
| 275 | 307 |
| 308 // Emit or stop emitting the pass-through asset between removing and | |
| 309 // adding outputs to ensure there are no collisions. | |
| 310 if (!newOutputs.containsId(primary.id)) { | |
| 311 _emitPassThrough(); | |
| 312 } else { | |
| 313 _dontEmitPassThrough(); | |
| 314 } | |
| 315 | |
| 276 // Store any new outputs or new contents for existing outputs. | 316 // Store any new outputs or new contents for existing outputs. |
| 277 for (var asset in newOutputs) { | 317 for (var asset in newOutputs) { |
| 278 var controller = _outputControllers[asset.id]; | 318 var controller = _outputControllers[asset.id]; |
| 279 if (controller != null) { | 319 if (controller != null) { |
| 280 controller.setAvailable(asset); | 320 controller.setAvailable(asset); |
| 281 } else { | 321 } else { |
| 282 var controller = new AssetNodeController.available(asset, this); | 322 var controller = new AssetNodeController.available(asset, this); |
| 283 _outputControllers[asset.id] = controller; | 323 _outputControllers[asset.id] = controller; |
| 284 _onAssetController.add(controller.node); | 324 _onAssetController.add(controller.node); |
| 285 } | 325 } |
| 286 } | 326 } |
| 287 }); | 327 }); |
| 288 } | 328 } |
| 289 | 329 |
| 290 /// Applies the transform in declarative mode so that it produces lazy | 330 /// Applies the transform in declarative mode so that it produces lazy |
| 291 /// outputs. | 331 /// outputs. |
| 292 Future _declareLazy() { | 332 Future _declareLazy() { |
| 293 var transformController = new DeclaringTransformController(this); | 333 var transformController = new DeclaringTransformController(this); |
| 294 | 334 |
| 295 return syncFuture(() { | 335 return syncFuture(() { |
| 296 return (transformer as LazyTransformer) | 336 return (transformer as LazyTransformer) |
| 297 .declareOutputs(transformController.transform); | 337 .declareOutputs(transformController.transform); |
| 298 }).then((_) { | 338 }).then((_) { |
| 299 if (_hasBecomeDirty || _onAssetController.isClosed) return; | 339 if (!_state.isProcessing || _onAssetController.isClosed) return; |
| 300 | 340 |
| 301 _consumePrimary = transformController.consumePrimary; | 341 _consumePrimary = transformController.consumePrimary; |
| 302 | 342 |
| 303 var newIds = transformController.outputIds; | 343 var newIds = transformController.outputIds; |
| 304 var invalidIds = | 344 var invalidIds = |
| 305 newIds.where((id) => id.package != phase.cascade.package).toSet(); | 345 newIds.where((id) => id.package != phase.cascade.package).toSet(); |
| 306 for (var id in invalidIds) { | 346 for (var id in invalidIds) { |
| 307 newIds.remove(id); | 347 newIds.remove(id); |
| 308 // TODO(nweiz): report this as a warning rather than a failing error. | 348 // TODO(nweiz): report this as a warning rather than a failing error. |
| 309 phase.cascade.reportError(new InvalidOutputException(info, id)); | 349 phase.cascade.reportError(new InvalidOutputException(info, id)); |
| 310 } | 350 } |
| 311 | 351 |
| 312 // Remove outputs that used to exist but don't anymore. | 352 // Remove outputs that used to exist but don't anymore. |
| 313 for (var id in _outputControllers.keys.toList()) { | 353 for (var id in _outputControllers.keys.toList()) { |
| 314 if (newIds.contains(id)) continue; | 354 if (newIds.contains(id)) continue; |
| 315 _outputControllers.remove(id).setRemoved(); | 355 _outputControllers.remove(id).setRemoved(); |
| 316 } | 356 } |
| 317 | 357 |
| 358 // Emit or stop emitting the pass-through asset between removing and | |
| 359 // adding outputs to ensure there are no collisions. | |
| 360 if (!newIds.contains(primary.id)) { | |
| 361 _emitPassThrough(); | |
| 362 } else { | |
| 363 _dontEmitPassThrough(); | |
| 364 } | |
| 365 | |
| 318 for (var id in newIds) { | 366 for (var id in newIds) { |
| 319 var controller = _outputControllers[id]; | 367 var controller = _outputControllers[id]; |
| 320 if (controller != null) { | 368 if (controller != null) { |
| 321 controller.setLazy(force); | 369 controller.setLazy(force); |
| 322 } else { | 370 } else { |
| 323 var controller = new AssetNodeController.lazy(id, force, this); | 371 var controller = new AssetNodeController.lazy(id, force, this); |
| 324 _outputControllers[id] = controller; | 372 _outputControllers[id] = controller; |
| 325 _onAssetController.add(controller.node); | 373 _onAssetController.add(controller.node); |
| 326 } | 374 } |
| 327 } | 375 } |
| 328 }); | 376 }); |
| 329 } | 377 } |
| 330 | 378 |
| 379 /// Cancels all subscriptions to secondary input nodes. | |
| 380 void _clearInputSubscriptions() { | |
| 381 for (var subscription in _inputSubscriptions.values) { | |
| 382 subscription.cancel(); | |
| 383 } | |
| 384 _inputSubscriptions.clear(); | |
| 385 } | |
| 386 | |
| 387 /// Marks this transformer as not applying to [primary]. | |
| 388 /// | |
| 389 /// This might be because [primary] isn't primary for [transformer], or | |
| 390 /// because [transformer] threw an error during [transformer.apply]. | |
| 391 void _doesNotApply() { | |
| 392 // Remove all the previously-emitted assets. | |
| 393 for (var controller in _outputControllers.values) { | |
| 394 controller.setRemoved(); | |
| 395 } | |
| 396 _outputControllers.clear(); | |
| 397 _emitPassThrough(); | |
| 398 } | |
| 399 | |
| 400 /// Emit the pass-through asset if it's not being emitted already. | |
| 401 void _emitPassThrough() { | |
| 402 assert(!_outputControllers.containsKey(primary.id)); | |
| 403 | |
| 404 if (_consumePrimary) return; | |
| 405 if (_passThroughController == null) { | |
| 406 _passThroughController = new AssetNodeController.from(primary); | |
| 407 _onAssetController.add(_passThroughController.node); | |
| 408 } else { | |
| 409 _passThroughController.setAvailable(primary.asset); | |
| 410 } | |
| 411 } | |
| 412 | |
| 413 /// Stop emitting the pass-through asset if it's being emitted already. | |
| 414 void _dontEmitPassThrough() { | |
| 415 if (_passThroughController == null) return; | |
| 416 _passThroughController.setRemoved(); | |
| 417 _passThroughController = null; | |
| 418 } | |
| 419 | |
| 331 String toString() => | 420 String toString() => |
| 332 "transform node in $_location for $transformer on $primary"; | 421 "transform node in $_location for $transformer on $primary"; |
| 333 } | 422 } |
| 423 | |
| 424 /// The enum of states that [TransformNode] can be in. | |
| 425 class _TransformNodeState { | |
| 426 /// The transform node is running [Transformer.isPrimary] or | |
| 427 /// [Transformer.apply] and doesn't need to re-run them. | |
| 428 /// | |
| 429 /// If there are no external changes by the time the processing finishes, this | |
| 430 /// will transition to [APPLIED] or [NOT_PRIMARY] depending on the result of | |
| 431 /// [Transformer.isPrimary]. If the primary input changes, this will | |
| 432 /// transition to [NEEDS_IS_PRIMARY]. If a secondary input changes, this will | |
| 433 /// transition to [NEEDS_APPLY]. | |
| 434 static final PROCESSING = const _TransformNodeState._("processing"); | |
| 435 | |
| 436 /// The transform is running [Transformer.isPrimary] or [Transformer.apply], | |
| 437 /// but since it started the primary input changed, so it will need to re-run | |
| 438 /// [Transformer.isPrimary]. | |
| 439 /// | |
| 440 /// This will always transition to [Transformer.PROCESSING]. | |
| 441 static final NEEDS_IS_PRIMARY = | |
| 442 const _TransformNodeState._("needs isPrimary"); | |
| 443 | |
| 444 /// The transform is running [Transformer.apply], but since it started a | |
| 445 /// secondary input changed, so it will need to re-run [Transformer.apply]. | |
| 446 /// | |
| 447 /// If there are no external changes by the time [Transformer.apply] finishes, | |
| 448 /// this will transition to [PROCESSING]. If the primary input changes, this | |
| 449 /// will transition to [NEEDS_IS_PRIMARY]. | |
| 450 static final NEEDS_APPLY = const _TransformNodeState._("needs apply"); | |
| 451 | |
| 452 /// The transform has finished running [Transformer.apply], whether or not it | |
| 453 /// emitted an error. | |
| 454 /// | |
| 455 /// If the primary input or a secondary input changes, this will transition to | |
| 456 /// [PROCESSING]. | |
| 457 static final APPLIED = const _TransformNodeState._("applied"); | |
| 458 | |
| 459 /// The transform has finished running [Transformer.isPrimary], which returned | |
| 460 /// `false`. | |
| 461 /// | |
| 462 /// If the primary input changes, this will transition to [PROCESSING]. | |
| 463 static final NOT_PRIMARY = const _TransformNodeState._("not primary"); | |
| 464 | |
| 465 /// Whether [this] is [PROCESSING]. | |
| 466 bool get isProcessing => this == _TransformNodeState.PROCESSING; | |
| 467 | |
| 468 /// Whether [this] is [NEEDS_IS_PRIMARY]. | |
| 469 bool get needsIsPrimary => this == _TransformNodeState.NEEDS_IS_PRIMARY; | |
| 470 | |
| 471 /// Whether [this] is [NEEDS_APPLY]. | |
| 472 bool get needsApply => this == _TransformNodeState.NEEDS_APPLY; | |
| 473 | |
| 474 /// Whether [this] is [APPLIED]. | |
| 475 bool get isApplied => this == _TransformNodeState.APPLIED; | |
| 476 | |
| 477 /// Whether [this] is [IS_NOT_PRIMARY]. | |
|
Bob Nystrom
2014/03/12 17:42:58
IS_NOT_PRIMARY -> NOT_PRIMARY.
nweiz
2014/03/12 21:44:47
Done.
| |
| 478 bool get isNotPrimary => this == _TransformNodeState.NOT_PRIMARY; | |
| 479 | |
| 480 /// Whether the transform has finished running [Transformer.isPrimary] and | |
| 481 /// [Transformer.apply]. | |
| 482 /// | |
| 483 /// Specifically, whether [this] is [APPLIED] or [IS_NOT_PRIMARY]. | |
| 484 bool get isDone => isApplied || isNotPrimary; | |
| 485 | |
| 486 final String name; | |
| 487 | |
| 488 const _TransformNodeState._(this.name); | |
| 489 | |
| 490 String toString() => name; | |
| 491 } | |
| OLD | NEW |