OLD | NEW |
| (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.graph.asset_cascade; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import '../asset/asset.dart'; | |
10 import '../asset/asset_id.dart'; | |
11 import '../asset/asset_node.dart'; | |
12 import '../asset/asset_set.dart'; | |
13 import '../errors.dart'; | |
14 import '../log.dart'; | |
15 import '../transformer/transformer.dart'; | |
16 import '../utils.dart'; | |
17 import '../utils/cancelable_future.dart'; | |
18 import 'node_status.dart'; | |
19 import 'node_streams.dart'; | |
20 import 'package_graph.dart'; | |
21 import 'phase.dart'; | |
22 | |
23 /// The asset cascade for an individual package. | |
24 /// | |
25 /// This keeps track of which [Transformer]s are applied to which assets, and | |
26 /// re-runs those transformers when their dependencies change. The transformed | |
27 /// asset nodes are accessible via [getAssetNode]. | |
28 /// | |
29 /// A cascade consists of one or more [Phases], each of which has one or more | |
30 /// [Transformer]s that run in parallel, potentially on the same inputs. The | |
31 /// inputs of the first phase are the source assets for this cascade's package. | |
32 /// The inputs of each successive phase are the outputs of the previous phase, | |
33 /// as well as any assets that haven't yet been transformed. | |
34 class AssetCascade { | |
35 /// The name of the package whose assets are managed. | |
36 final String package; | |
37 | |
38 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of | |
39 /// the current app. | |
40 final PackageGraph graph; | |
41 | |
42 /// The controllers for the [AssetNode]s that provide information about this | |
43 /// cascade's package's source assets. | |
44 final _sourceControllerMap = new Map<AssetId, AssetNodeController>(); | |
45 | |
46 /// Futures for source assets that are currently being loaded. | |
47 /// | |
48 /// These futures are cancelable so that if an asset is updated after a load | |
49 /// has been kicked off, the previous load can be ignored in favor of a new | |
50 /// one. | |
51 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>(); | |
52 | |
53 /// The list of phases in this cascade. | |
54 /// | |
55 /// This will always contain at least one phase, and the first phase will | |
56 /// never have any transformers. This ensures that every transformer can | |
57 /// request inputs from a previous phase. | |
58 final _phases = <Phase>[]; | |
59 | |
60 /// The subscription to the [Phase.onStatusChange] stream of the last [Phase] | |
61 /// in [_phases]. | |
62 StreamSubscription _phaseStatusSubscription; | |
63 | |
64 /// A stream that emits any errors from the cascade or the transformers. | |
65 /// | |
66 /// This emits errors as they're detected. If an error occurs in one part of | |
67 /// the cascade, unrelated parts will continue building. | |
68 Stream<BarbackException> get errors => _errorsController.stream; | |
69 final _errorsController = | |
70 new StreamController<BarbackException>.broadcast(sync: true); | |
71 | |
72 /// How far along [this] is in processing its assets. | |
73 NodeStatus get status { | |
74 // Just check the last phase, since it will check all the previous phases | |
75 // itself. | |
76 return _phases.last.status; | |
77 } | |
78 | |
79 /// The streams exposed by this cascade. | |
80 final _streams = new NodeStreams(); | |
81 Stream<LogEntry> get onLog => _streams.onLog; | |
82 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | |
83 | |
84 /// Returns all currently-available output assets from this cascade. | |
85 Future<AssetSet> get availableOutputs => new Future.value(new AssetSet.from( | |
86 _phases.last.availableOutputs.map((node) => node.asset))); | |
87 | |
88 /// Creates a new [AssetCascade]. | |
89 /// | |
90 /// It loads source assets within [package] using [provider]. | |
91 AssetCascade(this.graph, this.package) { | |
92 _addPhase(new Phase(this, package)); | |
93 } | |
94 | |
95 /// Gets the asset identified by [id]. | |
96 /// | |
97 /// If [id] is for a generated or transformed asset, this will wait until it | |
98 /// has been created and return it. This means that the returned asset will | |
99 /// always be [AssetState.AVAILABLE]. | |
100 /// | |
101 /// If the asset cannot be found, returns null. | |
102 Future<AssetNode> getAssetNode(AssetId id) { | |
103 assert(id.package == package); | |
104 | |
105 var oldLastPhase = _phases.last; | |
106 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary | |
107 // in some cases. Should optimize: | |
108 // * [id] may be generated before the compilation is finished. We should | |
109 // be able to quickly check whether there are any more in-place | |
110 // transformations that can be run on it. If not, we can return it early. | |
111 // * If [id] has never been generated and all active transformers provide | |
112 // metadata about the file names of assets it can emit, we can prove that | |
113 // none of them can emit [id] and fail early. | |
114 return oldLastPhase.getOutput(id).then((node) { | |
115 // The last phase may have changed if [updateSources] was called after | |
116 // requesting the output. In that case, we want the output from the new | |
117 // last phase. | |
118 if (_phases.last == oldLastPhase) return node; | |
119 return getAssetNode(id); | |
120 }); | |
121 } | |
122 | |
123 /// Adds [sources] to the graph's known set of source assets. | |
124 /// | |
125 /// Begins applying any transforms that can consume any of the sources. If a | |
126 /// given source is already known, it is considered modified and all | |
127 /// transforms that use it will be re-applied. | |
128 void updateSources(Iterable<AssetId> sources) { | |
129 for (var id in sources) { | |
130 var controller = _sourceControllerMap[id]; | |
131 if (controller != null) { | |
132 controller.setDirty(); | |
133 } else { | |
134 _sourceControllerMap[id] = new AssetNodeController(id); | |
135 _phases.first.addInput(_sourceControllerMap[id].node); | |
136 } | |
137 | |
138 // If this source was already loading, cancel the old load, since it may | |
139 // return out-of-date contents for the asset. | |
140 if (_loadingSources.containsKey(id)) _loadingSources[id].cancel(); | |
141 | |
142 _loadingSources[id] = new CancelableFuture<Asset>( | |
143 syncFuture(() => graph.provider.getAsset(id))); | |
144 _loadingSources[id].whenComplete(() { | |
145 _loadingSources.remove(id); | |
146 }).then((asset) { | |
147 var controller = _sourceControllerMap[id].setAvailable(asset); | |
148 }).catchError((error, stack) { | |
149 reportError(new AssetLoadException(id, error, stack)); | |
150 | |
151 // TODO(nweiz): propagate error information through asset nodes. | |
152 _sourceControllerMap.remove(id).setRemoved(); | |
153 }); | |
154 } | |
155 } | |
156 | |
157 /// Removes [removed] from the graph's known set of source assets. | |
158 void removeSources(Iterable<AssetId> removed) { | |
159 removed.forEach((id) { | |
160 // If the source was being loaded, cancel that load. | |
161 if (_loadingSources.containsKey(id)) _loadingSources.remove(id).cancel(); | |
162 | |
163 var controller = _sourceControllerMap.remove(id); | |
164 // Don't choke if an id is double-removed for some reason. | |
165 if (controller != null) controller.setRemoved(); | |
166 }); | |
167 } | |
168 | |
169 /// Sets this cascade's transformer phases to [transformers]. | |
170 /// | |
171 /// Elements of the inner iterable of [transformers] must be [Transformer]s, | |
172 /// [TransformerGroup]s, or [AggregateTransformer]s. | |
173 void updateTransformers(Iterable<Iterable> transformersIterable) { | |
174 var transformers = transformersIterable.toList(); | |
175 | |
176 // Always preserve a single phase with no transformers at the beginning of | |
177 // the cascade so that [TransformNode]s in the first populated phase will | |
178 // have something to request assets from. | |
179 for (var i = 0; i < transformers.length; i++) { | |
180 if (_phases.length > i + 1) { | |
181 _phases[i + 1].updateTransformers(transformers[i]); | |
182 continue; | |
183 } | |
184 | |
185 var phase = _phases.last.addPhase(); | |
186 _addPhase(phase); | |
187 phase.updateTransformers(transformers[i]); | |
188 } | |
189 | |
190 for (var i = transformers.length + 1; i < _phases.length; i++) { | |
191 _phases[i].remove(); | |
192 } | |
193 _phases.removeRange(transformers.length + 1, _phases.length); | |
194 | |
195 _phaseStatusSubscription.cancel(); | |
196 _phaseStatusSubscription = _phases.last.onStatusChange | |
197 .listen(_streams.changeStatus); | |
198 } | |
199 | |
200 /// Force all [LazyTransformer]s' transforms in this cascade to begin | |
201 /// producing concrete assets. | |
202 void forceAllTransforms() { | |
203 for (var phase in _phases) { | |
204 phase.forceAllTransforms(); | |
205 } | |
206 } | |
207 | |
208 void reportError(BarbackException error) { | |
209 _errorsController.add(error); | |
210 } | |
211 | |
212 /// Add [phase] to the end of [_phases] and watch its streams. | |
213 void _addPhase(Phase phase) { | |
214 _streams.onLogPool.add(phase.onLog); | |
215 if (_phaseStatusSubscription != null) _phaseStatusSubscription.cancel(); | |
216 _phaseStatusSubscription = | |
217 phase.onStatusChange.listen(_streams.changeStatus); | |
218 | |
219 _phases.add(phase); | |
220 } | |
221 | |
222 String toString() => "cascade for $package"; | |
223 } | |
OLD | NEW |