Unverified Commit e1eddb40 authored by Yuqian Li's avatar Yuqian Li Committed by GitHub

Revert the revert (#65602)

parent 92eb7945
......@@ -3,3 +3,13 @@
// found in the LICENSE file.
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end
// 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 UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
// 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 "GeneratedPluginRegistrant.h"
......@@ -109,6 +109,7 @@ class ComplexLayoutState extends State<ComplexLayout> {
Expanded(
child: ListView.builder(
key: const Key('complex-scroll'), // this key is used by the driver test
controller: ScrollController(), // So that the scroll offset can be tracked
itemBuilder: (BuildContext context, int index) {
if (index % 2 == 0)
return FancyImageItem(index, key: PageStorageKey<int>(index));
......
......@@ -3,7 +3,7 @@ description: A benchmark of a relatively complex layout.
environment:
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.2.2 <3.0.0"
dependencies:
flutter:
......@@ -46,6 +46,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
test: 1.16.0-nullsafety.4
e2e: 0.7.0
_fe_analyzer_shared: 7.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
analyzer: 0.39.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
......@@ -90,4 +91,4 @@ flutter:
- packages/flutter_gallery_assets/people/square/ali.png
- packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png
# PUBSPEC CHECKSUM: 4f3b
# PUBSPEC CHECKSUM: fe86
// 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.
// This test is a use case of flutter/flutter#60796
// the test should be run as:
// flutter drive -t test/using_array.dart --driver test_driver/scrolling_test_e2e_test.dart
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:e2e/e2e.dart';
import 'package:complex_layout/main.dart' as app;
class PointerDataTestBinding extends E2EWidgetsFlutterBinding {
// PointerData injection would usually be considered device input and therefore
// blocked by [TestWidgetsFlutterBinding]. Override this behavior
// to help events go into widget tree.
@override
void dispatchEvent(
PointerEvent event,
HitTestResult hitTestResult, {
TestBindingEventSource source = TestBindingEventSource.device,
}) {
super.dispatchEvent(event, hitTestResult, source: TestBindingEventSource.test);
}
}
/// A union of [ui.PointerDataPacket] and the time it should be sent.
class PointerDataRecord {
PointerDataRecord(this.timeStamp, List<ui.PointerData> data)
: data = ui.PointerDataPacket(data: data);
final ui.PointerDataPacket data;
final Duration timeStamp;
}
/// Generates the [PointerDataRecord] to simulate a drag operation from
/// `center - totalMove/2` to `center + totalMove/2`.
Iterable<PointerDataRecord> dragInputDatas(
final Duration epoch,
final Offset center, {
final Offset totalMove = const Offset(0, -400),
final Duration totalTime = const Duration(milliseconds: 2000),
final double frequency = 90,
}) sync* {
final Offset startLocation = (center - totalMove / 2) * ui.window.devicePixelRatio;
// The issue is about 120Hz input on 90Hz refresh rate device.
// We test 90Hz input on 60Hz device here, which shows similar pattern.
final int moveEventCount = totalTime.inMicroseconds * frequency ~/ const Duration(seconds: 1).inMicroseconds;
final Offset movePerEvent = totalMove / moveEventCount.toDouble() * ui.window.devicePixelRatio;
yield PointerDataRecord(epoch, <ui.PointerData>[
ui.PointerData(
timeStamp: epoch,
change: ui.PointerChange.add,
physicalX: startLocation.dx,
physicalY: startLocation.dy,
),
ui.PointerData(
timeStamp: epoch,
change: ui.PointerChange.down,
physicalX: startLocation.dx,
physicalY: startLocation.dy,
pointerIdentifier: 1,
),
]);
for (int t = 0; t < moveEventCount + 1; t++) {
final Offset position = startLocation + movePerEvent * t.toDouble();
yield PointerDataRecord(
epoch + totalTime * t ~/ moveEventCount,
<ui.PointerData>[ui.PointerData(
timeStamp: epoch + totalTime * t ~/ moveEventCount,
change: ui.PointerChange.move,
physicalX: position.dx,
physicalY: position.dy,
// Scrolling behavior depends on this delta rather
// than the position difference.
physicalDeltaX: movePerEvent.dx,
physicalDeltaY: movePerEvent.dy,
pointerIdentifier: 1,
)],
);
}
final Offset position = startLocation + totalMove;
yield PointerDataRecord(epoch + totalTime, <ui.PointerData>[ui.PointerData(
timeStamp: epoch + totalTime,
change: ui.PointerChange.up,
physicalX: position.dx,
physicalY: position.dy,
pointerIdentifier: 1,
)]);
}
enum TestScenario {
resampleOn90Hz,
resampleOn59Hz,
resampleOff90Hz,
resampleOff59Hz,
}
class ResampleFlagVariant extends TestVariant<TestScenario> {
ResampleFlagVariant(this.binding);
final E2EWidgetsFlutterBinding binding;
@override
final Set<TestScenario> values = Set<TestScenario>.from(TestScenario.values);
TestScenario currentValue;
bool get resample {
switch(currentValue) {
case TestScenario.resampleOn90Hz:
case TestScenario.resampleOn59Hz:
return true;
case TestScenario.resampleOff90Hz:
case TestScenario.resampleOff59Hz:
return false;
}
throw ArgumentError;
}
double get frequency {
switch(currentValue) {
case TestScenario.resampleOn90Hz:
case TestScenario.resampleOff90Hz:
return 90.0;
case TestScenario.resampleOn59Hz:
case TestScenario.resampleOff59Hz:
return 59.0;
}
throw ArgumentError;
}
Map<String, dynamic> result;
@override
String describeValue(TestScenario value) {
switch(value) {
case TestScenario.resampleOn90Hz:
return 'resample on with 90Hz input';
case TestScenario.resampleOn59Hz:
return 'resample on with 59Hz input';
case TestScenario.resampleOff90Hz:
return 'resample off with 90Hz input';
case TestScenario.resampleOff59Hz:
return 'resample off with 59Hz input';
}
throw ArgumentError;
}
@override
Future<bool> setUp(TestScenario value) async {
currentValue = value;
final bool original = binding.resamplingEnabled;
binding.resamplingEnabled = resample;
return original;
}
@override
Future<void> tearDown(TestScenario value, bool memento) async {
binding.resamplingEnabled = memento;
binding.reportData[describeValue(value)] = result;
}
}
Future<void> main() async {
final PointerDataTestBinding binding = PointerDataTestBinding();
assert(WidgetsBinding.instance == binding);
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.benchmarkLive;
binding.reportData ??= <String, dynamic>{};
final ResampleFlagVariant variant = ResampleFlagVariant(binding);
testWidgets('Smoothness test', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
final Finder scrollerFinder = find.byKey(const ValueKey<String>('complex-scroll'));
final ListView scroller = tester.widget<ListView>(scrollerFinder);
final ScrollController controller = scroller.controller;
final List<int> frameTimestamp = <int>[];
final List<double> scrollOffset = <double>[];
final List<Duration> delays = <Duration>[];
binding.addPersistentFrameCallback((Duration timeStamp) {
if (controller.hasClients) {
// This if is necessary because by the end of the test the widget tree
// is destroyed.
frameTimestamp.add(timeStamp.inMicroseconds);
scrollOffset.add(controller.offset);
}
});
Duration now() => binding.currentSystemFrameTimeStamp;
Future<void> scroll() async {
// Extra 50ms to avoid timeouts.
final Duration startTime = const Duration(milliseconds: 500) + now();
for (final PointerDataRecord record in dragInputDatas(
startTime,
tester.getCenter(scrollerFinder),
frequency: variant.frequency,
)) {
await tester.binding.delayed(record.timeStamp - now());
// This now measures how accurate the above delayed is.
final Duration delay = now() - record.timeStamp;
if (delays.length < frameTimestamp.length) {
while (delays.length < frameTimestamp.length - 1) {
delays.add(Duration.zero);
}
delays.add(delay);
} else if (delays.last < delay) {
delays.last = delay;
}
ui.window.onPointerDataPacket(record.data);
}
}
for (int n = 0; n < 5; n++) {
await scroll();
}
variant.result = scrollSummary(scrollOffset, delays, frameTimestamp);
await tester.pumpAndSettle();
scrollOffset.clear();
delays.clear();
await tester.idle();
}, semanticsEnabled: false, variant: variant);
}
/// Calculates the smoothness measure from `scrollOffset` and `delays` list.
///
/// Smoothness (`abs_jerk`) is measured by the absolute value of the discrete
/// 2nd derivative of the scroll offset.
///
/// It was experimented that jerk (3rd derivative of the position) is a good
/// measure the smoothness.
/// Here we are using 2nd derivative instead because the input is completely
/// linear and the expected acceleration should be strictly zero.
/// Observed acceleration is jumping from positive to negative within
/// adjacent frames, meaning mathematically the discrete 3-rd derivative
/// (`f[3] - 3*f[2] + 3*f[1] - f[0]`) is not a good approximation of jerk
/// (continuous 3-rd derivative), while discrete 2nd
/// derivative (`f[2] - 2*f[1] + f[0]`) on the other hand is a better measure
/// of how the scrolling deviate away from linear, and given the acceleration
/// should average to zero within two frames, it's also a good approximation
/// for jerk in terms of physics.
/// We use abs rather than square because square (2-norm) amplifies the
/// effect of the data point that's relatively large, but in this metric
/// we prefer smaller data point to have similar effect.
/// This is also why we count the number of data that's larger than a
/// threshold (and the result is tested not sensitive to this threshold),
/// which is effectively a 0-norm.
///
/// Frames that are too slow to build (longer than 40ms) or with input delay
/// longer than 16ms (1/60Hz) is filtered out to separate the janky due to slow
/// response.
///
/// The returned map has keys:
/// `average_abs_jerk`: average for the overall smoothness.
/// `janky_count`: number of frames with `abs_jerk` larger than 0.5.
/// `dropped_frame_count`: number of frames that are built longer than 40ms and
/// are not used for smoothness measurement.
/// `frame_timestamp`: the list of the timestamp for each frame, in the time
/// order.
/// `scroll_offset`: the scroll offset for each frame. Its length is the same as
/// `frame_timestamp`.
/// `input_delay`: the list of maximum delay time of the input simulation during
/// a frame. Its length is the same as `frame_timestamp`
Map<String, dynamic> scrollSummary(
List<double> scrollOffset,
List<Duration> delays,
List<int> frameTimestamp,
) {
double jankyCount = 0;
double absJerkAvg = 0;
int lostFrame = 0;
for (int i = 1; i < scrollOffset.length-1; i += 1) {
if (frameTimestamp[i+1] - frameTimestamp[i-1] > 40E3 ||
(i >= delays.length || delays[i] > const Duration(milliseconds: 16))) {
// filter data points from slow frame building or input simulation artifact
lostFrame += 1;
continue;
}
//
final double absJerk = (scrollOffset[i-1] + scrollOffset[i+1] - 2*scrollOffset[i]).abs();
absJerkAvg += absJerk;
if (absJerk > 0.5)
jankyCount += 1;
}
// expect(lostFrame < 0.1 * frameTimestamp.length, true);
absJerkAvg /= frameTimestamp.length - lostFrame;
return <String, dynamic>{
'janky_count': jankyCount,
'average_abs_jerk': absJerkAvg,
'dropped_frame_count': lostFrame,
'frame_timestamp': List<int>.from(frameTimestamp),
'scroll_offset': List<double>.from(scrollOffset),
'input_delay': delays.map<int>((Duration data) => data.inMicroseconds).toList(),
};
}
// 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:async';
import 'package:e2e/e2e_driver.dart' as driver;
Future<void> main() => driver.e2eDriver(
timeout: const Duration(minutes: 5),
responseDataCallback: (Map<String, dynamic> data) async {
await driver.writeResponseData(
data,
testOutputFilename: 'scroll_smoothness_test',
);
}
);
// 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:async';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createsScrollSmoothnessPerfTest());
}
......@@ -298,6 +298,54 @@ TaskFunction createsMultiWidgetConstructPerfE2ETest() {
).run;
}
TaskFunction createsScrollSmoothnessPerfTest() {
final String testDirectory =
'${flutterDirectory.path}/dev/benchmarks/complex_layout';
const String testTarget = 'test/measure_scroll_smoothness.dart';
return () {
return inDirectory<TaskResult>(testDirectory, () async {
final Device device = await devices.workingDevice;
await device.unlock();
final String deviceId = device.deviceId;
await flutter('packages', options: <String>['get']);
await flutter('drive', options: <String>[
'-v',
'--verbose-system-logs',
'--profile',
'-t', testTarget,
'-d',
deviceId,
]);
final Map<String, dynamic> data = json.decode(
file('$testDirectory/build/scroll_smoothness_test.json').readAsStringSync(),
) as Map<String, dynamic>;
final Map<String, dynamic> result = <String, dynamic>{};
void addResult(dynamic data, String suffix) {
assert(data is Map<String, dynamic>);
const List<String> metricKeys = <String>[
'janky_count',
'average_abs_jerk',
'dropped_frame_count',
];
for (final String key in metricKeys) {
result[key+suffix] = data[key];
}
}
addResult(data['resample on with 90Hz input'], '_with_resampler_90Hz');
addResult(data['resample on with 59Hz input'], '_with_resampler_59Hz');
addResult(data['resample off with 90Hz input'], '_without_resampler_90Hz');
addResult(data['resample off with 59Hz input'], '_without_resampler_59Hz');
return TaskResult.success(
result,
benchmarkScoreKeys: result.keys.toList(),
);
});
};
}
TaskFunction createFramePolicyIntegrationTest() {
final String testDirectory =
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks';
......
......@@ -114,6 +114,14 @@ tasks:
# Android on-device tests
complex_layout_android__scroll_smoothness:
description: >
Measures the smoothness of scrolling of the Complex Layout sample app on
Android.
stage: devicelab
required_agent_capabilities: ["linux/android"]
flaky: true
complex_layout_scroll_perf__timeline_summary:
description: >
Measures the runtime performance of the Complex Layout sample app on
......
......@@ -1504,7 +1504,6 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
renderView._pointers[event.pointer].decay = _kPointerDecay;
_handleViewNeedsPaint();
} else if (event.down) {
assert(event is PointerDownEvent);
renderView._pointers[event.pointer] = _LiveTestPointerRecord(
event.pointer,
event.position,
......
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