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

Side by Side Diff: pkg/barback/lib/src/phase_input.dart

Issue 23363002: Factor out an input-handling class from Phase in barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes. Created 7 years, 4 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
« no previous file with comments | « pkg/barback/lib/src/phase.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library barback.phase_input;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'asset.dart';
11 import 'asset_forwarder.dart';
12 import 'asset_node.dart';
13 import 'errors.dart';
14 import 'stream_pool.dart';
15 import 'transform_node.dart';
16 import 'transformer.dart';
17 import 'utils.dart';
18
19 /// A class for watching a single [AssetNode] and running any transforms that
20 /// take that node as a primary input.
21 class PhaseInput {
22 /// The phase for which this is an input.
23 final Phase _phase;
24
25 /// The transformers to (potentially) run against [input].
26 final Set<Transformer> _transformers;
27
28 /// The transforms currently applicable to [input].
29 ///
30 /// These are the transforms that have been "wired up": they represent a
31 /// repeatable transformation of a single concrete set of inputs. "dart2js" is
32 /// a transformer. "dart2js on web/main.dart" is a transform.
33 final _transforms = new Set<TransformNode>();
34
35 /// A forwarder for the input [AssetNode] for this phase.
36 ///
37 /// This is used to mark the node as removed should the input ever be removed.
38 final AssetForwarder _inputForwarder;
39
40 /// The asset node for this input.
41 AssetNode get input => _inputForwarder.node;
42
43 /// The controller that's used for the output node if [input] isn't consumed
44 /// by any transformers.
45 ///
46 /// This needs an intervening controller to ensure that the output can be
47 /// marked dirty when determining whether transforms apply, and removed if
48 /// they do. It's null if the asset is not being passed through.
49 AssetNodeController _passThroughController;
50
51 /// Whether [_passThroughController] has been newly created since [process]
52 /// last completed.
53 bool _newPassThrough = false;
54
55 /// A Future that will complete once the transformers that consume [input] are
56 /// determined.
57 Future _adjustTransformersFuture;
58
59 /// A stream that emits an event whenever this input becomes dirty and needs
60 /// [process] to be called.
61 ///
62 /// This may emit events when the input was already dirty or while processing
63 /// transforms. Events are emitted synchronously to ensure that the dirty
64 /// state is thoroughly propagated as soon as any assets are changed.
65 Stream get onDirty => _onDirtyPool.stream;
66 final _onDirtyPool = new StreamPool.broadcast();
67
68 /// A controller whose stream feeds into [_onDirtyPool].
69 ///
70 /// This is used whenever the input is changed or removed. It's sometimes
71 /// redundant with the events collected from [_transforms], but this stream is
72 /// necessary for removed inputs, and the transform stream is necessary for
73 /// modified secondary inputs.
74 final _onDirtyController = new StreamController.broadcast(sync: true);
75
76 /// Whether this input is dirty and needs [process] to be called.
77 bool get isDirty => _adjustTransformersFuture != null ||
78 _newPassThrough || _transforms.any((transform) => transform.isDirty);
79
80 PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers)
81 : _transformers = transformers.toSet(),
82 _inputForwarder = new AssetForwarder(input) {
83 _onDirtyPool.add(_onDirtyController.stream);
84
85 input.onStateChange.listen((state) {
86 if (state.isRemoved) {
87 remove();
88 } else if (_adjustTransformersFuture == null) {
89 _adjustTransformers();
90 }
91 });
92
93 _adjustTransformers();
94 }
95
96 /// Removes this input.
97 ///
98 /// This marks all outputs of the input as removed.
99 void remove() {
100 _onDirtyController.add(null);
101 _onDirtyPool.close();
102 _inputForwarder.close();
103 if (_passThroughController != null) {
104 _passThroughController.setRemoved();
105 _passThroughController = null;
106 }
107 }
108
109 /// Set this input's transformers to [transformers].
110 void updateTransformers(Set<Transformer> newTransformers) {
111 var oldTransformers = _transformers.toSet();
112 for (var removedTransformer in
113 oldTransformers.difference(newTransformers)) {
114 _transformers.remove(removedTransformer);
115
116 // If the transformers are being adjusted for [id], it will
117 // automatically pick up on [removedTransformer] being gone.
118 if (_adjustTransformersFuture != null) continue;
119
120 _transforms.removeWhere((transform) {
121 if (transform.transformer != removedTransformer) return false;
122 transform.remove();
123 return true;
124 });
125 }
126
127 if (_transforms.isEmpty && _adjustTransformersFuture == null &&
128 _passThroughController == null) {
129 _passThroughController =
130 new AssetNodeController.available(input.asset, input.transform);
131 _newPassThrough = true;
132 }
133
134 var brandNewTransformers = newTransformers.difference(oldTransformers);
135 if (brandNewTransformers.isEmpty) return;
136
137 brandNewTransformers.forEach(_transformers.add);
138 _adjustTransformers();
139 }
140
141 /// Asynchronously determines which transformers can consume [input] as a
142 /// primary input and creates transforms for them.
143 ///
144 /// This ensures that if [input] is modified or removed during or after the
145 /// time it takes to adjust its transformers, they're appropriately
146 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture].
147 void _adjustTransformers() {
148 // Mark the input as dirty. This may not actually end up creating any new
149 // transforms, but we want adding or removing a source asset to consistently
150 // kick off a build, even if that build does nothing.
151 _onDirtyController.add(null);
152
153 // If there's a pass-through for this input, mark it dirty while we figure
154 // out whether we need to add any transforms for it.
155 if (_passThroughController != null) _passThroughController.setDirty();
156
157 // Once the input is available, hook up transformers for it. If it changes
158 // while that's happening, try again.
159 _adjustTransformersFuture = _tryUntilStable((asset, transformers) {
160 var oldTransformers =
161 _transforms.map((transform) => transform.transformer).toSet();
162
163 return _removeStaleTransforms(asset, transformers).then((_) =>
164 _addFreshTransforms(transformers, oldTransformers));
165 }).then((_) => _adjustPassThrough()).catchError((error) {
166 if (error is! AssetNotFoundException || error.id != input.id) {
167 throw error;
168 }
169
170 // If the asset is removed, [_tryUntilStable] will throw an
171 // [AssetNotFoundException]. In that case, just remove it.
172 remove();
173 }).whenComplete(() {
174 _adjustTransformersFuture = null;
175 });
176
177 // Don't top-level errors coming from the input processing. Any errors will
178 // eventually be piped through [process]'s returned Future.
179 _adjustTransformersFuture.catchError((_) {});
180 }
181
182 // Remove any old transforms that used to have [asset] as a primary asset but
183 // no longer apply to its new contents.
184 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
185 return Future.wait(_transforms.map((transform) {
186 return newFuture(() {
187 if (!transformers.contains(transform.transformer)) return false;
188
189 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
190 // results.
191 return transform.transformer.isPrimary(asset);
192 }).then((isPrimary) {
193 if (isPrimary) return;
194 _transforms.remove(transform);
195 transform.remove();
196 });
197 }));
198 }
199
200 // Add new transforms for transformers that consider [input]'s asset to be a
201 // primary input.
202 //
203 // [oldTransformers] is the set of transformers for which there were
204 // transforms that had [input] as a primary input prior to this. They don't
205 // need to be checked, since their transforms were removed or preserved in
206 // [_removeStaleTransforms].
207 Future _addFreshTransforms(Set<Transformer> transformers,
208 Set<Transformer> oldTransformers) {
209 return Future.wait(transformers.map((transformer) {
210 if (oldTransformers.contains(transformer)) return new Future.value();
211
212 // If the asset is unavailable, the results of this [_adjustTransformers]
213 // run will be discarded, so we can just short-circuit.
214 if (input.asset == null) return new Future.value();
215
216 // We can safely access [input.asset] here even though it might have
217 // changed since (as above) if it has, [_adjustTransformers] will just be
218 // re-run.
219 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
220 // results.
221 return transformer.isPrimary(input.asset).then((isPrimary) {
222 if (!isPrimary) return;
223 var transform = new TransformNode(_phase, transformer, input);
224 _transforms.add(transform);
225 _onDirtyPool.add(transform.onDirty);
226 });
227 }));
228 }
229
230 /// Adjust whether [input] is passed through the phase unmodified, based on
231 /// whether it's consumed by other transforms in this phase.
232 ///
233 /// If [input] was already passed-through, this will update the passed-through
234 /// value.
235 void _adjustPassThrough() {
236 assert(input.state.isAvailable);
237
238 if (_transforms.isEmpty) {
239 if (_passThroughController != null) {
240 _passThroughController.setAvailable(input.asset);
241 } else {
242 _passThroughController =
243 new AssetNodeController.available(input.asset, input.transform);
244 _newPassThrough = true;
245 }
246 } else if (_passThroughController != null) {
247 _passThroughController.setRemoved();
248 _passThroughController = null;
249 _newPassThrough = false;
250 }
251 }
252
253 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
254 /// phase's transformers are modified.
255 Future _tryUntilStable(
256 Future callback(Asset asset, Set<Transformer> transformers)) {
257 var oldTransformers;
258 return input.tryUntilStable((asset) {
259 oldTransformers = _transformers.toSet();
260 return callback(asset, _transformers);
261 }).then((result) {
262 if (setEquals(oldTransformers, _transformers)) return result;
263 return _tryUntilStable(callback);
264 });
265 }
266
267 /// Processes the transforms for this input.
268 Future<Set<AssetNode>> process() {
269 if (_adjustTransformersFuture == null) return _processTransforms();
270 return _waitForInputs().then((_) => _processTransforms());
271 }
272
273 Future _waitForInputs() {
274 // Return a synchronous future so we can be sure [_adjustTransformers] isn't
275 // called between now and when the Future completes.
276 if (_adjustTransformersFuture == null) return new Future.sync(() {});
277 return _adjustTransformersFuture.then((_) => _waitForInputs());
278 }
279
280 /// Applies all currently wired up and dirty transforms.
281 Future<Set<AssetNode>> _processTransforms() {
282 if (input.state.isRemoved) return new Future.value(new Set());
283
284 if (_passThroughController != null) {
285 if (!_newPassThrough) return new Future.value(new Set());
286 _newPassThrough = false;
287 return new Future.value(
288 new Set<AssetNode>.from([_passThroughController.node]));
289 }
290
291 return Future.wait(_transforms.map((transform) {
292 if (!transform.isDirty) return new Future.value(new Set());
293 return transform.apply();
294 })).then((outputs) => unionAll(outputs));
295 }
296 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/phase.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698