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

Side by Side Diff: pkg/barback/lib/src/phase.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; 5 library barback.phase;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'asset_cascade.dart'; 9 import 'asset_cascade.dart';
10 import 'asset_id.dart'; 10 import 'asset_id.dart';
(...skipping 27 matching lines...) Expand all
38 38
39 /// A string describing the location of [this] in the transformer graph. 39 /// A string describing the location of [this] in the transformer graph.
40 final String _location; 40 final String _location;
41 41
42 /// The index of [this] in its parent cascade or group. 42 /// The index of [this] in its parent cascade or group.
43 final int _index; 43 final int _index;
44 44
45 /// The transformers that can access [inputs]. 45 /// The transformers that can access [inputs].
46 /// 46 ///
47 /// Their outputs will be available to the next phase. 47 /// Their outputs will be available to the next phase.
48 final Set<Transformer> _transformers; 48 final _transformers = new Set<Transformer>();
49 49
50 /// The groups for this phase. 50 /// The groups for this phase.
51 final _groups = new Map<TransformerGroup, GroupRunner>(); 51 final _groups = new Map<TransformerGroup, GroupRunner>();
52 52
53 /// The inputs for this phase. 53 /// The inputs for this phase.
54 /// 54 ///
55 /// For the first phase, these will be the source assets. For all other 55 /// For the first phase, these will be the source assets. For all other
56 /// phases, they will be the outputs from the previous phase. 56 /// phases, they will be the outputs from the previous phase.
57 final _inputs = new Map<AssetId, PhaseInput>(); 57 final _inputs = new Map<AssetId, PhaseInput>();
58 58
(...skipping 11 matching lines...) Expand all
70 /// that input isn't consumed by any transformers, it will be forwarded 70 /// that input isn't consumed by any transformers, it will be forwarded
71 /// through the PhaseInput. However, it's possible that it was consumed by a 71 /// through the PhaseInput. However, it's possible that it was consumed by a
72 /// group, and so shouldn't be forwarded through the phase as a whole. 72 /// group, and so shouldn't be forwarded through the phase as a whole.
73 /// 73 ///
74 /// In order to detect whether an output has been forwarded through a group or 74 /// In order to detect whether an output has been forwarded through a group or
75 /// a PhaseInput, we must be able to distinguish it from other outputs with 75 /// a PhaseInput, we must be able to distinguish it from other outputs with
76 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If 76 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If
77 /// so, it's been forwarded unmodified. 77 /// so, it's been forwarded unmodified.
78 final _inputOrigins = new Multiset<AssetNode>(); 78 final _inputOrigins = new Multiset<AssetNode>();
79 79
80 /// A stream that emits an event whenever this phase becomes dirty and needs 80 /// A stream that emits an event whenever [this] is no longer dirty.
81 /// to be run.
82 /// 81 ///
83 /// This may emit events when the phase was already dirty or while processing 82 /// This is synchronous in order to guarantee that it will emit an event as
84 /// transforms. Events are emitted synchronously to ensure that the dirty 83 /// soon as [isDirty] flips from `true` to `false`.
85 /// state is thoroughly propagated as soon as any assets are changed. 84 Stream get onDone => _onDoneController.stream;
86 Stream get onDirty => _onDirtyPool.stream; 85 final _onDoneController = new StreamController.broadcast(sync: true);
87 final _onDirtyPool = new StreamPool.broadcast();
88 86
89 /// A controller whose stream feeds into [_onDirtyPool]. 87 /// A stream that emits any new assets emitted by [this].
90 /// 88 ///
91 /// This is used whenever an input is added or transforms are changed. 89 /// Assets are emitted synchronously to ensure that any changes are thoroughly
92 final _onDirtyController = new StreamController.broadcast(sync: true); 90 /// propagated as soon as they occur. Only a phase with no [next] phase will
91 /// emit assets.
92 Stream<AssetNode> get onAsset => _onAssetController.stream;
93 final _onAssetController = new StreamController<AssetNode>(sync: true);
93 94
94 /// Whether this phase is dirty and needs to be run. 95 /// Whether [this] is dirty and still has more processing to do.
95 bool get isDirty => _inputs.values.any((input) => input.isDirty) || 96 bool get isDirty => _inputs.values.any((input) => input.isDirty) ||
96 _groups.values.any((group) => group.isDirty); 97 _groups.values.any((group) => group.isDirty);
97 98
98 /// A stream that emits an event whenever any transforms in this phase logs 99 /// A stream that emits an event whenever any transforms in this phase logs
99 /// an entry. 100 /// an entry.
100 Stream<LogEntry> get onLog => _onLogPool.stream; 101 Stream<LogEntry> get onLog => _onLogPool.stream;
101 final _onLogPool = new StreamPool<LogEntry>.broadcast(); 102 final _onLogPool = new StreamPool<LogEntry>.broadcast();
102 103
103 /// The phase after this one. 104 /// The phase after this one.
104 /// 105 ///
105 /// Outputs from this phase will be passed to it. 106 /// Outputs from this phase will be passed to it.
106 Phase get next => _next; 107 Phase get next => _next;
107 Phase _next; 108 Phase _next;
108 109
109 /// Returns all currently-available output assets for this phase. 110 /// Returns all currently-available output assets for this phase.
110 Set<AssetNode> get availableOutputs { 111 Set<AssetNode> get availableOutputs {
111 return _outputs.values 112 return _outputs.values
112 .map((output) => output.output) 113 .map((output) => output.output)
113 .where((node) => node.state.isAvailable) 114 .where((node) => node.state.isAvailable)
114 .toSet(); 115 .toSet();
115 } 116 }
116 117
117 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, 118 // TODO(nweiz): Rather than passing the cascade and the phase everywhere,
118 // create an interface that just exposes [getInput]. Emit errors via 119 // create an interface that just exposes [getInput]. Emit errors via
119 // [AssetNode]s. 120 // [AssetNode]s.
120 Phase(AssetCascade cascade, Iterable transformers, String location) 121 Phase(AssetCascade cascade, String location)
121 : this._(cascade, transformers, location, 0); 122 : this._(cascade, location, 0);
122 123
123 Phase._(this.cascade, Iterable transformers, this._location, this._index) 124 Phase._(this.cascade, this._location, this._index);
124 : _transformers = transformers.where((op) => op is Transformer).toSet() {
125 _onDirtyPool.add(_onDirtyController.stream);
126
127 for (var group in transformers.where((op) => op is TransformerGroup)) {
128 var runner = new GroupRunner(cascade, group, "$_location.$_index");
129 _groups[group] = runner;
130 _onDirtyPool.add(runner.onDirty);
131 _onLogPool.add(runner.onLog);
132 }
133 }
134 125
135 /// Adds a new asset as an input for this phase. 126 /// Adds a new asset as an input for this phase.
136 /// 127 ///
137 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase 128 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase
138 /// will automatically begin determining which transforms can consume it as a 129 /// will automatically begin determining which transforms can consume it as a
139 /// primary input. The transforms themselves won't be applied until [process] 130 /// primary input. The transforms themselves won't be applied until [process]
140 /// is called, however. 131 /// is called, however.
141 /// 132 ///
142 /// This should only be used for brand-new assets or assets that have been 133 /// This should only be used for brand-new assets or assets that have been
143 /// removed and re-created. The phase will automatically handle updated assets 134 /// removed and re-created. The phase will automatically handle updated assets
144 /// using the [AssetNode.onStateChange] stream. 135 /// using the [AssetNode.onStateChange] stream.
145 void addInput(AssetNode node) { 136 void addInput(AssetNode node) {
146 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); 137 if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
147 138
148 node.force(); 139 node.force();
149 140
150 // Each group is one channel along which an asset may be forwarded. Then 141 // Each group is one channel along which an asset may be forwarded. Then
151 // there's one additional channel for the non-grouped transformers. 142 // there's one additional channel for the non-grouped transformers.
152 var forwarder = new PhaseForwarder(_groups.length + 1); 143 var forwarder = new PhaseForwarder(_groups.length + 1);
153 _forwarders[node.id] = forwarder; 144 _forwarders[node.id] = forwarder;
154 forwarder.onForwarding.listen((asset) { 145 forwarder.onAsset.listen(_handleOutputWithoutForwarder);
155 _addOutput(asset);
156
157 var exception = _outputs[asset.id].collisionException;
158 if (exception != null) cascade.reportError(exception);
159 });
160 146
161 _inputOrigins.add(node.origin); 147 _inputOrigins.add(node.origin);
162 var input = new PhaseInput(this, node, _transformers, "$_location.$_index"); 148 var input = new PhaseInput(this, node, _transformers, "$_location.$_index");
163 _inputs[node.id] = input; 149 _inputs[node.id] = input;
164 input.input.whenRemoved(() { 150 input.input.whenRemoved(() {
165 _inputOrigins.remove(node.origin); 151 _inputOrigins.remove(node.origin);
166 _inputs.remove(node.id); 152 _inputs.remove(node.id);
167 _forwarders.remove(node.id).remove(); 153 _forwarders.remove(node.id).remove();
154 if (!isDirty) _onDoneController.add(null);
168 }); 155 });
169 _onDirtyPool.add(input.onDirty); 156 input.onAsset.listen(_handleOutput);
170 _onDirtyController.add(null);
171 _onLogPool.add(input.onLog); 157 _onLogPool.add(input.onLog);
158 input.onDone.listen((_) {
159 if (!isDirty) _onDoneController.add(isDirty);
Bob Nystrom 2014/03/05 22:13:25 .add(null);
nweiz 2014/03/06 00:29:08 Done.
160 });
172 161
173 for (var group in _groups.values) { 162 for (var group in _groups.values) {
174 group.addInput(node); 163 group.addInput(node);
175 } 164 }
176 } 165 }
177 166
178 /// Gets the asset node for an input [id]. 167 /// Gets the asset node for an input [id].
179 /// 168 ///
180 /// If an input with that ID cannot be found, returns null. 169 /// If an input with that ID cannot be found, returns null.
181 Future<AssetNode> getInput(AssetId id) { 170 Future<AssetNode> getInput(AssetId id) {
(...skipping 12 matching lines...) Expand all
194 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 183 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
195 if (!_outputs.containsKey(id)) return null; 184 if (!_outputs.containsKey(id)) return null;
196 var output = _outputs[id].output; 185 var output = _outputs[id].output;
197 output.force(); 186 output.force();
198 return output; 187 return output;
199 }); 188 });
200 } 189 }
201 190
202 /// Set this phase's transformers to [transformers]. 191 /// Set this phase's transformers to [transformers].
203 void updateTransformers(Iterable transformers) { 192 void updateTransformers(Iterable transformers) {
204 _onDirtyController.add(null);
205
206 var actualTransformers = transformers.where((op) => op is Transformer); 193 var actualTransformers = transformers.where((op) => op is Transformer);
207 _transformers.clear(); 194 _transformers.clear();
208 _transformers.addAll(actualTransformers); 195 _transformers.addAll(actualTransformers);
209 for (var input in _inputs.values) { 196 for (var input in _inputs.values) {
210 input.updateTransformers(actualTransformers); 197 input.updateTransformers(actualTransformers);
211 } 198 }
212 199
213 var newGroups = transformers.where((op) => op is TransformerGroup) 200 var newGroups = transformers.where((op) => op is TransformerGroup)
214 .toSet(); 201 .toSet();
215 var oldGroups = _groups.keys.toSet(); 202 var oldGroups = _groups.keys.toSet();
216 for (var removed in oldGroups.difference(newGroups)) { 203 for (var removed in oldGroups.difference(newGroups)) {
217 _groups.remove(removed).remove(); 204 _groups.remove(removed).remove();
218 } 205 }
219 206
220 for (var added in newGroups.difference(oldGroups)) { 207 for (var added in newGroups.difference(oldGroups)) {
221 var runner = new GroupRunner(cascade, added, "$_location.$_index"); 208 var runner = new GroupRunner(cascade, added, "$_location.$_index");
222 _groups[added] = runner; 209 _groups[added] = runner;
223 _onDirtyPool.add(runner.onDirty); 210 runner.onAsset.listen(_handleOutput);
224 _onLogPool.add(runner.onLog); 211 _onLogPool.add(runner.onLog);
212 runner.onDone.listen((_) {
213 if (!isDirty) _onDoneController.add(null);
214 });
225 for (var input in _inputs.values) { 215 for (var input in _inputs.values) {
226 runner.addInput(input.input); 216 runner.addInput(input.input);
227 } 217 }
228 } 218 }
229 219
230 for (var forwarder in _forwarders.values) { 220 for (var forwarder in _forwarders.values) {
231 forwarder.numChannels = _groups.length + 1; 221 forwarder.numChannels = _groups.length + 1;
232 } 222 }
233 } 223 }
234 224
235 /// Force all [LazyTransformer]s' transforms in this phase to begin producing 225 /// Force all [LazyTransformer]s' transforms in this phase to begin producing
236 /// concrete assets. 226 /// concrete assets.
237 void forceAllTransforms() { 227 void forceAllTransforms() {
238 for (var group in _groups.values) { 228 for (var group in _groups.values) {
239 group.forceAllTransforms(); 229 group.forceAllTransforms();
240 } 230 }
241 231
242 for (var input in _inputs.values) { 232 for (var input in _inputs.values) {
243 input.forceAllTransforms(); 233 input.forceAllTransforms();
244 } 234 }
245 } 235 }
246 236
247 /// Add a new phase after this one with [transformers]. 237 /// Add a new phase after this one.
248 /// 238 ///
249 /// This may only be called on a phase with no phase following it. 239 /// This may only be called on a phase with no phase following it.
250 Phase addPhase(Iterable transformers) { 240 Phase addPhase() {
251 assert(_next == null); 241 assert(_next == null);
252 _next = new Phase._(cascade, transformers, _location, _index + 1); 242 _next = new Phase._(cascade, _location, _index + 1);
253 for (var output in _outputs.values.toList()) { 243 for (var output in _outputs.values.toList()) {
254 // Remove [output]'s listeners because now they should get the asset from 244 // Remove [output]'s listeners because now they should get the asset from
255 // [_next], rather than this phase. Any transforms consuming [output] will 245 // [_next], rather than this phase. Any transforms consuming [output] will
256 // be re-run and will consume the output from the new final phase. 246 // be re-run and will consume the output from the new final phase.
257 output.removeListeners(); 247 output.removeListeners();
258 } 248 }
259 return _next; 249 return _next;
260 } 250 }
261 251
262 /// Mark this phase as removed. 252 /// Mark this phase as removed.
263 /// 253 ///
264 /// This will remove all the phase's outputs and all following phases. 254 /// This will remove all the phase's outputs and all following phases.
265 void remove() { 255 void remove() {
266 removeFollowing(); 256 removeFollowing();
267 for (var input in _inputs.values.toList()) { 257 for (var input in _inputs.values.toList()) {
268 input.remove(); 258 input.remove();
269 } 259 }
270 for (var group in _groups.values) { 260 for (var group in _groups.values) {
271 group.remove(); 261 group.remove();
272 } 262 }
273 _onDirtyPool.close(); 263 _onAssetController.close();
274 _onLogPool.close(); 264 _onLogPool.close();
275 } 265 }
276 266
277 /// Remove all phases after this one. 267 /// Remove all phases after this one.
278 void removeFollowing() { 268 void removeFollowing() {
279 if (_next == null) return; 269 if (_next == null) return;
280 _next.remove(); 270 _next.remove();
281 _next = null; 271 _next = null;
282 } 272 }
283 273
284 /// Processes this phase. 274 /// Add [asset] as an output of this phase.
285 /// 275 void _handleOutput(AssetNode asset) {
286 /// Returns a future that completes when processing is done. If there is 276 if (_inputOrigins.contains(asset.origin)) {
287 /// nothing to process, returns `null`. 277 _forwarders[asset.id].addIntermediateAsset(asset);
288 Future process() { 278 } else {
289 if (!isDirty) return null; 279 _handleOutputWithoutForwarder(asset);
290
291 var outputIds = new Set<AssetId>();
292 void _handleOutputs(Set<AssetNode> outputs) {
293 for (var asset in outputs) {
294 if (_inputOrigins.contains(asset.origin)) {
295 _forwarders[asset.id].addIntermediateAsset(asset);
296 continue;
297 }
298
299 outputIds.add(asset.id);
300 _addOutput(asset);
301 }
302 } 280 }
303
304 var outputFutures = [];
305 outputFutures.addAll(_inputs.values.map((input) {
306 if (!input.isDirty) return new Future.value(new Set());
307 return input.process().then(_handleOutputs);
308 }));
309 outputFutures.addAll(_groups.values.map((group) {
310 if (!group.isDirty) return new Future.value(new Set());
311 return group.process().then(_handleOutputs);
312 }));
313
314 return Future.wait(outputFutures).then((_) {
315 // Report collisions in a deterministic order.
316 outputIds = outputIds.toList();
317 outputIds.sort((a, b) => a.compareTo(b));
318 for (var id in outputIds) {
319 // It's possible the output was removed before other transforms in this
320 // phase finished.
321 if (!_outputs.containsKey(id)) continue;
322 var exception = _outputs[id].collisionException;
323 if (exception != null) cascade.reportError(exception);
324 }
325 });
326 } 281 }
327 282
328 /// Add [asset] as an output of this phase. 283 /// Add [asset] as an output of this phase without checking if it's a
329 void _addOutput(AssetNode asset) { 284 /// forwarded asset.
285 void _handleOutputWithoutForwarder(AssetNode asset) {
330 if (_outputs.containsKey(asset.id)) { 286 if (_outputs.containsKey(asset.id)) {
331 _outputs[asset.id].add(asset); 287 _outputs[asset.id].add(asset);
332 } else { 288 } else {
333 _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index"); 289 _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index");
334 _outputs[asset.id].onAsset.listen((output) { 290 _outputs[asset.id].onAsset.listen(_emit,
335 if (_next != null) _next.addInput(output); 291 onDone: () => _outputs.remove(asset.id));
336 }, onDone: () => _outputs.remove(asset.id)); 292 _emit(_outputs[asset.id].output);
337 if (_next != null) _next.addInput(_outputs[asset.id].output); 293 }
294
295 var exception = _outputs[asset.id].collisionException;
296 if (exception != null) cascade.reportError(exception);
297 }
298
299 /// Emit [asset] as an output of this phase.
300 ///
301 /// This should be called after [_handleOutput], so that collisions are
302 /// resolved.
303 void _emit(AssetNode asset) {
304 if (_next != null) {
305 _next.addInput(asset);
306 } else {
307 _onAssetController.add(asset);
338 } 308 }
339 } 309 }
340 310
341 String toString() => "phase $_location.$_index"; 311 String toString() => "phase $_location.$_index";
342 } 312 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698