mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-03-10 16:14:39 +00:00
internal/impl: add scaffolding for lazy extension support
Once the ExtensionFieldV1.Value field is unexported, it forces all read operations to go through GetValue, which can perform some form of lazy retrieval. The SetEagerValue method is equivalent to setting the Value directly. The SetLazyValue method permits setting a lazy equivalent of the Value by simply storing a function closure returning the value. Change-Id: I410f74924f0957f5eadc82eea3669ca335a02701 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/177619 Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
838f1f50f9
commit
40b83d67fc
@ -6,6 +6,8 @@ package impl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
pref "google.golang.org/protobuf/reflect/protoreflect"
|
||||
piface "google.golang.org/protobuf/runtime/protoiface"
|
||||
@ -59,13 +61,13 @@ func (p legacyExtensionFields) Len() (n int) {
|
||||
|
||||
func (p legacyExtensionFields) Has(n pref.FieldNumber) bool {
|
||||
x := p.x.Get(n)
|
||||
if x.Value == nil {
|
||||
if !x.HasValue() {
|
||||
return false
|
||||
}
|
||||
t := extensionTypeFromDesc(x.Desc)
|
||||
d := t.Descriptor()
|
||||
if d.IsList() {
|
||||
return t.ValueOf(x.Value).List().Len() > 0
|
||||
return t.ValueOf(x.GetValue()).List().Len() > 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -77,7 +79,7 @@ func (p legacyExtensionFields) Get(n pref.FieldNumber) pref.Value {
|
||||
}
|
||||
t := extensionTypeFromDesc(x.Desc)
|
||||
d := t.Descriptor()
|
||||
if x.Value == nil {
|
||||
if !x.HasValue() {
|
||||
// NOTE: x.Value is never nil for Lists since they are always populated
|
||||
// during ExtensionFieldTypes.Register.
|
||||
if d.Kind() == pref.MessageKind || d.Kind() == pref.GroupKind {
|
||||
@ -85,7 +87,7 @@ func (p legacyExtensionFields) Get(n pref.FieldNumber) pref.Value {
|
||||
}
|
||||
return d.Default()
|
||||
}
|
||||
return t.ValueOf(x.Value)
|
||||
return t.ValueOf(x.GetValue())
|
||||
}
|
||||
|
||||
func (p legacyExtensionFields) Set(n pref.FieldNumber, v pref.Value) {
|
||||
@ -94,7 +96,7 @@ func (p legacyExtensionFields) Set(n pref.FieldNumber, v pref.Value) {
|
||||
panic("no extension descriptor registered")
|
||||
}
|
||||
t := extensionTypeFromDesc(x.Desc)
|
||||
x.Value = t.InterfaceOf(v)
|
||||
x.SetEagerValue(t.InterfaceOf(v))
|
||||
p.x.Set(n, x)
|
||||
}
|
||||
|
||||
@ -106,10 +108,10 @@ func (p legacyExtensionFields) Clear(n pref.FieldNumber) {
|
||||
t := extensionTypeFromDesc(x.Desc)
|
||||
d := t.Descriptor()
|
||||
if d.IsList() {
|
||||
t.ValueOf(x.Value).List().Truncate(0)
|
||||
t.ValueOf(x.GetValue()).List().Truncate(0)
|
||||
return
|
||||
}
|
||||
x.Value = nil
|
||||
x.SetEagerValue(nil)
|
||||
p.x.Set(n, x)
|
||||
}
|
||||
|
||||
@ -167,7 +169,7 @@ func (p legacyExtensionTypes) Register(t pref.ExtensionType) {
|
||||
if d.IsList() {
|
||||
// If the field is repeated, initialize the entry with an empty list
|
||||
// so that future Get operations can return a mutable and concrete list.
|
||||
x.Value = t.InterfaceOf(t.New())
|
||||
x.SetEagerValue(t.InterfaceOf(t.New()))
|
||||
}
|
||||
p.x.Set(d.Number(), x)
|
||||
}
|
||||
@ -180,12 +182,12 @@ func (p legacyExtensionTypes) Remove(t pref.ExtensionType) {
|
||||
x := p.x.Get(d.Number())
|
||||
if d.IsList() {
|
||||
// Treat an empty repeated field as unpopulated.
|
||||
v := reflect.ValueOf(x.Value)
|
||||
if x.Value == nil || v.IsNil() || v.Elem().Len() == 0 {
|
||||
x.Value = nil
|
||||
v := reflect.ValueOf(x.GetValue())
|
||||
if !x.HasValue() || v.IsNil() || v.Elem().Len() == 0 {
|
||||
x.SetEagerValue(nil)
|
||||
}
|
||||
}
|
||||
if x.Value != nil {
|
||||
if x.GetValue() != nil {
|
||||
panic("value for extension descriptor still populated")
|
||||
}
|
||||
p.x.Clear(d.Number())
|
||||
@ -254,20 +256,72 @@ type ExtensionFieldV1 struct {
|
||||
// will be set.
|
||||
Desc *piface.ExtensionDescV1 // TODO: switch to protoreflect.ExtensionType
|
||||
|
||||
// Value is a concrete value for the extension field. Let the type of
|
||||
// Desc.ExtensionType be the "API type" and the type of Value be the
|
||||
// "storage type". The API type and storage type are the same except:
|
||||
// * for scalars (except []byte), where the API type uses *T,
|
||||
// while the storage type uses T.
|
||||
// * for repeated fields, where the API type uses []T,
|
||||
// while the storage type uses *[]T.
|
||||
// value is either the value of GetValue,
|
||||
// or a *lazyExtensionValue that then returns the value of GetValue.
|
||||
//
|
||||
// The reason for the divergence is so that the storage type more naturally
|
||||
// matches what is expected of when retrieving the values through the
|
||||
// protobuf reflection APIs.
|
||||
//
|
||||
// The Value may only be populated if Desc is also populated.
|
||||
Value interface{} // TODO: switch to protoreflect.Value
|
||||
// TODO: unexport this.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// HasValue reports whether a value is set for the extension field.
|
||||
// This may be called concurrently.
|
||||
func (f ExtensionFieldV1) HasValue() bool {
|
||||
return f.Value != nil
|
||||
}
|
||||
|
||||
// GetValue returns the concrete value for the extension field.
|
||||
// Let the type of Desc.ExtensionType be the "API type" and
|
||||
// the type of GetValue be the "storage type".
|
||||
// The API type and storage type are the same except:
|
||||
// * for scalars (except []byte), where the API type uses *T,
|
||||
// while the storage type uses T.
|
||||
// * for repeated fields, where the API type uses []T,
|
||||
// while the storage type uses *[]T.
|
||||
//
|
||||
// The reason for the divergence is so that the storage type more naturally
|
||||
// matches what is expected of when retrieving the values through the
|
||||
// protobuf reflection APIs.
|
||||
//
|
||||
// GetValue is only populated if Desc is also populated.
|
||||
// This may be called concurrently.
|
||||
//
|
||||
// TODO: switch interface{} to protoreflect.Value
|
||||
func (f ExtensionFieldV1) GetValue() interface{} {
|
||||
if f, ok := f.Value.(*lazyExtensionValue); ok {
|
||||
return f.GetValue()
|
||||
}
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// SetEagerValue sets the current value of the extension.
|
||||
// This must not be called concurrently.
|
||||
func (f *ExtensionFieldV1) SetEagerValue(v interface{}) {
|
||||
f.Value = v
|
||||
}
|
||||
|
||||
// SetLazyValue sets a value that is to be lazily evaluated upon first use.
|
||||
// The returned value must not be nil.
|
||||
// This must not be called concurrently.
|
||||
func (f *ExtensionFieldV1) SetLazyValue(v func() interface{}) {
|
||||
f.Value = &lazyExtensionValue{value: v}
|
||||
}
|
||||
|
||||
type lazyExtensionValue struct {
|
||||
once uint32 // atomically set if value is valid
|
||||
mu sync.Mutex // protects value
|
||||
value interface{} // either the value itself or a func() interface{}
|
||||
}
|
||||
|
||||
func (v *lazyExtensionValue) GetValue() interface{} {
|
||||
if atomic.LoadUint32(&v.once) == 0 {
|
||||
v.mu.Lock()
|
||||
if f, ok := v.value.(func() interface{}); ok {
|
||||
v.value = f()
|
||||
}
|
||||
atomic.StoreUint32(&v.once, 1)
|
||||
v.mu.Unlock()
|
||||
}
|
||||
return v.value
|
||||
}
|
||||
|
||||
type legacyExtensionMap map[int32]ExtensionFieldV1
|
||||
|
Loading…
x
Reference in New Issue
Block a user