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

Unified Diff: pkg/barback/lib/src/asset_cascade.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, 10 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/barback/lib/src/asset_node.dart » ('j') | pkg/barback/lib/src/group_runner.dart » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/barback/lib/src/asset_cascade.dart
diff --git a/pkg/barback/lib/src/asset_cascade.dart b/pkg/barback/lib/src/asset_cascade.dart
index 8c30be3d0647a4b89d45e18b1c8674edf8ba0a98..316ab726c01ecfa5f11e4a8c8e17b56d6f0f3f39 100644
--- a/pkg/barback/lib/src/asset_cascade.dart
+++ b/pkg/barback/lib/src/asset_cascade.dart
@@ -12,14 +12,12 @@ import 'asset_id.dart';
import 'asset_node.dart';
import 'asset_set.dart';
import 'log.dart';
-import 'build_result.dart';
import 'cancelable_future.dart';
import 'errors.dart';
import 'package_graph.dart';
import 'phase.dart';
import 'stream_pool.dart';
import 'transformer.dart';
-import 'utils.dart';
/// The asset cascade for an individual package.
///
@@ -53,80 +51,45 @@ class AssetCascade {
final _phases = <Phase>[];
- /// A stream that emits a [BuildResult] each time the build is completed,
- /// whether or not it succeeded.
- ///
- /// If an unexpected error in barback itself occurs, it will be emitted
- /// through this stream's error channel.
- Stream<BuildResult> get results => _resultsController.stream;
- final _resultsController = new StreamController<BuildResult>.broadcast();
-
/// A stream that emits any errors from the cascade or the transformers.
///
/// This emits errors as they're detected. If an error occurs in one part of
/// the cascade, unrelated parts will continue building.
- ///
- /// This will not emit programming errors from barback itself. Those will be
- /// emitted through the [results] stream's error channel.
Stream<BarbackException> get errors => _errorsController.stream;
- final _errorsController = new StreamController<BarbackException>.broadcast();
-
- /// A stream that emits an event whenever this cascade becomes dirty.
- ///
- /// After this stream emits an event, [results] will emit an event once the
- /// cascade is no longer dirty.
- ///
- /// This may emit events when the cascade was already dirty. Events are
- /// emitted synchronously to ensure that the dirty state is thoroughly
- /// propagated as soon as any assets are changed.
- Stream get onDirty => _onDirtyPool.stream;
- final _onDirtyPool = new StreamPool.broadcast();
-
- /// A controller whose stream feeds into [_onDirtyPool].
- final _onDirtyController = new StreamController.broadcast(sync: true);
+ final _errorsController =
+ new StreamController<BarbackException>.broadcast(sync: true);
/// A stream that emits an event whenever any transforms in this cascade logs
/// an entry.
Stream<LogEntry> get onLog => _onLogPool.stream;
final _onLogPool = new StreamPool<LogEntry>.broadcast();
- /// The errors that have occurred since the current build started.
- ///
- /// This will be empty if no build is occurring.
- Queue<BarbackException> _accumulatedErrors;
-
- /// The number of errors that have been logged since the current build
- /// started.
- int _numLogErrors;
+ /// Whether [this] is dirty and still has more processing to do.
+ bool get isDirty => _phases.any((phase) => phase.isDirty);
- /// A future that completes when the currently running build process finishes.
+ /// A stream that emits an event whenever [this] is no longer dirty.
///
- /// If no build it in progress, is `null`.
- Future _processDone;
-
- /// Whether any source assets have been updated or removed since processing
- /// last began.
- var _newChanges = false;
+ /// This is synchronous in order to guarantee that it will emit an event as
+ /// soon as [isDirty] flips from `true` to `false`.
+ Stream get onDone => _onDoneController.stream;
+ final _onDoneController = new StreamController<bool>.broadcast(sync: true);
Bob Nystrom 2014/03/05 22:13:25 Ditch the <bool>.
nweiz 2014/03/06 00:29:08 Done.
/// Returns all currently-available output assets from this cascade.
AssetSet get availableOutputs =>
new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset));
+ /// A map of asset ids to completers for [getAssetNode] requests.
+ ///
+ /// If an asset node is requested before it's available, we put a completer in
+ /// this map to wait for the asset to be generated. If it's not generated, the
+ /// completer should complete to `null`.
+ final _pendingAssetRequests = new Map<AssetId, Queue<Completer<AssetNode>>>();
+
/// Creates a new [AssetCascade].
///
/// It loads source assets within [package] using [provider].
AssetCascade(this.graph, this.package) {
- _onDirtyPool.add(_onDirtyController.stream);
- _addPhase(new Phase(this, [], package));
-
- // Keep track of logged errors so we can know that the build failed.
- onLog.listen((entry) {
- if (entry.level == LogLevel.ERROR) {
- // TODO(nweiz): keep track of stack chain.
- _accumulatedErrors.add(
- new TransformerException(entry.transform, entry.message, null));
- }
- });
+ _addPhase(new Phase(this, package));
}
/// Gets the asset identified by [id].
@@ -148,18 +111,29 @@ class AssetCascade {
// metadata about the file names of assets it can emit, we can prove that
// none of them can emit [id] and fail early.
return _phases.last.getOutput(id).then((node) {
- // If the requested asset is available, we can just return it.
- if (node != null && node.state.isAvailable) return node;
-
- if (_processDone != null) {
- // If there's a build running, that build might generate the asset, so
- // we wait for it to complete and then try again.
- return _processDone.then((_) => getAssetNode(id));
+ if (node != null) {
+ // If the requested asset is available, we can just return it.
+ if (node.state.isAvailable) return node;
+
+ // If the requested asset exists but isn't yet available, wait to see if
+ // it becomes available. If it's removed before becoming available, try
+ // again, since it could be generated again.
+ node.force();
+ return node.whenAvailable((_) => node).catchError((error) {
+ if (error is! AssetNotFoundException) throw error;
+ return getAssetNode(id);
+ });
}
- // If the asset hasn't been built and nothing is building now, the asset
- // won't be generated, so we return null.
- return null;
+ // If the cascade isn't dirty, the phase won't generate the requested
+ // asset in the future.
+ if (!isDirty) return null;
+
+ // If the cascade is dirty, store a completer for the asset node. If it's
+ // generated in the future, we'll complete this completer.
+ var completers = _pendingAssetRequests.putIfAbsent(id, () => new Queue());
Bob Nystrom 2014/03/05 22:13:25 Why do you need a queue here? Can you just reuse t
nweiz 2014/03/06 00:29:08 Done.
+ completers.add(new Completer.sync());
+ return completers.last.future;
});
}
@@ -222,7 +196,9 @@ class AssetCascade {
continue;
}
- _addPhase(_phases.last.addPhase(transformers[i]));
+ var phase = _phases.last.addPhase();
+ _addPhase(phase);
+ phase.updateTransformers(transformers[i]);
}
if (transformers.length == 0) {
@@ -242,68 +218,55 @@ class AssetCascade {
}
void reportError(BarbackException error) {
- _accumulatedErrors.add(error);
_errorsController.add(error);
}
- /// Add [phase] to the end of [_phases] and watch its [onDirty] stream.
+ /// Add [phase] to the end of [_phases] and watch its streams.
void _addPhase(Phase phase) {
- _onDirtyPool.add(phase.onDirty);
_onLogPool.add(phase.onLog);
- phase.onDirty.listen((_) {
- _newChanges = true;
- _waitForProcess();
+ phase.onAsset.listen(_providePendingAsset);
+
+ phase.onDone.listen((_) {
+ if (isDirty) return;
+
+ // This cascade has finished building. If anyone's still waiting for
+ // assets, cut off the wait; we won't be generating them, at least until a
+ // source asset changes.
+ _pendingAssetRequests.forEach((_, completers) {
+ for (var completer in completers) {
+ completer.complete(null);
+ }
+ });
+ _pendingAssetRequests.clear();
+ _onDoneController.add(null);
});
- _phases.add(phase);
- }
- /// Starts the build process asynchronously if there is work to be done.
- ///
- /// Returns a future that completes with the background processing is done.
- /// If there is no work to do, returns a future that completes immediately.
- /// All errors that occur during processing will be caught (and routed to the
- /// [results] stream) before they get to the returned future, so it is safe
- /// to discard it.
- Future _waitForProcess() {
- if (_processDone != null) return _processDone;
-
- _accumulatedErrors = new Queue();
- _numLogErrors = 0;
- return _processDone = _process().then((_) {
- // Report the build completion.
- // TODO(rnystrom): Put some useful data in here.
- _resultsController.add(
- new BuildResult(_accumulatedErrors));
- _processDone = null;
- _accumulatedErrors = null;
- });
+ _phases.add(phase);
}
- /// Starts the background processing.
- ///
- /// Returns a future that completes when all assets have been processed.
- Future _process() {
- _newChanges = false;
- return newFuture(() {
- // Find the first phase that has work to do and do it.
- var future;
- for (var phase in _phases) {
- future = phase.process();
- if (future != null) break;
- }
-
- // If all phases are done and no new updates have come in, we're done.
- if (future == null) {
- // If changes have come in, start over.
- if (_newChanges) return _process();
+ /// Provide an asset to a pending [getAssetNode] call.
+ void _providePendingAsset(AssetNode asset) {
+ // If anyone's waiting for this asset, provide it to them.
+ var requests = _pendingAssetRequests.remove(asset.id);
+ if (requests == null) return;
- // Otherwise, everything is done.
- return null;
+ for (var request in requests) {
+ if (asset.state.isAvailable) {
Bob Nystrom 2014/03/05 22:13:25 This seems weird to me. Doesn't it make more sense
nweiz 2014/03/06 00:29:08 N/A since I got rid of the queue.
+ request.complete(asset);
+ continue;
}
- // Process that phase and then loop onto the next.
- return future.then((_) => _process());
- });
+ // A lazy asset may be emitted while still dirty. If so, we wait until
+ // it's either available or removed before trying again to access it. We
+ // retry the entire [getAsset] process because the state of the graph may
+ // have changed dramatically by the time it's available.
+ assert(asset.state.isDirty);
+ asset.force();
+ asset.whenStateChanges()
+ .then((_) => getAssetNode(asset.id))
+ .then(request.complete)
+ .catchError(request.completeError);
+ }
}
String toString() => "cascade for $package";
« no previous file with comments | « no previous file | pkg/barback/lib/src/asset_node.dart » ('j') | pkg/barback/lib/src/group_runner.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698