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

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

Issue 21275003: Move barback to a more event-based model. (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.transform_node; 5 library barback.transform_node;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'asset.dart'; 9 import 'asset.dart';
10 import 'asset_id.dart'; 10 import 'asset_id.dart';
11 import 'asset_node.dart'; 11 import 'asset_node.dart';
12 import 'asset_set.dart'; 12 import 'asset_set.dart';
13 import 'errors.dart'; 13 import 'errors.dart';
14 import 'phase.dart'; 14 import 'phase.dart';
15 import 'transform.dart'; 15 import 'transform.dart';
16 import 'transformer.dart'; 16 import 'transformer.dart';
17 17
18 /// Describes a transform on a set of assets and its relationship to the build 18 /// Describes a transform on a set of assets and its relationship to the build
19 /// dependency graph. 19 /// dependency graph.
20 /// 20 ///
21 /// Keeps track of whether it's dirty and needs to be run and which assets it 21 /// Keeps track of whether it's dirty and needs to be run and which assets it
22 /// depends on. 22 /// depends on.
23 class TransformNode { 23 class TransformNode {
24 /// The [Phase] that this transform runs in. 24 /// The [Phase] that this transform runs in.
25 final Phase phase; 25 final Phase phase;
26 26
27 /// The [Transformer] to apply to this node's inputs. 27 /// The [Transformer] to apply to this node's inputs.
28 final Transformer _transformer; 28 final Transformer transformer;
29 29
30 /// The node for the primary asset this transform depends on. 30 /// The node for the primary asset this transform depends on.
31 final AssetNode primary; 31 final AssetNode primary;
32 32
33 /// The subscription to [primary]'s [AssetNode.onStateChange] stream.
34 StreamSubscription _primarySubscription;
35
33 /// True if an input has been modified since the last time this transform 36 /// True if an input has been modified since the last time this transform
34 /// was run. 37 /// began running.
35 bool get isDirty => _isDirty; 38 bool get isDirty => _isDirty;
36 var _isDirty = true; 39 var _isDirty = true;
37 40
38 /// The inputs read by this transform the last time it was run. 41 /// The inputs read by this transform the last time it was run.
39 /// 42 ///
40 /// Used to tell if an input was removed in a later run. 43 /// Used to tell if an input was added or removed in a later run.
41 var _inputs = new Set<AssetNode>(); 44 var _inputs = new Set<AssetNode>();
42 45
43 /// The outputs created by this transform the last time it was run. 46 /// The subscriptions to each input's [AssetNode.onStateChange] stream.
47 var _inputSubscriptions = new Map<AssetId, StreamSubscription>();
48
49 /// The controllers for the asset nodes emitted by this node.
50 var _outputControllers = new Map<AssetId, AssetNodeController>();
51
52 TransformNode(this.phase, this.transformer, this.primary) {
53 _primarySubscription = primary.onStateChange.listen((state) {
54 if (state.isRemoved) {
55 remove();
56 } else {
57 _dirty();
58 }
59 });
60 }
61
62 /// Marks this transform as removed.
44 /// 63 ///
45 /// Used to tell if an output was removed in a later run. 64 /// This causes all of the transform's outputs to be marked as removed as
46 Set<AssetId> get outputs => _outputs; 65 /// well. Normally this will be automatically done internally based on events
47 var _outputs = new Set<AssetId>(); 66 /// from the primary input, but it's possible for a transform to no longer be
67 /// valid even if its primary input still exists.
68 void remove() {
69 _isDirty = true;
70 _primarySubscription.cancel();
71 for (var subscription in _inputSubscriptions.values) {
72 subscription.cancel();
73 }
74 for (var controller in _outputControllers.values) {
75 controller.setRemoved();
76 }
77 }
48 78
49 TransformNode(this.phase, this._transformer, this.primary); 79 /// Marks this transform as dirty.
50 80 ///
51 /// Marks this transform as needing to be run. 81 /// This causes all of the transform's outputs to be marked as dirty as well.
52 void dirty() { 82 void _dirty() {
53 _isDirty = true; 83 _isDirty = true;
84 for (var controller in _outputControllers.values) {
85 controller.setDirty();
86 }
54 } 87 }
55 88
56 /// Applies this transform. 89 /// Applies this transform.
57 /// 90 ///
58 /// Returns a [TransformOutputs] describing the resulting outputs compared to 91 /// Returns a set of asset nodes representing the outputs from this transform
59 /// previous runs. 92 /// that weren't emitted last time it was run.
60 Future<TransformOutputs> apply() { 93 Future<Set<AssetNode>> apply() {
61 var newInputs = new Set<AssetNode>(); 94 var newInputs = new Set<AssetNode>();
62 var newOutputs = new AssetSet(); 95 var newOutputs = new AssetSet();
63 var transform = createTransform(this, newInputs, newOutputs); 96 var transform = createTransform(this, newInputs, newOutputs);
64 return _transformer.apply(transform).catchError((error) { 97 _isDirty = false;
65 // Catch all transformer errors and pipe them to the results stream. This 98 return transformer.apply(transform).catchError((error) {
66 // is so a broken transformer doesn't take down the whole graph. 99 // If the transform became dirty while processing, ignore any errors from
100 // it.
101 if (_isDirty) return;
102
103 // Catch all transformer errors and pipe them to the results stream.
104 // This is so a broken transformer doesn't take down the whole graph.
67 phase.cascade.reportError(error); 105 phase.cascade.reportError(error);
68 106
69 // Don't allow partial results from a failed transform. 107 // Don't allow partial results from a failed transform.
70 newOutputs.clear(); 108 newOutputs.clear();
71 }).then((_) { 109 }).then((_) {
72 _isDirty = false; 110 if (_isDirty) return [];
73 111
74 // Stop watching any inputs that were removed. 112 _adjustInputs(newInputs);
75 for (var oldInput in _inputs) { 113 return _adjustOutputs(newOutputs);
76 oldInput.consumers.remove(this);
77 }
78
79 // Watch any new inputs so this transform will be re-processed when an
80 // input is modified.
81 for (var newInput in newInputs) {
82 newInput.consumers.add(this);
83 }
84
85 _inputs = newInputs;
86
87 // See which outputs are missing from the last run.
88 var outputIds = newOutputs.map((asset) => asset.id).toSet();
89 var invalidIds = outputIds
90 .where((id) => id.package != phase.cascade.package).toSet();
91 outputIds.removeAll(invalidIds);
92
93 for (var id in invalidIds) {
94 // TODO(nweiz): report this as a warning rather than a failing error.
95 phase.cascade.reportError(
96 new InvalidOutputException(phase.cascade.package, id));
97 }
98
99 var removed = _outputs.difference(outputIds);
100 _outputs = outputIds;
101
102 return new TransformOutputs(newOutputs, removed);
103 }); 114 });
104 } 115 }
116
117 /// Adjusts the inputs of the transform to reflect the inputs consumed on its
118 /// most recent run.
119 void _adjustInputs(Set<AssetNode> newInputs) {
120 // Stop watching any inputs that were removed.
121 for (var oldInput in _inputs.difference(newInputs)) {
122 _inputSubscriptions.remove(oldInput.id).cancel();
123 }
124
125 // Watch any new inputs so this transform will be re-processed when an
126 // input is modified.
127 for (var newInput in newInputs.difference(_inputs)) {
128 if (newInput.id == primary.id) continue;
Bob Nystrom 2013/07/31 20:05:41 How can this case get hit?
nweiz 2013/07/31 22:47:53 This case is hit whenever the transform loads the
129 // TODO(nweiz): support the case where a new secondary input changes
130 // after it's been loaded by the transform but before the transform has
131 // finished running.
132 _inputSubscriptions[newInput.id] = newInput.onStateChange
133 .listen((_) => _dirty());
134 }
135
136 _inputs = newInputs;
137 }
138
139 /// Adjusts the outputs of the transform to reflect the outputs emitted on its
140 /// most recent run.
141 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) {
142 // Any ids that are for the wrong package are invalid.
Bob Nystrom 2013/07/31 20:05:41 "for the wrong" -> "to a different"
nweiz 2013/07/31 22:47:53 Done.
143 var invalidIds = newOutputs
144 .map((asset) => asset.id)
145 .where((id) => id.package != phase.cascade.package)
146 .toSet();
147 for (var id in invalidIds) {
148 newOutputs.removeId(id);
149 // TODO(nweiz): report this as a warning rather than a failing error.
150 phase.cascade.reportError(
151 new InvalidOutputException(phase.cascade.package, id));
152 }
153
154 // Remove outputs that used to exist but don't anymore.
155 for (var id in _outputControllers.keys.toList()) {
Bob Nystrom 2013/07/31 20:05:41 If we make AssetSet implement Set, you could do:
nweiz 2013/07/31 22:47:53 Eh, I think this is clear enough.
156 if (newOutputs.containsId(id)) continue;
157 _outputControllers.remove(id).setRemoved();
158 }
159
160 var brandNewOutputs = new Set<AssetNode>();
161 // Store any new outputs or new contents for existing outputs.
162 for (var asset in newOutputs) {
163 var controller = _outputControllers[asset.id];
Bob Nystrom 2013/07/31 20:05:41 Use putIfAbsent() here?
nweiz 2013/07/31 22:47:53 That doesn't work well when we want to call [setAv
Bob Nystrom 2013/07/31 23:20:15 You could just call that from within the pubIfAbse
164 if (controller != null) {
165 controller.setAvailable(asset);
166 } else {
167 var controller = new AssetNodeController.available(asset);
168 _outputControllers[asset.id] = controller;
169 brandNewOutputs.add(controller.node);
170 }
171 }
172
173 return brandNewOutputs;
174 }
105 } 175 }
106
107 /// The result of running a [Transform], compared to the previous time it was
108 /// applied.
109 class TransformOutputs {
110 /// The outputs that are new or were modified since the last run.
111 final AssetSet updated;
112
113 /// The outputs that were created by the previous run but were not generated
114 /// by the most recent run.
115 final Set<AssetId> removed;
116
117 TransformOutputs(this.updated, this.removed);
118 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698