Unverified Commit 40670c09 authored by liyuqian's avatar liyuqian Committed by GitHub

Allow multiple TimingsCallbacks (#43676)

This fixes https://github.com/flutter/flutter/issues/39277

The following tests cover this change:
- packages/flutter/test/foundation/service_extensions_test.dart
- packages/flutter/test/scheduler/scheduler_test.dart
parent 46edc802
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:developer' show Flow, Timeline; import 'dart:developer' show Flow, Timeline;
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming; import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, TimingsCallback;
import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue; import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -203,15 +203,69 @@ mixin SchedulerBinding on BindingBase, ServicesBinding { ...@@ -203,15 +203,69 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
if (!kReleaseMode) { if (!kReleaseMode) {
int frameNumber = 0; int frameNumber = 0;
addTimingsCallback((List<FrameTiming> timings) {
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
window.onReportTimings = (List<FrameTiming> timings) {
for (FrameTiming frameTiming in timings) { for (FrameTiming frameTiming in timings) {
frameNumber += 1; frameNumber += 1;
_profileFramePostEvent(frameNumber, frameTiming); _profileFramePostEvent(frameNumber, frameTiming);
} }
}; });
}
}
final List<TimingsCallback> _timingsCallbacks = <TimingsCallback>[];
/// Add a [TimingsCallback] that receives [FrameTiming] sent from the engine.
///
/// This can be used, for example, to monitor the performance in release mode,
/// or to get a signal when the first frame is rasterized.
///
/// This is preferred over using [Window.onReportTimings] directly because
/// [addTimingsCallback] allows multiple callbacks.
///
/// If the same callback is added twice, it will be executed twice.
void addTimingsCallback(TimingsCallback callback) {
// TODO(liyuqian): once this is merged, modify the doc of
// [Window.onReportTimings] inside the engine repo to recommend using this
// API instead of using [Window.onReportTimings] directly.
_timingsCallbacks.add(callback);
if (_timingsCallbacks.length == 1) {
assert(window.onReportTimings == null);
window.onReportTimings = _executeTimingsCallbacks;
}
assert(window.onReportTimings == _executeTimingsCallbacks);
}
/// Removes a callback that was earlier added by [addTimingsCallback].
void removeTimingsCallback(TimingsCallback callback) {
assert(_timingsCallbacks.contains(callback));
_timingsCallbacks.remove(callback);
if (_timingsCallbacks.isEmpty) {
window.onReportTimings = null;
}
}
void _executeTimingsCallbacks(List<FrameTiming> timings) {
final List<TimingsCallback> clonedCallbacks =
List<TimingsCallback>.from(_timingsCallbacks);
for (TimingsCallback callback in clonedCallbacks) {
try {
if (_timingsCallbacks.contains(callback)) {
callback(timings);
}
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
context: ErrorDescription('while executing callbacks for FrameTiming'),
informationCollector: () sync* {
yield DiagnosticsProperty<TimingsCallback>(
'The TimingsCallback that gets executed was',
callback,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
} }
} }
......
...@@ -755,25 +755,17 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -755,25 +755,17 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
if (_needToReportFirstFrame && _reportFirstFrame) { if (_needToReportFirstFrame && _reportFirstFrame) {
assert(!_firstFrameCompleter.isCompleted); assert(!_firstFrameCompleter.isCompleted);
// TODO(liyuqian): use a broadcast stream approach
// use frameTimings. https://github.com/flutter/flutter/issues/38838 TimingsCallback firstFrameCallback;
// ignore: deprecated_member_use firstFrameCallback = (List<FrameTiming> timings) {
final TimingsCallback oldCallback = WidgetsBinding.instance.window.onReportTimings;
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
WidgetsBinding.instance.window.onReportTimings = (List<FrameTiming> timings) {
if (!kReleaseMode) { if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame'); developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{}); developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
} }
if (oldCallback != null) { SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
oldCallback(timings);
}
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
WidgetsBinding.instance.window.onReportTimings = oldCallback;
_firstFrameCompleter.complete(); _firstFrameCompleter.complete();
}; };
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
} }
try { try {
......
...@@ -78,11 +78,7 @@ class TestServiceExtensionsBinding extends BindingBase ...@@ -78,11 +78,7 @@ class TestServiceExtensionsBinding extends BindingBase
await flushMicrotasks(); await flushMicrotasks();
if (ui.window.onDrawFrame != null) if (ui.window.onDrawFrame != null)
ui.window.onDrawFrame(); ui.window.onDrawFrame();
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
if (ui.window.onReportTimings != null) if (ui.window.onReportTimings != null)
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
ui.window.onReportTimings(<ui.FrameTiming>[]); ui.window.onReportTimings(<ui.FrameTiming>[]);
} }
......
...@@ -132,8 +132,6 @@ void main() { ...@@ -132,8 +132,6 @@ void main() {
}); });
test('Flutter.Frame event fired', () async { test('Flutter.Frame event fired', () async {
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
window.onReportTimings(<FrameTiming>[FrameTiming(<int>[ window.onReportTimings(<FrameTiming>[FrameTiming(<int>[
// build start, build finish // build start, build finish
10000, 15000, 10000, 15000,
...@@ -152,6 +150,18 @@ void main() { ...@@ -152,6 +150,18 @@ void main() {
expect(event['raster'], 4000); expect(event['raster'], 4000);
}); });
test('TimingsCallback exceptions are caught', () {
FlutterErrorDetails errorCaught;
FlutterError.onError = (FlutterErrorDetails details) {
errorCaught = details;
};
SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
throw Exception('Test');
});
window.onReportTimings(<FrameTiming>[]);
expect(errorCaught.exceptionAsString(), equals('Exception: Test'));
});
test('currentSystemFrameTimeStamp is the raw timestamp', () { test('currentSystemFrameTimeStamp is the raw timestamp', () {
Duration lastTimeStamp; Duration lastTimeStamp;
Duration lastSystemTimeStamp; Duration lastSystemTimeStamp;
......
...@@ -286,13 +286,9 @@ class TestWindow implements Window { ...@@ -286,13 +286,9 @@ class TestWindow implements Window {
} }
@override @override
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
TimingsCallback get onReportTimings => _window.onReportTimings; TimingsCallback get onReportTimings => _window.onReportTimings;
@override @override
set onReportTimings(TimingsCallback callback) { set onReportTimings(TimingsCallback callback) {
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
_window.onReportTimings = callback; _window.onReportTimings = callback;
} }
......
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