@@ -40,14 +40,16 @@ import 'phased_value.dart';
40
40
/// not all files have been generated yet. So, results must be returned based on
41
41
/// incomplete data, as needed.
42
42
class LibraryCycleGraphLoader {
43
+ final List <int > _runningAtPhases = [];
44
+
43
45
/// The dependencies of loaded assets, as far as is known.
44
46
///
45
47
/// Source files do not change during the build, so as soon as loaded
46
48
/// their value is a [PhasedValue.fixed] that is valid for the whole build.
47
49
///
48
50
/// A generated file that could not yet be loaded is a
49
51
/// [PhasedValue.unavailable] specify the phase when it will be generated.
50
- /// When to finish loading the asset is tracked in [_assetDepsToLoadByPhase ] .
52
+ /// When to finish loading the asset is tracked in [_idsToLoad ] .
51
53
///
52
54
/// A generated file that _has_ been loaded is a [PhasedValue.generated]
53
55
/// specifying both the phase it was generated at and its parsed dependencies.
@@ -56,7 +58,7 @@ class LibraryCycleGraphLoader {
56
58
/// Generated assets that were loaded before they were generated.
57
59
///
58
60
/// The `key` is the phase at which they have been generated and can be read.
59
- final Map <int , Set <AssetId >> _assetDepsToLoadByPhase = {};
61
+ final Map <int , List <AssetId >> _idsToLoad = {};
60
62
61
63
/// Newly [_load] ed assets to process for the first time in [_buildCycles] .
62
64
Set <AssetId > _newAssets = {};
@@ -76,7 +78,7 @@ class LibraryCycleGraphLoader {
76
78
/// Clears all data.
77
79
void clear () {
78
80
_assetDeps.clear ();
79
- _assetDepsToLoadByPhase .clear ();
81
+ _idsToLoad .clear ();
80
82
_newAssets.clear ();
81
83
_cycles.clear ();
82
84
_graphs.clear ();
@@ -88,55 +90,67 @@ class LibraryCycleGraphLoader {
88
90
/// Assets are loaded to [_assetDeps] .
89
91
///
90
92
/// If assets are encountered that have not yet been generated, they are
91
- /// added to [_assetDepsToLoadByPhase ] , and will be loaded eagerly by any
93
+ /// added to [_idsToLoad ] , and will be loaded eagerly by any
92
94
/// call to `_load` with an `assetDepsLoader` at a late enough phase.
93
95
///
94
96
/// Newly seen assets are noted in [_newAssets] for further processing by
95
97
/// [_buildCycles] .
96
98
Future <void > _load (AssetDepsLoader assetDepsLoader, AssetId id) async {
97
- final idsToLoad = [id];
98
- // Finish loading any assets that were `_load`ed before they were generated
99
- // and have now been generated.
100
- for (final phase in _assetDepsToLoadByPhase.keys.toList (growable: false )) {
101
- if (phase <= assetDepsLoader.phase) {
102
- idsToLoad.addAll (_assetDepsToLoadByPhase.remove (phase)! );
103
- }
104
- }
99
+ final thisPhaseIdsToLoad = _idsToLoad[assetDepsLoader.phase] ?? = [];
100
+ thisPhaseIdsToLoad.add (id);
105
101
106
- while (idsToLoad.isNotEmpty) {
107
- final idToLoad = idsToLoad.removeLast ();
102
+ while (_idsToLoad.entries
103
+ .where ((entry) => entry.key <= assetDepsLoader.phase)
104
+ .any ((entry) => entry.value.isNotEmpty)) {
105
+ final firstPhase = (_idsToLoad.keys..toList ().sort ()).firstWhere (
106
+ (key) => key <= assetDepsLoader.phase && _idsToLoad[key]! .isNotEmpty);
107
+ final idToLoad = _idsToLoad[firstPhase]! .last;
108
+ print ('($_runningAtPhases , $id ) take $idToLoad at phase $firstPhase ' );
108
109
109
110
// Nothing to do if deps were already loaded, unless they expire and
110
111
// [assetDepsLoader] is at a late enough phase to see the updated value.
111
112
final alreadyLoadedAssetDeps = _assetDeps[idToLoad];
112
113
if (alreadyLoadedAssetDeps != null &&
113
114
! alreadyLoadedAssetDeps.isExpiredAt (phase: assetDepsLoader.phase)) {
115
+ _idsToLoad[firstPhase]! .remove (idToLoad);
114
116
continue ;
115
117
}
116
118
119
+ print (
120
+ '($_runningAtPhases , $id ) Load $idToLoad at ${assetDepsLoader .phase }' );
117
121
final assetDeps =
118
122
_assetDeps[idToLoad] = await assetDepsLoader.load (idToLoad);
119
123
120
124
// First time seeing the asset, mark for computation of cycles and
121
125
// graphs given the initial state of the build.
122
126
if (alreadyLoadedAssetDeps == null ) {
127
+ print ('For phase $firstPhase , Add to new assets: $idToLoad ' );
123
128
_newAssets.add (idToLoad);
124
129
}
125
130
126
131
if (assetDeps.isComplete) {
127
132
// "isComplete" means it's a source file or a generated value that has
128
133
// already been generated. It has deps, so mark them for loading.
134
+ print (
135
+ '($_runningAtPhases , $id ) Completed $idToLoad : ${assetDeps .lastValue .deps }' );
129
136
for (final dep in assetDeps.lastValue.deps) {
130
- idsToLoad.add (dep);
137
+ final phaseIdsToLoad = _idsToLoad[0 ] ?? = [];
138
+ phaseIdsToLoad.add (dep);
139
+ print ('($_runningAtPhases , $id ) add $dep to _idsToLoad at 0' );
131
140
}
141
+ print ('Ehm: $thisPhaseIdsToLoad --- $_idsToLoad ' );
132
142
} else {
133
143
// It's a generated source that has not yet been generated. Mark it for
134
144
// loading later.
135
- (_assetDepsToLoadByPhase[assetDeps.values.last.expiresAfter ! + 1 ] ?? =
136
- {} )
145
+ print ( '($ _runningAtPhases , $ id ) Add $ idToLoad to IDs to load later' );
146
+ (_idsToLoad[assetDeps.values.last.expiresAfter ! + 1 ] ?? = [] )
137
147
.add (idToLoad);
138
148
}
149
+
150
+ _idsToLoad[firstPhase]! .remove (idToLoad);
139
151
}
152
+
153
+ print ('($_runningAtPhases , $id ) returning: $_idsToLoad ' );
140
154
}
141
155
142
156
/// Computes [_cycles] for all [_newAssets] at phase 0, then for all assets
@@ -170,7 +184,9 @@ class LibraryCycleGraphLoader {
170
184
171
185
// Edges for strongly connected components computation.
172
186
Iterable <AssetId > edgesFromId (AssetId id) {
187
+ print ('Edges for $id ' );
173
188
final deps = _assetDeps[id]! .valueAt (phase: phase).deps;
189
+ return deps;
174
190
175
191
// Check edge against cycles that have already been computed
176
192
// at the current `phase`. Newly discovered assets at the same phase
@@ -181,28 +197,29 @@ class LibraryCycleGraphLoader {
181
197
);
182
198
}
183
199
200
+ print ('Up to $upToPhase , compute $phase ' );
201
+
184
202
// Do the strongly connected components computation and convert
185
203
// from its output, a list of lists of IDs, to a list of [LibraryCycle].
186
204
final newComponentLists = stronglyConnectedComponents (
187
205
idsToComputeCyclesFrom,
188
206
edgesFromId,
189
207
);
190
- final newCycles =
191
- newComponentLists.map ((list) {
192
- // Compare to the library cycle computed at `phase - 1`. If the
193
- // cycles are the same size then they must have the same contents,
194
- // because cycles only change by growing as phases progress. In that
195
- // case, reuse the existing [LibraryCycle].
196
- final maybePhasedCycle = _cycles[list.first];
197
- if (maybePhasedCycle != null ) {
198
- final value = maybePhasedCycle.valueAt (phase: phase - 1 );
199
- if (value.ids.length == list.length) {
200
- return value;
201
- }
202
- }
203
- // The cycle is new or has changed, return a new value.
204
- return LibraryCycle ((b) => b..ids.replace (list));
205
- }).toList ();
208
+ final newCycles = newComponentLists.map ((list) {
209
+ // Compare to the library cycle computed at `phase - 1`. If the
210
+ // cycles are the same size then they must have the same contents,
211
+ // because cycles only change by growing as phases progress. In that
212
+ // case, reuse the existing [LibraryCycle].
213
+ final maybePhasedCycle = _cycles[list.first];
214
+ if (maybePhasedCycle != null ) {
215
+ final value = maybePhasedCycle.valueAt (phase: phase - 1 );
216
+ if (value.ids.length == list.length) {
217
+ return value;
218
+ }
219
+ }
220
+ // The cycle is new or has changed, return a new value.
221
+ return LibraryCycle ((b) => b..ids.replace (list));
222
+ }).toList ();
206
223
207
224
// Build graphs from cycles.
208
225
_buildGraphs (phase, newCycles: newCycles);
@@ -212,10 +229,9 @@ class LibraryCycleGraphLoader {
212
229
// it gets a new dep that leads back to the cycle then that whole path
213
230
// joins the cycle. Get this expirey phase from the graph built by
214
231
// `_buildGraphs`.
215
- final expiresAfter =
216
- _graphs[cycle.ids.first]!
217
- .expiringValueAt (phase: phase)
218
- .expiresAfter;
232
+ final expiresAfter = _graphs[cycle.ids.first]!
233
+ .expiringValueAt (phase: phase)
234
+ .expiresAfter;
219
235
220
236
// Merge the computed cycle into any existing phased value for each ID.
221
237
// The phased value can differ by ID: they are in the same cycle at this
@@ -224,21 +240,19 @@ class LibraryCycleGraphLoader {
224
240
// Nevertheless, the case in which the phased values are the same is a
225
241
// common one, so use a temporary map from old value to new value to
226
242
// avoid creating many equal but not identical phased values.
227
- final updatedValueByOldValue =
228
- Map <
229
- PhasedValue <LibraryCycle >?,
230
- PhasedValue <LibraryCycle >
231
- >.identity ();
243
+ final updatedValueByOldValue = Map <PhasedValue <LibraryCycle >?,
244
+ PhasedValue <LibraryCycle >>.identity ();
232
245
233
246
for (final id in cycle.ids) {
234
247
final existingCycle = _cycles[id];
235
248
_cycles[id] = updatedValueByOldValue.putIfAbsent (existingCycle, () {
236
249
if (existingCycle == null ) {
237
250
return PhasedValue .of (cycle, expiresAfter: expiresAfter);
238
251
}
239
- return existingCycle.followedBy (
240
- ExpiringValue <LibraryCycle >(cycle, expiresAfter: expiresAfter),
241
- );
252
+ final newValue =
253
+ ExpiringValue <LibraryCycle >(cycle, expiresAfter: expiresAfter);
254
+ if (existingCycle.values.contains (newValue)) return existingCycle;
255
+ return existingCycle.followedBy (newValue);
242
256
});
243
257
}
244
258
}
@@ -315,21 +329,19 @@ class LibraryCycleGraphLoader {
315
329
// Nevertheless, the case in which the phased values are the same is a
316
330
// common one, so use a temporary map from old value to new value to
317
331
// avoid creating many equal but not identical phased values.
318
- final updatedValueByOldValue =
319
- Map <
320
- PhasedValue <LibraryCycleGraph >?,
321
- PhasedValue <LibraryCycleGraph >
322
- >.identity ();
332
+ final updatedValueByOldValue = Map <PhasedValue <LibraryCycleGraph >?,
333
+ PhasedValue <LibraryCycleGraph >>.identity ();
323
334
324
335
for (final idToUpdate in root.ids) {
325
336
final oldValue = _graphs[idToUpdate];
326
337
_graphs[idToUpdate] = updatedValueByOldValue.putIfAbsent (oldValue, () {
327
338
if (oldValue == null ) {
328
339
return PhasedValue .of (graph.build (), expiresAfter: expiresAfter);
329
340
}
330
- return oldValue.followedBy (
331
- ExpiringValue (graph.build (), expiresAfter: expiresAfter),
332
- );
341
+ final newValue =
342
+ ExpiringValue (graph.build (), expiresAfter: expiresAfter);
343
+ if (oldValue.values.contains (newValue)) return oldValue;
344
+ return oldValue.followedBy (newValue);
333
345
});
334
346
}
335
347
}
@@ -344,9 +356,20 @@ class LibraryCycleGraphLoader {
344
356
AssetDepsLoader assetDepsLoader,
345
357
AssetId id,
346
358
) async {
347
- await _load (assetDepsLoader, id);
348
- _buildCycles (assetDepsLoader.phase);
349
- return _cycles[id]! ;
359
+ if (_runningAtPhases.isNotEmpty &&
360
+ assetDepsLoader.phase >= _runningAtPhases.last) {
361
+ throw StateError ('Cannot recurse at phase ${assetDepsLoader .phase }, '
362
+ 'already running at an equal or earlier phase. '
363
+ 'Already running at : $_runningAtPhases ' );
364
+ }
365
+ _runningAtPhases.add (assetDepsLoader.phase);
366
+ try {
367
+ await _load (assetDepsLoader, id);
368
+ _buildCycles (assetDepsLoader.phase);
369
+ return _cycles[id]! ;
370
+ } finally {
371
+ _runningAtPhases.removeLast ();
372
+ }
350
373
}
351
374
352
375
/// Returns the [LibraryCycleGraph] of [id] at all phases before the
@@ -386,7 +409,7 @@ class LibraryCycleGraphLoader {
386
409
String toString () => '''
387
410
LibraryCycleGraphLoader(
388
411
_assetDeps: $_assetDeps ,
389
- _assetDepsToLoadByPhase: $_assetDepsToLoadByPhase ,
412
+ _assetDepsToLoadByPhase: $_idsToLoad ,
390
413
_newAssets: $_newAssets ,
391
414
_cycles: $_cycles ,
392
415
_graphs: $_graphs ,
0 commit comments