Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(359)

Side by Side Diff: pkg/barback/lib/src/graph/transform_node.dart

Issue 267393009: Transition barback's infrastructure to an aggregate-based model. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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.graph.transform_node; 5 library barback.graph.transform_node;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import '../asset/asset.dart'; 9 import '../asset/asset.dart';
10 import '../asset/asset_id.dart'; 10 import '../asset/asset_id.dart';
11 import '../asset/asset_node.dart'; 11 import '../asset/asset_node.dart';
12 import '../asset/asset_node_set.dart';
12 import '../errors.dart'; 13 import '../errors.dart';
13 import '../log.dart'; 14 import '../log.dart';
14 import '../transformer/aggregate_transform.dart'; 15 import '../transformer/aggregate_transform.dart';
16 import '../transformer/aggregate_transformer.dart';
15 import '../transformer/declaring_aggregate_transform.dart'; 17 import '../transformer/declaring_aggregate_transform.dart';
16 import '../transformer/declaring_transform.dart'; 18 import '../transformer/declaring_aggregate_transformer.dart';
17 import '../transformer/declaring_transformer.dart'; 19 import '../transformer/lazy_aggregate_transformer.dart';
18 import '../transformer/lazy_transformer.dart'; 20 import '../utils.dart';
19 import '../transformer/transform.dart';
20 import '../transformer/transformer.dart';
21 import 'node_status.dart'; 21 import 'node_status.dart';
22 import 'node_streams.dart'; 22 import 'node_streams.dart';
23 import 'phase.dart'; 23 import 'phase.dart';
24 24
25 /// Describes a transform on a set of assets and its relationship to the build 25 /// Describes a transform on a set of assets and its relationship to the build
26 /// dependency graph. 26 /// dependency graph.
27 /// 27 ///
28 /// Keeps track of whether it's dirty and needs to be run and which assets it 28 /// Keeps track of whether it's dirty and needs to be run and which assets it
29 /// depends on. 29 /// depends on.
30 class TransformNode { 30 class TransformNode {
31 /// The aggregate key for this node. 31 /// The aggregate key for this node.
32 final String key; 32 final String key;
33 33
34 /// The [Phase] that this transform runs in. 34 /// The [Phase] that this transform runs in.
35 final Phase phase; 35 final Phase phase;
36 36
37 /// The [Transformer] to apply to this node's inputs. 37 /// The [AggregateTransformer] to apply to this node's inputs.
38 final Transformer transformer; 38 final AggregateTransformer transformer;
39 39
40 /// The node for the primary asset this transform depends on. 40 /// The primary asset nodes this transform runs on.
41 final AssetNode primary; 41 final _primaries = new AssetNodeSet();
42 42
43 /// A string describing the location of [this] in the transformer graph. 43 /// A string describing the location of [this] in the transformer graph.
44 final String _location; 44 final String _location;
45 45
46 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. 46 /// The subscription to the [_primaries]' [AssetNode.onStateChange] streams.
47 StreamSubscription _primarySubscription; 47 final _primarySubscriptions = new Map<AssetId, StreamSubscription>();
48 48
49 /// The subscription to [phase]'s [Phase.onAsset] stream. 49 /// The subscription to [phase]'s [Phase.onAsset] stream.
50 StreamSubscription<AssetNode> _phaseSubscription; 50 StreamSubscription<AssetNode> _phaseAssetSubscription;
51
52 /// The subscription to [phase]'s [Phase.onStatusChange] stream.
53 StreamSubscription<NodeStatus> _phaseStatusSubscription;
51 54
52 /// How far along [this] is in processing its assets. 55 /// How far along [this] is in processing its assets.
53 NodeStatus get status { 56 NodeStatus get status {
54 if (_state == _State.APPLIED || _state == _State.DECLARED) { 57 if (_state == _State.APPLIED || _state == _State.DECLARED) {
55 return NodeStatus.IDLE; 58 return NodeStatus.IDLE;
56 } 59 }
57 60
58 if (_declaring && _state != _State.DECLARING) { 61 if (_declaring && _state != _State.DECLARING &&
62 _state != _State.NEEDS_DECLARE) {
59 return NodeStatus.MATERIALIZING; 63 return NodeStatus.MATERIALIZING;
60 } else { 64 } else {
61 return NodeStatus.RUNNING; 65 return NodeStatus.RUNNING;
62 } 66 }
63 } 67 }
64 68
69 /// The [TransformInfo] describing this node.
70 ///
71 /// [TransformInfo] is the publicly-visible representation of a transform
72 /// node.
73 TransformInfo get info => new TransformInfo(transformer,
74 new AssetId(phase.cascade.package, key));
75
65 /// Whether this is a declaring transform. 76 /// Whether this is a declaring transform.
66 /// 77 ///
67 /// This is usually identical to `transformer is DeclaringTransformer`, but if 78 /// This is usually identical to `transformer is
68 /// a declaring and non-lazy transformer emits an error during 79 /// DeclaringAggregateTransformer`, but if a declaring and non-lazy
69 /// `declareOutputs` it's treated as though it wasn't declaring. 80 /// transformer emits an error during `declareOutputs` it's treated as though
70 bool get _declaring => transformer is DeclaringTransformer && 81 /// it wasn't declaring.
82 bool get _declaring => transformer is DeclaringAggregateTransformer &&
71 (_state == _State.DECLARING || _declaredOutputs != null); 83 (_state == _State.DECLARING || _declaredOutputs != null);
72 84
73 /// Whether this transform has been forced since it last finished applying. 85 /// Whether this transform has been forced since it last finished applying.
74 /// 86 ///
75 /// A transform being forced means it should run until it generates outputs 87 /// A transform being forced means it should run until it generates outputs
76 /// and is no longer dirty. This is always true for non-declaring 88 /// and is no longer dirty. This is always true for non-declaring
77 /// transformers, since they always need to eagerly generate outputs. 89 /// transformers, since they always need to eagerly generate outputs.
78 bool _forced; 90 bool _forced;
79 91
80 /// The subscriptions to each input's [AssetNode.onStateChange] stream. 92 /// The subscriptions to each secondary input's [AssetNode.onStateChange]
81 final _inputSubscriptions = new Map<AssetId, StreamSubscription>(); 93 /// stream.
94 final _secondarySubscriptions = new Map<AssetId, StreamSubscription>();
82 95
83 /// The controllers for the asset nodes emitted by this node. 96 /// The controllers for the asset nodes emitted by this node.
84 final _outputControllers = new Map<AssetId, AssetNodeController>(); 97 final _outputControllers = new Map<AssetId, AssetNodeController>();
85 98
86 /// The ids of inputs the transformer tried and failed to read last time it 99 /// The ids of inputs the transformer tried and failed to read last time it
87 /// ran. 100 /// ran.
88 final _missingInputs = new Set<AssetId>(); 101 final _missingInputs = new Set<AssetId>();
89 102
90 /// The controller that's used to pass [primary] through [this] if it's not 103 /// The controllers that are used to pass each primary input through [this] if
91 /// consumed or overwritten. 104 /// it's not consumed or overwritten.
92 /// 105 ///
93 /// This needs an intervening controller to ensure that the output can be 106 /// This needs an intervening controller to ensure that the output can be
94 /// marked dirty when determining whether [this] will consume or overwrite it, 107 /// marked dirty when determining whether [this] will consume or overwrite it,
95 /// and be marked removed if it does. [_passThroughController] will be null 108 /// and be marked removed if it does. No pass-through controller will exist
96 /// if the asset is not being passed through. 109 /// for primary inputs that are not being passed through.
97 AssetNodeController _passThroughController; 110 final _passThroughControllers = new Map<AssetId, AssetNodeController>();
98 111
99 /// The asset node for this transform. 112 /// The asset node for this transform.
100 final _streams = new NodeStreams(); 113 final _streams = new NodeStreams();
101 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; 114 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange;
102 Stream<AssetNode> get onAsset => _streams.onAsset; 115 Stream<AssetNode> get onAsset => _streams.onAsset;
103 Stream<LogEntry> get onLog => _streams.onLog; 116 Stream<LogEntry> get onLog => _streams.onLog;
104 117
105 /// The current state of [this]. 118 /// The current state of [this].
106 var _state = _State.DECLARING; 119 var _state = _State.DECLARED;
107 120
108 /// Whether [this] has been marked as removed. 121 /// Whether [this] has been marked as removed.
109 bool get _isRemoved => _streams.onAssetController.isClosed; 122 bool get _isRemoved => _streams.onAssetController.isClosed;
110 123
111 // If [transformer] is declaring but not lazy and [primary] is available, we 124 // If [transformer] is declaring but not lazy and [primary] is available, we
112 // can run [apply] even if [force] hasn't been called, since [transformer] 125 // can run [apply] even if [force] hasn't been called, since [transformer]
113 // should run eagerly if possible. 126 // should run eagerly if possible.
114 bool get _canRunDeclaringEagerly => 127 bool get _canRunDeclaringEagerly =>
115 _declaring && transformer is! LazyTransformer && 128 _declaring && transformer is! LazyAggregateTransformer &&
116 primary.state.isAvailable; 129 _primaries.every((input) => input.state.isAvailable);
117 130
118 /// Whether the most recent run of this transform has declared that it 131 /// Which primary inputs the most recent run of this transform has declared
119 /// consumes the primary input. 132 /// that it consumes.
120 /// 133 ///
121 /// Defaults to `false`. This is not meaningful unless [_state] is 134 /// This starts out `null`, indicating that the transform hasn't declared
122 /// [_State.APPLIED] or [_State.DECLARED]. 135 /// anything yet. This is not meaningful unless [_state] is [_State.APPLIED]
123 bool _consumePrimary = false; 136 /// or [_State.DECLARED].
137 Set<AssetId> _consumedPrimaries;
124 138
125 /// The set of output ids that [transformer] declared it would emit. 139 /// The set of output ids that [transformer] declared it would emit.
126 /// 140 ///
127 /// This is only non-null if [transformer] is a [DeclaringTransformer] and its 141 /// This is only non-null if [transformer] is a
128 /// [declareOutputs] has been run successfully. 142 /// [DeclaringAggregateTransformer] and its [declareOutputs] has been run
143 /// successfully.
129 Set<AssetId> _declaredOutputs; 144 Set<AssetId> _declaredOutputs;
130 145
131 TransformNode(this.phase, Transformer transformer, AssetNode primary, 146 /// The controller for the currently-running
132 this._location) 147 /// [DeclaringAggregateTransformer.declareOutputs] call's
133 : key = primary.id.path, 148 /// [DeclaringAggregateTransform].
134 transformer = transformer, 149 ///
135 primary = primary { 150 /// This will be non-`null` when
136 _forced = transformer is! DeclaringTransformer; 151 /// [DeclaringAggregateTransformer.declareOutputs] is running. This means that
137 if (_forced) primary.force(); 152 /// it's always non-`null` when [_state] is [_State.DECLARING], sometimes
153 /// non-`null` when it's [_State.NEEDS_DECLARE], and always `null` otherwise.
154 DeclaringAggregateTransformController _declareController;
138 155
139 _primarySubscription = primary.onStateChange.listen((state) { 156 /// The controller for the currently-running [AggregateTransformer.apply] call 's
Bob Nystrom 2014/05/08 20:30:48 Long line.
nweiz 2014/05/08 21:12:36 Done.
140 if (state.isRemoved) { 157 /// [AggregateTransform].
141 remove(); 158 ///
142 } else { 159 /// This will be non-`null` when [AggregateTransform.apply] is running, which
143 if (_forced) primary.force(); 160 /// means that it's always non-`null` when [_state] is [_State.APPLYING] or
144 _dirty(); 161 /// [_State.NEEDS_APPLY], sometimes non-`null` when it's
145 } 162 /// [_State.NEEDS_DECLARE], and always `null` otherwise.
146 }); 163 AggregateTransformController _applyController;
147 164
148 _phaseSubscription = phase.previous.onAsset.listen((node) { 165 TransformNode(this.phase, this.transformer, this.key, this._location) {
166 _forced = transformer is! DeclaringAggregateTransformer;
167
168 _phaseAssetSubscription = phase.previous.onAsset.listen((node) {
149 if (!_missingInputs.contains(node.id)) return; 169 if (!_missingInputs.contains(node.id)) return;
150 if (_forced) node.force(); 170 if (_forced) node.force();
151 _dirty(); 171 _dirty();
152 }); 172 });
153 173
154 _declareOutputs().then((_) { 174 _phaseStatusSubscription = phase.previous.onStatusChange.listen((status) {
155 if (_forced || _canRunDeclaringEagerly) { 175 if (status == NodeStatus.RUNNING) return;
156 _apply(); 176
157 } else { 177 _maybeFinishDeclareController();
158 _state = _State.DECLARED; 178 _maybeFinishApplyController();
159 _streams.changeStatus(NodeStatus.IDLE);
160 }
161 }); 179 });
180
181 _run();
162 } 182 }
163 183
164 /// The [TransformInfo] describing this node. 184 /// Adds [input] as a primary input for this node.
165 /// 185 void addInput(AssetNode input) {
Bob Nystrom 2014/05/08 20:30:48 addInput -> addPrimary?
nweiz 2014/05/08 21:12:36 Done.
166 /// [TransformInfo] is the publicly-visible representation of a transform 186 _primaries.add(input);
167 /// node. 187 if (_forced) input.force();
168 TransformInfo get info => new TransformInfo(transformer, primary.id); 188
189 _primarySubscriptions[input.id] = input.onStateChange
190 .listen((_) => _onPrimaryStateChange(input));
191
192 if (_state == _State.DECLARING && !_declareController.isDone) {
193 // If we're running `declareOutputs` and its id stream isn't closed yet,
194 // pass this in as another id.
195 _declareController.addId(input.id);
196 _maybeFinishDeclareController();
197 } else if (_state == _State.APPLYING) {
198 // If we're running `apply`, we need to wait until [input] is available
199 // before we pass it into the stream. If it's available now, great; if
200 // not, [_onPrimaryStateChange] will handle it.
201 if (!input.state.isAvailable) return;
202 _onPrimaryStateChange(input);
203 _maybeFinishApplyController();
204 } else {
205 // Otherwise, a new input means we'll need to re-run `declareOutputs`.
206 _restartRun();
207 }
208 }
169 209
170 /// Marks this transform as removed. 210 /// Marks this transform as removed.
171 /// 211 ///
172 /// This causes all of the transform's outputs to be marked as removed as 212 /// This causes all of the transform's outputs to be marked as removed as
173 /// well. Normally this will be automatically done internally based on events 213 /// well. Normally this will be automatically done internally based on events
174 /// from the primary input, but it's possible for a transform to no longer be 214 /// from the primary input, but it's possible for a transform to no longer be
175 /// valid even if its primary input still exists. 215 /// valid even if its primary input still exists.
176 void remove() { 216 void remove() {
177 _streams.close(); 217 _streams.close();
178 _primarySubscription.cancel(); 218 _phaseAssetSubscription.cancel();
179 _phaseSubscription.cancel(); 219 _phaseStatusSubscription.cancel();
180 _clearInputSubscriptions(); 220 if (_declareController != null) _declareController.cancel();
221 if (_applyController != null) _applyController.cancel();
222 _clearSecondarySubscriptions();
181 _clearOutputs(); 223 _clearOutputs();
182 if (_passThroughController != null) { 224
183 _passThroughController.setRemoved(); 225 for (var subscription in _primarySubscriptions.values) {
184 _passThroughController = null; 226 subscription.cancel();
185 } 227 }
228 _primarySubscriptions.clear();
229
230 for (var controller in _passThroughControllers.values) {
231 controller.setRemoved();
232 }
233 _passThroughControllers.clear();
186 } 234 }
187 235
188 /// If [this] is deferred, ensures that its concrete outputs will be 236 /// If [this] is deferred, ensures that its concrete outputs will be
189 /// generated. 237 /// generated.
190 void force() { 238 void force() {
191 if (_forced || _state == _State.APPLIED) return; 239 if (_forced || _state == _State.APPLIED) return;
192 primary.force(); 240 for (var input in _primaries) {
241 input.force();
242 }
243
193 _forced = true; 244 _forced = true;
194 if (_state == _State.DECLARED) _dirty(); 245 if (_state == _State.DECLARED) _apply();
195 } 246 }
196 247
197 /// Marks this transform as dirty. 248 /// Marks this transform as dirty.
198 /// 249 ///
199 /// This causes all of the transform's outputs to be marked as dirty as well. 250 /// Specifically, this should be called when one of the transform's inputs'
251 /// contents change, or when a secondary input is removed. Primary inputs
252 /// being added or removed are handled by [addInput] and
253 /// [_onPrimaryStateChange].
200 void _dirty() { 254 void _dirty() {
201 // If we're in the process of running [declareOutputs], we already know that 255 if (_state == _State.DECLARING || _state == _State.NEEDS_DECLARE ||
202 // [apply] needs to be run so there's nothing we need to mark as dirty. 256 _state == _State.NEEDS_APPLY) {
203 if (_state == _State.DECLARING) return; 257 // If we already know that [_apply] needs to be run, there's nothing to do
258 // here.
259 return;
260 }
204 261
205 if (!_forced && !_canRunDeclaringEagerly) { 262 if (!_forced && !_canRunDeclaringEagerly) {
206 // [forced] should only ever be false for a declaring transformer. 263 // [forced] should only ever be false for a declaring transformer.
207 assert(_declaring); 264 assert(_declaring);
208 265
209 // If we've finished applying, transition to MATERIALIZING, indicating 266 // If we've finished applying, transition to DECLARED, indicating that we
210 // that we know what outputs [apply] will emit but we're waiting to emit 267 // know what outputs [apply] will emit but we're waiting to emit them
211 // them concretely until [force] is called. If we're still applying, we'll 268 // concretely until [force] is called. If we're still applying, we'll
212 // transition to MATERIALIZING once we finish. 269 // transition to DECLARED once we finish.
213 if (_state == _State.APPLIED) _state = _State.DECLARED; 270 if (_state == _State.APPLIED) _state = _State.DECLARED;
214 for (var controller in _outputControllers.values) { 271 for (var controller in _outputControllers.values) {
215 controller.setLazy(force); 272 controller.setLazy(force);
216 } 273 }
217 _emitDeclaredOutputs(); 274 _emitDeclaredOutputs();
218 return; 275 return;
219 } 276 }
220 277
221 if (_passThroughController != null) _passThroughController.setDirty();
222 for (var controller in _outputControllers.values) {
223 controller.setDirty();
224 }
225
226 if (_state == _State.APPLIED) { 278 if (_state == _State.APPLIED) {
227 if (_declaredOutputs != null) _emitDeclaredOutputs(); 279 if (_declaredOutputs != null) _emitDeclaredOutputs();
228 _apply(); 280 _apply();
229 } else if (_state == _State.DECLARED) { 281 } else if (_state == _State.DECLARED) {
230 _apply(); 282 _apply();
231 } else { 283 } else {
232 _state = _State.NEEDS_APPLY; 284 _state = _State.NEEDS_APPLY;
233 } 285 }
234 } 286 }
235 287
288 /// The callback called when [input]'s state changes.
289 void _onPrimaryStateChange(AssetNode input) {
290 if (input.state.isRemoved) {
291 _primarySubscriptions.remove(input.id);
292
293 if (_primaries.isEmpty) {
294 // If there are no more primary inputs, there's no more use for this
295 // node in the graph. It will be re-created by its
296 // [TransformerClassifier] if a new input with [key] is added.
297 remove();
298 return;
299 }
300
301 // Any change to the number of primary inputs requires that we re-run the
302 // transformation.
303 _restartRun();
304 } else if (input.state.isAvailable) {
305 if (_state == _State.DECLARED && _canRunDeclaringEagerly) {
306 // If [this] is fully declared but hasn't started applying, this input
307 // becoming available may mean that all inputs are available, in which
308 // case we can run apply eagerly.
309 _apply();
310 return;
311 }
312
313 // If we're not actively passing concrete assets to the transformer, the
314 // distinction between a dirty asset and an available one isn't relevant.
315 if (_state != _State.APPLYING) return;
316
317 if (_applyController.isDone) {
318 // If we get a new asset after we've closed the asset stream, we need to
319 // re-run declare and then apply.
320 _restartRun();
321 } else {
322 // If the new asset comes before the asset stream is done, we can just
323 // pass it to the stream.
324 _applyController.addInput(input.asset);
325 _maybeFinishApplyController();
326 }
327 } else {
328 if (_forced) input.force();
329 if (_state == _State.APPLYING && !_applyController.addedId(input.id)) {
330 // If the input hasn't yet been added to the transform's input stream,
331 // there's no need to consider the transformation dirty.
332 return;
333 }
334 _dirty();
335 }
336 }
337
338 /// Run the entire transformation, including both `declareOutputs` (if
339 /// applicable) and `apply`.
340 void _run() {
341 assert(_state != _State.DECLARING);
342 assert(_state != _State.APPLYING);
343
344 _markOutputsDirty();
345 _declareOutputs(() {
346 if (_forced || _canRunDeclaringEagerly) {
347 _apply();
348 } else {
349 _state = _State.DECLARED;
350 _streams.changeStatus(NodeStatus.IDLE);
351 }
352 });
353 }
354
355 /// Restart the entire transformation, including `declareOutputs` if
356 /// applicable.
357 void _restartRun() {
358 if (_state == _State.DECLARED || _state == _State.APPLIED) {
359 // If we're currently idle, we can restart the transformation immediately.
360 _run();
361 return;
362 }
363
364 // If we're actively running `declareOutputs` or `apply`, cancel the
365 // transforms and transition to `NEEDS_DECLARE`. Once the transformer's
366 // method returns, we'll transition to `DECLARING`.
367 if (_declareController != null) _declareController.cancel();
368 if (_applyController != null) _applyController.cancel();
369 _state = _State.NEEDS_DECLARE;
370 }
371
236 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty 372 /// Runs [transform.declareOutputs] and emits the resulting assets as dirty
237 /// assets. 373 /// assets.
238 Future _declareOutputs() { 374 ///
239 if (transformer is! DeclaringTransformer) return new Future.value(); 375 /// Calls [callback] when it's finished. This doesn't return a future so that
376 /// [callback] is called synchronously if there are no outputs to declare. If
377 /// [this] is removed while inputs are being declared, [callback] will not be
378 /// called.
379 void _declareOutputs(void callback()) {
380 if (transformer is! DeclaringAggregateTransformer) {
381 callback();
382 return;
383 }
240 384
385 _state = _State.DECLARING;
241 var controller = new DeclaringAggregateTransformController(this); 386 var controller = new DeclaringAggregateTransformController(this);
387 _declareController = controller;
Bob Nystrom 2014/05/08 20:30:48 No reason to make a local for this.
nweiz 2014/05/08 21:12:36 The instance variable gets nulled out in the whenC
Bob Nystrom 2014/05/08 23:56:13 Ah, right. I didn't notice it was used down below.
242 _streams.onLogPool.add(controller.onLog); 388 _streams.onLogPool.add(controller.onLog);
243 controller.idController.add(primary.id); 389 for (var primary in _primaries) {
244 return newDeclaringTransform(controller.transform).then((transform) { 390 controller.addId(primary.id);
245 return (transformer as DeclaringTransformer).declareOutputs(transform); 391 }
392
393 syncFuture(() {
394 return (transformer as DeclaringAggregateTransformer)
395 .declareOutputs(controller.transform);
396 }).whenComplete(() {
397 controller.cancel();
Bob Nystrom 2014/05/08 20:30:48 Document that calling cancel here deliberately eve
nweiz 2014/05/08 21:12:36 Done.
398 _declareController = null;
246 }).then((_) { 399 }).then((_) {
247 if (_isRemoved) return; 400 if (_isRemoved) return;
248 if (controller.loggedError) return; 401 if (_state == _State.NEEDS_DECLARE) {
402 _declareOutputs(callback);
403 return;
404 }
249 405
250 _consumePrimary = controller.consumedPrimaries.contains(primary.id); 406 if (controller.loggedError) {
Bob Nystrom 2014/05/08 20:30:48 Document something like: "If declaration fails, fa
nweiz 2014/05/08 21:12:36 Done.
407 if (transformer is! LazyAggregateTransformer) _forced = true;
408 callback();
409 return;
410 }
411
412 _consumedPrimaries = controller.consumedPrimaries;
251 _declaredOutputs = controller.outputIds; 413 _declaredOutputs = controller.outputIds;
252 var invalidIds = _declaredOutputs 414 var invalidIds = _declaredOutputs
253 .where((id) => id.package != phase.cascade.package).toSet(); 415 .where((id) => id.package != phase.cascade.package).toSet();
254 for (var id in invalidIds) { 416 for (var id in invalidIds) {
255 _declaredOutputs.remove(id); 417 _declaredOutputs.remove(id);
256 // TODO(nweiz): report this as a warning rather than a failing error. 418 // TODO(nweiz): report this as a warning rather than a failing error.
257 phase.cascade.reportError(new InvalidOutputException(info, id)); 419 phase.cascade.reportError(new InvalidOutputException(info, id));
258 } 420 }
259 421
260 if (!_declaredOutputs.contains(primary.id)) _emitPassThrough(); 422 for (var primary in _primaries) {
423 if (_declaredOutputs.contains(primary.id)) continue;
424 _emitPassThrough(primary.id);
425 }
261 _emitDeclaredOutputs(); 426 _emitDeclaredOutputs();
427 callback();
262 }).catchError((error, stackTrace) { 428 }).catchError((error, stackTrace) {
263 if (_isRemoved) return; 429 if (_isRemoved) return;
264 if (transformer is! LazyTransformer) _forced = true; 430 if (transformer is! LazyAggregateTransformer) _forced = true;
265 phase.cascade.reportError(_wrapException(error, stackTrace)); 431 phase.cascade.reportError(_wrapException(error, stackTrace));
432 callback();
266 }); 433 });
267 } 434 }
268 435
269 /// Emits a dirty asset node for all outputs that were declared by the 436 /// Emits a dirty asset node for all outputs that were declared by the
270 /// transformer. 437 /// transformer.
271 /// 438 ///
272 /// This won't emit any outputs for which there already exist output 439 /// This won't emit any outputs for which there already exist output
273 /// controllers. It should only be called for transforms that have declared 440 /// controllers. It should only be called for transforms that have declared
274 /// their outputs. 441 /// their outputs.
275 void _emitDeclaredOutputs() { 442 void _emitDeclaredOutputs() {
276 assert(_declaredOutputs != null); 443 assert(_declaredOutputs != null);
277 for (var id in _declaredOutputs) { 444 for (var id in _declaredOutputs) {
278 if (_outputControllers.containsKey(id)) continue; 445 if (_outputControllers.containsKey(id)) continue;
279 var controller = _forced 446 var controller = _forced
280 ? new AssetNodeController(id, this) 447 ? new AssetNodeController(id, this)
281 : new AssetNodeController.lazy(id, force, this); 448 : new AssetNodeController.lazy(id, force, this);
282 _outputControllers[id] = controller; 449 _outputControllers[id] = controller;
283 _streams.onAssetController.add(controller.node); 450 _streams.onAssetController.add(controller.node);
284 } 451 }
285 } 452 }
286 453
454 //// Mark all emitted and passed-through outputs of this transform as dirty.
455 void _markOutputsDirty() {
456 for (var controller in _passThroughControllers.values) {
457 controller.setDirty();
458 }
459 for (var controller in _outputControllers.values) {
460 if (_forced) {
461 controller.setDirty();
462 } else {
463 controller.setLazy(force);
464 }
465 }
466 }
467
287 /// Applies this transform. 468 /// Applies this transform.
288 void _apply() { 469 void _apply() {
289 assert(!_isRemoved); 470 assert(!_isRemoved);
290 471
291 // Clear input subscriptions here as well as in [_process] because [_apply] 472 _markOutputsDirty();
292 // may be restarted independently if only a secondary input changes. 473 _clearSecondarySubscriptions();
293 _clearInputSubscriptions();
294 _state = _State.APPLYING; 474 _state = _State.APPLYING;
295 _streams.changeStatus(status); 475 _streams.changeStatus(status);
296 _runApply().then((hadError) { 476 _runApply().then((hadError) {
297 if (_isRemoved) return; 477 if (_isRemoved) return;
298 478
299 if (_state == _State.DECLARED) return; 479 if (_state == _State.DECLARED) return;
300 480
301 if (_state == _State.NEEDS_APPLY) { 481 if (_state == _State.NEEDS_DECLARE) {
302 _apply(); 482 _run();
303 return; 483 return;
304 } 484 }
305 485
Bob Nystrom 2014/05/08 20:30:48 Document this please. Something like: "If we got i
nweiz 2014/05/08 21:12:36 Done.
486 if (_state == _State.NEEDS_APPLY) {
487 if (_forced || _canRunDeclaringEagerly) {
488 _apply();
489 } else {
490 _state = _State.DECLARED;
491 }
492 return;
493 }
494
306 if (_declaring) _forced = false; 495 if (_declaring) _forced = false;
307 496
308 assert(_state == _State.APPLYING); 497 assert(_state == _State.APPLYING);
309 if (hadError) { 498 if (hadError) {
310 _clearOutputs(); 499 _clearOutputs();
311 // If the transformer threw an error, we don't want to emit the 500 // If the transformer threw an error, we don't want to emit the
312 // pass-through asset in case it will be overwritten by the transformer. 501 // pass-through assets in case they'll be overwritten by the
313 // However, if the transformer declared that it wouldn't overwrite or 502 // transformer. However, if the transformer declared that it wouldn't
314 // consume the pass-through asset, we can safely emit it. 503 // overwrite or consume a pass-through asset, we can safely emit it.
315 if (_declaredOutputs != null && !_consumePrimary && 504 if (_declaredOutputs != null) {
316 !_declaredOutputs.contains(primary.id)) { 505 for (var id in _primaries.map((node) => node.id)) {
Bob Nystrom 2014/05/08 20:30:48 Nit: Using map() here seems a bit gratuitous. Why
nweiz 2014/05/08 21:12:36 Done.
317 _emitPassThrough(); 506 if (_consumedPrimaries.contains(id) ||
318 } else { 507 _declaredOutputs.contains(id)) {
319 _dontEmitPassThrough(); 508 _dontEmitPassThrough(id);
509 } else {
510 _emitPassThrough(id);
511 }
512 }
320 } 513 }
321 } 514 }
322 515
323 _state = _State.APPLIED; 516 _state = _State.APPLIED;
324 _streams.changeStatus(NodeStatus.IDLE); 517 _streams.changeStatus(NodeStatus.IDLE);
325 }); 518 });
326 } 519 }
327 520
328 /// Gets the asset for an input [id]. 521 /// Gets the asset for an input [id].
329 /// 522 ///
330 /// If an input with [id] cannot be found, throws an [AssetNotFoundException]. 523 /// If an input with [id] cannot be found, throws an [AssetNotFoundException].
331 Future<Asset> getInput(AssetId id) { 524 Future<Asset> getInput(AssetId id) {
332 return phase.previous.getOutput(id).then((node) { 525 return phase.previous.getOutput(id).then((node) {
333 // Throw if the input isn't found. This ensures the transformer's apply 526 // Throw if the input isn't found. This ensures the transformer's apply
334 // is exited. We'll then catch this and report it through the proper 527 // is exited. We'll then catch this and report it through the proper
335 // results stream. 528 // results stream.
336 if (node == null) { 529 if (node == null) {
337 _missingInputs.add(id); 530 _missingInputs.add(id);
338 throw new AssetNotFoundException(id); 531 throw new AssetNotFoundException(id);
339 } 532 }
340 533
341 _inputSubscriptions.putIfAbsent(node.id, () { 534 _secondarySubscriptions.putIfAbsent(node.id, () {
342 return node.onStateChange.listen((state) => _dirty()); 535 return node.onStateChange.listen((_) => _dirty());
343 }); 536 });
344 537
345 return node.asset; 538 return node.asset;
346 }); 539 });
347 } 540 }
348 541
349 /// Run [Transformer.apply] as soon as [primary] is available. 542 /// Run [Transformer.apply] as soon as [primary] is available.
Bob Nystrom 2014/05/08 20:30:48 Update doc comment.
nweiz 2014/05/08 21:12:36 Done.
350 /// 543 ///
351 /// Returns whether or not an error occurred while running the transformer. 544 /// Returns whether or not an error occurred while running the transformer.
352 Future<bool> _runApply() { 545 Future<bool> _runApply() {
353 var controller = new AggregateTransformController(this); 546 var controller = new AggregateTransformController(this);
547 _applyController = controller;
354 _streams.onLogPool.add(controller.onLog); 548 _streams.onLogPool.add(controller.onLog);
549 for (var primary in _primaries) {
550 if (!primary.state.isAvailable) continue;
551 controller.addInput(primary.asset);
552 }
355 553
356 return primary.whenAvailable((_) { 554 return syncFuture(() {
357 if (_isRemoved) return null; 555 return transformer.apply(controller.transform);
358 _state = _State.APPLYING; 556 }).whenComplete(() {
359 controller.inputController.add(primary.asset); 557 controller.cancel();
Bob Nystrom 2014/05/08 20:30:48 Document that doing this deliberately even on succ
nweiz 2014/05/08 21:12:36 Done.
360 return newTransform(controller.transform).then((transform) { 558 _applyController = null;
361 return transformer.apply(transform);
362 });
363 }).then((_) { 559 }).then((_) {
364 if (!_forced && !primary.state.isAvailable) { 560 assert(_state != _State.DECLARED);
561 assert(_state != _State.APPLIED);
562
563 if (!_forced && _primaries.any((node) => !node.state.isAvailable)) {
365 _state = _State.DECLARED; 564 _state = _State.DECLARED;
366 _streams.changeStatus(NodeStatus.IDLE); 565 _streams.changeStatus(NodeStatus.IDLE);
367 return false; 566 return false;
368 } 567 }
369 568
370 if (_isRemoved) return false; 569 if (_isRemoved) return false;
371 if (_state == _State.NEEDS_APPLY) return false; 570 if (_state == _State.NEEDS_APPLY) return false;
372 if (_state == _State.DECLARING) return false; 571 if (_state == _State.NEEDS_DECLARE) return false;
Bob Nystrom 2014/05/08 20:30:48 What if _state is DECLARING? Can that still happen
nweiz 2014/05/08 21:12:36 There's no way to transition to DECLARING until `a
Bob Nystrom 2014/05/08 23:56:13 Add an assert above then?
nweiz 2014/05/19 21:11:09 Done.
nweiz 2014/05/19 21:11:09 Done.
373 if (controller.loggedError) return true; 572 if (controller.loggedError) return true;
374 _handleApplyResults(controller); 573 _handleApplyResults(controller);
375 return false; 574 return false;
376 }).catchError((error, stackTrace) { 575 }).catchError((error, stackTrace) {
377 // If the transform became dirty while processing, ignore any errors from 576 // If the transform became dirty while processing, ignore any errors from
378 // it. 577 // it.
379 if (_state == _State.NEEDS_APPLY || _isRemoved) return false; 578 if (_state == _State.NEEDS_APPLY || _isRemoved) return false;
380 579
381 // Catch all transformer errors and pipe them to the results stream. This 580 // Catch all transformer errors and pipe them to the results stream. This
382 // is so a broken transformer doesn't take down the whole graph. 581 // is so a broken transformer doesn't take down the whole graph.
383 phase.cascade.reportError(_wrapException(error, stackTrace)); 582 phase.cascade.reportError(_wrapException(error, stackTrace));
384 return true; 583 return true;
385 }); 584 });
386 } 585 }
387 586
388 /// Handle the results of running [Transformer.apply]. 587 /// Handle the results of running [Transformer.apply].
389 /// 588 ///
390 /// [controller] should be the controller for the [AggegateTransform] passed 589 /// [controller] should be the controller for the [AggegateTransform] passed
391 /// to [AggregateTransformer.apply]. 590 /// to [AggregateTransformer.apply].
392 void _handleApplyResults(AggregateTransformController controller) { 591 void _handleApplyResults(AggregateTransformController controller) {
393 _consumePrimary = controller.consumedPrimaries.contains(primary.id); 592 _consumedPrimaries = controller.consumedPrimaries;
394 593
395 var newOutputs = controller.outputs; 594 var newOutputs = controller.outputs;
396 // Any ids that are for a different package are invalid. 595 // Any ids that are for a different package are invalid.
397 var invalidIds = newOutputs 596 var invalidIds = newOutputs
398 .map((asset) => asset.id) 597 .map((asset) => asset.id)
399 .where((id) => id.package != phase.cascade.package) 598 .where((id) => id.package != phase.cascade.package)
400 .toSet(); 599 .toSet();
401 for (var id in invalidIds) { 600 for (var id in invalidIds) {
402 newOutputs.removeId(id); 601 newOutputs.removeId(id);
403 // TODO(nweiz): report this as a warning rather than a failing error. 602 // TODO(nweiz): report this as a warning rather than a failing error.
404 phase.cascade.reportError(new InvalidOutputException(info, id)); 603 phase.cascade.reportError(new InvalidOutputException(info, id));
405 } 604 }
406 605
407 // Remove outputs that used to exist but don't anymore. 606 // Remove outputs that used to exist but don't anymore.
408 for (var id in _outputControllers.keys.toList()) { 607 for (var id in _outputControllers.keys.toList()) {
409 if (newOutputs.containsId(id)) continue; 608 if (newOutputs.containsId(id)) continue;
410 _outputControllers.remove(id).setRemoved(); 609 _outputControllers.remove(id).setRemoved();
411 } 610 }
412 611
413 // Emit or stop emitting the pass-through asset between removing and 612 // Emit or stop emitting pass-through assets between removing and adding
414 // adding outputs to ensure there are no collisions. 613 // outputs to ensure there are no collisions.
415 if (!_consumePrimary && !newOutputs.containsId(primary.id)) { 614 for (var id in _primaries.map((node) => node.id)) {
416 _emitPassThrough(); 615 if (_consumedPrimaries.contains(id) || newOutputs.containsId(id)) {
417 } else { 616 _dontEmitPassThrough(id);
418 _dontEmitPassThrough(); 617 } else {
618 _emitPassThrough(id);
Bob Nystrom 2014/05/08 20:30:48 What do you think of: _emitPassThrough -> _passTh
nweiz 2014/05/08 21:12:36 Done.
619 }
419 } 620 }
420 621
421 // Store any new outputs or new contents for existing outputs. 622 // Store any new outputs or new contents for existing outputs.
422 for (var asset in newOutputs) { 623 for (var asset in newOutputs) {
423 var controller = _outputControllers[asset.id]; 624 var controller = _outputControllers[asset.id];
424 if (controller != null) { 625 if (controller != null) {
425 controller.setAvailable(asset); 626 controller.setAvailable(asset);
426 } else { 627 } else {
427 var controller = new AssetNodeController.available(asset, this); 628 var controller = new AssetNodeController.available(asset, this);
428 _outputControllers[asset.id] = controller; 629 _outputControllers[asset.id] = controller;
429 _streams.onAssetController.add(controller.node); 630 _streams.onAssetController.add(controller.node);
430 } 631 }
431 } 632 }
432 } 633 }
433 634
434 /// Cancels all subscriptions to secondary input nodes. 635 /// Cancels all subscriptions to secondary input nodes.
435 void _clearInputSubscriptions() { 636 void _clearSecondarySubscriptions() {
436 _missingInputs.clear(); 637 _missingInputs.clear();
437 for (var subscription in _inputSubscriptions.values) { 638 for (var subscription in _secondarySubscriptions.values) {
438 subscription.cancel(); 639 subscription.cancel();
439 } 640 }
440 _inputSubscriptions.clear(); 641 _secondarySubscriptions.clear();
441 } 642 }
442 643
443 /// Removes all output assets. 644 /// Removes all output assets.
444 void _clearOutputs() { 645 void _clearOutputs() {
445 // Remove all the previously-emitted assets. 646 // Remove all the previously-emitted assets.
446 for (var controller in _outputControllers.values) { 647 for (var controller in _outputControllers.values) {
447 controller.setRemoved(); 648 controller.setRemoved();
448 } 649 }
449 _outputControllers.clear(); 650 _outputControllers.clear();
450 } 651 }
451 652
452 /// Emit the pass-through asset if it's not being emitted already. 653 /// Emit the pass-through node for the primary input [id] if it's not being
453 void _emitPassThrough() { 654 /// emitted already.
454 assert(!_outputControllers.containsKey(primary.id)); 655 void _emitPassThrough(AssetId id) {
656 assert(!_outputControllers.containsKey(id));
455 657
456 if (_consumePrimary) return; 658 if (_consumedPrimaries.contains(id)) return;
457 if (_passThroughController == null) { 659 var controller = _passThroughControllers[id];
458 _passThroughController = new AssetNodeController.from(primary); 660 var primary = _primaries[id];
459 _streams.onAssetController.add(_passThroughController.node); 661 if (controller == null) {
662 controller = new AssetNodeController.from(primary);
663 _passThroughControllers[id] = controller;
664 _streams.onAssetController.add(controller.node);
460 } else if (primary.state.isDirty) { 665 } else if (primary.state.isDirty) {
461 _passThroughController.setDirty(); 666 controller.setDirty();
462 } else if (!_passThroughController.node.state.isAvailable) { 667 } else if (!controller.node.state.isAvailable) {
463 _passThroughController.setAvailable(primary.asset); 668 controller.setAvailable(primary.asset);
464 } 669 }
465 } 670 }
466 671
467 /// Stop emitting the pass-through asset if it's being emitted already. 672 /// Stops emitting the pass-through node for the primary input [id] if it's
468 void _dontEmitPassThrough() { 673 /// being emitted.
469 if (_passThroughController == null) return; 674 void _dontEmitPassThrough(AssetId id) {
470 _passThroughController.setRemoved(); 675 var controller = _passThroughControllers.remove(id);
471 _passThroughController = null; 676 if (controller == null) return;
677 controller.setRemoved();
678 }
679
680 /// If `declareOutputs` is running and all previous phases have declared their
681 /// outputs, mark [_declareController] as done.
682 void _maybeFinishDeclareController() {
683 if (_declareController == null) return;
684 if (phase.previous.status == NodeStatus.RUNNING) return;
685 _declareController.done();
686 }
687
688 /// If `apply` is running, all previous phases have declared their outputs,
689 /// and all primary inputs are available and thus have been passed to the
690 /// transformer, mark [_declareController] as done.
Bob Nystrom 2014/05/08 20:30:48 _applyController.
nweiz 2014/05/08 21:12:36 Done.
691 void _maybeFinishApplyController() {
692 if (_applyController == null) return;
693 if (_primaries.any((input) => !input.state.isAvailable)) return;
694 if (phase.previous.status == NodeStatus.RUNNING) return;
695 _applyController.done();
472 } 696 }
473 697
474 BarbackException _wrapException(error, StackTrace stackTrace) { 698 BarbackException _wrapException(error, StackTrace stackTrace) {
475 if (error is! AssetNotFoundException) { 699 if (error is! AssetNotFoundException) {
476 return new TransformerException(info, error, stackTrace); 700 return new TransformerException(info, error, stackTrace);
477 } else { 701 } else {
478 return new MissingInputException(info, error.id); 702 return new MissingInputException(info, error.id);
479 } 703 }
480 } 704 }
481 705
482 /// Emit a warning about the transformer on [id].
483 void _warn(String message) {
484 _streams.onLogController.add(
485 new LogEntry(info, primary.id, LogLevel.WARNING, message, null));
486 }
487
488 String toString() => 706 String toString() =>
489 "transform node in $_location for $transformer on $primary ($_state, " 707 "transform node in $_location for $transformer on ${info.primaryId} "
490 "$status, ${_forced ? '' : 'un'}forced)"; 708 "($_state, $status, ${_forced ? '' : 'un'}forced)";
491 } 709 }
492 710
493 /// The enum of states that [TransformNode] can be in. 711 /// The enum of states that [TransformNode] can be in.
494 class _State { 712 class _State {
495 /// The transform is running [DeclaringTransformer.declareOutputs]. 713 /// The transform is running [DeclaringAggregateTransformer.declareOutputs].
496 /// 714 ///
497 /// This is the initial state of the transformer, and it will only occur once 715 /// If the set of primary inputs changes while in this state, it will
Bob Nystrom 2014/05/08 20:30:48 Is this true of any change, or just modifications/
nweiz 2014/05/08 21:12:36 A new primary will only reset it if the id stream
498 /// since [DeclaringTransformer.declareOutputs] is independent of the contents 716 /// transition to [NEEDS_DECLARE]. If the [TransformNode] is still in this
499 /// of the primary input. Once the method finishes running, this will 717 /// state when `declareOutputs` finishes running, it will transition to
500 /// transition to [APPLYING] if the transform is non-lazy and the input is 718 /// [APPLYING] if the transform is non-lazy and all of its primary inputs are
501 /// available, and [DECLARED] otherwise. 719 /// available, and [DECLARED] otherwise.
502 /// 720 ///
503 /// Non-declaring transformers will transition out of this state and into 721 /// Non-declaring transformers will transition out of this state and into
504 /// [APPLYING] immediately. 722 /// [APPLYING] immediately.
505 static final DECLARING = const _State._("declaring outputs"); 723 static const DECLARING = const _State._("declaring outputs");
724
725 /// The transform is running [AggregateTransformer.declareOutputs] or
726 /// [AggregateTransform.apply], but a primary input was added or removed after
727 /// it started, so it will need to re-run `declareOutputs`.
728 ///
729 /// The [TransformNode] will transition to [DECLARING] once `declareOutputs`
730 /// or `apply` finishes running.
731 static const NEEDS_DECLARE = const _State._("needs declare");
506 732
507 /// The transform is deferred and has run 733 /// The transform is deferred and has run
508 /// [DeclaringTransformer.declareOutputs] but hasn't yet been forced. 734 /// [DeclaringAggregateTransformer.declareOutputs] but hasn't yet been forced.
509 /// 735 ///
510 /// This will transition to [APPLYING] when one of the outputs has been 736 /// The [TransformNode] will transition to [APPLYING] when one of the outputs
511 /// forced. 737 /// has been forced or if the transformer is non-lazy and all of its primary
512 static final DECLARED = const _State._("declared"); 738 /// inputs become available.
739 static const DECLARED = const _State._("declared");
513 740
514 /// The transform is running [Transformer.apply]. 741 /// The transform is running [AggregateTransformer.apply].
515 /// 742 ///
516 /// If an input changes while in this state, it will transition to 743 /// If an input's contents change or a secondary input is added or removed
517 /// [NEEDS_APPLY]. If the [TransformNode] is still in this state when 744 /// while in this state, the [TransformNode] will transition to [NEEDS_APPLY].
518 /// [Transformer.apply] finishes running, it will transition to [APPLIED]. 745 /// If a primary input is added or removed, it will transition to
519 static final APPLYING = const _State._("applying"); 746 /// [NEEDS_DECLARE]. If the it's still in this state when `apply` finishes
Bob Nystrom 2014/05/08 20:30:48 "the it's" -> "it's"
nweiz 2014/05/08 21:12:36 Done.
747 /// running, it will transition to [APPLIED].
748 static const APPLYING = const _State._("applying");
520 749
521 /// The transform is running [Transformer.apply], but an input changed after 750 /// The transform is running [AggregateTransformer.apply], but an input's
522 /// it started, so it will need to re-run [Transformer.apply]. 751 /// contents changed or a secondary input was added or removed after it
752 /// started, so it will need to re-run `apply`.
523 /// 753 ///
524 /// This will transition to [APPLYING] once [Transformer.apply] finishes 754 /// If a primary input is added or removed while in this state, the
525 /// running. 755 /// [TranformNode] will transition to [NEEDS_DECLARE]. If it's still in this
526 static final NEEDS_APPLY = const _State._("needs apply"); 756 /// state when `apply` finishes running, it will transition to [APPLYING].
757 static const NEEDS_APPLY = const _State._("needs apply");
527 758
528 /// The transform has finished running [Transformer.apply], whether or not it 759 /// The transform has finished running [AggregateTransformer.apply], whether
529 /// emitted an error. 760 /// or not it emitted an error.
530 /// 761 ///
531 /// If the transformer is deferred, the [TransformNode] can also be in this 762 /// If an input's contents change or a secondary input is added or removed,
532 /// state when [Transformer.declareOutputs] has been run but 763 /// the [TransformNode] will transition to [DECLARED] if the transform is
533 /// [Transformer.apply] has not. 764 /// declaring and [APPLYING] otherwise. If a primary input is added or
534 /// 765 /// removed, this will transition to [DECLARING].
535 /// If an input changes, this will transition to [DECLARED] if the transform 766 static const APPLIED = const _State._("applied");
536 /// is deferred and [APPLYING] otherwise.
537 static final APPLIED = const _State._("applied");
538 767
539 final String name; 768 final String name;
540 769
541 const _State._(this.name); 770 const _State._(this.name);
542 771
543 String toString() => name; 772 String toString() => name;
544 } 773 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698