This reverts commit ec8693e80b.
This commit is contained in:
@@ -157,6 +157,13 @@ 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;
|
||||
|
||||
/// The main window to which this binding is bound.
|
||||
///
|
||||
/// A number of additional bindings are defined as extensions of
|
||||
@@ -871,3 +878,23 @@ 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;
|
||||
}
|
||||
|
||||
@@ -514,14 +514,16 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
|
||||
@override
|
||||
Future<void> performReassemble() async {
|
||||
await super.performReassemble();
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Preparing Hot Reload (layout)');
|
||||
}
|
||||
try {
|
||||
renderView.reassemble();
|
||||
} finally {
|
||||
if (BindingBase.debugReassembleConfig?.widgetName == null) {
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
Timeline.startSync('Preparing Hot Reload (layout)');
|
||||
}
|
||||
try {
|
||||
renderView.reassemble();
|
||||
} finally {
|
||||
if (!kReleaseMode) {
|
||||
Timeline.finishSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
scheduleWarmUpFrame();
|
||||
|
||||
@@ -412,7 +412,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
registerServiceExtension(
|
||||
name: 'fastReassemble',
|
||||
callback: (Map<String, Object> params) async {
|
||||
await reassembleApplication();
|
||||
// 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'};
|
||||
},
|
||||
);
|
||||
@@ -469,7 +478,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
|
||||
Future<void> _forceRebuild() {
|
||||
if (renderViewElement != null) {
|
||||
buildOwner!.reassemble(renderViewElement!);
|
||||
buildOwner!.reassemble(renderViewElement!, null);
|
||||
return endOfFrame;
|
||||
}
|
||||
return Future<void>.value();
|
||||
@@ -936,7 +945,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
|
||||
}());
|
||||
|
||||
if (renderViewElement != null) {
|
||||
buildOwner!.reassemble(renderViewElement!);
|
||||
buildOwner!.reassemble(renderViewElement!, BindingBase.debugReassembleConfig);
|
||||
}
|
||||
return super.performReassemble();
|
||||
}
|
||||
|
||||
@@ -3028,13 +3028,14 @@ class BuildOwner {
|
||||
/// changed implementations.
|
||||
///
|
||||
/// This is expensive and should not be called except during development.
|
||||
void reassemble(Element root) {
|
||||
void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
|
||||
if (!kReleaseMode) {
|
||||
Timeline.startSync('Preparing Hot Reload (widgets)');
|
||||
}
|
||||
try {
|
||||
assert(root._parent == null);
|
||||
assert(root.owner == this);
|
||||
root._debugReassembleConfig = reassembleConfig;
|
||||
root.reassemble();
|
||||
} finally {
|
||||
if (!kReleaseMode) {
|
||||
@@ -3142,6 +3143,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
_widget = widget;
|
||||
|
||||
Element? _parent;
|
||||
DebugReassembleConfig? _debugReassembleConfig;
|
||||
_NotificationNode? _notificationTree;
|
||||
|
||||
/// Compare two widgets for equality.
|
||||
@@ -3271,10 +3273,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
|
||||
@mustCallSuper
|
||||
@protected
|
||||
void reassemble() {
|
||||
markNeedsBuild();
|
||||
if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
|
||||
markNeedsBuild();
|
||||
_debugReassembleConfig = null;
|
||||
}
|
||||
visitChildren((Element child) {
|
||||
child._debugReassembleConfig = _debugReassembleConfig;
|
||||
child.reassemble();
|
||||
});
|
||||
_debugReassembleConfig = null;
|
||||
}
|
||||
|
||||
bool _debugIsInScope(Element target) {
|
||||
@@ -4918,7 +4925,9 @@ class StatefulElement extends ComponentElement {
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
state.reassemble();
|
||||
if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
|
||||
state.reassemble();
|
||||
}
|
||||
super.reassemble();
|
||||
}
|
||||
|
||||
@@ -6454,3 +6463,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -911,7 +911,7 @@ mixin WidgetInspectorService {
|
||||
Future<void> forceRebuild() {
|
||||
final WidgetsBinding binding = WidgetsBinding.instance;
|
||||
if (binding.renderViewElement != null) {
|
||||
binding.buildOwner!.reassemble(binding.renderViewElement!);
|
||||
binding.buildOwner!.reassemble(binding.renderViewElement!, null);
|
||||
return binding.endOfFrame;
|
||||
}
|
||||
return Future<void>.value();
|
||||
|
||||
123
packages/flutter/test/widgets/fast_reassemble_test.dart
Normal file
123
packages/flutter/test/widgets/fast_reassemble_test.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
// 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.renderViewElement!, config);
|
||||
|
||||
expect(Foo.count, 0);
|
||||
expect(Bar.count, 1);
|
||||
expect(Fizz.count, 1);
|
||||
|
||||
config = DebugReassembleConfig(widgetName: 'Fizz');
|
||||
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.renderViewElement!, config);
|
||||
|
||||
expect(Foo.count, 0);
|
||||
expect(Bar.count, 1);
|
||||
expect(Fizz.count, 2);
|
||||
|
||||
config = DebugReassembleConfig(widgetName: 'NoMatch');
|
||||
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.renderViewElement!, config);
|
||||
|
||||
expect(Foo.count, 0);
|
||||
expect(Bar.count, 1);
|
||||
expect(Fizz.count, 2);
|
||||
|
||||
config = DebugReassembleConfig();
|
||||
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.renderViewElement!, config);
|
||||
|
||||
expect(Foo.count, 1);
|
||||
expect(Bar.count, 2);
|
||||
expect(Fizz.count, 3);
|
||||
|
||||
WidgetsBinding.instance.buildOwner!.reassemble(WidgetsBinding.instance.renderViewElement!, 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;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
|
||||
final WidgetsBinding binding = WidgetsBinding.instance;
|
||||
|
||||
if (binding.renderViewElement != null) {
|
||||
binding.buildOwner!.reassemble(binding.renderViewElement!);
|
||||
binding.buildOwner!.reassemble(binding.renderViewElement!, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user