github.com/philpearl/plenc@v0.0.15/plenccodec/string.go (about) 1 package plenccodec 2 3 import ( 4 "sync" 5 "sync/atomic" 6 "unsafe" 7 8 "github.com/philpearl/plenc/plenccore" 9 ) 10 11 // StringCodec is a codec for an string 12 type StringCodec struct{} 13 14 // size returns the number of bytes needed to encode a string 15 func (StringCodec) size(ptr unsafe.Pointer) int { 16 return len(*(*string)(ptr)) 17 } 18 19 // append encodes a string 20 func (StringCodec) append(data []byte, ptr unsafe.Pointer) []byte { 21 s := *(*string)(ptr) 22 return append(data, s...) 23 } 24 25 // Read decodes a string 26 func (StringCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) { 27 *(*string)(ptr) = string(data) 28 return len(data), nil 29 } 30 31 // New creates a pointer to a new string header 32 func (StringCodec) New() unsafe.Pointer { 33 return unsafe.Pointer(new(string)) 34 } 35 36 // WireType returns the wire type used to encode this type 37 func (StringCodec) WireType() plenccore.WireType { 38 return plenccore.WTLength 39 } 40 41 // Omit indicates whether this field should be omitted 42 func (StringCodec) Omit(ptr unsafe.Pointer) bool { 43 return len(*(*string)(ptr)) == 0 44 } 45 46 func (StringCodec) Descriptor() Descriptor { 47 return Descriptor{Type: FieldTypeString} 48 } 49 50 func (c StringCodec) Size(ptr unsafe.Pointer, tag []byte) int { 51 l := c.size(ptr) 52 if len(tag) > 0 { 53 l += len(tag) + plenccore.SizeVarUint(uint64(l)) 54 } 55 return l 56 } 57 58 func (c StringCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte { 59 if len(tag) != 0 { 60 data = append(data, tag...) 61 data = plenccore.AppendVarUint(data, uint64(c.size(ptr))) 62 } 63 return c.append(data, ptr) 64 } 65 66 // BytesCodec is a codec for a byte slice 67 type BytesCodec struct{} 68 69 // size returns the number of bytes needed to encode a string 70 func (BytesCodec) size(ptr unsafe.Pointer) int { 71 return len(*(*[]byte)(ptr)) 72 } 73 74 // append encodes a []byte 75 func (BytesCodec) append(data []byte, ptr unsafe.Pointer) []byte { 76 s := *(*[]byte)(ptr) 77 return append(data, s...) 78 } 79 80 // Read decodes a []byte 81 func (BytesCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) { 82 // really must copy this data to be safe from the underlying buffer changing 83 // later 84 *(*[]byte)(ptr) = append([]byte(nil), data...) 85 return len(data), nil 86 } 87 88 // New creates a pointer to a new bool 89 func (c BytesCodec) New() unsafe.Pointer { 90 return unsafe.Pointer(new([]byte)) 91 } 92 93 // WireType returns the wire type used to encode this type 94 func (c BytesCodec) WireType() plenccore.WireType { 95 return plenccore.WTLength 96 } 97 98 // Omit indicates whether this field should be omitted 99 func (c BytesCodec) Omit(ptr unsafe.Pointer) bool { 100 return len(*(*[]byte)(ptr)) == 0 101 } 102 103 func (BytesCodec) Descriptor() Descriptor { 104 return Descriptor{Type: FieldTypeString} 105 } 106 107 func (c BytesCodec) Size(ptr unsafe.Pointer, tag []byte) int { 108 l := c.size(ptr) 109 if len(tag) != 0 { 110 l += len(tag) + plenccore.SizeVarUint(uint64(l)) 111 } 112 return l 113 } 114 115 func (c BytesCodec) Append(data []byte, ptr unsafe.Pointer, tag []byte) []byte { 116 if len(tag) != 0 { 117 data = append(data, tag...) 118 data = plenccore.AppendVarUint(data, uint64(c.size(ptr))) 119 } 120 return c.append(data, ptr) 121 } 122 123 type Interner interface { 124 WithInterning() Codec 125 } 126 127 type InternedStringCodec struct { 128 sync.Mutex 129 strings unsafe.Pointer 130 StringCodec 131 } 132 133 func (c *InternedStringCodec) Read(data []byte, ptr unsafe.Pointer, wt plenccore.WireType) (n int, err error) { 134 p := atomic.LoadPointer(&c.strings) 135 m := *(*map[string]string)((unsafe.Pointer)(&p)) 136 137 s, ok := m[string(data)] 138 if !ok { 139 s = c.addString(data) 140 } 141 142 *(*string)(ptr) = s 143 return len(data), nil 144 } 145 146 func (c *InternedStringCodec) addString(data []byte) string { 147 c.Lock() 148 defer c.Unlock() 149 p := atomic.LoadPointer(&c.strings) 150 m := *(*map[string]string)((unsafe.Pointer)(&p)) 151 152 s, ok := m[string(data)] 153 if !ok { 154 // We completely replace the map with a new one, so the old one can 155 // be read without locks. We're expecting the number of different values 156 // to be small, so that this is a reasonable thing to do. 157 m2 := make(map[string]string, len(m)+1) 158 for k, v := range m { 159 m2[k] = v 160 } 161 s = string(data) 162 m2[s] = s 163 164 atomic.StorePointer(&c.strings, *(*unsafe.Pointer)(unsafe.Pointer(&m2))) 165 } 166 return s 167 } 168 169 func (c StringCodec) WithInterning() Codec { 170 ic := &InternedStringCodec{} 171 m := map[string]string{} 172 atomic.StorePointer(&ic.strings, *(*unsafe.Pointer)(unsafe.Pointer(&m))) 173 return ic 174 }