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

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

Issue 187263003: Move Barback to a more thoroughly push-based model. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 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.phase_input; 5 library barback.phase_input;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection';
9 8
10 import 'asset.dart';
11 import 'asset_forwarder.dart'; 9 import 'asset_forwarder.dart';
12 import 'asset_node.dart'; 10 import 'asset_node.dart';
13 import 'errors.dart'; 11 import 'errors.dart';
14 import 'log.dart'; 12 import 'log.dart';
15 import 'phase.dart'; 13 import 'phase.dart';
16 import 'stream_pool.dart'; 14 import 'stream_pool.dart';
17 import 'transform_node.dart'; 15 import 'transform_node.dart';
18 import 'transformer.dart'; 16 import 'transformer.dart';
19 import 'utils.dart'; 17 import 'utils.dart';
20 18
(...skipping 25 matching lines...) Expand all
46 AssetNode get input => _inputForwarder.node; 44 AssetNode get input => _inputForwarder.node;
47 45
48 /// The controller that's used for the output node if [input] isn't consumed 46 /// The controller that's used for the output node if [input] isn't consumed
49 /// by any transformers. 47 /// by any transformers.
50 /// 48 ///
51 /// This needs an intervening controller to ensure that the output can be 49 /// This needs an intervening controller to ensure that the output can be
52 /// marked dirty when determining whether transforms apply, and removed if 50 /// marked dirty when determining whether transforms apply, and removed if
53 /// they do. It's null if the asset is not being passed through. 51 /// they do. It's null if the asset is not being passed through.
54 AssetNodeController _passThroughController; 52 AssetNodeController _passThroughController;
55 53
56 /// Whether [_passThroughController] has been newly created since [process] 54 /// A stream that emits an event whenever [this] is no longer dirty.
57 /// last completed. 55 ///
58 bool _newPassThrough = false; 56 /// This is synchronous in order to guarantee that it will emit an event as
57 /// soon as [isDirty] flips from `true` to `false`.
58 Stream get onDone => _onDoneController.stream;
59 final _onDoneController = new StreamController.broadcast(sync: true);
59 60
60 /// A Future that will complete once the transformers that consume [input] are 61 /// A stream that emits any new assets emitted by [this].
61 /// determined. 62 ///
62 Future _adjustTransformersFuture; 63 /// Assets are emitted synchronously to ensure that any changes are thoroughly
64 /// propagated as soon as they occur.
65 Stream<AssetNode> get onAsset => _onAssetPool.stream;
66 final _onAssetPool = new StreamPool<AssetNode>();
63 67
64 /// A stream that emits an event whenever this input becomes dirty and needs 68 /// A controller for emitting assets.
65 /// [process] to be called.
66 /// 69 ///
67 /// This may emit events when the input was already dirty or while processing 70 /// This will be added to [_onAssetPool]. It's used to emit pass-through
68 /// transforms. Events are emitted synchronously to ensure that the dirty 71 /// assets.
69 /// state is thoroughly propagated as soon as any assets are changed. 72 final _onAssetController = new StreamController<AssetNode>(sync: true);
70 Stream get onDirty => _onDirtyPool.stream;
71 final _onDirtyPool = new StreamPool.broadcast();
72 73
73 /// A controller whose stream feeds into [_onDirtyPool]. 74 /// Whether [this] is dirty and still has more processing to do.
74 /// 75 bool get isDirty => _isAdjustingTransformers ||
75 /// This is used whenever the input is changed or removed. It's sometimes 76 _transforms.any((transform) => transform.isDirty);
76 /// redundant with the events collected from [_transforms], but this stream is
77 /// necessary for removed inputs, and the transform stream is necessary for
78 /// modified secondary inputs.
79 final _onDirtyController = new StreamController.broadcast(sync: true);
80 77
81 /// Whether this input is dirty and needs [process] to be called. 78 /// Whether [this] has been rmeoved.
82 bool get isDirty => _adjustTransformersFuture != null || 79 bool get _isRemoved => _onAssetController.isClosed;
83 _newPassThrough || _transforms.any((transform) => transform.isDirty); 80
81 /// Whether [input] has become dirty since [_adjustTransformers] last started
82 /// running.
83 bool _hasBecomeDirty = false;
84
85 /// Whether [_isAdjustingTransformers] is currently running.
86 bool _isAdjustingTransformers = false;
Bob Nystrom 2014/03/05 22:13:25 Instead of flags, maybe a state enum? Are all perm
nweiz 2014/03/06 00:29:08 I considered a state enum, but [_hasBecomeDirty] a
Bob Nystrom 2014/03/06 17:21:01 SGTM, though if you later find yourself adding ano
84 87
85 /// A stream that emits an event whenever any transforms that use [input] as 88 /// A stream that emits an event whenever any transforms that use [input] as
86 /// their primary input log an entry. 89 /// their primary input log an entry.
87 Stream<LogEntry> get onLog => _onLogPool.stream; 90 Stream<LogEntry> get onLog => _onLogPool.stream;
88 final _onLogPool = new StreamPool<LogEntry>.broadcast(); 91 final _onLogPool = new StreamPool<LogEntry>.broadcast();
89 92
90 PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers, 93 PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers,
91 this._location) 94 this._location)
92 : _transformers = transformers.toSet(), 95 : _transformers = transformers.toSet(),
93 _inputForwarder = new AssetForwarder(input) { 96 _inputForwarder = new AssetForwarder(input) {
94 _onDirtyPool.add(_onDirtyController.stream); 97 _onAssetPool.add(_onAssetController.stream);
95 98
96 input.onStateChange.listen((state) { 99 input.onStateChange.listen((state) {
97 if (state.isRemoved) { 100 if (state.isRemoved) {
98 remove(); 101 remove();
99 } else if (_adjustTransformersFuture == null) { 102 } else {
100 _adjustTransformers(); 103 _dirty();
101 } 104 }
102 }); 105 });
103 106
104 _adjustTransformers(); 107 _adjustTransformers();
105 } 108 }
106 109
107 /// Removes this input. 110 /// Removes this input.
108 /// 111 ///
109 /// This marks all outputs of the input as removed. 112 /// This marks all outputs of the input as removed.
110 void remove() { 113 void remove() {
111 _onDirtyController.add(null); 114 _onDoneController.close();
112 _onDirtyPool.close(); 115 _hasBecomeDirty = false;
116 _onAssetPool.close();
117 _onAssetController.close();
113 _onLogPool.close(); 118 _onLogPool.close();
114 _inputForwarder.close(); 119 _inputForwarder.close();
115 if (_passThroughController != null) { 120 if (_passThroughController != null) {
116 _passThroughController.setRemoved(); 121 _passThroughController.setRemoved();
117 _passThroughController = null; 122 _passThroughController = null;
118 } 123 }
119 } 124 }
120 125
126 /// Mark [this] as dirty and start re-running [_adjustTransformers] if
127 /// necessary.
128 void _dirty() {
129 // If there's a pass-through for this input, mark it dirty until we figure
130 // out whether we need to add any transforms for it.
131 if (_passThroughController != null) _passThroughController.setDirty();
132 _hasBecomeDirty = true;
133 if (!_isAdjustingTransformers) _adjustTransformers();
134 }
135
121 /// Set this input's transformers to [transformers]. 136 /// Set this input's transformers to [transformers].
122 void updateTransformers(Iterable<Transformer> newTransformersIterable) { 137 void updateTransformers(Iterable<Transformer> newTransformersIterable) {
123 var newTransformers = newTransformersIterable.toSet(); 138 var newTransformers = newTransformersIterable.toSet();
124 var oldTransformers = _transformers.toSet(); 139 var oldTransformers = _transformers.toSet();
125 for (var removedTransformer in 140 var removedTransformers = oldTransformers.difference(newTransformers);
126 oldTransformers.difference(newTransformers)) { 141 for (var removedTransformer in removedTransformers) {
127 _transformers.remove(removedTransformer); 142 _transformers.remove(removedTransformer);
128
129 // If the transformers are being adjusted for [id], it will
130 // automatically pick up on [removedTransformer] being gone.
131 if (_adjustTransformersFuture != null) continue;
132
133 _transforms.removeWhere((transform) {
134 if (transform.transformer != removedTransformer) return false;
135 transform.remove();
136 return true;
137 });
138 }
139
140 if (_transforms.isEmpty && _adjustTransformersFuture == null &&
141 _passThroughController == null) {
142 _passThroughController = new AssetNodeController.from(input);
143 _newPassThrough = true;
144 } 143 }
145 144
146 var brandNewTransformers = newTransformers.difference(oldTransformers); 145 var brandNewTransformers = newTransformers.difference(oldTransformers);
147 if (brandNewTransformers.isEmpty) return; 146 brandNewTransformers.forEach(_transformers.add);
148 147
149 brandNewTransformers.forEach(_transformers.add); 148 if (removedTransformers.isNotEmpty || brandNewTransformers.isNotEmpty) {
150 if (_adjustTransformersFuture == null) _adjustTransformers(); 149 _dirty();
150 }
151 } 151 }
152 152
153 /// Force all [LazyTransformer]s' transforms in this input to begin producing 153 /// Force all [LazyTransformer]s' transforms in this input to begin producing
154 /// concrete assets. 154 /// concrete assets.
155 void forceAllTransforms() { 155 void forceAllTransforms() {
156 for (var transform in _transforms) { 156 for (var transform in _transforms) {
157 transform.force(); 157 transform.force();
158 } 158 }
159 } 159 }
160 160
161 /// Asynchronously determines which transformers can consume [input] as a 161 /// Asynchronously determines which transformers can consume [input] as a
162 /// primary input and creates transforms for them. 162 /// primary input and creates transforms for them.
163 /// 163 ///
164 /// This ensures that if [input] is modified or removed during or after the 164 /// This ensures that if [input] is modified or removed during or after the
165 /// time it takes to adjust its transformers, they're appropriately 165 /// time it takes to adjust its transformers, they're appropriately
166 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. 166 /// re-adjusted.
167 void _adjustTransformers() { 167 void _adjustTransformers() {
168 // Mark the input as dirty. This may not actually end up creating any new 168 assert(!_isRemoved);
169 // transforms, but we want adding or removing a source asset to consistently
170 // kick off a build, even if that build does nothing.
171 _onDirtyController.add(null);
172 169
173 // If there's a pass-through for this input, mark it dirty while we figure 170 _isAdjustingTransformers = true;
174 // out whether we need to add any transforms for it. 171 input.whenAvailable((asset) {
175 if (_passThroughController != null) _passThroughController.setDirty(); 172 _hasBecomeDirty = false;
176 173
177 // Once the input is available, hook up transformers for it. If it changes 174 // Take a snapshot of the existing transformers that apply to this input.
178 // while that's happening, try again. 175 // Since [_removeStaleTransforms] will check each of these transformers to
179 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { 176 // be sure [input] is still primary for them, we use this set to avoid
177 // needlessly re-checking in [_addFreshTransforms].
180 var oldTransformers = 178 var oldTransformers =
181 _transforms.map((transform) => transform.transformer).toSet(); 179 _transforms.map((transform) => transform.transformer).toSet();
182 180
183 return _removeStaleTransforms(asset, transformers).then((_) => 181 return _removeStaleTransforms().then((_) {
184 _addFreshTransforms(transformers, oldTransformers)); 182 if (_hasBecomeDirty || _isRemoved) return null;
185 }).then((_) => _adjustPassThrough()).catchError((error) { 183 return _addFreshTransforms(oldTransformers);
186 if (error is! AssetNotFoundException || error.id != input.id) { 184 });
187 throw error; 185 }).then((_) {
188 } 186 if (_hasBecomeDirty || _isRemoved) return null;
187 _adjustPassThrough();
188 }).catchError((error, stackTrace) {
189 if (error is! AssetNotFoundException || error.id != input.id) throw error;
189 190
190 // If the asset is removed, [_tryUntilStable] will throw an 191 // If the asset is removed, [input.whenAvailable] will throw an
191 // [AssetNotFoundException]. In that case, just remove it. 192 // [AssetNotFoundException]. In that case, just remove it.
192 remove(); 193 remove();
193 }).whenComplete(() { 194 }).then((_) {
194 _adjustTransformersFuture = null; 195 if (_isRemoved) return;
196
197 _isAdjustingTransformers = false;
198 if (_hasBecomeDirty) {
199 _adjustTransformers();
200 } else if (!isDirty) {
201 _onDoneController.add(null);
202 }
195 }); 203 });
196 } 204 }
197 205
198 // Remove any old transforms that used to have [asset] as a primary asset but 206 // Remove any old transforms that used to have [input]'s asset as a primary
199 // no longer apply to its new contents. 207 // asset but no longer apply to its new contents.
200 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) { 208 Future _removeStaleTransforms() {
209 assert(input.state.isAvailable);
210
201 return Future.wait(_transforms.map((transform) { 211 return Future.wait(_transforms.map((transform) {
202 return newFuture(() { 212 return syncFuture(() {
203 if (!transformers.contains(transform.transformer)) return false; 213 if (!_transformers.contains(transform.transformer)) return false;
204 214
205 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to 215 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
206 // results (issue 16162). 216 // results (issue 16162).
207 return transform.transformer.isPrimary(asset); 217 return transform.transformer.isPrimary(input.asset);
208 }).then((isPrimary) { 218 }).then((isPrimary) {
209 if (isPrimary) return; 219 if (_hasBecomeDirty) return;
210 _transforms.remove(transform); 220 if (isPrimary) {
211 transform.remove(); 221 transform.markPrimary();
222 } else if (_transforms.remove(transform)) {
223 transform.remove();
224 }
212 }); 225 });
213 })); 226 }));
214 } 227 }
215 228
216 // Add new transforms for transformers that consider [input]'s asset to be a 229 // Add new transforms for transformers that consider [input]'s asset to be a
217 // primary input. 230 // primary input.
218 // 231 //
219 // [oldTransformers] is the set of transformers for which there were 232 // [oldTransformers] is the set of transformers for which there were
220 // transforms that had [input] as a primary input prior to this. They don't 233 // transforms that had [input] as a primary input prior to this. They don't
221 // need to be checked, since their transforms were removed or preserved in 234 // need to be checked, since their transforms were removed or preserved in
222 // [_removeStaleTransforms]. 235 // [_removeStaleTransforms].
223 Future _addFreshTransforms(Set<Transformer> transformers, 236 Future _addFreshTransforms(Set<Transformer> oldTransformers) {
224 Set<Transformer> oldTransformers) { 237 assert(input.state.isAvailable);
225 return Future.wait(transformers.map((transformer) { 238
239 return Future.wait(_transformers.map((transformer) {
226 if (oldTransformers.contains(transformer)) return new Future.value(); 240 if (oldTransformers.contains(transformer)) return new Future.value();
227 241
228 // If the asset is unavailable, the results of this [_adjustTransformers]
229 // run will be discarded, so we can just short-circuit.
230 if (input.asset == null) return new Future.value();
231
232 // We can safely access [input.asset] here even though it might have
233 // changed since (as above) if it has, [_adjustTransformers] will just be
234 // re-run.
235 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to 242 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
236 // results. 243 // results.
237 return transformer.isPrimary(input.asset).then((isPrimary) { 244 return transformer.isPrimary(input.asset).then((isPrimary) {
238 if (!isPrimary) return; 245 if (_hasBecomeDirty || !isPrimary) return;
239 var transform = new TransformNode( 246 var transform = new TransformNode(
240 _phase, transformer, input, _location); 247 _phase, transformer, input, _location);
241 _transforms.add(transform); 248 _transforms.add(transform);
242 _onDirtyPool.add(transform.onDirty); 249 _onAssetPool.add(transform.onAsset);
243 _onLogPool.add(transform.onLog); 250 _onLogPool.add(transform.onLog);
251 transform.onDone.listen((_) {
252 if (!isDirty) _onDoneController.add(null);
253 }, onDone: () => _transforms.remove(transform));
244 }); 254 });
245 })); 255 }));
246 } 256 }
247 257
248 /// Adjust whether [input] is passed through the phase unmodified, based on 258 /// Adjust whether [input] is passed through the phase unmodified, based on
249 /// whether it's consumed by other transforms in this phase. 259 /// whether it's consumed by other transforms in this phase.
250 /// 260 ///
251 /// If [input] was already passed-through, this will update the passed-through 261 /// If [input] was already passed-through, this will update the passed-through
252 /// value. 262 /// value.
253 void _adjustPassThrough() { 263 void _adjustPassThrough() {
254 assert(input.state.isAvailable); 264 assert(input.state.isAvailable);
255 265
256 if (_transforms.isEmpty) { 266 if (_transforms.isEmpty) {
257 if (_passThroughController != null) { 267 if (_passThroughController != null) {
258 _passThroughController.setAvailable(input.asset); 268 _passThroughController.setAvailable(input.asset);
259 } else { 269 } else {
260 _passThroughController = new AssetNodeController.from(input); 270 _passThroughController = new AssetNodeController.from(input);
261 _newPassThrough = true; 271 _onAssetController.add(_passThroughController.node);
262 } 272 }
263 } else if (_passThroughController != null) { 273 } else if (_passThroughController != null) {
264 _passThroughController.setRemoved(); 274 _passThroughController.setRemoved();
265 _passThroughController = null; 275 _passThroughController = null;
266 _newPassThrough = false;
267 } 276 }
268 } 277 }
269 278
270 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
271 /// phase's transformers are modified.
272 Future _tryUntilStable(
273 Future callback(Asset asset, Set<Transformer> transformers)) {
274 var oldTransformers;
275 return input.tryUntilStable((asset) {
276 oldTransformers = _transformers.toSet();
277 return callback(asset, _transformers);
278 }).then((result) {
279 if (setEquals(oldTransformers, _transformers)) return result;
280 return _tryUntilStable(callback);
281 });
282 }
283
284 /// Processes the transforms for this input.
285 ///
286 /// Returns the set of newly-created asset nodes that transforms have emitted
287 /// for this input. The assets returned this way are guaranteed not to be
288 /// [AssetState.REMOVED].
289 Future<Set<AssetNode>> process() {
290 return _waitForTransformers(() => _processTransforms()).then((outputs) {
291 if (input.state.isRemoved) return new Set();
292 return outputs;
293 });
294 }
295
296 /// Runs [callback] once all the transformers are adjusted correctly and the
297 /// input is ready to be processed.
298 ///
299 /// If the transformers are already properly adjusted, [callback] is called
300 /// synchronously to ensure that [_adjustTransformers] isn't called before the
301 /// callback.
302 Future _waitForTransformers(callback()) {
303 if (_adjustTransformersFuture == null) return syncFuture(callback);
304 return _adjustTransformersFuture.then(
305 (_) => _waitForTransformers(callback));
306 }
307
308 /// Applies all currently wired up and dirty transforms.
309 Future<Set<AssetNode>> _processTransforms() {
310 if (input.state.isRemoved) return new Future.value(new Set());
311
312 if (_passThroughController != null) {
313 if (!_newPassThrough) return new Future.value(new Set());
314 _newPassThrough = false;
315 return new Future.value(
316 new Set<AssetNode>.from([_passThroughController.node]));
317 }
318
319 return Future.wait(_transforms.map((transform) {
320 if (!transform.isDirty) return new Future.value(new Set());
321 return transform.apply();
322 })).then((outputs) => unionAll(outputs));
323 }
324
325 String toString() => "phase input in $_location for $input"; 279 String toString() => "phase input in $_location for $input";
326 } 280 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698