Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support hot reload for applications that don't use the framework #5868

Merged
merged 1 commit into from
Sep 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/flutter/lib/src/foundation/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ abstract class BindingBase {
name: 'exit',
callback: _exitApplication
);
registerSignalServiceExtension(
name: 'frameworkPresent',
callback: () => null
);
assert(() { _debugServiceExtensionsRegistered = true; return true; });
}

Expand All @@ -124,6 +128,7 @@ abstract class BindingBase {
FlutterError.resetErrorCount();
}


/// Registers a service extension method with the given name (full
/// name "ext.flutter.name"), which takes no arguments and returns
/// no value.
Expand Down
49 changes: 33 additions & 16 deletions packages/flutter_tools/lib/src/hot.dart
Original file line number Diff line number Diff line change
Expand Up @@ -414,19 +414,25 @@ class HotRunner extends ResidentRunner {
firstFrameTimer.start();
await _updateDevFS();
await _launchFromDevFS(_package, _mainPath);
bool waitForFrame =
await currentView.uiIsolate.flutterFrameworkPresent();
Status restartStatus =
logger.startProgress('Waiting for application to start...');
// Wait for the first frame to be rendered.
await firstFrameTimer.firstFrame();
if (waitForFrame) {
// Wait for the first frame to be rendered.
await firstFrameTimer.firstFrame();
}
restartStatus.stop(showElapsedTime: true);
printStatus('Restart time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
if (benchmarkMode) {
benchmarkData['hotRestartMillisecondsToFrame'] =
firstFrameTimer.elapsed.inMilliseconds;
if (waitForFrame) {
printStatus('Restart time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
if (benchmarkMode) {
benchmarkData['hotRestartMillisecondsToFrame'] =
firstFrameTimer.elapsed.inMilliseconds;
}
flutterUsage.sendTiming('hot', 'restart', firstFrameTimer.elapsed);
}
flutterUsage.sendEvent('hot', 'restart');
flutterUsage.sendTiming('hot', 'restart', firstFrameTimer.elapsed);
}

/// Returns [true] if the reload was successful.
Expand Down Expand Up @@ -489,22 +495,33 @@ class HotRunner extends ResidentRunner {
await _evictDirtyAssets();
Status reassembleStatus =
logger.startProgress('Reassembling application...');
bool waitForFrame = true;
try {
await currentView.uiIsolate.flutterReassemble();
waitForFrame = (await currentView.uiIsolate.flutterReassemble() != null);
} catch (_) {
reassembleStatus.stop(showElapsedTime: true);
printError('Reassembling application failed.');
return false;
}
reassembleStatus.stop(showElapsedTime: true);
await firstFrameTimer.firstFrame();
printStatus('Hot reload time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
if (benchmarkMode) {
benchmarkData['hotReloadMillisecondsToFrame'] =
firstFrameTimer.elapsed.inMilliseconds;
try {
/* ensure that a frame is scheduled */
await currentView.uiIsolate.uiWindowScheduleFrame();
} catch (_) {
/* ignore any errors */
}
if (waitForFrame) {
// When the framework is present, we can wait for the first frame
// event and measure reload itme.
await firstFrameTimer.firstFrame();
printStatus('Hot reload time: '
'${getElapsedAsMilliseconds(firstFrameTimer.elapsed)}');
if (benchmarkMode) {
benchmarkData['hotReloadMillisecondsToFrame'] =
firstFrameTimer.elapsed.inMilliseconds;
}
flutterUsage.sendTiming('hot', 'reload', firstFrameTimer.elapsed);
}
flutterUsage.sendTiming('hot', 'reload', firstFrameTimer.elapsed);
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/resident_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ abstract class ResidentRunner {
Future<Null> _debugDumpRenderTree() async {
if (vmService != null)
await vmService.vm.refreshViews();

await currentView.uiIsolate.flutterDebugDumpRenderTree();
}

Expand Down
57 changes: 45 additions & 12 deletions packages/flutter_tools/lib/src/vmservice.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:convert' show BASE64;
import 'dart:io';

import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
import 'package:json_rpc_2/error_code.dart' as rpc_error_code;
import 'package:web_socket_channel/io.dart';

import 'globals.dart';
Expand Down Expand Up @@ -766,12 +767,28 @@ class Isolate extends ServiceObjectOwner {

// Flutter extension methods.

// Invoke a flutter extension method, if the flutter extension is not
// available, returns null.
Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw(
String method, [Map<String, dynamic> params]) async {
try {
return await invokeRpcRaw(method, params);
} catch (e) {
// If an application is not using the framework
if (_isMethodNotFoundException(e))
return null;
rethrow;
}
}

// Debug dump extension methods.

Future<Map<String, dynamic>> flutterDebugDumpApp() {
return invokeRpcRaw('ext.flutter.debugDumpApp');
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp');
}

Future<Map<String, dynamic>> flutterDebugDumpRenderTree() {
return invokeRpcRaw('ext.flutter.debugDumpRenderTree');
return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree');
}

// Loader page extension methods.
Expand All @@ -797,20 +814,36 @@ class Isolate extends ServiceObjectOwner {
}).catchError((dynamic error) => null);
}

/// Causes the application to pick up any changed code.
Future<Map<String, dynamic>> flutterReassemble() {
return invokeRpcRaw('ext.flutter.reassemble');
static bool _isMethodNotFoundException(dynamic e) {
return (e is rpc.RpcException) &&
(e.code == rpc_error_code.METHOD_NOT_FOUND);
}

Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) {
return invokeRpcRaw('ext.flutter.evict', <String, dynamic>{
'value': assetPath
});
// Reload related extension methods.
Future<Map<String, dynamic>> flutterReassemble() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.reassemble');
}

Future<bool> flutterFrameworkPresent() async {
return (await invokeFlutterExtensionRpcRaw('ext.flutter.frameworkPresent') != null);
}

Future<Map<String, dynamic>> uiWindowScheduleFrame() async {
return await invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame');
}

Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.evict',
<String, dynamic>{
'value': assetPath
}
);
}

Future<Map<String, dynamic>> flutterExit() {
return invokeRpcRaw('ext.flutter.exit').timeout(
const Duration(seconds: 2), onTimeout: () => null);
// Application control extension methods.
Future<Map<String, dynamic>> flutterExit() async {
return await invokeFlutterExtensionRpcRaw('ext.flutter.exit').timeout(
const Duration(seconds: 2), onTimeout: () => null);
}
}

Expand Down