Commit 728e2a56 authored by John McCutchan's avatar John McCutchan Committed by GitHub

Add FlutterView and ViewManager and hook them into the ResidentRunner. (#5345)

- [x] Refactor view support into separate classes.
- [x] Make the hot runner grab the main view and call runFromSource on it.
- [x] Remove Device.needsDevFS (because it is always true).
parent b761ffaf
...@@ -365,8 +365,12 @@ class DevFS { ...@@ -365,8 +365,12 @@ class DevFS {
if (_dirtyEntries.length > 0) { if (_dirtyEntries.length > 0) {
status = logger.startProgress('Updating files...'); status = logger.startProgress('Updating files...');
if (_httpWriter != null) { if (_httpWriter != null) {
await _httpWriter.write(_dirtyEntries, try {
progressReporter: progressReporter); await _httpWriter.write(_dirtyEntries,
progressReporter: progressReporter);
} catch (e) {
printError("Could not update files on device: $e");
}
} else { } else {
// Make service protocol requests for each. // Make service protocol requests for each.
for (DevFSEntry entry in _dirtyEntries) { for (DevFSEntry entry in _dirtyEntries) {
......
...@@ -192,9 +192,6 @@ abstract class Device { ...@@ -192,9 +192,6 @@ abstract class Device {
/// Does this device implement support for hot reloading / restarting? /// Does this device implement support for hot reloading / restarting?
bool get supportsHotMode => false; bool get supportsHotMode => false;
/// Does this device need a DevFS to support hot mode?
bool get needsDevFS => true;
/// Run from a file. Necessary for hot mode. /// Run from a file. Necessary for hot mode.
Future<bool> runFromFile(ApplicationPackage package, Future<bool> runFromFile(ApplicationPackage package,
String scriptUri, String scriptUri,
......
...@@ -23,6 +23,7 @@ import 'devfs.dart'; ...@@ -23,6 +23,7 @@ import 'devfs.dart';
import 'observatory.dart'; import 'observatory.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
import 'toolchain.dart'; import 'toolchain.dart';
import 'view.dart';
String getDevFSLoaderScript() { String getDevFSLoaderScript() {
return path.absolute(path.join(Cache.flutterRoot, return path.absolute(path.join(Cache.flutterRoot,
...@@ -238,17 +239,13 @@ class HotRunner extends ResidentRunner { ...@@ -238,17 +239,13 @@ class HotRunner extends ResidentRunner {
await startEchoingDeviceLog(); await startEchoingDeviceLog();
if (device.needsDevFS) { printStatus('Launching loader on ${device.name}...');
printStatus('Launching loader on ${device.name}...');
} else {
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name}...');
}
// Start the loader. // Start the loader.
Future<LaunchResult> futureResult = device.startApp( Future<LaunchResult> futureResult = device.startApp(
_package, _package,
debuggingOptions.buildMode, debuggingOptions.buildMode,
mainPath: device.needsDevFS ? getDevFSLoaderScript() : _mainPath, mainPath: getDevFSLoaderScript(),
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
platformArgs: platformArgs, platformArgs: platformArgs,
route: route route: route
...@@ -266,49 +263,44 @@ class HotRunner extends ResidentRunner { ...@@ -266,49 +263,44 @@ class HotRunner extends ResidentRunner {
LaunchResult result = await futureResult; LaunchResult result = await futureResult;
if (!result.started) { if (!result.started) {
if (device.needsDevFS) { printError('Error launching DevFS loader on ${device.name}.');
printError('Error launching DevFS loader on ${device.name}.');
} else {
printError('Error launching ${getDisplayPath(_mainPath)} on ${device.name}.');
}
await stopEchoingDeviceLog(); await stopEchoingDeviceLog();
return 2; return 2;
} }
await connectToServiceProtocol(result.observatoryPort); await connectToServiceProtocol(result.observatoryPort);
if (device.needsDevFS) { try {
try { Uri baseUri = await _initDevFS();
Uri baseUri = await _initDevFS(); if (connectionInfoCompleter != null) {
if (connectionInfoCompleter != null) { connectionInfoCompleter.complete(
connectionInfoCompleter.complete( new DebugConnectionInfo(result.observatoryPort, baseUri: baseUri.toString())
new DebugConnectionInfo(result.observatoryPort, baseUri: baseUri.toString()) );
);
}
} catch (error) {
printError('Error initializing DevFS: $error');
return 3;
} }
_loaderShowMessage('Connecting...', progress: 0); } catch (error) {
bool devfsResult = await _updateDevFS( printError('Error initializing DevFS: $error');
progressReporter: (int progress, int max) { return 3;
if (progress % 10 == 0) }
_loaderShowMessage('Syncing files to device...', progress: progress, max: max); _loaderShowMessage('Connecting...', progress: 0);
} bool devfsResult = await _updateDevFS(
); progressReporter: (int progress, int max) {
if (!devfsResult) { if (progress % 10 == 0)
_loaderShowMessage('Failed.'); _loaderShowMessage('Syncing files to device...', progress: progress, max: max);
printError('Could not perform initial file synchronization.');
return 3;
} }
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); );
_loaderShowMessage('Launching...'); if (!devfsResult) {
await _launchFromDevFS(_package, _mainPath); _loaderShowMessage('Failed.');
} else { printError('Could not perform initial file synchronization.');
if (connectionInfoCompleter != null) return 3;
connectionInfoCompleter.complete(new DebugConnectionInfo(result.observatoryPort));
} }
await viewManager.refresh();
printStatus('Connected to view: ${viewManager.mainView}');
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...');
_loaderShowMessage('Launching...');
await _launchFromDevFS(_package, _mainPath);
_startReadingFromControlPipe(); _startReadingFromControlPipe();
printStatus('Application running.'); printStatus('Application running.');
...@@ -383,9 +375,6 @@ class HotRunner extends ResidentRunner { ...@@ -383,9 +375,6 @@ class HotRunner extends ResidentRunner {
} }
Future<Null> _evictDirtyAssets() async { Future<Null> _evictDirtyAssets() async {
if (_devFS == null) {
return;
}
if (_devFS.dirtyAssetEntries.length == 0) { if (_devFS.dirtyAssetEntries.length == 0) {
return; return;
} }
...@@ -408,26 +397,8 @@ class HotRunner extends ResidentRunner { ...@@ -408,26 +397,8 @@ class HotRunner extends ResidentRunner {
Future<Null> _launchInView(String entryPath, Future<Null> _launchInView(String entryPath,
String packagesPath, String packagesPath,
String assetsDirectoryPath) async { String assetsDirectoryPath) async {
String viewId = await serviceProtocol.getFirstViewId(); FlutterView view = viewManager.mainView;
// When this completer completes the isolate is running. return view.runFromSource(entryPath, packagesPath, assetsDirectoryPath);
// TODO(johnmccutchan): Have the framework send an event after the first
// frame is rendered and use that instead of 'runnable'.
Completer<Null> completer = new Completer<Null>();
StreamSubscription<Event> subscription =
serviceProtocol.onIsolateEvent.listen((Event event) {
if (event.kind == 'IsolateStart') {
printTrace('Isolate is spawned.');
} else if (event.kind == 'IsolateRunnable') {
printTrace('Isolate is runnable.');
completer.complete(null);
}
});
await serviceProtocol.runInView(viewId,
entryPath,
packagesPath,
assetsDirectoryPath);
await completer.future;
await subscription.cancel();
} }
Future<Null> _launchFromDevFS(ApplicationPackage package, Future<Null> _launchFromDevFS(ApplicationPackage package,
...@@ -444,27 +415,11 @@ class HotRunner extends ResidentRunner { ...@@ -444,27 +415,11 @@ class HotRunner extends ResidentRunner {
deviceAssetsDirectoryPath); deviceAssetsDirectoryPath);
} }
Future<Null> _launchFromDisk(ApplicationPackage package,
String mainScript) async {
Uri baseUri = new Uri.directory(_projectRootPath);
String entryPath = path.relative(mainScript, from: _projectRootPath);
String diskEntryPath = baseUri.resolve(entryPath).toFilePath();
String diskPackagesPath = baseUri.resolve('.packages').toFilePath();
String diskAssetsDirectoryPath = baseUri.resolve('build/flx').toFilePath();
await _launchInView(diskEntryPath,
diskPackagesPath,
diskAssetsDirectoryPath);
}
Future<Null> _restartFromSources() async { Future<Null> _restartFromSources() async {
FirstFrameTimer firstFrameTimer = new FirstFrameTimer(serviceProtocol); FirstFrameTimer firstFrameTimer = new FirstFrameTimer(serviceProtocol);
firstFrameTimer.start(); firstFrameTimer.start();
if (_devFS == null) { await _updateDevFS();
await _launchFromDisk(_package, _mainPath); await _launchFromDevFS(_package, _mainPath);
} else {
await _updateDevFS();
await _launchFromDevFS(_package, _mainPath);
}
Status restartStatus = Status restartStatus =
logger.startProgress('Waiting for application to start...'); logger.startProgress('Waiting for application to start...');
// Wait for the first frame to be rendered. // Wait for the first frame to be rendered.
......
...@@ -362,9 +362,6 @@ class IOSSimulator extends Device { ...@@ -362,9 +362,6 @@ class IOSSimulator extends Device {
@override @override
bool get supportsHotMode => true; bool get supportsHotMode => true;
@override
bool get needsDevFS => true;
_IOSSimulatorLogReader _logReader; _IOSSimulatorLogReader _logReader;
_IOSSimulatorDevicePortForwarder _portForwarder; _IOSSimulatorDevicePortForwarder _portForwarder;
......
...@@ -128,9 +128,14 @@ class Observatory { ...@@ -128,9 +128,14 @@ class Observatory {
} }
} }
Future<String> getFirstViewId() async { Future<List<Map<String, String>>> getViewList() async {
Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews'); Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews');
List<Map<String, String>> views = response['views']; List<Map<String, String>> views = response['views'];
return views;
}
Future<String> getFirstViewId() async {
List<Map<String, String>> views = await getViewList();
return views[0]['id']; return views[0]['id'];
} }
......
...@@ -12,6 +12,7 @@ import 'build_info.dart'; ...@@ -12,6 +12,7 @@ import 'build_info.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'observatory.dart'; import 'observatory.dart';
import 'view.dart';
// Shared code between different resident application runners. // Shared code between different resident application runners.
abstract class ResidentRunner { abstract class ResidentRunner {
...@@ -28,6 +29,7 @@ abstract class ResidentRunner { ...@@ -28,6 +29,7 @@ abstract class ResidentRunner {
final Completer<int> _finished = new Completer<int>(); final Completer<int> _finished = new Completer<int>();
Observatory serviceProtocol; Observatory serviceProtocol;
ViewManager viewManager;
StreamSubscription<String> _loggingSubscription; StreamSubscription<String> _loggingSubscription;
/// Start the app and keep the process running during its lifetime. /// Start the app and keep the process running during its lifetime.
...@@ -96,6 +98,11 @@ abstract class ResidentRunner { ...@@ -96,6 +98,11 @@ abstract class ResidentRunner {
serviceProtocol.onIsolateEvent.listen((Event event) { serviceProtocol.onIsolateEvent.listen((Event event) {
printTrace(event.toString()); printTrace(event.toString());
}); });
// Setup view manager and refresh the view list.
viewManager = new ViewManager(serviceProtocol);
await viewManager.refresh();
// Listen for service protocol connection to close. // Listen for service protocol connection to close.
serviceProtocol.done.whenComplete(() { serviceProtocol.done.whenComplete(() {
appFinished(); appFinished();
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'globals.dart';
import 'observatory.dart';
/// Peered to a Android/iOS FlutterView widget on a device.
class FlutterView {
FlutterView(this.viewId, this.viewManager);
final String viewId;
final ViewManager viewManager;
String _uiIsolateId;
String get uiIsolateId => _uiIsolateId;
Future<Null> runFromSource(String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
return viewManager._runFromSource(this,
entryPath,
packagesPath,
assetsDirectoryPath);
}
@override
String toString() => viewId;
@override
bool operator ==(FlutterView other) {
return other.viewId == viewId;
}
@override
int get hashCode => viewId.hashCode;
}
/// Manager of FlutterViews.
class ViewManager {
ViewManager(this.serviceProtocol);
final Observatory serviceProtocol;
Future<Null> refresh() async {
List<Map<String, String>> viewList = await serviceProtocol.getViewList();
for (Map<String, String> viewDescription in viewList) {
FlutterView view = new FlutterView(viewDescription['id'], this);
if (!views.contains(view)) {
// Canonicalize views against the view set.
views.add(view);
}
}
}
// TODO(johnmccutchan): Report errors when running failed.
Future<Null> _runFromSource(FlutterView view,
String entryPath,
String packagesPath,
String assetsDirectoryPath) async {
final String viewId = await serviceProtocol.getFirstViewId();
// When this completer completes the isolate is running.
final Completer<Null> completer = new Completer<Null>();
final StreamSubscription<Event> subscription =
serviceProtocol.onIsolateEvent.listen((Event event) {
// TODO(johnmccutchan): Listen to the debug stream and catch initial
// launch errors.
if (event.kind == 'IsolateRunnable') {
printTrace('Isolate is runnable.');
completer.complete(null);
}
});
await serviceProtocol.runInView(viewId,
entryPath,
packagesPath,
assetsDirectoryPath);
await completer.future;
await subscription.cancel();
}
// TODO(johnmccutchan): Remove this accessor and make the runner multi-view
// aware.
FlutterView get mainView {
return views.first;
}
final Set<FlutterView> views = new Set<FlutterView>();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment