|  | // Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  | // | 
|  | // Benchmark for runtimeType patterns as used in Flutter. | 
|  |  | 
|  | // ignore_for_file: prefer_const_constructors | 
|  | // ignore_for_file: avoid_function_literals_in_foreach_calls | 
|  |  | 
|  | import 'dart:typed_data'; | 
|  |  | 
|  | import 'package:benchmark_harness/benchmark_harness.dart'; | 
|  |  | 
|  | abstract class Key { | 
|  | const factory Key(String value) = ValueKey<String>; | 
|  | const Key.empty(); | 
|  | } | 
|  |  | 
|  | abstract class LocalKey extends Key { | 
|  | const LocalKey() : super.empty(); | 
|  | } | 
|  |  | 
|  | class ValueKey<T> extends LocalKey { | 
|  | const ValueKey(this.value); | 
|  | final T value; | 
|  | @override | 
|  | bool operator ==(Object other) { | 
|  | if (other.runtimeType != runtimeType) return false; | 
|  | return other is ValueKey<T> && other.value == value; | 
|  | } | 
|  |  | 
|  | @override | 
|  | int get hashCode => value.hashCode; | 
|  | } | 
|  |  | 
|  | abstract class Widget { | 
|  | const Widget({this.key}); | 
|  | final Key? key; | 
|  |  | 
|  | @pragma('dart2js:noInline') | 
|  | static bool canUpdate(Widget oldWidget, Widget newWidget) { | 
|  | return oldWidget.runtimeType == newWidget.runtimeType && | 
|  | oldWidget.key == newWidget.key; | 
|  | } | 
|  | } | 
|  |  | 
|  | class AWidget extends Widget { | 
|  | const AWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class BWidget extends Widget { | 
|  | const BWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class CWidget extends Widget { | 
|  | const CWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class DWidget extends Widget { | 
|  | const DWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class EWidget extends Widget { | 
|  | const EWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class FWidget extends Widget { | 
|  | const FWidget({Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class WWidget<W extends Widget> extends Widget { | 
|  | final W? ref; | 
|  | const WWidget({this.ref, Key? key}) : super(key: key); | 
|  | } | 
|  |  | 
|  | class WidgetCanUpdateBenchmark extends BenchmarkBase { | 
|  | WidgetCanUpdateBenchmark() : super('RuntimeType.Widget.canUpdate.byType'); | 
|  |  | 
|  | // All widgets have different types. | 
|  | static List<Widget> _widgets() => [ | 
|  | AWidget(), | 
|  | BWidget(), | 
|  | CWidget(), | 
|  | DWidget(), | 
|  | EWidget(), | 
|  | FWidget(), | 
|  | WWidget<AWidget>(), | 
|  | WWidget<BWidget>(ref: const BWidget()), | 
|  | WWidget<CWidget>(ref: CWidget()), | 
|  | const WWidget<DWidget>(ref: DWidget()), | 
|  | ]; | 
|  | // Bulk up list to reduce loop overheads. | 
|  | final List<Widget> widgets = _widgets() + _widgets() + _widgets(); | 
|  |  | 
|  | @override | 
|  | void exercise() => run(); | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | for (var w1 in widgets) { | 
|  | for (var w2 in widgets) { | 
|  | if (Widget.canUpdate(w1, w2) != Widget.canUpdate(w2, w1)) { | 
|  | throw 'Hmm $w1 $w2'; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Normalize by number of calls to [Widgets.canUpdate]. | 
|  | @override | 
|  | double measure() => super.measure() / (widgets.length * widgets.length * 2); | 
|  | } | 
|  |  | 
|  | class ValueKeyEqualBenchmark extends BenchmarkBase { | 
|  | ValueKeyEqualBenchmark() : super('RuntimeType.Widget.canUpdate.byKey'); | 
|  |  | 
|  | // All widgets the same class but distinguished on keys. | 
|  | static List<Widget> _widgets() => [ | 
|  | AWidget(), | 
|  | AWidget(key: ValueKey(1)), | 
|  | AWidget(key: ValueKey(1)), | 
|  | AWidget(key: ValueKey(2)), | 
|  | AWidget(key: ValueKey(2)), | 
|  | AWidget(key: ValueKey(3)), | 
|  | AWidget(key: ValueKey('one')), | 
|  | AWidget(key: ValueKey('two')), | 
|  | AWidget(key: ValueKey('three')), | 
|  | AWidget(key: ValueKey(Duration(seconds: 5))), | 
|  | ]; | 
|  | // Bulk up list to reduce loop overheads. | 
|  | final List<Widget> widgets = _widgets() + _widgets() + _widgets(); | 
|  |  | 
|  | @override | 
|  | void exercise() => run(); | 
|  |  | 
|  | @override | 
|  | void run() { | 
|  | for (var w1 in widgets) { | 
|  | for (var w2 in widgets) { | 
|  | if (Widget.canUpdate(w1, w2) != Widget.canUpdate(w2, w1)) { | 
|  | throw 'Hmm $w1 $w2'; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Normalize by number of calls to [Widgets.canUpdate]. | 
|  | @override | 
|  | double measure() => super.measure() / (widgets.length * widgets.length * 2); | 
|  | } | 
|  |  | 
|  | void pollute() { | 
|  | // Various bits of code to make environment less unrealistic. | 
|  | void check(dynamic a, dynamic b) { | 
|  | if (a.runtimeType != b.runtimeType) throw 'mismatch $a $b'; | 
|  | } | 
|  |  | 
|  | check(Uint8List(1), Uint8List(2)); // dart2js needs native interceptors. | 
|  | check(Int16List(1), Int16List(2)); | 
|  | check([], []); | 
|  | check(<bool>{}, <bool>{}); | 
|  | } | 
|  |  | 
|  | void main() { | 
|  | pollute(); | 
|  |  | 
|  | final benchmarks = [ | 
|  | WidgetCanUpdateBenchmark(), | 
|  | ValueKeyEqualBenchmark(), | 
|  | ]; | 
|  |  | 
|  | // Warm up all benchmarks before running any. | 
|  | benchmarks.forEach((bm) => bm.run()); | 
|  |  | 
|  | benchmarks.forEach((bm) => bm.report()); | 
|  | } |