Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request DartPerformanceMode.latency during transitions #110600

Merged
merged 15 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 88 additions & 1 deletion packages/flutter/lib/src/scheduler/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import 'dart:async';
import 'dart:collection';
import 'dart:developer' show Flow, Timeline, TimelineTask;
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, PlatformDispatcher, TimingsCallback;
import 'dart:ui' show AppLifecycleState, DartPerformanceMode, FramePhase, FrameTiming, PlatformDispatcher, TimingsCallback;

import 'package:collection/collection.dart' show HeapPriorityQueue, PriorityQueue;
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -183,6 +183,34 @@ enum SchedulerPhase {
postFrameCallbacks,
}

/// This callback is invoked when a request for [DartPerformanceMode] is disposed.
///
/// See also:
///
/// * [PerformanceModeRequestHandle] for more information on the lifecycle of the handle.
typedef PerformanceModeCleaupCallback = void Function();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this type isn't used anywhere, it can be a VoidCallback instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is still used in PerformanceModeRequestHandle, I think this typedef makes it easier to see when this callback will be invoked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant that the type is not used in a public APi, so this can be private or use an existing type - right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, will change it to private.


/// An opaque handle that keeps a request for [DartPerformanceMode] active until
/// disposed.
///
/// To create a [PerformanceModeRequestHandle], use [SchedulerBinding.requestPerformanceMode].
/// The component that makes the request is responsible for disposing the handle.
class PerformanceModeRequestHandle {
PerformanceModeRequestHandle._(PerformanceModeCleaupCallback this._cleanup);

PerformanceModeCleaupCallback? _cleanup;

/// Call this method to signal to [SchedulerBinding] that a request for a [DartPerformanceMode]
/// is no longer needed.
///
/// This method must only be called once per object.
void dispose() {
assert(_cleanup != null);
_cleanup!();
_cleanup = null;
}
}

/// Scheduler for running the following:
///
/// * _Transient callbacks_, triggered by the system's
Expand Down Expand Up @@ -605,6 +633,19 @@ mixin SchedulerBinding on BindingBase {
return true;
}

/// Asserts that there are no pending performance mode requests. If there are
/// any pending requests, throws an exception as it indicates undisposed
/// performance mode requests.
bool debugAssertNoPendingPerformanceModeRequests(String reason) {
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
assert(() {
if (_performanceMode != null) {
throw FlutterError(reason);
}
return true;
}());
return true;
}

/// Prints the stack for where the current transient callback was registered.
///
/// A transient frame callback is one that was registered with
Expand Down Expand Up @@ -1085,6 +1126,52 @@ mixin SchedulerBinding on BindingBase {
}
}

DartPerformanceMode? _performanceMode;
int _numPerformanceModeRequests = 0;

/// Request a specific [DartPerformanceMode].
///
/// Returns `null` if the request was not successful due to conflicting performance mode requests.
/// Two requests are said to be in conflict if they are not of the same [DartPerformanceMode] type,
/// and an explicit request for a performance mode has been made prior.
///
/// Requestor is responsible for calling [PerformanceModeRequestHandle.dispose] when it no longer
/// requires the performance mode.
PerformanceModeRequestHandle? requestPerformanceMode(DartPerformanceMode mode) {
// conflicting requests are not allowed.
if (_performanceMode != null && _performanceMode != mode) {
return null;
}

if (_performanceMode == mode) {
_numPerformanceModeRequests++;
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
} else if (_performanceMode == null) {
_performanceMode = mode;
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
_numPerformanceModeRequests = 1;
}

return PerformanceModeRequestHandle._(() => _disposePerformanceModeRequest());
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
}

/// Remove a request for a specific [DartPerformanceMode].
///
/// If all the pending requests have been disposed, the engine will revert to the
/// [DartPerformanceMode.balanced] performance mode.
void _disposePerformanceModeRequest() {
_numPerformanceModeRequests--;
if (_numPerformanceModeRequests == 0) {
_performanceMode = null;
PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.balanced);
}
}

/// Returns the current [DartPerformanceMode] requested. If no requests have
/// been made, returns `null`.
@visibleForTesting
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
DartPerformanceMode? getRequestedPerformanceMode() {
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
return _performanceMode;
}

/// Called by the engine to produce a new frame.
///
/// This method is called immediately after [handleBeginFrame]. It calls all
Expand Down
16 changes: 16 additions & 0 deletions packages/flutter/lib/src/widgets/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
Future<T?> get completed => _transitionCompleter.future;
final Completer<T?> _transitionCompleter = Completer<T?>();

/// Handle to the performance mode request.
///
/// When the route is installed, the performance mode is requested, and this is
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
/// then disposed when the route is disposed. Requesting [DartPerformanceMode.latency]
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
/// indicated to the engine that the transition is latency sensitive and to delay
/// non-essential work while this handle is active.
PerformanceModeRequestHandle? _performanceModeRequestHandle;

/// {@template flutter.widgets.TransitionRoute.transitionDuration}
/// The duration the transition going forwards.
///
Expand Down Expand Up @@ -221,6 +229,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
if (overlayEntries.isNotEmpty) {
overlayEntries.first.opaque = opaque;
}
_performanceModeRequestHandle?.dispose();
_performanceModeRequestHandle = null;
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
Expand All @@ -236,6 +246,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
if (!isActive) {
navigator!.finalizeRoute(this);
_popFinalized = true;
_performanceModeRequestHandle?.dispose();
_performanceModeRequestHandle = null;
}
break;
}
Expand All @@ -249,6 +261,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
_animation = createAnimation()
..addStatusListener(_handleStatusChanged);
assert(_animation != null, '$runtimeType.createAnimation() returned null.');
_performanceModeRequestHandle =
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
SchedulerBinding.instance.requestPerformanceMode(ui.DartPerformanceMode.latency);
super.install();
if (_animation!.isCompleted && overlayEntries.isNotEmpty) {
overlayEntries.first.opaque = opaque;
Expand Down Expand Up @@ -465,6 +479,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
void dispose() {
assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
_animation?.removeStatusListener(_handleStatusChanged);
_performanceModeRequestHandle?.dispose();
_performanceModeRequestHandle = null;
if (willDisposeAnimationController) {
_controller?.dispose();
}
Expand Down
56 changes: 56 additions & 0 deletions packages/flutter/test/scheduler/performance_mode_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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';
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved

import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
late SchedulerBinding binding;

setUpAll(() {
WidgetsFlutterBinding.ensureInitialized();
binding = SchedulerBinding.instance;
});

test('PerformanceModeHandler make one request', () async {
final PerformanceModeRequestHandle? requestHandle = binding.requestPerformanceMode(DartPerformanceMode.latency);
expect(requestHandle, isNotNull);
expect(binding.getRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
requestHandle?.dispose();
expect(binding.getRequestedPerformanceMode(), isNull);
});

test('PerformanceModeHandler make conflicting requests', () async {
final PerformanceModeRequestHandle? requestHandle1 = binding.requestPerformanceMode(DartPerformanceMode.latency);
expect(requestHandle1, isNotNull);

final PerformanceModeRequestHandle? requestHandle2 = binding.requestPerformanceMode(DartPerformanceMode.throughput);
expect(requestHandle2, isNull);

expect(binding.getRequestedPerformanceMode(), equals(DartPerformanceMode.latency));

requestHandle1?.dispose();
expect(binding.getRequestedPerformanceMode(), isNull);
});

test('PerformanceModeHandler revert only after last requestor disposed',
() async {
final PerformanceModeRequestHandle? requestHandle1 = binding.requestPerformanceMode(DartPerformanceMode.latency);
expect(requestHandle1, isNotNull);

expect(binding.getRequestedPerformanceMode(), equals(DartPerformanceMode.latency));

final PerformanceModeRequestHandle? requestHandle2 = binding.requestPerformanceMode(DartPerformanceMode.latency);
expect(requestHandle2, isNotNull);

expect(binding.getRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
requestHandle1?.dispose();
expect(binding.getRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
requestHandle2?.dispose();
expect(binding.getRequestedPerformanceMode(), isNull);
});
}
iskakaushik marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions packages/flutter_test/lib/src/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
assert(debugAssertNoTransientCallbacks(
'An animation is still running even after the widget tree was disposed.'
));
assert(debugAssertNoPendingPerformanceModeRequests(
'A performance mode was requested and not disposed by a test.'
));
assert(debugAssertAllFoundationVarsUnset(
'The value of a foundation debug variable was changed by the test.',
debugPrintOverride: debugPrintOverride,
Expand Down
1 change: 1 addition & 0 deletions packages/integration_test/test/binding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Future<void> main() async {
));
expect(tester.binding, binding);
binding.reportData = <String, dynamic>{'answer': 42};
await tester.pump();
});

testWidgets('hitTesting works when using setSurfaceSize', (WidgetTester tester) async {
Expand Down