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

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: Fix a library name 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 necessary
20 /// transforms on that node.
Bob Nystrom 2013/08/21 19:47:38 Can you clarify here whether it runs transforms wh
nweiz 2013/08/21 20:33:51 Done.
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 /// Theis 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(Iterable<Transformer> transformers) {
111 var newTransformers = transformers.toSet();
112 var oldTransformers = _transformers.toSet();
113 for (var removedTransformer in
114 oldTransformers.difference(newTransformers)) {
115 _transformers.remove(removedTransformer);
116
117 // If the transformers are being adjusted for [id], it will
118 // automatically pick up on [removedTransformer] being gone.
119 if (_adjustTransformersFuture != null) continue;
120
121 _transforms.removeWhere((transform) {
122 if (transform.transformer != removedTransformer) return false;
123 transform.remove();
124 return true;
125 });
126 }
127
128 if (_transforms.isEmpty && _adjustTransformersFuture == null &&
129 _passThroughController == null) {
130 _passThroughController =
131 new AssetNodeController.available(input.asset, input.transform);
132 _newPassThrough = true;
133 }
134
135 var brandNewTransformers = newTransformers.difference(oldTransformers);
136 if (brandNewTransformers.isEmpty) return;
137
138 brandNewTransformers.forEach(_transformers.add);
139 _adjustTransformers();
140 }
141
142 /// Asynchronously determines which transformers can consume [input] as a
143 /// primary input and creates transforms for them.
144 ///
145 /// This ensures that if [input] is modified or removed during or after the
146 /// time it takes to adjust its transformers, they're appropriately
147 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture].
148 void _adjustTransformers() {
149 // Mark the input as dirty. This may not actually end up creating any new
150 // transforms, but we want adding or removing a source asset to consistently
151 // kick off a build, even if that build does nothing.
152 _onDirtyController.add(null);
153
154 // If there's a pass-through for this input, mark it dirty while we figure
155 // out whether we need to add any transforms for it.
156 if (_passThroughController != null) _passThroughController.setDirty();
157
158 // Once the input is available, hook up transformers for it. If it changes
159 // while that's happening, try again.
160 _adjustTransformersFuture = _tryUntilStable((asset, transformers) {
161 var oldTransformers =
162 _transforms.map((transform) => transform.transformer).toSet();
163
164 return _removeStaleTransforms(asset, transformers).then((_) =>
165 _addFreshTransforms(transformers, oldTransformers));
166 }).then((_) => _adjustPassThrough()).catchError((error) {
167 if (error is! AssetNotFoundException || error.id != input.id) {
168 throw error;
169 }
170
171 // If the asset is removed, [_tryUntilStable] will throw an
172 // [AssetNotFoundException]. In that case, just remove it.
173 remove();
174 }).whenComplete(() {
175 _adjustTransformersFuture = null;
176 });
177
178 // Don't top-level errors coming from the input processing. Any errors will
179 // eventually be piped through [process]'s returned Future.
180 _adjustTransformersFuture.catchError((_) {});
181 }
182
183 // Remove any old transforms that used to have [asset] as a primary asset but
184 // no longer apply to its new contents.
185 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
186 return Future.wait(_transforms.map((transform) {
187 return newFuture(() {
188 if (!transformers.contains(transform.transformer)) return false;
189
190 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
191 // results.
192 return transform.transformer.isPrimary(asset);
193 }).then((isPrimary) {
194 if (isPrimary) return;
195 _transforms.remove(transform);
196 transform.remove();
197 });
198 }));
199 }
200
201 // Add new transforms for transformers that consider [input]'s asset to be a
202 // primary input.
203 //
204 // [oldTransformers] is the set of transformers for which there were
205 // transforms that had [input] as a primary input prior to this. They don't
206 // need to be checked, since their transforms were removed or preserved in
207 // [_removeStaleTransforms].
208 Future _addFreshTransforms(Set<Transformer> transformers,
209 Set<Transformer> oldTransformers) {
210 return Future.wait(transformers.map((transformer) {
211 if (oldTransformers.contains(transformer)) return new Future.value();
212
213 // If the asset is unavailable, the results of this [_adjustTransformers]
214 // run will be discarded, so we can just short-circuit.
215 if (input.asset == null) return new Future.value();
216
217 // We can safely access [input.asset] here even though it might have
218 // changed since (as above) if it has, [_adjustTransformers] will just be
219 // re-run.
220 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
221 // results.
222 return transformer.isPrimary(input.asset).then((isPrimary) {
223 if (!isPrimary) return;
224 var transform = new TransformNode(_phase, transformer, input);
225 _transforms.add(transform);
226 _onDirtyPool.add(transform.onDirty);
227 });
228 }));
229 }
230
231 /// Adjust whether [input] is passed through the phase unmodified, based on
232 /// whether it's consumed by other transforms in this phase.
233 ///
234 /// If [input] was already passed-through, this will update the passed-through
235 /// value.
236 void _adjustPassThrough() {
237 assert(input.state.isAvailable);
238
239 if (_transforms.isEmpty) {
240 if (_passThroughController != null) {
241 _passThroughController.setAvailable(input.asset);
242 } else {
243 _passThroughController =
244 new AssetNodeController.available(input.asset, input.transform);
245 _newPassThrough = true;
246 }
247 } else if (_passThroughController != null) {
248 _passThroughController.setRemoved();
249 _passThroughController = null;
250 _newPassThrough = false;
251 }
252 }
253
254 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
255 /// phase's transformers are modified.
256 Future _tryUntilStable(
257 Future callback(Asset asset, Set<Transformer> transformers)) {
258 var oldTransformers;
259 return input.tryUntilStable((asset) {
260 oldTransformers = _transformers.toSet();
261 return callback(asset, _transformers);
262 }).then((result) {
263 if (setEquals(oldTransformers, _transformers)) return result;
264 return _tryUntilStable(callback);
265 });
266 }
267
268 /// Processes the transforms for this input.
269 Future<Set<AssetNode>> process() {
270 if (_adjustTransformersFuture == null) return _processTransforms();
271 return _waitForInputs().then((_) => _processTransforms());
272 }
273
274 Future _waitForInputs() {
275 // Return a synchronous future so we can be sure [_adjustTransformers] isn't
276 // called between now and when the Future completes.
277 if (_adjustTransformersFuture == null) return new Future.sync(() {});
278 return _adjustTransformersFuture.then((_) => _waitForInputs());
279 }
280
281 /// Applies all currently wired up and dirty transforms.
282 Future<Set<AssetNode>> _processTransforms() {
283 if (input.state.isRemoved) return new Future.value(new Set());
284
285 if (_passThroughController != null) {
286 if (!_newPassThrough) return new Future.value(new Set());
287 _newPassThrough = false;
288 return new Future.value(
289 new Set<AssetNode>.from([_passThroughController.node]));
290 }
291
292 return Future.wait(_transforms.map((transform) {
293 if (!transform.isDirty) return new Future.value(new Set());
294 return transform.apply();
295 })).then((outputs) => unionAll(outputs));
296 }
297 }
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