Unverified Commit 0a35cf95 authored by Polina Cherkasova's avatar Polina Cherkasova Committed by GitHub

Instrument State, Layer, RenderObject and Element. (#111328)

parent 6e491d88
...@@ -215,11 +215,11 @@ class ChangeNotifier implements Listenable { ...@@ -215,11 +215,11 @@ class ChangeNotifier implements Listenable {
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
assert(ChangeNotifier.debugAssertNotDisposed(this)); assert(ChangeNotifier.debugAssertNotDisposed(this));
if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) { if (kFlutterMemoryAllocationsEnabled && !_creationDispatched) {
MemoryAllocations.instance.dispatchObjectEvent(ObjectCreated( MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterFoundationLibrary, library: _flutterFoundationLibrary,
className: 'ChangeNotifier', className: '$ChangeNotifier',
object: this, object: this,
)); );
_creationDispatched = true; _creationDispatched = true;
} }
if (_count == _listeners.length) { if (_count == _listeners.length) {
...@@ -326,7 +326,7 @@ class ChangeNotifier implements Listenable { ...@@ -326,7 +326,7 @@ class ChangeNotifier implements Listenable {
return true; return true;
}()); }());
if (kFlutterMemoryAllocationsEnabled && _creationDispatched) { if (kFlutterMemoryAllocationsEnabled && _creationDispatched) {
MemoryAllocations.instance.dispatchObjectEvent(ObjectDisposed(object: this)); MemoryAllocations.instance.dispatchObjectDisposed(object: this);
} }
_listeners = _emptyListeners; _listeners = _emptyListeners;
_count = 0; _count = 0;
...@@ -464,11 +464,11 @@ class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> { ...@@ -464,11 +464,11 @@ class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value. /// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value) { ValueNotifier(this._value) {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectEvent(ObjectCreated( MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterFoundationLibrary, library: _flutterFoundationLibrary,
className: 'ValueNotifier', className: '$ValueNotifier',
object: this, object: this,
)); );
} }
_creationDispatched = true; _creationDispatched = true;
} }
......
...@@ -258,6 +258,34 @@ class MemoryAllocations { ...@@ -258,6 +258,34 @@ class MemoryAllocations {
_tryDefragmentListeners(); _tryDefragmentListeners();
} }
/// Create [ObjectCreated] and invoke [dispatchObjectEvent] if there are listeners.
///
/// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
void dispatchObjectCreated({
required String library,
required String className,
required Object object,
}) {
if (!hasListeners) {
return;
}
dispatchObjectEvent(ObjectCreated(
library: library,
className: className,
object: object,
));
}
/// Create [ObjectDisposed] and invoke [dispatchObjectEvent] if there are listeners.
///
/// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
void dispatchObjectDisposed({required Object object}) {
if (!hasListeners) {
return;
}
dispatchObjectEvent(ObjectDisposed(object: object));
}
void _subscribeToSdkObjects() { void _subscribeToSdkObjects() {
assert(ui.Image.onCreate == null); assert(ui.Image.onCreate == null);
assert(ui.Image.onDispose == null); assert(ui.Image.onDispose == null);
...@@ -283,7 +311,7 @@ class MemoryAllocations { ...@@ -283,7 +311,7 @@ class MemoryAllocations {
void _imageOnCreate(ui.Image image) { void _imageOnCreate(ui.Image image) {
dispatchObjectEvent(ObjectCreated( dispatchObjectEvent(ObjectCreated(
library: _dartUiLibrary, library: _dartUiLibrary,
className: 'Image', className: '${ui.Image}',
object: image, object: image,
)); ));
} }
...@@ -291,7 +319,7 @@ class MemoryAllocations { ...@@ -291,7 +319,7 @@ class MemoryAllocations {
void _pictureOnCreate(ui.Picture picture) { void _pictureOnCreate(ui.Picture picture) {
dispatchObjectEvent(ObjectCreated( dispatchObjectEvent(ObjectCreated(
library: _dartUiLibrary, library: _dartUiLibrary,
className: 'Picture', className: '${ui.Picture}',
object: picture, object: picture,
)); ));
} }
......
...@@ -73,6 +73,8 @@ class AnnotationResult<T> { ...@@ -73,6 +73,8 @@ class AnnotationResult<T> {
} }
} }
const String _flutterRenderingLibrary = 'package:flutter/rendering.dart';
/// A composited layer. /// A composited layer.
/// ///
/// During painting, the render tree generates a tree of composited layers that /// During painting, the render tree generates a tree of composited layers that
...@@ -135,6 +137,17 @@ class AnnotationResult<T> { ...@@ -135,6 +137,17 @@ class AnnotationResult<T> {
/// * [RenderView.compositeFrame], which implements this recomposition protocol /// * [RenderView.compositeFrame], which implements this recomposition protocol
/// for painting [RenderObject] trees on the display. /// for painting [RenderObject] trees on the display.
abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
/// Creates an instance of Layer.
Layer() {
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterRenderingLibrary,
className: '$Layer',
object: this,
);
}
}
final Map<int, VoidCallback> _callbacks = <int, VoidCallback>{}; final Map<int, VoidCallback> _callbacks = <int, VoidCallback>{};
static int _nextCallbackId = 0; static int _nextCallbackId = 0;
...@@ -320,6 +333,9 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { ...@@ -320,6 +333,9 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
_debugDisposed = true; _debugDisposed = true;
return true; return true;
}()); }());
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
_engineLayer?.dispose(); _engineLayer?.dispose();
_engineLayer = null; _engineLayer = null;
} }
......
...@@ -1248,6 +1248,8 @@ class PipelineOwner { ...@@ -1248,6 +1248,8 @@ class PipelineOwner {
} }
} }
const String _flutterRenderingLibrary = 'package:flutter/rendering.dart';
/// An object in the render tree. /// An object in the render tree.
/// ///
/// The [RenderObject] class hierarchy is the core of the rendering /// The [RenderObject] class hierarchy is the core of the rendering
...@@ -1376,6 +1378,13 @@ class PipelineOwner { ...@@ -1376,6 +1378,13 @@ class PipelineOwner {
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
/// Initializes internal fields for subclasses. /// Initializes internal fields for subclasses.
RenderObject() { RenderObject() {
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterRenderingLibrary,
className: '$RenderObject',
object: this,
);
}
_needsCompositing = isRepaintBoundary || alwaysNeedsCompositing; _needsCompositing = isRepaintBoundary || alwaysNeedsCompositing;
_wasRepaintBoundary = isRepaintBoundary; _wasRepaintBoundary = isRepaintBoundary;
} }
...@@ -1436,6 +1445,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -1436,6 +1445,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
@mustCallSuper @mustCallSuper
void dispose() { void dispose() {
assert(!_debugDisposed); assert(!_debugDisposed);
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
_layerHandle.layer = null; _layerHandle.layer = null;
assert(() { assert(() {
// TODO(dnfield): Enable this assert once clients have had a chance to // TODO(dnfield): Enable this assert once clients have had a chance to
......
...@@ -816,6 +816,8 @@ enum _StateLifecycle { ...@@ -816,6 +816,8 @@ enum _StateLifecycle {
/// The signature of [State.setState] functions. /// The signature of [State.setState] functions.
typedef StateSetter = void Function(VoidCallback fn); typedef StateSetter = void Function(VoidCallback fn);
const String _flutterWidgetsLibrary = 'package:flutter/widgets.dart';
/// The logic and internal state for a [StatefulWidget]. /// The logic and internal state for a [StatefulWidget].
/// ///
/// State is information that (1) can be read synchronously when the widget is /// State is information that (1) can be read synchronously when the widget is
...@@ -999,6 +1001,13 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -999,6 +1001,13 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
@mustCallSuper @mustCallSuper
void initState() { void initState() {
assert(_debugLifecycleState == _StateLifecycle.created); assert(_debugLifecycleState == _StateLifecycle.created);
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterWidgetsLibrary,
className: '$State',
object: this,
);
}
} }
/// Called whenever the widget configuration changes. /// Called whenever the widget configuration changes.
...@@ -1235,6 +1244,9 @@ abstract class State<T extends StatefulWidget> with Diagnosticable { ...@@ -1235,6 +1244,9 @@ abstract class State<T extends StatefulWidget> with Diagnosticable {
_debugLifecycleState = _StateLifecycle.defunct; _debugLifecycleState = _StateLifecycle.defunct;
return true; return true;
}()); }());
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
} }
/// Describes the part of the user interface represented by this widget. /// Describes the part of the user interface represented by this widget.
...@@ -3215,7 +3227,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -3215,7 +3227,15 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
/// Typically called by an override of [Widget.createElement]. /// Typically called by an override of [Widget.createElement].
Element(Widget widget) Element(Widget widget)
: assert(widget != null), : assert(widget != null),
_widget = widget; _widget = widget {
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectCreated(
library: _flutterWidgetsLibrary,
className: '$Element',
object: this,
);
}
}
Element? _parent; Element? _parent;
DebugReassembleConfig? _debugReassembleConfig; DebugReassembleConfig? _debugReassembleConfig;
...@@ -4132,6 +4152,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext { ...@@ -4132,6 +4152,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
assert(_widget != null); // Use the private property to avoid a CastError during hot reload. assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
assert(depth != null); assert(depth != null);
assert(owner != null); assert(owner != null);
if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
// Use the private property to avoid a CastError during hot reload. // Use the private property to avoid a CastError during hot reload.
final Key? key = _widget?.key; final Key? key = _widget?.key;
if (key is GlobalKey) { if (key is GlobalKey) {
......
// 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:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final MemoryAllocations ma = MemoryAllocations.instance;
setUp(() {
assert(!ma.hasListeners);
});
test('Publishers dispatch events in debug mode', () async {
int eventCount = 0;
void listener(ObjectEvent event) => eventCount++;
ma.addListener(listener);
final int expectedEventCount = await _activateFlutterObjectsAndReturnCountOfEvents();
expect(eventCount, expectedEventCount);
ma.removeListener(listener);
expect(ma.hasListeners, isFalse);
});
}
class _TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() {}
@override
Rect get paintBounds => throw UnimplementedError();
@override
void performLayout() {}
@override
void performResize() {}
@override
Rect get semanticBounds => throw UnimplementedError();
}
class _TestLayer extends Layer{
@override
void addToScene(ui.SceneBuilder builder) {}
}
/// Create and dispose Flutter objects to fire memory allocation events.
Future<int> _activateFlutterObjectsAndReturnCountOfEvents() async {
int count = 0;
final RenderObject renderObject = _TestRenderObject(); count++;
final Layer layer = _TestLayer(); count++;
renderObject.dispose(); count++;
layer.dispose(); count++;
return count;
}
// 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() {
final MemoryAllocations ma = MemoryAllocations.instance;
setUp(() {
assert(!ma.hasListeners);
});
test('Publishers dispatch events in debug mode', () async {
int eventCount = 0;
void listener(ObjectEvent event) => eventCount++;
ma.addListener(listener);
final int expectedEventCount = await _activateFlutterObjectsAndReturnCountOfEvents();
expect(eventCount, expectedEventCount);
ma.removeListener(listener);
expect(ma.hasListeners, isFalse);
});
testWidgets('State dispatches events in debug mode', (WidgetTester tester) async {
bool stateCreated = false;
bool stateDisposed = false;
void listener(ObjectEvent event) {
if (event is ObjectCreated && event.object is State) {
stateCreated = true;
}
if (event is ObjectDisposed && event.object is State) {
stateDisposed = true;
}
}
ma.addListener(listener);
await tester.pumpWidget(const _TestStatefulWidget());
expect(stateCreated, isTrue);
expect(stateDisposed, isFalse);
await tester.pumpWidget(const SizedBox.shrink());
expect(stateCreated, isTrue);
expect(stateDisposed, isTrue);
ma.removeListener(listener);
expect(ma.hasListeners, isFalse);
});
}
class _TestLeafRenderObjectWidget extends LeafRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return _TestRenderObject();
}
}
class _TestElement extends RootRenderObjectElement{
_TestElement(): super(_TestLeafRenderObjectWidget());
void makeInactive() {
assignOwner(BuildOwner(focusManager: FocusManager()));
mount(null, null);
deactivate();
}
}
class _TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() {}
@override
Rect get paintBounds => throw UnimplementedError();
@override
void performLayout() {}
@override
void performResize() {}
@override
Rect get semanticBounds => throw UnimplementedError();
}
class _TestStatefulWidget extends StatefulWidget {
const _TestStatefulWidget();
@override
State<_TestStatefulWidget> createState() => _TestStatefulWidgetState();
}
class _TestStatefulWidgetState extends State<_TestStatefulWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
/// Create and dispose Flutter objects to fire memory allocation events.
Future<int> _activateFlutterObjectsAndReturnCountOfEvents() async {
int count = 0;
final _TestElement element = _TestElement(); count++;
final RenderObject renderObject = _TestRenderObject(); count++;
element.makeInactive(); element.unmount(); count += 3;
renderObject.dispose(); count++;
return count;
}
...@@ -19,16 +19,17 @@ void main() { ...@@ -19,16 +19,17 @@ void main() {
expect(kFlutterMemoryAllocationsEnabled, isFalse); expect(kFlutterMemoryAllocationsEnabled, isFalse);
}); });
test( testWidgets(
'$MemoryAllocations is noop when kFlutterMemoryAllocationsEnabled is false.', '$MemoryAllocations is noop when kFlutterMemoryAllocationsEnabled is false.',
() async { (WidgetTester tester) async {
ObjectEvent? recievedEvent; ObjectEvent? recievedEvent;
ObjectEvent listener(ObjectEvent event) => recievedEvent = event; ObjectEvent listener(ObjectEvent event) => recievedEvent = event;
ma.addListener(listener); ma.addListener(listener);
_checkSdkHandlersNotSet(); _checkSdkHandlersNotSet();
expect(ma.hasListeners, isFalse);
await _activateFlutterObjects(); await _activateFlutterObjects(tester);
_checkSdkHandlersNotSet(); _checkSdkHandlersNotSet();
expect(recievedEvent, isNull); expect(recievedEvent, isNull);
expect(ma.hasListeners, isFalse); expect(ma.hasListeners, isFalse);
...@@ -47,16 +48,21 @@ void _checkSdkHandlersNotSet() { ...@@ -47,16 +48,21 @@ void _checkSdkHandlersNotSet() {
} }
/// Create and dispose Flutter objects to fire memory allocation events. /// Create and dispose Flutter objects to fire memory allocation events.
Future<void> _activateFlutterObjects() async { Future<void> _activateFlutterObjects(WidgetTester tester) async {
final ValueNotifier<bool> valueNotifier = ValueNotifier<bool>(true); final ValueNotifier<bool> valueNotifier = ValueNotifier<bool>(true);
final ChangeNotifier changeNotifier = ChangeNotifier()..addListener(() {}); final ChangeNotifier changeNotifier = ChangeNotifier()..addListener(() {});
final Image image = await _createImage();
final Picture picture = _createPicture(); final Picture picture = _createPicture();
valueNotifier.dispose(); valueNotifier.dispose();
changeNotifier.dispose(); changeNotifier.dispose();
image.dispose();
picture.dispose(); picture.dispose();
// TODO(polina-c): Remove the condition after
// https://github.com/flutter/flutter/issues/110599 is fixed.
if (!kIsWeb) {
final Image image = await _createImage();
image.dispose();
}
} }
Future<Image> _createImage() async { Future<Image> _createImage() async {
......
// 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:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final MemoryAllocations ma = MemoryAllocations.instance;
setUp(() {
assert(!ma.hasListeners);
});
testWidgets(
'$MemoryAllocations is noop when kFlutterMemoryAllocationsEnabled is false.',
(WidgetTester tester) async {
ObjectEvent? recievedEvent;
ObjectEvent listener(ObjectEvent event) => recievedEvent = event;
ma.addListener(listener);
expect(ma.hasListeners, isFalse);
await _activateFlutterObjects(tester);
expect(recievedEvent, isNull);
expect(ma.hasListeners, isFalse);
ma.removeListener(listener);
},
);
}
class _TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() {}
@override
Rect get paintBounds => throw UnimplementedError();
@override
void performLayout() {}
@override
void performResize() {}
@override
Rect get semanticBounds => throw UnimplementedError();
}
class _TestLayer extends Layer{
@override
void addToScene(SceneBuilder builder) {}
}
/// Create and dispose Flutter objects to fire memory allocation events.
Future<void> _activateFlutterObjects(WidgetTester tester) async {
final RenderObject renderObject = _TestRenderObject();
final Layer layer = _TestLayer();
renderObject.dispose();
// It is ok to use protected members for testing.
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
layer.dispose();
}
// 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() {
final MemoryAllocations ma = MemoryAllocations.instance;
setUp(() {
assert(!ma.hasListeners);
});
testWidgets(
'$MemoryAllocations is noop when kFlutterMemoryAllocationsEnabled is false.',
(WidgetTester tester) async {
ObjectEvent? recievedEvent;
ObjectEvent listener(ObjectEvent event) => recievedEvent = event;
ma.addListener(listener);
expect(ma.hasListeners, isFalse);
await _activateFlutterObjects(tester);
expect(recievedEvent, isNull);
expect(ma.hasListeners, isFalse);
ma.removeListener(listener);
},
);
}
class _TestLeafRenderObjectWidget extends LeafRenderObjectWidget {
@override
RenderObject createRenderObject(BuildContext context) {
return _TestRenderObject();
}
}
class _TestRenderObject extends RenderObject {
@override
void debugAssertDoesMeetConstraints() {}
@override
Rect get paintBounds => throw UnimplementedError();
@override
void performLayout() {}
@override
void performResize() {}
@override
Rect get semanticBounds => throw UnimplementedError();
}
class _TestElement extends RootRenderObjectElement{
_TestElement(): super(_TestLeafRenderObjectWidget());
void makeInactive() {
assignOwner(BuildOwner(focusManager: FocusManager()));
mount(null, null);
deactivate();
}
}
class _MyStatefulWidget extends StatefulWidget {
const _MyStatefulWidget();
@override
State<_MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<_MyStatefulWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
}
/// Create and dispose Flutter objects to fire memory allocation events.
Future<void> _activateFlutterObjects(WidgetTester tester) async {
final _TestElement element = _TestElement();
element.makeInactive(); element.unmount();
// Create and dispose State:
await tester.pumpWidget(const _MyStatefulWidget());
await tester.pumpWidget(const SizedBox.shrink());
}
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