Unverified Commit a2e25749 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Remove the fast reassemble / single widget reload feature (#132255)

Fixes https://github.com/flutter/flutter/issues/132157
parent 6cf5dbe3
......@@ -168,13 +168,6 @@ abstract class BindingBase {
static Type? _debugInitializedType;
static bool _debugServiceExtensionsRegistered = false;
/// Additional configuration used by the framework during hot reload.
///
/// See also:
///
/// * [DebugReassembleConfig], which describes the configuration.
static DebugReassembleConfig? debugReassembleConfig;
/// Deprecated. Will be removed in a future version of Flutter.
///
/// This property has been deprecated to prepare for Flutter's upcoming
......@@ -989,23 +982,3 @@ abstract class BindingBase {
Future<void> _exitApplication() async {
exit(0);
}
/// Additional configuration used for hot reload reassemble optimizations.
///
/// Do not extend, implement, or mixin this class. This may only be instantiated
/// in debug mode.
class DebugReassembleConfig {
/// Create a new [DebugReassembleConfig].
///
/// Throws a [FlutterError] if this is called in profile or release mode.
DebugReassembleConfig({
this.widgetName,
}) {
if (!kDebugMode) {
throw FlutterError('Cannot instantiate DebugReassembleConfig in profile or release mode.');
}
}
/// The name of the widget that was modified, or `null` if the change was elsewhere.
final String? widgetName;
}
......@@ -603,18 +603,16 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
@override
Future<void> performReassemble() async {
await super.performReassemble();
if (BindingBase.debugReassembleConfig?.widgetName == null) {
if (!kReleaseMode) {
FlutterTimeline.startSync('Preparing Hot Reload (layout)');
if (!kReleaseMode) {
FlutterTimeline.startSync('Preparing Hot Reload (layout)');
}
try {
for (final RenderView renderView in renderViews) {
renderView.reassemble();
}
try {
for (final RenderView renderView in renderViews) {
renderView.reassemble();
}
} finally {
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
} finally {
if (!kReleaseMode) {
FlutterTimeline.finishSync();
}
}
scheduleWarmUpFrame();
......
......@@ -505,23 +505,6 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
},
);
registerServiceExtension(
name: WidgetsServiceExtensions.fastReassemble.name,
callback: (Map<String, Object> params) async {
// This mirrors the implementation of the 'reassemble' callback registration
// in lib/src/foundation/binding.dart, but with the extra binding config used
// to skip some reassemble work.
final String? className = params['className'] as String?;
BindingBase.debugReassembleConfig = DebugReassembleConfig(widgetName: className);
try {
await reassembleApplication();
} finally {
BindingBase.debugReassembleConfig = null;
}
return <String, String>{'type': 'Success'};
},
);
// Expose the ability to send Widget rebuilds as [Timeline] events.
registerBoolServiceExtension(
name: WidgetsServiceExtensions.profileWidgetBuilds.name,
......@@ -560,7 +543,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
Future<void> _forceRebuild() {
if (rootElement != null) {
buildOwner!.reassemble(rootElement!, null);
buildOwner!.reassemble(rootElement!);
return endOfFrame;
}
return Future<void>.value();
......@@ -1090,7 +1073,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
}());
if (rootElement != null) {
buildOwner!.reassemble(rootElement!, BindingBase.debugReassembleConfig);
buildOwner!.reassemble(rootElement!);
}
return super.performReassemble();
}
......
......@@ -3252,14 +3252,13 @@ class BuildOwner {
/// changed implementations.
///
/// This is expensive and should not be called except during development.
void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
void reassemble(Element root) {
if (!kReleaseMode) {
FlutterTimeline.startSync('Preparing Hot Reload (widgets)');
}
try {
assert(root._parent == null);
assert(root.owner == this);
root._debugReassembleConfig = reassembleConfig;
root.reassemble();
} finally {
if (!kReleaseMode) {
......@@ -3374,7 +3373,6 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
}
Element? _parent;
DebugReassembleConfig? _debugReassembleConfig;
_NotificationNode? _notificationTree;
/// Compare two widgets for equality.
......@@ -3526,15 +3524,10 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
@mustCallSuper
@protected
void reassemble() {
if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
markNeedsBuild();
_debugReassembleConfig = null;
}
markNeedsBuild();
visitChildren((Element child) {
child._debugReassembleConfig = _debugReassembleConfig;
child.reassemble();
});
_debugReassembleConfig = null;
}
bool _debugIsInScope(Element target) {
......@@ -5585,9 +5578,7 @@ class StatefulElement extends ComponentElement {
@override
void reassemble() {
if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
state.reassemble();
}
state.reassemble();
super.reassemble();
}
......@@ -6952,9 +6943,3 @@ class _NullWidget extends Widget {
@override
Element createElement() => throw UnimplementedError();
}
// Whether a [DebugReassembleConfig] indicates that an element holding [widget] can skip
// a reassemble.
bool _debugShouldReassemble(DebugReassembleConfig? config, Widget? widget) {
return config == null || config.widgetName == null || widget?.runtimeType.toString() == config.widgetName;
}
......@@ -970,7 +970,7 @@ mixin WidgetInspectorService {
Future<void> forceRebuild() {
final WidgetsBinding binding = WidgetsBinding.instance;
if (binding.rootElement != null) {
binding.buildOwner!.reassemble(binding.rootElement!, null);
binding.buildOwner!.reassemble(binding.rootElement!);
return binding.endOfFrame;
}
return Future<void>.value();
......
// Copyright 2014 The Flutter 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 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('reassemble with a className only marks subtrees from the first matching element as dirty', (WidgetTester tester) async {
await tester.pumpWidget(
const Foo(Bar(Fizz(SizedBox())))
);
expect(Foo.count, 0);
expect(Bar.count, 0);
expect(Fizz.count, 0);
DebugReassembleConfig config = DebugReassembleConfig(widgetName: 'Bar');
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.rootElement!, config);
expect(Foo.count, 0);
expect(Bar.count, 1);
expect(Fizz.count, 1);
config = DebugReassembleConfig(widgetName: 'Fizz');
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.rootElement!, config);
expect(Foo.count, 0);
expect(Bar.count, 1);
expect(Fizz.count, 2);
config = DebugReassembleConfig(widgetName: 'NoMatch');
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.rootElement!, config);
expect(Foo.count, 0);
expect(Bar.count, 1);
expect(Fizz.count, 2);
config = DebugReassembleConfig();
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.rootElement!, config);
expect(Foo.count, 1);
expect(Bar.count, 2);
expect(Fizz.count, 3);
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.rootElement!, null);
expect(Foo.count, 2);
expect(Bar.count, 3);
expect(Fizz.count, 4);
});
}
class Foo extends StatefulWidget {
const Foo(this.child, {super.key});
final Widget child;
static int count = 0;
@override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
@override
void reassemble() {
Foo.count += 1;
super.reassemble();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
class Bar extends StatefulWidget {
const Bar(this.child, {super.key});
final Widget child;
static int count = 0;
@override
State<Bar> createState() => _BarState();
}
class _BarState extends State<Bar> {
@override
void reassemble() {
Bar.count += 1;
super.reassemble();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
class Fizz extends StatefulWidget {
const Fizz(this.child, {super.key});
final Widget child;
static int count = 0;
@override
State<Fizz> createState() => _FizzState();
}
class _FizzState extends State<Fizz> {
@override
void reassemble() {
Fizz.count += 1;
super.reassemble();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
......@@ -115,7 +115,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
final WidgetsBinding binding = WidgetsBinding.instance;
if (binding.rootElement != null) {
binding.buildOwner!.reassemble(binding.rootElement!, null);
binding.buildOwner!.reassemble(binding.rootElement!);
}
}
......
......@@ -33,7 +33,7 @@ String getDefaultCachedKernelPath({
}) {
final StringBuffer buffer = StringBuffer();
final List<String> cacheFrontEndOptions = extraFrontEndOptions.toList()
..removeWhere((String arg) => arg.startsWith('--enable-experiment=') || arg == '--flutter-widget-cache');
..removeWhere((String arg) => arg.startsWith('--enable-experiment='));
buffer.writeAll(dartDefines);
buffer.writeAll(cacheFrontEndOptions);
String buildPrefix = '';
......
......@@ -401,7 +401,6 @@ class UpdateFSReport {
bool success = false,
int invalidatedSourcesCount = 0,
int syncedBytes = 0,
this.fastReassembleClassName,
int scannedSourcesCount = 0,
Duration compileDuration = Duration.zero,
Duration transferDuration = Duration.zero,
......@@ -423,7 +422,6 @@ class UpdateFSReport {
Duration get findInvalidatedDuration => _findInvalidatedDuration;
bool _success;
String? fastReassembleClassName;
int _invalidatedSourcesCount;
int _syncedBytes;
int _scannedSourcesCount;
......@@ -435,7 +433,6 @@ class UpdateFSReport {
if (!report._success) {
_success = false;
}
fastReassembleClassName ??= report.fastReassembleClassName;
_invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes;
_scannedSourcesCount += report._scannedSourcesCount;
......@@ -495,7 +492,6 @@ class DevFS {
DateTime? lastCompiled;
DateTime? _previousCompiled;
PackageConfig? lastPackageConfig;
File? _widgetCacheOutputFile;
Uri? _baseUri;
Uri? get baseUri => _baseUri;
......@@ -555,22 +551,6 @@ class DevFS {
lastCompiled = _previousCompiled;
}
/// If the build method of a single widget was modified, return the widget name.
///
/// If any other changes were made, or there is an error scanning the file,
/// return `null`.
String? _checkIfSingleWidgetReloadApplied() {
final File? widgetCacheOutputFile = _widgetCacheOutputFile;
if (widgetCacheOutputFile != null && widgetCacheOutputFile.existsSync()) {
final String widget = widgetCacheOutputFile.readAsStringSync().trim();
if (widget.isNotEmpty) {
return widget;
}
}
return null;
}
/// Updates files on the device.
///
/// Returns the number of bytes synced.
......@@ -596,7 +576,6 @@ class DevFS {
final DateTime candidateCompileTime = DateTime.now();
didUpdateFontManifest = false;
lastPackageConfig = packageConfig;
_widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache');
// Update modified files
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
......@@ -741,7 +720,6 @@ class DevFS {
success: true,
syncedBytes: syncedBytes,
invalidatedSourcesCount: invalidatedFiles.length,
fastReassembleClassName: _checkIfSingleWidgetReloadApplied(),
compileDuration: compileTimer.elapsed,
transferDuration: transferTimer.elapsed,
);
......
......@@ -44,9 +44,6 @@ abstract class FeatureFlags {
/// Whether custom devices are enabled.
bool get areCustomDevicesEnabled => false;
/// Whether fast single widget reloads are enabled.
bool get isSingleWidgetReloadEnabled => false;
/// Whether WebAssembly compilation for Flutter Web is enabled.
bool get isFlutterWebWasmEnabled => false;
......@@ -62,7 +59,6 @@ const List<Feature> allFeatures = <Feature>[
flutterLinuxDesktopFeature,
flutterMacOSDesktopFeature,
flutterWindowsDesktopFeature,
singleWidgetReload,
flutterAndroidFeature,
flutterIOSFeature,
flutterFuchsiaFeature,
......@@ -140,20 +136,6 @@ const Feature flutterCustomDevicesFeature = Feature(
),
);
/// The fast hot reload feature for https://github.com/flutter/flutter/issues/61407.
const Feature singleWidgetReload = Feature(
name: 'Hot reload optimization for changes to class body of a single widget',
configSetting: 'single-widget-reload-optimization',
environmentOverride: 'FLUTTER_SINGLE_WIDGET_RELOAD',
master: FeatureChannelSetting(
available: true,
enabledByDefault: true,
),
beta: FeatureChannelSetting(
available: true,
),
);
/// Enabling WebAssembly compilation from `flutter build web`
const Feature flutterWebWasm = Feature(
name: 'WebAssembly compilation from flutter build web',
......
......@@ -44,9 +44,6 @@ class FlutterFeatureFlags implements FeatureFlags {
@override
bool get areCustomDevicesEnabled => isEnabled(flutterCustomDevicesFeature);
@override
bool get isSingleWidgetReloadEnabled => isEnabled(singleWidgetReload);
@override
bool get isFlutterWebWasmEnabled => isEnabled(flutterWebWasm);
......
......@@ -446,7 +446,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive).
fullRestart: true,
reason: reason,
overallTimeInMs: elapsed.inMilliseconds,
fastReassemble: false,
).send();
}
return OperationResult.ok;
......
......@@ -58,7 +58,6 @@ class CustomDimensions {
this.commandRunAndroidEmbeddingVersion,
this.commandPackagesAndroidEmbeddingVersion,
this.nullSafety,
this.fastReassemble,
this.nullSafeMigratedLibraries,
this.nullSafeTotalLibraries,
this.hotEventCompileTimeInMs,
......@@ -118,17 +117,17 @@ class CustomDimensions {
final String? commandRunAndroidEmbeddingVersion; // cd45
final String? commandPackagesAndroidEmbeddingVersion; // cd46
final bool? nullSafety; // cd47
final bool? fastReassemble; // cd48
// cd48 was fastReassemble but that feature was removed
final int? nullSafeMigratedLibraries; // cd49
final int? nullSafeTotalLibraries; // cd50
final int? hotEventCompileTimeInMs; // cd 51
final int? hotEventFindInvalidatedTimeInMs; // cd 52
final int? hotEventScannedSourcesCount; // cd 53
final int? hotEventReassembleTimeInMs; // cd 54
final int? hotEventReloadVMTimeInMs; // cd 55
final bool? commandRunEnableImpeller; // cd 56
final String? commandRunIOSInterfaceType; // cd 57
final bool? commandRunIsTest; // cd 58
final int? hotEventCompileTimeInMs; // cd51
final int? hotEventFindInvalidatedTimeInMs; // cd52
final int? hotEventScannedSourcesCount; // cd53
final int? hotEventReassembleTimeInMs; // cd54
final int? hotEventReloadVMTimeInMs; // cd55
final bool? commandRunEnableImpeller; // cd56
final String? commandRunIOSInterfaceType; // cd57
final bool? commandRunIsTest; // cd58
/// Convert to a map that will be used to upload to the analytics backend.
Map<String, String> toMap() => <String, String>{
......@@ -179,7 +178,6 @@ class CustomDimensions {
if (commandRunAndroidEmbeddingVersion != null) CustomDimensionsEnum.commandRunAndroidEmbeddingVersion.cdKey: commandRunAndroidEmbeddingVersion.toString(),
if (commandPackagesAndroidEmbeddingVersion != null) CustomDimensionsEnum.commandPackagesAndroidEmbeddingVersion.cdKey: commandPackagesAndroidEmbeddingVersion.toString(),
if (nullSafety != null) CustomDimensionsEnum.nullSafety.cdKey: nullSafety.toString(),
if (fastReassemble != null) CustomDimensionsEnum.fastReassemble.cdKey: fastReassemble.toString(),
if (nullSafeMigratedLibraries != null) CustomDimensionsEnum.nullSafeMigratedLibraries.cdKey: nullSafeMigratedLibraries.toString(),
if (nullSafeTotalLibraries != null) CustomDimensionsEnum.nullSafeTotalLibraries.cdKey: nullSafeTotalLibraries.toString(),
if (hotEventCompileTimeInMs != null) CustomDimensionsEnum.hotEventCompileTimeInMs.cdKey: hotEventCompileTimeInMs.toString(),
......@@ -247,7 +245,6 @@ class CustomDimensions {
commandRunAndroidEmbeddingVersion: other.commandRunAndroidEmbeddingVersion ?? commandRunAndroidEmbeddingVersion,
commandPackagesAndroidEmbeddingVersion: other.commandPackagesAndroidEmbeddingVersion ?? commandPackagesAndroidEmbeddingVersion,
nullSafety: other.nullSafety ?? nullSafety,
fastReassemble: other.fastReassemble ?? fastReassemble,
nullSafeMigratedLibraries: other.nullSafeMigratedLibraries ?? nullSafeMigratedLibraries,
nullSafeTotalLibraries: other.nullSafeTotalLibraries ?? nullSafeTotalLibraries,
hotEventCompileTimeInMs: other.hotEventCompileTimeInMs ?? hotEventCompileTimeInMs,
......@@ -309,7 +306,6 @@ class CustomDimensions {
commandRunAndroidEmbeddingVersion: _extractString(map, CustomDimensionsEnum.commandRunAndroidEmbeddingVersion),
commandPackagesAndroidEmbeddingVersion: _extractString(map, CustomDimensionsEnum.commandPackagesAndroidEmbeddingVersion),
nullSafety: _extractBool(map, CustomDimensionsEnum.nullSafety),
fastReassemble: _extractBool(map, CustomDimensionsEnum.fastReassemble),
nullSafeMigratedLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeMigratedLibraries),
nullSafeTotalLibraries: _extractInt(map, CustomDimensionsEnum.nullSafeTotalLibraries),
hotEventCompileTimeInMs: _extractInt(map, CustomDimensionsEnum.hotEventCompileTimeInMs),
......@@ -397,7 +393,7 @@ enum CustomDimensionsEnum {
commandRunAndroidEmbeddingVersion, // cd45
commandPackagesAndroidEmbeddingVersion, // cd46
nullSafety, // cd47
fastReassemble, // cd48
obsolete1, // cd48 (was fastReassemble)
nullSafeMigratedLibraries, // cd49
nullSafeTotalLibraries, // cd50
hotEventCompileTimeInMs, // cd51
......
......@@ -39,7 +39,6 @@ class HotEvent extends UsageEvent {
required this.sdkName,
required this.emulator,
required this.fullRestart,
required this.fastReassemble,
this.reason,
this.finalLibraryCount,
this.syncedLibraryCount,
......@@ -63,7 +62,6 @@ class HotEvent extends UsageEvent {
final String sdkName;
final bool emulator;
final bool fullRestart;
final bool fastReassemble;
final int? finalLibraryCount;
final int? syncedLibraryCount;
final int? syncedClassesCount;
......@@ -94,7 +92,6 @@ class HotEvent extends UsageEvent {
hotEventInvalidatedSourcesCount: invalidatedSourcesCount,
hotEventTransferTimeInMs: transferTimeInMs,
hotEventOverallTimeInMs: overallTimeInMs,
fastReassemble: fastReassemble,
hotEventCompileTimeInMs: compileTimeInMs,
hotEventFindInvalidatedTimeInMs: findInvalidatedTimeInMs,
hotEventScannedSourcesCount: scannedSourcesCount,
......
......@@ -34,7 +34,6 @@ import 'compile.dart';
import 'convert.dart';
import 'devfs.dart';
import 'device.dart';
import 'features.dart';
import 'globals.dart' as globals;
import 'ios/application_package.dart';
import 'ios/devices.dart';
......@@ -169,11 +168,8 @@ class FlutterDevice {
platform: platform,
);
} else {
// The flutter-widget-cache feature only applies to run mode.
List<String> extraFrontEndOptions = buildInfo.extraFrontEndOptions;
extraFrontEndOptions = <String>[
if (featureFlags.isSingleWidgetReloadEnabled)
'--flutter-widget-cache',
'--enable-experiment=alternative-invalidation-strategy',
...extraFrontEndOptions,
];
......
......@@ -20,7 +20,6 @@ import 'convert.dart';
import 'dart/package_map.dart';
import 'devfs.dart';
import 'device.dart';
import 'features.dart';
import 'globals.dart' as globals;
import 'project.dart';
import 'reporting/reporting.dart';
......@@ -415,7 +414,6 @@ class HotRunner extends ResidentRunner {
sdkName: _sdkName!,
emulator: _emulator!,
fullRestart: false,
fastReassemble: false,
overallTimeInMs: appStartedTimer.elapsed.inMilliseconds,
compileTimeInMs: totalCompileTime.inMilliseconds,
transferTimeInMs: totalLaunchAppTime.inMilliseconds,
......@@ -802,7 +800,6 @@ class HotRunner extends ResidentRunner {
emulator: emulator!,
fullRestart: true,
reason: reason,
fastReassemble: false,
overallTimeInMs: restartTimer.elapsed.inMilliseconds,
syncedBytes: result.updateFSReport?.syncedBytes,
invalidatedSourcesCount: result.updateFSReport?.invalidatedSourcesCount,
......@@ -828,7 +825,6 @@ class HotRunner extends ResidentRunner {
emulator: emulator!,
fullRestart: true,
reason: reason,
fastReassemble: false,
).send();
}
status?.cancel();
......@@ -878,7 +874,6 @@ class HotRunner extends ResidentRunner {
emulator: emulator!,
fullRestart: false,
reason: reason,
fastReassemble: false,
).send();
} else {
HotEvent('exception',
......@@ -887,7 +882,6 @@ class HotRunner extends ResidentRunner {
emulator: emulator!,
fullRestart: false,
reason: reason,
fastReassemble: false,
).send();
}
return OperationResult(errorCode, errorMessage, fatal: true);
......@@ -971,7 +965,6 @@ class HotRunner extends ResidentRunner {
viewCache,
onSlow,
reloadMessage,
updatedDevFS.fastReassembleClassName,
);
shouldReportReloadTime = reassembleResult.shouldReportReloadTime;
if (reassembleResult.reassembleViews.isEmpty) {
......@@ -1005,7 +998,6 @@ class HotRunner extends ResidentRunner {
syncedBytes: updatedDevFS.syncedBytes,
invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount,
transferTimeInMs: updatedDevFS.transferDuration.inMilliseconds,
fastReassemble: featureFlags.isSingleWidgetReloadEnabled && updatedDevFS.fastReassembleClassName != null,
compileTimeInMs: updatedDevFS.compileDuration.inMilliseconds,
findInvalidatedTimeInMs: updatedDevFS.findInvalidatedDuration.inMilliseconds,
scannedSourcesCount: updatedDevFS.scannedSourcesCount,
......@@ -1225,7 +1217,6 @@ Future<OperationResult> defaultReloadSourcesHelper(
emulator: emulator!,
fullRestart: false,
reason: reason,
fastReassemble: false,
usage: usage,
).send();
// Reset devFS lastCompileTime to ensure the file will still be marked
......@@ -1288,7 +1279,6 @@ typedef ReassembleHelper = Future<ReassembleResult> Function(
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
String? fastReassembleClassName,
);
Future<ReassembleResult> _defaultReassembleHelper(
......@@ -1296,7 +1286,6 @@ Future<ReassembleResult> _defaultReassembleHelper(
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
String? fastReassembleClassName,
) async {
// Check if any isolates are paused and reassemble those that aren't.
final Map<FlutterView, FlutterVmService?> reassembleViews = <FlutterView, FlutterVmService?>{};
......@@ -1325,17 +1314,9 @@ Future<ReassembleResult> _defaultReassembleHelper(
reassembleViews[view] = device.vmService;
// If the tool identified a change in a single widget, do a fast instead
// of a full reassemble.
Future<void> reassembleWork;
if (fastReassembleClassName != null) {
reassembleWork = device.vmService!.flutterFastReassemble(
isolateId: view.uiIsolate!.id!,
className: fastReassembleClassName,
);
} else {
reassembleWork = device.vmService!.flutterReassemble(
isolateId: view.uiIsolate!.id!,
);
}
final Future<void> reassembleWork = device.vmService!.flutterReassemble(
isolateId: view.uiIsolate!.id!,
);
reassembleFutures.add(reassembleWork.then(
(Object? obj) => obj,
onError: (Object error, StackTrace stackTrace) {
......
......@@ -809,19 +809,6 @@ class FlutterVmService {
);
}
Future<Map<String, Object?>?> flutterFastReassemble({
required String isolateId,
required String className,
}) {
return invokeFlutterExtensionRpcRaw(
'ext.flutter.fastReassemble',
isolateId: isolateId,
args: <String, Object>{
'className': className,
},
);
}
Future<bool> flutterAlreadyPaintedFirstUsefulFrame({
required String isolateId,
}) async {
......
......@@ -118,14 +118,14 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
});
testWithoutContext('--flutter-widget-cache and --enable-experiment are removed from getDefaultCachedKernelPath hash', () {
testWithoutContext('--enable-experiment is removed from getDefaultCachedKernelPath hash', () {
final FileSystem fileSystem = MemoryFileSystem.test();
final Config config = Config.test();
expect(getDefaultCachedKernelPath(
trackWidgetCreation: true,
dartDefines: <String>[],
extraFrontEndOptions: <String>['--enable-experiment=foo', '--flutter-widget-cache'],
extraFrontEndOptions: <String>['--enable-experiment=foo'],
fileSystem: fileSystem,
config: config,
), 'build/cache.dill.track.dill');
......@@ -133,7 +133,7 @@ void main() {
expect(getDefaultCachedKernelPath(
trackWidgetCreation: true,
dartDefines: <String>['foo=bar'],
extraFrontEndOptions: <String>['--enable-experiment=foo', '--flutter-widget-cache'],
extraFrontEndOptions: <String>['--enable-experiment=foo'],
fileSystem: fileSystem,
config: config,
), 'build/06ad47d8e64bd28de537b62ff85357c4.cache.dill.track.dill');
......@@ -141,7 +141,7 @@ void main() {
expect(getDefaultCachedKernelPath(
trackWidgetCreation: false,
dartDefines: <String>[],
extraFrontEndOptions: <String>['--enable-experiment=foo', '--flutter-widget-cache'],
extraFrontEndOptions: <String>['--enable-experiment=foo'],
fileSystem: fileSystem,
config: config,
), 'build/cache.dill');
......@@ -149,7 +149,7 @@ void main() {
expect(getDefaultCachedKernelPath(
trackWidgetCreation: true,
dartDefines: <String>[],
extraFrontEndOptions: <String>['--enable-experiment=foo', '--flutter-widget-cache', '--foo=bar'],
extraFrontEndOptions: <String>['--enable-experiment=foo', '--foo=bar'],
fileSystem: fileSystem,
config: config,
), 'build/95b595cca01caa5f0ca0a690339dd7f6.cache.dill.track.dill');
......
......@@ -178,7 +178,6 @@ void main() {
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
String? fastReassembleClassName,
) async => ReassembleResult(
<FlutterView?, FlutterVmService?>{null: null},
false,
......@@ -296,7 +295,6 @@ void main() {
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: true,
fastReassemble: false,
hotEventOverallTimeInMs: 64000,
hotEventSyncedBytes: 4,
hotEventInvalidatedSourcesCount: 2,
......@@ -379,7 +377,6 @@ void main() {
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
String? fastReassembleClassName,
) async => ReassembleResult(
<FlutterView?, FlutterVmService?>{null: null},
false,
......@@ -402,7 +399,6 @@ void main() {
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
hotEventCompileTimeInMs: 16000,
hotEventFindInvalidatedTimeInMs: 64000,
hotEventScannedSourcesCount: 16,
......
......@@ -25,7 +25,6 @@ import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
......@@ -424,7 +423,6 @@ void main() {
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
......@@ -480,7 +478,6 @@ void main() {
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
......@@ -527,7 +524,6 @@ void main() {
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
......@@ -766,96 +762,6 @@ void main() {
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner can perform fast reassemble', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
listViews,
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
const FakeVmServiceRequest(
method: kReloadSourcesServiceName,
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill',
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{
'loadedLibraryCount': 1,
},
},
),
FakeVmServiceRequest(
method: 'getIsolatePauseEvent',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedEvent.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.fastReassemble',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'className': 'FOO',
},
),
]);
final FakeDelegateFlutterDevice flutterDevice = FakeDelegateFlutterDevice(
device,
BuildInfo.debug,
FakeResidentCompiler(),
devFS,
)..vmService = fakeVmServiceHost!.vmService;
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
devFS.nextUpdateReport = UpdateFSReport(
success: true,
fastReassembleClassName: 'FOO',
invalidatedSourcesCount: 1,
);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.code, 0);
final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
expect(event.category, 'hot');
expect(event.parameter, 'reload');
expect(event.parameters?.fastReassemble, true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform(),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
Usage: () => TestUsage(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
}));
testUsingContext('ResidentRunner reports hot reload time details', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
......@@ -893,10 +799,9 @@ void main() {
jsonResponse: fakeUnpausedEvent.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.fastReassemble',
method: 'ext.flutter.reassemble',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'className': 'FOO',
},
),
]);
......@@ -917,7 +822,6 @@ void main() {
);
devFS.nextUpdateReport = UpdateFSReport(
success: true,
fastReassembleClassName: 'FOO',
invalidatedSourcesCount: 1,
);
......@@ -942,7 +846,6 @@ void main() {
Platform: () => FakePlatform(),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
Usage: () => TestUsage(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
}));
testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
......@@ -1225,7 +1128,6 @@ void main() {
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: true,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
......@@ -1984,31 +1886,7 @@ flutter:
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('FlutterDevice passes flutter-widget-cache flag when feature is enabled', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
final DefaultResidentCompiler? residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
),
target: null, platform: FakePlatform(),
)).generator as DefaultResidentCompiler?;
expect(residentCompiler!.extraFrontEndOptions,
contains('--flutter-widget-cache'));
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
});
testUsingContext('FlutterDevice passes alternative-invalidation-strategy flag', () async {
testUsingContext('FlutterDevice passes alternative-invalidation-strategy flag', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
......@@ -2032,7 +1910,7 @@ flutter:
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('FlutterDevice passes initializeFromDill parameter if specified', () async {
testUsingContext('FlutterDevice passes initializeFromDill parameter if specified', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
......
// Copyright 2014 The Flutter 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 'package:flutter_tools/src/base/file_system.dart';
import '../src/common.dart';
import 'test_data/single_widget_reload_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
final SingleWidgetReloadProject project = SingleWidgetReloadProject();
late FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_reload_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext('newly added code executes during hot reload with single widget reloads, but only invalidated widget', () async {
final StringBuffer stdout = StringBuffer();
final StreamSubscription<String> subscription = flutter.stdout.listen(stdout.writeln);
await flutter.run(singleWidgetReloads: true);
project.uncommentHotReloadPrint();
try {
await flutter.hotReload();
expect(stdout.toString(), allOf(
contains('(((TICK 1)))'),
contains('(((((RELOAD WORKED)))))'),
// Does not invalidate parent widget, so second tick is not output.
isNot(contains('(((TICK 2)))'),
)));
} finally {
await subscription.cancel();
}
});
testWithoutContext('changes outside of the class body triggers a full reload', () async {
final StringBuffer stdout = StringBuffer();
final StreamSubscription<String> subscription = flutter.stdout.listen(stdout.writeln);
await flutter.run(singleWidgetReloads: true);
project.modifyFunction();
try {
await flutter.hotReload();
expect(stdout.toString(), allOf(
contains('(((TICK 1)))'),
contains('(((TICK 2)))'),
));
} finally {
await subscription.cancel();
}
});
}
// Copyright 2014 The Flutter 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 '../test_utils.dart';
import 'project.dart';
class SingleWidgetReloadProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: '>=3.0.0-0 <4.0.0'
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'''
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
runApp(MyApp());
}
int count = 1;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// PARENT WIDGET
print('((((TICK $count))))');
count += 1;
return MaterialApp(
title: 'Flutter Demo',
home: SecondWidget(),
);
}
}
class SecondWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Do not remove the next line, it's uncommented by a test to verify that
// hot reloading worked:
// printHotReloadWorked();
return Container();
}
}
void printHotReloadWorked() {
// The call to this function is uncommented by a test to verify that hot
// reloading worked.
print('(((((RELOAD WORKED)))))');
}
''';
Uri get parentWidgetUri => mainDart;
int get parentWidgetLine => lineContaining(main, '// PARENT WIDGET');
void uncommentHotReloadPrint() {
final String newMainContents = main.replaceAll(
'// printHotReloadWorked();',
'printHotReloadWorked();',
);
writeFile(
fileSystem.path.join(dir.path, 'lib', 'main.dart'),
newMainContents,
writeFutureModifiedDate: true,
);
}
void modifyFunction() {
final String newMainContents = main.replaceAll(
'(((((RELOAD WORKED)))))',
'(((((RELOAD WORKED 2)))))',
);
writeFile(
fileSystem.path.join(dir.path, 'lib', 'main.dart'),
newMainContents,
writeFutureModifiedDate: true,
);
}
}
......@@ -90,7 +90,6 @@ abstract class FlutterTestDriver {
List<String> arguments, {
String? script,
bool withDebugger = false,
bool singleWidgetReloads = false,
}) async {
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
if (withDebugger) {
......@@ -114,8 +113,6 @@ abstract class FlutterTestDriver {
environment: <String, String>{
'FLUTTER_TEST': 'true',
'FLUTTER_WEB': 'true',
if (singleWidgetReloads)
'FLUTTER_SINGLE_WIDGET_RELOAD': 'true',
},
);
......@@ -511,7 +508,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool chrome = false,
bool expressionEvaluation = true,
bool structuredErrors = false,
bool singleWidgetReloads = false,
bool serveObservatory = false,
String? script,
List<String>? additionalCommandArgs,
......@@ -542,7 +538,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
startPaused: startPaused,
pauseOnExceptions: pauseOnExceptions,
script: script,
singleWidgetReloads: singleWidgetReloads,
);
}
......@@ -551,7 +546,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool withDebugger = false,
bool startPaused = false,
bool pauseOnExceptions = false,
bool singleWidgetReloads = false,
bool serveObservatory = false,
List<String>? additionalCommandArgs,
}) async {
......@@ -573,7 +567,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
withDebugger: withDebugger,
startPaused: startPaused,
pauseOnExceptions: pauseOnExceptions,
singleWidgetReloads: singleWidgetReloads,
attachPort: port,
);
}
......@@ -585,7 +578,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool withDebugger = false,
bool startPaused = false,
bool pauseOnExceptions = false,
bool singleWidgetReloads = false,
int? attachPort,
}) async {
assert(!startPaused || withDebugger);
......@@ -593,7 +585,6 @@ class FlutterRunTestDriver extends FlutterTestDriver {
args,
script: script,
withDebugger: withDebugger,
singleWidgetReloads: singleWidgetReloads,
);
final Completer<void> prematureExitGuard = Completer<void>();
......@@ -806,13 +797,11 @@ class FlutterTestTestDriver extends FlutterTestDriver {
bool withDebugger = false,
bool pauseOnExceptions = false,
Future<void> Function()? beforeStart,
bool singleWidgetReloads = false,
}) async {
await super._setupProcess(
args,
script: script,
withDebugger: withDebugger,
singleWidgetReloads: singleWidgetReloads,
);
// Stash the PID so that we can terminate the VM more reliably than using
......
......@@ -443,7 +443,6 @@ class TestFeatureFlags implements FeatureFlags {
this.isMacOSEnabled = false,
this.isWebEnabled = false,
this.isWindowsEnabled = false,
this.isSingleWidgetReloadEnabled = false,
this.isAndroidEnabled = true,
this.isIOSEnabled = true,
this.isFuchsiaEnabled = false,
......@@ -463,9 +462,6 @@ class TestFeatureFlags implements FeatureFlags {
@override
final bool isWindowsEnabled;
@override
final bool isSingleWidgetReloadEnabled;
@override
final bool isAndroidEnabled;
......@@ -492,8 +488,6 @@ class TestFeatureFlags implements FeatureFlags {
return isMacOSEnabled;
case flutterWindowsDesktopFeature:
return isWindowsEnabled;
case singleWidgetReload:
return isSingleWidgetReloadEnabled;
case flutterAndroidFeature:
return isAndroidEnabled;
case flutterIOSFeature:
......
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