-
Notifications
You must be signed in to change notification settings - Fork 197
Improve enum decoding #980
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
Changes from all commits
78c88d9
6fe139a
0f51158
bd1ca9f
b887c49
b81767f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -91,7 +91,7 @@ class FieldInfo<T> { | |||||||||||||||||||||||||||
| /// Mapping from enum integer values to enum values. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
| /// Only available in enum fields. | ||||||||||||||||||||||||||||
| final ValueOfFunc? valueOf; | ||||||||||||||||||||||||||||
| final Map<int, ProtobufEnum>? enumValueMap; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /// Function to verify items when adding to a repeated field. | ||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||
|
|
@@ -101,7 +101,7 @@ class FieldInfo<T> { | |||||||||||||||||||||||||||
| FieldInfo(this.name, this.tagNumber, this.index, this.type, | ||||||||||||||||||||||||||||
| {dynamic defaultOrMaker, | ||||||||||||||||||||||||||||
| this.subBuilder, | ||||||||||||||||||||||||||||
| this.valueOf, | ||||||||||||||||||||||||||||
| this.enumValueMap, | ||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of getting this new parameter, why can't we take
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We have one function to decode enum protobuf.dart/protobuf/lib/src/protobuf/builder_info.dart Lines 324 to 334 in 006d3aa
For it to index a list sometimes and map other times, we would need a type test, or a closure, as in Re: generating the list or map from the protobuf.dart/protoc_plugin/lib/src/generated/plugin.pbenum.dart Lines 30 to 31 in 006d3aa
And then this static field needs to be passed to Does this make sense?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. But instead of passing the representation (e.g. This could be a direct call, it could have a Something like // package:protobuf
class ProtobufEnum {}
class ProtobufEnumDescriptor {
final bool _binarySearch;
final List<ProtobufEnum?> _byValue;
ProtobufEnumDescriptor(this._binarySearch, this._byValue);
ProtobufEnum? lookup(int value) { ... }
}
// foo.pbenum.dart
class FooEnum extends ProtobufEnum {
...
static final info_ = ProtobufEnumDescriptor(values, useBinarySearch: <if sparse>);
}
// foo.pb.dart
class Foo extends GeneratedMessage {
final info_ = BuilderInfo()
..enumField(FooEnum.info_);
}Then the decoder would see ProtobufEnum? _decodeEnum(int tagNumber, ExtensionRegistry? registry, int rawValue) {
final f = enumDescriptorOf(tagNumber);
if (f != null) {
return f.lookup(rawValue); // direct call, passing `rawValue` unboxed, does array lookup or binary search
}
...;
}Would that work?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would work and I agree that it should improve things. Compared to the current PR, it's similar to passing a list (instead of a map) + a bool to whether the list should be binary searched or indexed. A An I'll first update the other PR with binary search because that's easy to do. Then update benchmarks to have some sparse enums/enums with large encodings. We can merge the benchmarks separately. Then revisit this idea. |
||||||||||||||||||||||||||||
| this.enumValues, | ||||||||||||||||||||||||||||
| this.defaultEnumValue, | ||||||||||||||||||||||||||||
| String? protoName}) | ||||||||||||||||||||||||||||
|
|
@@ -112,7 +112,7 @@ class FieldInfo<T> { | |||||||||||||||||||||||||||
| assert(!_isGroupOrMessage(type) || | ||||||||||||||||||||||||||||
| subBuilder != null || | ||||||||||||||||||||||||||||
| _isMapField(type)), | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || valueOf != null); | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || enumValueMap != null); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Represents a field that has been removed by a program transformation. | ||||||||||||||||||||||||||||
| FieldInfo.dummy(this.index) | ||||||||||||||||||||||||||||
|
|
@@ -121,19 +121,22 @@ class FieldInfo<T> { | |||||||||||||||||||||||||||
| tagNumber = 0, | ||||||||||||||||||||||||||||
| type = 0, | ||||||||||||||||||||||||||||
| makeDefault = null, | ||||||||||||||||||||||||||||
| valueOf = null, | ||||||||||||||||||||||||||||
| enumValueMap = null, | ||||||||||||||||||||||||||||
| check = null, | ||||||||||||||||||||||||||||
| enumValues = null, | ||||||||||||||||||||||||||||
| defaultEnumValue = null, | ||||||||||||||||||||||||||||
| subBuilder = null; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| FieldInfo.repeated(this.name, this.tagNumber, this.index, this.type, | ||||||||||||||||||||||||||||
| CheckFunc<T> this.check, this.subBuilder, | ||||||||||||||||||||||||||||
| {this.valueOf, this.enumValues, this.defaultEnumValue, String? protoName}) | ||||||||||||||||||||||||||||
| {this.enumValueMap, | ||||||||||||||||||||||||||||
| this.enumValues, | ||||||||||||||||||||||||||||
| this.defaultEnumValue, | ||||||||||||||||||||||||||||
| String? protoName}) | ||||||||||||||||||||||||||||
| : makeDefault = (() => PbList<T>(check: check)), | ||||||||||||||||||||||||||||
| _protoName = protoName, | ||||||||||||||||||||||||||||
| assert(_isRepeated(type)), | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || valueOf != null); | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || enumValueMap != null); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| static MakeDefaultFunc? findMakeDefault(int type, dynamic defaultOrMaker) { | ||||||||||||||||||||||||||||
| if (defaultOrMaker == null) return PbFieldType._defaultForType(type); | ||||||||||||||||||||||||||||
|
|
@@ -277,7 +280,7 @@ class MapFieldInfo<K, V> extends FieldInfo<PbMap<K, V>?> { | |||||||||||||||||||||||||||
| defaultOrMaker: () => PbMap<K, V>(keyFieldType, valueFieldType), | ||||||||||||||||||||||||||||
| defaultEnumValue: defaultEnumValue, | ||||||||||||||||||||||||||||
| protoName: protoName) { | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || valueOf != null); | ||||||||||||||||||||||||||||
| assert(!_isEnum(type) || enumValueMap != null); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| FieldInfo get valueFieldInfo => | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't these changes removing those parameters from public APIs? They seem to be breaking changes. So old generated code will no longer work with newest
package:protobuf. Ifpackage:protobufhas to upgrade to new major version then it causes trouble for ecosystem:If an app depends transitively on A and B and A uses old and B uses new protobuf version, then we get constraint conflict.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's a breaking change. You need to update
protoc_pluginandprotobuftogether and re-generate your proto classes.We've done it in the past, for example with the most recent
protobufandprotoc_plugins.Yes, but you can't do any major bumps and breaking changes if you want to avoid this. We deal with it the same way we've dealt with it in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So from your experience it doesn't cause too much churn on the ecosystem? Then it's ok, but you'll have to bump major version number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have too much experience update downstream with major version bumps, but I expect this particular change to not be a big problem.
Maybe someone who works on updating dependencies can comment how much churn this kind of thing causes.
Internally, no one notice this change other than maybe their benchmarks getting faster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Releasing a major version (w/ planning, batching breaking changes, planning deprecations, ...) should be just a normal part of the process. The diamond constraint issue becomes a problem when one of the packages in the cycle has become unmaintained (rev'ing their deps doesn't happen or happens w/ lots of latency). You're more likely to run into that when more things depend on the package being released - when it's further down the overall ecosystem stack.
For something like protobuf I'd just do regular planning for a new major version but would still rev. when necessary. Even for breaking changes, you want to minimize the work for customers - they will eventually need to rev from 3 => 4, or 4 => 5.
There are a few packages that are very core to the ecosystem, and you have to be cautious about rev'ing, or choose to not rev at all (like, the direct deps of flutter - https://github.com/flutter/flutter/blob/master/packages/flutter/pubspec.yaml#L8).