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

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

Issue 23038018: Factor a PhaseOutput class out of Phase. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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
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 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'asset_cascade.dart'; 10 import 'asset_cascade.dart';
11 import 'asset_id.dart'; 11 import 'asset_id.dart';
12 import 'asset_node.dart'; 12 import 'asset_node.dart';
13 import 'asset_set.dart'; 13 import 'asset_set.dart';
14 import 'errors.dart'; 14 import 'errors.dart';
15 import 'phase_input.dart'; 15 import 'phase_input.dart';
16 import 'phase_output.dart';
16 import 'stream_pool.dart'; 17 import 'stream_pool.dart';
17 import 'transformer.dart'; 18 import 'transformer.dart';
18 import 'utils.dart'; 19 import 'utils.dart';
19 20
20 /// One phase in the ordered series of transformations in an [AssetCascade]. 21 /// One phase in the ordered series of transformations in an [AssetCascade].
21 /// 22 ///
22 /// Each phase can access outputs from previous phases and can in turn pass 23 /// Each phase can access outputs from previous phases and can in turn pass
23 /// outputs to later phases. Phases are processed strictly serially. All 24 /// outputs to later phases. Phases are processed strictly serially. All
24 /// transforms in a phase will be complete before moving on to the next phase. 25 /// transforms in a phase will be complete before moving on to the next phase.
25 /// Within a single phase, all transforms will be run in parallel. 26 /// Within a single phase, all transforms will be run in parallel.
(...skipping 11 matching lines...) Expand all
37 /// 38 ///
38 /// Their outputs will be available to the next phase. 39 /// Their outputs will be available to the next phase.
39 final Set<Transformer> _transformers; 40 final Set<Transformer> _transformers;
40 41
41 /// The inputs for this phase. 42 /// The inputs for this phase.
42 /// 43 ///
43 /// For the first phase, these will be the source assets. For all other 44 /// For the first phase, these will be the source assets. For all other
44 /// phases, they will be the outputs from the previous phase. 45 /// phases, they will be the outputs from the previous phase.
45 final _inputs = new Map<AssetId, PhaseInput>(); 46 final _inputs = new Map<AssetId, PhaseInput>();
46 47
47 /// A map of output ids to the asset node outputs for those ids and the 48 /// The outputs for this phase.
48 /// transforms that produced those asset nodes. 49 final _outputs = new Map<AssetId, PhaseOutput>();
49 ///
50 /// Usually there's only one node for a given output id. However, it's
51 /// possible for multiple transformers to output an asset with the same id. In
52 /// that case, the chronologically first output emitted is passed forward. We
53 /// keep track of the other nodes so that if that output is removed, we know
54 /// which asset to replace it with.
55 final _outputs = new Map<AssetId, Queue<AssetNode>>();
56 50
57 /// A stream that emits an event whenever this phase becomes dirty and needs 51 /// A stream that emits an event whenever this phase becomes dirty and needs
58 /// to be run. 52 /// to be run.
59 /// 53 ///
60 /// This may emit events when the phase was already dirty or while processing 54 /// This may emit events when the phase was already dirty or while processing
61 /// transforms. Events are emitted synchronously to ensure that the dirty 55 /// transforms. Events are emitted synchronously to ensure that the dirty
62 /// state is thoroughly propagated as soon as any assets are changed. 56 /// state is thoroughly propagated as soon as any assets are changed.
63 Stream get onDirty => _onDirtyPool.stream; 57 Stream get onDirty => _onDirtyPool.stream;
64 final _onDirtyPool = new StreamPool.broadcast(); 58 final _onDirtyPool = new StreamPool.broadcast();
65 59
66 /// A controller whose stream feeds into [_onDirtyPool]. 60 /// A controller whose stream feeds into [_onDirtyPool].
67 /// 61 ///
68 /// This is used whenever an input is added or transforms are changed. 62 /// This is used whenever an input is added or transforms are changed.
69 final _onDirtyController = new StreamController.broadcast(sync: true); 63 final _onDirtyController = new StreamController.broadcast(sync: true);
70 64
71 /// The phase after this one. 65 /// The phase after this one.
72 /// 66 ///
73 /// Outputs from this phase will be passed to it. 67 /// Outputs from this phase will be passed to it.
74 Phase get next => _next; 68 Phase get next => _next;
75 Phase _next; 69 Phase _next;
76 70
77 /// Returns all currently-available output assets for this phase. 71 /// Returns all currently-available output assets for this phase.
78 AssetSet get availableOutputs { 72 AssetSet get availableOutputs {
79 return new AssetSet.from(_outputs.values 73 return new AssetSet.from(_outputs.values
80 .map((queue) => queue.first) 74 .map((output) => output.output)
81 .where((node) => node.state.isAvailable) 75 .where((node) => node.state.isAvailable)
82 .map((node) => node.asset)); 76 .map((node) => node.asset));
83 } 77 }
84 78
85 Phase(this.cascade, Iterable<Transformer> transformers) 79 Phase(this.cascade, Iterable<Transformer> transformers)
86 : _transformers = transformers.toSet() { 80 : _transformers = transformers.toSet() {
87 _onDirtyPool.add(_onDirtyController.stream); 81 _onDirtyPool.add(_onDirtyController.stream);
88 } 82 }
89 83
90 /// Adds a new asset as an input for this phase. 84 /// Adds a new asset as an input for this phase.
(...skipping 27 matching lines...) Expand all
118 }); 112 });
119 } 113 }
120 114
121 /// Gets the asset node for an output [id]. 115 /// Gets the asset node for an output [id].
122 /// 116 ///
123 /// If an output with that ID cannot be found, returns null. 117 /// If an output with that ID cannot be found, returns null.
124 Future<AssetNode> getOutput(AssetId id) { 118 Future<AssetNode> getOutput(AssetId id) {
125 return newFuture(() { 119 return newFuture(() {
126 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 120 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
127 if (!_outputs.containsKey(id)) return null; 121 if (!_outputs.containsKey(id)) return null;
128 return _outputs[id].first; 122 return _outputs[id].output;
129 }); 123 });
130 } 124 }
131 125
132 /// Set this phase's transformers to [transformers]. 126 /// Set this phase's transformers to [transformers].
133 void updateTransformers(Iterable<Transformer> transformers) { 127 void updateTransformers(Iterable<Transformer> transformers) {
134 _onDirtyController.add(null); 128 _onDirtyController.add(null);
135 _transformers.clear(); 129 _transformers.clear();
136 _transformers.addAll(transformers); 130 _transformers.addAll(transformers);
137 for (var input in _inputs.values) { 131 for (var input in _inputs.values) {
138 input.updateTransformers(_transformers); 132 input.updateTransformers(_transformers);
139 } 133 }
140 } 134 }
141 135
142 /// Add a new phase after this one with [transformers]. 136 /// Add a new phase after this one with [transformers].
143 /// 137 ///
144 /// This may only be called on a phase with no phase following it. 138 /// This may only be called on a phase with no phase following it.
145 Phase addPhase(Iterable<Transformer> transformers) { 139 Phase addPhase(Iterable<Transformer> transformers) {
146 assert(_next == null); 140 assert(_next == null);
147 _next = new Phase(cascade, transformers); 141 _next = new Phase(cascade, transformers);
148 for (var outputs in _outputs.values) { 142 for (var outputs in _outputs.values) {
149 _next.addInput(outputs.first); 143 _next.addInput(outputs.output);
150 } 144 }
151 return _next; 145 return _next;
152 } 146 }
153 147
154 /// Processes this phase. 148 /// Processes this phase.
155 /// 149 ///
156 /// Returns a future that completes when processing is done. If there is 150 /// Returns a future that completes when processing is done. If there is
157 /// nothing to process, returns `null`. 151 /// nothing to process, returns `null`.
158 Future process() { 152 Future process() {
159 if (!_inputs.values.any((input) => input.isDirty)) return null; 153 if (!_inputs.values.any((input) => input.isDirty)) return null;
160 154
155 var outputIds = new Set<AssetId>();
161 return Future.wait(_inputs.values.map((input) { 156 return Future.wait(_inputs.values.map((input) {
162 if (!input.isDirty) return new Future.value(new Set()); 157 if (!input.isDirty) return new Future.value(new Set());
163 return input.process().then((outputs) { 158 return input.process().then((outputs) {
164 return outputs.where(_addOutput).map((output) => output.id).toSet(); 159 for (var asset in outputs) {
160 outputIds.add(asset.id);
161 if (_outputs.containsKey(asset.id)) {
162 _outputs[asset.id].add(asset);
163 } else {
164 _outputs[asset.id] = new PhaseOutput(this, asset);
165 _outputs[asset.id].output.whenRemoved
166 .then((_) => _outputs.remove(asset.id));
167 if (_next != null) _next.addInput(_outputs[asset.id].output);
168 }
169 }
165 }); 170 });
166 })).then((collisionsList) { 171 })).then((_) {
167 // Report collisions in a deterministic order. 172 // Report collisions in a deterministic order.
168 var collisions = unionAll(collisionsList).toList(); 173 outputIds = outputIds.toList();
169 collisions.sort((a, b) => a.compareTo(b)); 174 outputIds.sort((a, b) => a.compareTo(b));
170 for (var collision in collisions) { 175 for (var id in outputIds) {
171 // Ensure that there's still a collision. It's possible it was resolved 176 // It's possible the output was removed before other transforms in this
172 // while another transform was running. 177 // phase finished.
173 if (_outputs[collision].length <= 1) continue; 178 if (!_outputs.containsKey(id)) continue;
174 cascade.reportError(new AssetCollisionException( 179 var exception = _outputs[id].collisionException;
175 _outputs[collision].where((asset) => asset.transform != null) 180 if (exception != null) cascade.reportError(exception);
176 .map((asset) => asset.transform.info),
177 collision));
178 } 181 }
179 }); 182 });
180 } 183 }
181
182 /// Add [output] as an output of this phase, forwarding it to the next phase
183 /// if necessary.
184 ///
185 /// Returns whether or not [output] collides with another pre-existing output.
186 bool _addOutput(AssetNode output) {
187 _handleOutputRemoval(output);
188
189 if (_outputs.containsKey(output.id)) {
190 _outputs[output.id].add(output);
191 return true;
192 }
193
194 _outputs[output.id] = new Queue<AssetNode>.from([output]);
195 if (_next != null) _next.addInput(output);
196 return false;
197 }
198
199 /// Properly resolve collisions when [output] is removed.
200 void _handleOutputRemoval(AssetNode output) {
201 output.whenRemoved.then((_) {
202 var assets = _outputs[output.id];
203 if (assets.length == 1) {
204 assert(assets.single == output);
205 _outputs.remove(output.id);
206 return;
207 }
208
209 // If there was more than one asset, we're resolving a collision --
210 // possibly partially.
211 var wasFirst = assets.first == output;
212 assets.remove(output);
213
214 // If this was the first asset, we need to pass the next asset
215 // (chronologically) to the next phase. Pump the event queue first to give
216 // [_next] a chance to handle the removal of its input before getting a
217 // new input.
218 if (wasFirst && _next != null) {
219 newFuture(() => _next.addInput(assets.first));
220 }
221
222 // If there's still a collision, report it. This lets the user know
223 // if they've successfully resolved the collision or not.
224 if (assets.length > 1) {
225 // Pump the event queue to ensure that the removal of the input triggers
226 // a new build to which we can attach the error.
227 newFuture(() => cascade.reportError(new AssetCollisionException(
228 assets.where((asset) => asset.transform != null)
229 .map((asset) => asset.transform.info),
230 output.id)));
231 }
232 });
233 }
234 } 184 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698