Skip to content

Improve enum decoding -- alternative with binary search instead of map lookup #985

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

Closed
wants to merge 11 commits into from
59 changes: 43 additions & 16 deletions protobuf/lib/src/protobuf/protobuf_enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@
// 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.

// ignore_for_file: non_constant_identifier_names

part of '../../protobuf.dart';

/// A base class for all proto enum types.
///
/// All proto `enum` classes inherit from [ProtobufEnum]. For example, given
/// the following enum defined in a proto file:
/// All proto `enum` classes inherit from [ProtobufEnum]. For example, given the
/// following enum defined in a proto file:
///
/// message MyMessage {
/// enum Color {
/// RED = 0;
/// GREEN = 1;
/// BLUE = 2;
/// };
/// // ...
/// }
/// ```
/// message MyMessage {
/// enum Color {
/// RED = 0;
/// GREEN = 1;
/// BLUE = 2;
/// };
/// // ...
/// }
/// ```
///
/// the generated Dart file will include a `MyMessage_Color` class that extends
/// `ProtobufEnum`. It will also include a `const MyMessage_Color` for each of
Expand All @@ -37,16 +41,39 @@ class ProtobufEnum {
/// Creates a new constant [ProtobufEnum] using [value] and [name].
const ProtobufEnum(this.value, this.name);

/// Creates a Map for all of the [ProtobufEnum]s in [byIndex], mapping each
/// [ProtobufEnum]'s [value] to the [ProtobufEnum].
static Map<int, T> initByValue<T extends ProtobufEnum>(List<T> byIndex) {
final byValue = <int, T>{};
for (final v in byIndex) {
byValue[v.value] = v;
/// @nodoc
static List<T?> $_initDenseList<T extends ProtobufEnum>(List<T> byIndex) {
if (byIndex.isEmpty) return [];
final byValue = List<T?>.filled(byIndex.last.value + 1, null);
for (final enumValue in byIndex) {
byValue[enumValue.value] = enumValue;
}
return byValue;
}

/// @nodoc
static List<T> $_initSparseList<T extends ProtobufEnum>(List<T> byIndex) =>
byIndex.toList()..sort((e1, e2) => e1.value.compareTo(e2.value));

/// @nodoc
static T? $_binarySearch<T extends ProtobufEnum>(
List<T> sortedList, int value) {
var min = 0;
var max = sortedList.length;
while (min < max) {
final mid = min + ((max - min) >> 1);
final element = sortedList[mid];
final comp = element.value.compareTo(value);
if (comp == 0) return element;
if (comp < 0) {
min = mid + 1;
} else {
max = mid;
}
}
return null;
}

/// Returns this enum's [name] or the [value] if names are not represented.
@override
String toString() => name == '' ? value.toString() : name;
Expand Down
1 change: 1 addition & 0 deletions protoc_plugin/lib/protoc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:convert';

import 'package:collection/collection.dart';
import 'package:protobuf/protobuf.dart';

import 'const_generator.dart' show writeJsonConst;
Expand Down
31 changes: 25 additions & 6 deletions protoc_plugin/lib/src/enum_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ class EnumGenerator extends ProtobufContainer {
}
}
out.println();

out.println('static const $coreImportPrefix.List<$classname> values ='
' <$classname> [');
for (final val in _canonicalValues) {
Expand All @@ -175,11 +174,31 @@ class EnumGenerator extends ProtobufContainer {
out.println('];');
out.println();

out.println(
'static final $coreImportPrefix.Map<$coreImportPrefix.int, $classname> _byValue ='
' $protobufImportPrefix.ProtobufEnum.initByValue(values);');
out.println('static $classname? valueOf($coreImportPrefix.int value) =>'
' _byValue[value];');
var useList = _canonicalValues.isEmpty;
if (_canonicalValues.isNotEmpty) {
if (_canonicalValues.every((val) => !val.number.isNegative)) {
if (_canonicalValues.length / (_canonicalValues.last.number + 1) >=
0.7) {
useList = true;
}
}
}

if (useList) {
out.println(
'static final $coreImportPrefix.List<$classname?> _byValue ='
' $protobufImportPrefix.ProtobufEnum.\$_initDenseList(values);');

out.println('static $classname? valueOf($coreImportPrefix.int value) =>'
' value < 0 || value >= _byValue.length ? null : _byValue[value];');
} else {
out.println('static final $coreImportPrefix.List<$classname> _byValue ='
' $protobufImportPrefix.ProtobufEnum.\$_initSparseList(values);');

out.println('static $classname? valueOf($coreImportPrefix.int value) =>'
' $protobufImportPrefix.ProtobufEnum.\$_binarySearch(_byValue, value);');
}

out.println();

out.println('const $classname._(super.v, super.n);');
Expand Down
40 changes: 22 additions & 18 deletions protoc_plugin/lib/src/generated/descriptor.pbenum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ class FieldDescriptorProto_Type extends $pb.ProtobufEnum {
TYPE_SINT64,
];

static final $core.Map<$core.int, FieldDescriptorProto_Type> _byValue =
$pb.ProtobufEnum.initByValue(values);
static FieldDescriptorProto_Type? valueOf($core.int value) => _byValue[value];
static final $core.List<FieldDescriptorProto_Type?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static FieldDescriptorProto_Type? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const FieldDescriptorProto_Type._(super.v, super.n);
}
Expand All @@ -111,10 +112,10 @@ class FieldDescriptorProto_Label extends $pb.ProtobufEnum {
LABEL_REPEATED,
];

static final $core.Map<$core.int, FieldDescriptorProto_Label> _byValue =
$pb.ProtobufEnum.initByValue(values);
static final $core.List<FieldDescriptorProto_Label?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static FieldDescriptorProto_Label? valueOf($core.int value) =>
_byValue[value];
value < 0 || value >= _byValue.length ? null : _byValue[value];

const FieldDescriptorProto_Label._(super.v, super.n);
}
Expand All @@ -137,9 +138,10 @@ class FileOptions_OptimizeMode extends $pb.ProtobufEnum {
LITE_RUNTIME,
];

static final $core.Map<$core.int, FileOptions_OptimizeMode> _byValue =
$pb.ProtobufEnum.initByValue(values);
static FileOptions_OptimizeMode? valueOf($core.int value) => _byValue[value];
static final $core.List<FileOptions_OptimizeMode?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static FileOptions_OptimizeMode? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const FileOptions_OptimizeMode._(super.v, super.n);
}
Expand All @@ -159,9 +161,10 @@ class FieldOptions_CType extends $pb.ProtobufEnum {
STRING_PIECE,
];

static final $core.Map<$core.int, FieldOptions_CType> _byValue =
$pb.ProtobufEnum.initByValue(values);
static FieldOptions_CType? valueOf($core.int value) => _byValue[value];
static final $core.List<FieldOptions_CType?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static FieldOptions_CType? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const FieldOptions_CType._(super.v, super.n);
}
Expand All @@ -185,9 +188,10 @@ class FieldOptions_JSType extends $pb.ProtobufEnum {
JS_NUMBER,
];

static final $core.Map<$core.int, FieldOptions_JSType> _byValue =
$pb.ProtobufEnum.initByValue(values);
static FieldOptions_JSType? valueOf($core.int value) => _byValue[value];
static final $core.List<FieldOptions_JSType?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static FieldOptions_JSType? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const FieldOptions_JSType._(super.v, super.n);
}
Expand All @@ -212,10 +216,10 @@ class MethodOptions_IdempotencyLevel extends $pb.ProtobufEnum {
IDEMPOTENT,
];

static final $core.Map<$core.int, MethodOptions_IdempotencyLevel> _byValue =
$pb.ProtobufEnum.initByValue(values);
static final $core.List<MethodOptions_IdempotencyLevel?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static MethodOptions_IdempotencyLevel? valueOf($core.int value) =>
_byValue[value];
value < 0 || value >= _byValue.length ? null : _byValue[value];

const MethodOptions_IdempotencyLevel._(super.v, super.n);
}
Expand Down
6 changes: 3 additions & 3 deletions protoc_plugin/lib/src/generated/plugin.pbenum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ class CodeGeneratorResponse_Feature extends $pb.ProtobufEnum {
FEATURE_PROTO3_OPTIONAL,
];

static final $core.Map<$core.int, CodeGeneratorResponse_Feature> _byValue =
$pb.ProtobufEnum.initByValue(values);
static final $core.List<CodeGeneratorResponse_Feature?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static CodeGeneratorResponse_Feature? valueOf($core.int value) =>
_byValue[value];
value < 0 || value >= _byValue.length ? null : _byValue[value];

const CodeGeneratorResponse_Feature._(super.v, super.n);
}
Expand Down
7 changes: 4 additions & 3 deletions protoc_plugin/test/goldens/deprecations.pbenum
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ class A extends $pb.ProtobufEnum {
A2,
];

static final $core.Map<$core.int, A> _byValue =
$pb.ProtobufEnum.initByValue(values);
static A? valueOf($core.int value) => _byValue[value];
static final $core.List<A?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static A? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const A._(super.v, super.n);
}
Expand Down
7 changes: 4 additions & 3 deletions protoc_plugin/test/goldens/doc_comments.pbenum
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ class A extends $pb.ProtobufEnum {
A2,
];

static final $core.Map<$core.int, A> _byValue =
$pb.ProtobufEnum.initByValue(values);
static A? valueOf($core.int value) => _byValue[value];
static final $core.List<A?> _byValue =
$pb.ProtobufEnum.$_initDenseList(values);
static A? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];

const A._(super.v, super.n);
}
Expand Down
4 changes: 2 additions & 2 deletions protoc_plugin/test/goldens/enum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class PhoneType extends $pb.ProtobufEnum {
WORK,
];

static final $core.Map<$core.int, PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values);
static PhoneType? valueOf($core.int value) => _byValue[value];
static final $core.List<PhoneType?> _byValue = $pb.ProtobufEnum.$_initDenseList(values);
static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value];

const PhoneType._(super.v, super.n);
}
Expand Down
4 changes: 2 additions & 2 deletions protoc_plugin/test/goldens/messageGeneratorEnums
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ class PhoneNumber_PhoneType extends $pb.ProtobufEnum {
WORK,
];

static final $core.Map<$core.int, PhoneNumber_PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values);
static PhoneNumber_PhoneType? valueOf($core.int value) => _byValue[value];
static final $core.List<PhoneNumber_PhoneType?> _byValue = $pb.ProtobufEnum.$_initDenseList(values);
static PhoneNumber_PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value];

const PhoneNumber_PhoneType._(super.v, super.n);
}
Expand Down
4 changes: 2 additions & 2 deletions protoc_plugin/test/goldens/topLevelEnum.pbenum
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class PhoneType extends $pb.ProtobufEnum {
WORK,
];

static final $core.Map<$core.int, PhoneType> _byValue = $pb.ProtobufEnum.initByValue(values);
static PhoneType? valueOf($core.int value) => _byValue[value];
static final $core.List<PhoneType?> _byValue = $pb.ProtobufEnum.$_initDenseList(values);
static PhoneType? valueOf($core.int value) => value < 0 || value >= _byValue.length ? null : _byValue[value];

const PhoneType._(super.v, super.n);
}
Expand Down
Loading