Unverified Commit 02b300d9 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Revert "delete fast reassemble code (#102842)" (#102856)

This reverts commit ec8693e8.
parent 7f7a70c8
...@@ -157,6 +157,13 @@ abstract class BindingBase { ...@@ -157,6 +157,13 @@ abstract class BindingBase {
static Type? _debugInitializedType; static Type? _debugInitializedType;
static bool _debugServiceExtensionsRegistered = false; 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. /// The main window to which this binding is bound.
/// ///
/// A number of additional bindings are defined as extensions of /// A number of additional bindings are defined as extensions of
...@@ -871,3 +878,23 @@ abstract class BindingBase { ...@@ -871,3 +878,23 @@ abstract class BindingBase {
Future<void> _exitApplication() async { Future<void> _exitApplication() async {
exit(0); 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 ...@@ -514,14 +514,16 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture
@override @override
Future<void> performReassemble() async { Future<void> performReassemble() async {
await super.performReassemble(); await super.performReassemble();
if (!kReleaseMode) { if (BindingBase.debugReassembleConfig?.widgetName == null) {
Timeline.startSync('Preparing Hot Reload (layout)');
}
try {
renderView.reassemble();
} finally {
if (!kReleaseMode) { if (!kReleaseMode) {
Timeline.finishSync(); Timeline.startSync('Preparing Hot Reload (layout)');
}
try {
renderView.reassemble();
} finally {
if (!kReleaseMode) {
Timeline.finishSync();
}
} }
} }
scheduleWarmUpFrame(); scheduleWarmUpFrame();
......
...@@ -412,7 +412,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -412,7 +412,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
registerServiceExtension( registerServiceExtension(
name: 'fastReassemble', name: 'fastReassemble',
callback: (Map<String, Object> params) async { 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'}; return <String, String>{'type': 'Success'};
}, },
); );
...@@ -469,7 +478,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -469,7 +478,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
Future<void> _forceRebuild() { Future<void> _forceRebuild() {
if (renderViewElement != null) { if (renderViewElement != null) {
buildOwner!.reassemble(renderViewElement!); buildOwner!.reassemble(renderViewElement!, null);
return endOfFrame; return endOfFrame;
} }
return Future<void>.value(); return Future<void>.value();
...@@ -936,7 +945,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -936,7 +945,7 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
}()); }());
if (renderViewElement != null) { if (renderViewElement != null) {
buildOwner!.reassemble(renderViewElement!); buildOwner!.reassemble(renderViewElement!, BindingBase.debugReassembleConfig);
} }
return super.performReassemble(); return super.performReassemble();
} }
......
...@@ -3028,13 +3028,14 @@ class BuildOwner { ...@@ -3028,13 +3028,14 @@ class BuildOwner {
/// changed implementations. /// changed implementations.
/// ///
/// This is expensive and should not be called except during development. /// This is expensive and should not be called except during development.
void reassemble(Element root) { void reassemble(Element root, DebugReassembleConfig? reassembleConfig) {
if (!kReleaseMode) { if (!kReleaseMode) {
Timeline.startSync('Preparing Hot Reload (widgets)'); Timeline.startSync('Preparing Hot Reload (widgets)');
} }
try { try {
assert(root._parent == null); assert(root._parent == null);
assert(root.owner == this); assert(root.owner == this);
root._debugReassembleConfig = reassembleConfig;
root.reassemble(); root.reassemble();
} finally { } finally {
if (!kReleaseMode) { if (!kReleaseMode) {
...@@ -3142,6 +3143,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -3142,6 +3143,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
_widget = widget; _widget = widget;
Element? _parent; Element? _parent;
DebugReassembleConfig? _debugReassembleConfig;
_NotificationNode? _notificationTree; _NotificationNode? _notificationTree;
/// Compare two widgets for equality. /// Compare two widgets for equality.
...@@ -3271,10 +3273,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -3271,10 +3273,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
@mustCallSuper @mustCallSuper
@protected @protected
void reassemble() { void reassemble() {
markNeedsBuild(); if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
markNeedsBuild();
_debugReassembleConfig = null;
}
visitChildren((Element child) { visitChildren((Element child) {
child._debugReassembleConfig = _debugReassembleConfig;
child.reassemble(); child.reassemble();
}); });
_debugReassembleConfig = null;
} }
bool _debugIsInScope(Element target) { bool _debugIsInScope(Element target) {
...@@ -4918,7 +4925,9 @@ class StatefulElement extends ComponentElement { ...@@ -4918,7 +4925,9 @@ class StatefulElement extends ComponentElement {
@override @override
void reassemble() { void reassemble() {
state.reassemble(); if (_debugShouldReassemble(_debugReassembleConfig, _widget)) {
state.reassemble();
}
super.reassemble(); super.reassemble();
} }
...@@ -6454,3 +6463,9 @@ class _NullWidget extends Widget { ...@@ -6454,3 +6463,9 @@ class _NullWidget extends Widget {
@override @override
Element createElement() => throw UnimplementedError(); 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 { ...@@ -911,7 +911,7 @@ mixin WidgetInspectorService {
Future<void> forceRebuild() { Future<void> forceRebuild() {
final WidgetsBinding binding = WidgetsBinding.instance; final WidgetsBinding binding = WidgetsBinding.instance;
if (binding.renderViewElement != null) { if (binding.renderViewElement != null) {
binding.buildOwner!.reassemble(binding.renderViewElement!); binding.buildOwner!.reassemble(binding.renderViewElement!, null);
return binding.endOfFrame; return binding.endOfFrame;
} }
return Future<void>.value(); 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.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 { ...@@ -59,7 +59,7 @@ class TestWidgetInspectorService extends Object with WidgetInspectorService {
final WidgetsBinding binding = WidgetsBinding.instance; final WidgetsBinding binding = WidgetsBinding.instance;
if (binding.renderViewElement != null) { if (binding.renderViewElement != null) {
binding.buildOwner!.reassemble(binding.renderViewElement!); binding.buildOwner!.reassemble(binding.renderViewElement!, null);
} }
} }
......
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