├── .gitignore ├── LICENSE ├── README.md ├── decode.go ├── encode.go ├── example └── bloomfilter_test.go ├── gocodec.go ├── gocodec_array.go ├── gocodec_base.go ├── gocodec_dispatch.go ├── gocodec_pointer.go ├── gocodec_root.go ├── gocodec_single_pointer_fix.go ├── gocodec_slice.go ├── gocodec_string.go ├── gocodec_struct.go ├── level_0 ├── array_test.go ├── float32_test.go ├── float64_test.go ├── int16_test.go ├── int32_test.go ├── int64_test.go ├── int8_test.go ├── int_test.go ├── struct_test.go ├── uint16_test.go ├── uint32_test.go ├── uint64_test.go ├── uint8_test.go ├── uint_test.go └── uintptr_test.go ├── level_1 ├── array_test.go ├── byte_slice_test.go ├── int_slice_test.go ├── ptr_int_test.go ├── string_test.go └── struct_test.go ├── level_2 ├── ptr_test.go ├── slice_test.go └── struct_test.go ├── test.sh └── unsafe.go /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage.txt 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gocodec 2 | 3 | inplace decoding go language value, read data off memory mapped file without copying or allocation. 4 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "reflect" 5 | "fmt" 6 | "unsafe" 7 | "errors" 8 | "io" 9 | "github.com/v2pro/plz/countlog" 10 | "encoding/hex" 11 | ) 12 | 13 | type Iterator struct { 14 | allocator Allocator 15 | objectSeq ObjectSeq 16 | cfg *frozenConfig 17 | buf []byte 18 | self []byte 19 | cursor []byte 20 | Error error 21 | } 22 | 23 | func (cfg *frozenConfig) NewIterator(buf []byte) *Iterator { 24 | return &Iterator{cfg: cfg, buf: buf, allocator: defaultAllocator} 25 | } 26 | 27 | func (iter *Iterator) Reset(buf []byte) { 28 | iter.buf = buf 29 | iter.cursor = nil 30 | iter.Error = nil 31 | } 32 | 33 | func (iter *Iterator) NextSize() uint32 { 34 | if len(iter.buf) < 8 { 35 | return 0 36 | } 37 | return *(*uint32)(unsafe.Pointer(&iter.buf[0])) 38 | } 39 | 40 | func (iter *Iterator) Skip() []byte { 41 | size := iter.NextSize() 42 | skipped := iter.buf[:size] 43 | iter.buf = iter.buf[size:] 44 | return skipped 45 | } 46 | 47 | func (iter *Iterator) CopyThenUnmarshal(candidatePointer interface{}) interface{} { 48 | size := iter.NextSize() 49 | if size == 0 { 50 | iter.Error = io.EOF 51 | return nil 52 | } 53 | copied := iter.allocator.Allocate(iter.objectSeq, iter.buf[:size]) 54 | nextBuf := iter.buf[size:] 55 | iter.Reset(copied) 56 | result := iter.Unmarshal(candidatePointer) 57 | iter.Reset(nextBuf) 58 | return result 59 | } 60 | 61 | func (iter *Iterator) CopyThenUnmarshalCandidates(candidatePointers ...interface{}) interface{} { 62 | size := iter.NextSize() 63 | if size == 0 { 64 | iter.Error = io.EOF 65 | return nil 66 | } 67 | copied := iter.allocator.Allocate(iter.objectSeq, iter.buf[:size]) 68 | nextBuf := iter.buf[size:] 69 | iter.Reset(copied) 70 | result := iter.UnmarshalCandidates(candidatePointers...) 71 | iter.Reset(nextBuf) 72 | return result 73 | } 74 | 75 | func (iter *Iterator) ObjectSeq(objectSeq ObjectSeq) { 76 | iter.objectSeq = objectSeq 77 | } 78 | 79 | func (iter *Iterator) Allocator(allocator Allocator) { 80 | iter.allocator = allocator 81 | } 82 | 83 | func (iter *Iterator) Unmarshal(candidatePointer interface{}) interface{} { 84 | size := iter.NextSize() 85 | if size == 0 { 86 | iter.Error = io.EOF 87 | return nil 88 | } 89 | thisBuf := iter.buf[:size] 90 | defer func() { 91 | recovered := recover() 92 | if recovered != nil { 93 | countlog.Fatal("event!gocodec.failed to unmarshal", 94 | "err", recovered, 95 | "buf", hex.EncodeToString(thisBuf), 96 | "stacktrace", countlog.ProvideStacktrace) 97 | iter.ReportError("Unmarshal", fmt.Errorf("%v", recovered)) 98 | } 99 | }() 100 | nextBuf := iter.buf[size:] 101 | sig := *(*uint32)(unsafe.Pointer(&iter.buf[4])) 102 | var decoder RootDecoder 103 | var val interface{} 104 | valType := reflect.TypeOf(candidatePointer).Elem() 105 | tryDecoder, err := decoderOfType(iter.cfg, valType) 106 | if err != nil { 107 | iter.ReportError("DecodeVal", err) 108 | return nil 109 | } 110 | if tryDecoder.Signature() != sig { 111 | iter.ReportError("DecodeVal", errors.New("no decoder matches the signature")) 112 | return nil 113 | } 114 | decoder = tryDecoder 115 | val = candidatePointer 116 | decoder.DecodeEmptyInterface((*emptyInterface)(unsafe.Pointer(&val)), iter) 117 | iter.buf = nextBuf 118 | return val 119 | } 120 | 121 | func (iter *Iterator) UnmarshalCandidates(candidatePointers ...interface{}) interface{} { 122 | size := iter.NextSize() 123 | if size == 0 { 124 | iter.Error = io.EOF 125 | return nil 126 | } 127 | thisBuf := iter.buf[:size] 128 | defer func() { 129 | recovered := recover() 130 | if recovered != nil { 131 | countlog.Fatal("event!gocodec.failed to unmarshal", 132 | "err", recovered, 133 | "buf", hex.EncodeToString(thisBuf), 134 | "stacktrace", countlog.ProvideStacktrace) 135 | iter.ReportError("Unmarshal", fmt.Errorf("%v", recovered)) 136 | } 137 | }() 138 | nextBuf := iter.buf[size:] 139 | sig := *(*uint32)(unsafe.Pointer(&iter.buf[4])) 140 | var decoder RootDecoder 141 | var val interface{} 142 | for _, candidatePointer := range candidatePointers { 143 | valType := reflect.TypeOf(candidatePointer).Elem() 144 | tryDecoder, err := decoderOfType(iter.cfg, valType) 145 | if err != nil { 146 | iter.ReportError("DecodeVal", err) 147 | return nil 148 | } 149 | if tryDecoder.Signature() == sig { 150 | decoder = tryDecoder 151 | val = candidatePointer 152 | break 153 | } 154 | } 155 | if decoder == nil { 156 | iter.ReportError("DecodeVal", errors.New("no decoder matches the signature")) 157 | return nil 158 | } 159 | decoder.DecodeEmptyInterface((*emptyInterface)(unsafe.Pointer(&val)), iter) 160 | iter.buf = nextBuf 161 | return val 162 | } 163 | 164 | func (iter *Iterator) ReportError(operation string, err error) { 165 | if iter.Error != nil { 166 | return 167 | } 168 | iter.Error = fmt.Errorf("%s: %s", operation, err) 169 | } 170 | 171 | func (iter *Iterator) Buffer() []byte { 172 | return iter.buf 173 | } 174 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | type Stream struct { 10 | cfg *frozenConfig 11 | // there are two pointers 12 | // buf + cursor => the input of encoder 13 | // buf + len(buf) => the output of encoder 14 | buf []byte 15 | cursor uintptr 16 | Error error 17 | } 18 | 19 | func (cfg *frozenConfig) NewStream(buf []byte) *Stream { 20 | return &Stream{cfg: cfg, buf: buf} 21 | } 22 | 23 | func (stream *Stream) Reset(buf []byte) { 24 | stream.buf = buf 25 | stream.cursor = 0 26 | } 27 | 28 | func (stream *Stream) Marshal(val interface{}) uint32 { 29 | valType := reflect.TypeOf(val) 30 | encoder, err := encoderOfType(stream.cfg, valType) 31 | if err != nil { 32 | stream.ReportError("EncodeVal", err) 33 | return 0 34 | } 35 | baseCursor := len(stream.buf) 36 | stream.buf = append(stream.buf, []byte{ 37 | 0, 0, 0, 0, // size 38 | 0, 0, 0, 0, // signature 39 | }...) 40 | encoder.EncodeEmptyInterface(ptrOfEmptyInterface(val), stream) 41 | if stream.Error != nil { 42 | return 0 43 | } 44 | pSize := unsafe.Pointer(&stream.buf[baseCursor]) 45 | size := uint32(len(stream.buf) - baseCursor) 46 | *(*uint32)(pSize) = size 47 | pSig := unsafe.Pointer(&stream.buf[baseCursor+4]) 48 | *(*uint32)(pSig) = encoder.Signature() 49 | return size 50 | } 51 | 52 | func (stream *Stream) Buffer() []byte { 53 | return stream.buf 54 | } 55 | 56 | func (stream *Stream) ReportError(operation string, err error) { 57 | if stream.Error != nil { 58 | return 59 | } 60 | stream.Error = fmt.Errorf("%s: %s", operation, err) 61 | } 62 | -------------------------------------------------------------------------------- /example/bloomfilter_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/willf/bloom" 6 | "github.com/stretchr/testify/require" 7 | "github.com/esdb/gocodec" 8 | "io/ioutil" 9 | "github.com/edsrzf/mmap-go" 10 | "os" 11 | "github.com/json-iterator/go" 12 | ) 13 | 14 | func Test_bloomfilter(t *testing.T) { 15 | should := require.New(t) 16 | f := bloom.New(1000*1024, 4) 17 | f.Add([]byte("hello")) 18 | f.Add([]byte("world")) 19 | should.True(f.Test([]byte("hello"))) 20 | should.False(f.Test([]byte("hi"))) 21 | encoded, err := gocodec.Marshal(*f) 22 | should.Nil(err) 23 | should.NotNil(encoded) 24 | ioutil.WriteFile("/tmp/bloomfilter.bin", encoded, 0666) 25 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*bloom.BloomFilter)(nil)) 26 | should.Nil(err) 27 | f2 := decoded.(*bloom.BloomFilter) 28 | should.True(f2.Test([]byte("hello"))) 29 | should.False(f2.Test([]byte("hi"))) 30 | decoded, err = gocodec.Unmarshal(encoded, (*bloom.BloomFilter)(nil)) 31 | should.Nil(err) 32 | } 33 | 34 | func Test_mmap(t *testing.T) { 35 | should := require.New(t) 36 | f, err := os.Open("/tmp/bloomfilter.bin") 37 | should.Nil(err) 38 | mem, err := mmap.Map(f, mmap.COPY, 0) 39 | should.Nil(err) 40 | decoded, err := gocodec.Unmarshal(mem, (*bloom.BloomFilter)(nil)) 41 | should.Nil(err) 42 | f2 := decoded.(*bloom.BloomFilter) 43 | should.True(f2.Test([]byte("hello"))) 44 | should.False(f2.Test([]byte("hi"))) 45 | mem.Unmap() 46 | } 47 | 48 | func Test_json(t *testing.T) { 49 | should := require.New(t) 50 | f := bloom.New(1000*1024, 4) 51 | f.Add([]byte("hello")) 52 | f.Add([]byte("world")) 53 | encoded, err := jsoniter.Marshal(f) 54 | should.Nil(err) 55 | ioutil.WriteFile("/tmp/bloomfilter.json", encoded, 0666) 56 | } 57 | 58 | func Benchmark(b *testing.B) { 59 | api := gocodec.Config{}.Froze() 60 | b.Run("gocodec", func(b *testing.B) { 61 | f, _ := os.OpenFile("/tmp/bloomfilter.bin", os.O_RDONLY, 0) 62 | b.ReportAllocs() 63 | for i := 0; i < b.N; i++ { 64 | mem, _ := mmap.Map(f, mmap.COPY, 0) 65 | _, err := api.Unmarshal(mem, (*bloom.BloomFilter)(nil)) 66 | if err != nil { 67 | b.Error(err) 68 | } 69 | mem.Unmap() 70 | } 71 | }) 72 | //b.Run("json", func(b *testing.B) { 73 | // f, _ := os.Open("/tmp/bloomfilter.json") 74 | // b.ReportAllocs() 75 | // for i := 0; i < b.N; i++ { 76 | // var f2 bloom.BloomFilter 77 | // //bytes, _ := ioutil.ReadFile("/tmp/bloomfilter.bin") 78 | // mem, _ := mmap.Map(f, mmap.COPY, 0) 79 | // err := jsoniter.Unmarshal(mem, &f2) 80 | // if err != nil { 81 | // b.Error(err) 82 | // } 83 | // mem.Unmap() 84 | // } 85 | //}) 86 | } 87 | -------------------------------------------------------------------------------- /gocodec.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "unsafe" 5 | "reflect" 6 | "sync" 7 | ) 8 | 9 | type ObjectSeq uint64 10 | 11 | type Allocator interface { 12 | Allocate(ObjectSeq, []byte) []byte 13 | } 14 | 15 | type DefaultAllocator struct { 16 | } 17 | 18 | func (allocator *DefaultAllocator) Allocate(objectSeq ObjectSeq, original []byte) []byte { 19 | return append([]byte(nil), original...) 20 | } 21 | 22 | var defaultAllocator = &DefaultAllocator{} 23 | 24 | type Config struct { 25 | ReadonlyDecode bool 26 | } 27 | 28 | type API interface { 29 | Marshal(val interface{}) ([]byte, error) 30 | Unmarshal(buf []byte, candidatePointer interface{}) (interface{}, error) 31 | UnmarshalCandidates(buf []byte, candidatePointers ...interface{}) (interface{}, error) 32 | NewIterator(buf []byte) *Iterator 33 | NewStream(buf []byte) *Stream 34 | } 35 | 36 | type ValEncoder interface { 37 | Encode(ptr unsafe.Pointer, stream *Stream) 38 | Type() reflect.Type 39 | IsNoop() bool 40 | Signature() uint32 41 | } 42 | 43 | type RootEncoder interface { 44 | Type() reflect.Type 45 | Signature() uint32 46 | EncodeEmptyInterface(ptr unsafe.Pointer, stream *Stream) 47 | } 48 | 49 | type ValDecoder interface { 50 | Decode(iter *Iterator) 51 | Type() reflect.Type 52 | IsNoop() bool 53 | Signature() uint32 54 | HasPointer() bool 55 | } 56 | 57 | type RootDecoder interface { 58 | Type() reflect.Type 59 | Signature() uint32 60 | DecodeEmptyInterface(ptr *emptyInterface, iter *Iterator) 61 | } 62 | 63 | type frozenConfig struct { 64 | readonlyDecode bool 65 | allocator Allocator 66 | decoderCache *sync.Map 67 | encoderCache *sync.Map 68 | } 69 | 70 | func (cfg Config) Froze() API { 71 | api := &frozenConfig{ 72 | readonlyDecode: cfg.ReadonlyDecode, 73 | decoderCache: &sync.Map{}, 74 | encoderCache: &sync.Map{}, 75 | } 76 | return api 77 | } 78 | 79 | var DefaultConfig = Config{}.Froze() 80 | var ReadonlyConfig = Config{ReadonlyDecode: true}.Froze() 81 | 82 | func Marshal(obj interface{}) ([]byte, error) { 83 | return DefaultConfig.Marshal(obj) 84 | } 85 | 86 | func Unmarshal(buf []byte, candidatePointer interface{}) (interface{}, error) { 87 | return DefaultConfig.Unmarshal(buf, candidatePointer) 88 | } 89 | 90 | func UnmarshalCandidates(buf []byte, candidatePointers ...interface{}) (interface{}, error) { 91 | return DefaultConfig.UnmarshalCandidates(buf, candidatePointers...) 92 | } 93 | 94 | func NewIterator(buf []byte) *Iterator { 95 | return DefaultConfig.NewIterator(buf) 96 | } 97 | 98 | func NewStream(buf []byte) *Stream { 99 | return DefaultConfig.NewStream(buf) 100 | } 101 | 102 | func (cfg *frozenConfig) Marshal(val interface{}) ([]byte, error) { 103 | stream := cfg.NewStream(nil) 104 | stream.Marshal(val) 105 | return stream.Buffer(), stream.Error 106 | } 107 | 108 | func (cfg *frozenConfig) Unmarshal(buf []byte, candidatePointer interface{}) (interface{}, error) { 109 | iter := cfg.NewIterator(buf) 110 | val := iter.Unmarshal(candidatePointer) 111 | return val, iter.Error 112 | } 113 | 114 | func (cfg *frozenConfig) UnmarshalCandidates(buf []byte, candidatePointers ...interface{}) (interface{}, error) { 115 | iter := cfg.NewIterator(buf) 116 | val := iter.UnmarshalCandidates(candidatePointers...) 117 | return val, iter.Error 118 | } 119 | -------------------------------------------------------------------------------- /gocodec_array.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import "unsafe" 4 | 5 | type arrayEncoder struct { 6 | BaseCodec 7 | arrayLength int 8 | elementSize uintptr 9 | elemEncoder ValEncoder 10 | } 11 | 12 | func (encoder *arrayEncoder) Encode(prArray unsafe.Pointer, stream *Stream) { 13 | if encoder.IsNoop() { 14 | return 15 | } 16 | cursor := stream.cursor 17 | prElem := uintptr(prArray) 18 | for i := 0; i < encoder.arrayLength; i++ { 19 | stream.cursor = cursor // stream.cursor will change in the elemEncoder 20 | encoder.elemEncoder.Encode(unsafe.Pointer(prElem), stream) 21 | cursor = cursor + encoder.elementSize 22 | prElem = prElem + encoder.elementSize 23 | } 24 | } 25 | 26 | func (encoder *arrayEncoder) IsNoop() bool { 27 | return encoder.elemEncoder == nil 28 | } 29 | 30 | type arrayDecoderWithoutPointer struct { 31 | BaseCodec 32 | arrayLength int 33 | elementSize uintptr 34 | elemDecoder ValDecoder 35 | } 36 | 37 | func (decoder *arrayDecoderWithoutPointer) Decode(iter *Iterator) { 38 | if decoder.IsNoop() { 39 | return 40 | } 41 | cursor := iter.cursor 42 | for i := 0; i < decoder.arrayLength; i++ { 43 | iter.cursor = cursor // iter.cursor will change in elemDecoder 44 | decoder.elemDecoder.Decode(iter) 45 | cursor = cursor[decoder.elementSize:] 46 | } 47 | } 48 | 49 | func (decoder *arrayDecoderWithoutPointer) IsNoop() bool { 50 | return decoder.elemDecoder == nil 51 | } 52 | 53 | type arrayDecoderWithPointer struct { 54 | BaseCodec 55 | arrayLength int 56 | elementSize uintptr 57 | elemDecoder ValDecoder 58 | } 59 | 60 | func (decoder *arrayDecoderWithPointer) Decode(iter *Iterator) { 61 | if decoder.IsNoop() { 62 | return 63 | } 64 | cursor := iter.cursor 65 | self := iter.self 66 | for i := 0; i < decoder.arrayLength; i++ { 67 | iter.cursor = cursor // iter.cursor will change in elemDecoder 68 | iter.self = self 69 | decoder.elemDecoder.Decode(iter) 70 | cursor = cursor[decoder.elementSize:] 71 | self = self[decoder.elementSize:] 72 | } 73 | } 74 | 75 | func (decoder *arrayDecoderWithPointer) IsNoop() bool { 76 | return decoder.elemDecoder == nil 77 | } 78 | 79 | func (decoder *arrayDecoderWithPointer) HasPointer() bool { 80 | return true 81 | } 82 | -------------------------------------------------------------------------------- /gocodec_base.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | type BaseCodec struct { 9 | valType reflect.Type 10 | signature uint32 11 | } 12 | 13 | func newBaseCodec(valType reflect.Type, signature uint32) *BaseCodec { 14 | return &BaseCodec{valType: valType, signature: signature} 15 | } 16 | 17 | func (codec *BaseCodec) Encode(stream *Stream) { 18 | panic("not implemented") 19 | } 20 | 21 | func (codec *BaseCodec) Decode(iter *Iterator) { 22 | panic("not implemented") 23 | } 24 | 25 | func (codec *BaseCodec) Type() reflect.Type { 26 | return codec.valType 27 | } 28 | 29 | func (codec *BaseCodec) IsNoop() bool { 30 | return false 31 | } 32 | 33 | func (codec *BaseCodec) Signature() uint32 { 34 | return codec.signature 35 | } 36 | 37 | func (codec *BaseCodec) HasPointer() bool { 38 | return false 39 | } 40 | 41 | type NoopCodec struct { 42 | BaseCodec 43 | } 44 | 45 | func (codec *NoopCodec) IsNoop() bool { 46 | return true 47 | } 48 | 49 | func (codec *NoopCodec) Decode(iter *Iterator) { 50 | } 51 | 52 | func (codec *NoopCodec) Encode(ptr unsafe.Pointer, stream *Stream) { 53 | } 54 | -------------------------------------------------------------------------------- /gocodec_dispatch.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "reflect" 5 | "fmt" 6 | ) 7 | 8 | func (cfg *frozenConfig) addDecoderToCache(cacheKey reflect.Type, decoder RootDecoder) { 9 | cfg.decoderCache.Store(cacheKey, decoder) 10 | } 11 | 12 | func (cfg *frozenConfig) addEncoderToCache(cacheKey reflect.Type, encoder RootEncoder) { 13 | cfg.encoderCache.Store(cacheKey, encoder) 14 | } 15 | 16 | func (cfg *frozenConfig) getDecoderFromCache(cacheKey reflect.Type) RootDecoder { 17 | decoder, found := cfg.decoderCache.Load(cacheKey) 18 | if found { 19 | return decoder.(RootDecoder) 20 | } 21 | return nil 22 | } 23 | 24 | func (cfg *frozenConfig) getEncoderFromCache(cacheKey reflect.Type) RootEncoder { 25 | encoder, found := cfg.encoderCache.Load(cacheKey) 26 | if found { 27 | return encoder.(RootEncoder) 28 | } 29 | return nil 30 | } 31 | 32 | func encoderOfType(cfg *frozenConfig, valType reflect.Type) (RootEncoder, error) { 33 | cacheKey := valType 34 | rootEncoder := cfg.getEncoderFromCache(cacheKey) 35 | if rootEncoder != nil { 36 | return rootEncoder, nil 37 | } 38 | encoder, err := createEncoderOfType(cfg, valType) 39 | if err != nil { 40 | return nil, err 41 | } 42 | rootEncoder = wrapRootEncoder(encoder) 43 | cfg.addEncoderToCache(cacheKey, rootEncoder) 44 | return rootEncoder, err 45 | } 46 | 47 | func wrapRootEncoder(encoder ValEncoder) RootEncoder { 48 | valType := encoder.Type() 49 | valKind := valType.Kind() 50 | rootEncoder := rootEncoder{valType, encoder.Signature(), encoder} 51 | switch valKind { 52 | case reflect.Struct: 53 | if valType.NumField() == 1 && valType.Field(0).Type.Kind() == reflect.Ptr { 54 | return &singlePointerFix{rootEncoder} 55 | } 56 | case reflect.Array: 57 | if valType.Len() == 1 && valType.Elem().Kind() == reflect.Ptr { 58 | return &singlePointerFix{rootEncoder} 59 | } 60 | case reflect.Ptr: 61 | return &singlePointerFix{rootEncoder} 62 | } 63 | return &rootEncoder 64 | } 65 | 66 | func decoderOfType(cfg *frozenConfig, valType reflect.Type) (RootDecoder, error) { 67 | cacheKey := valType 68 | rootDecoder := cfg.getDecoderFromCache(cacheKey) 69 | if rootDecoder != nil { 70 | return rootDecoder, nil 71 | } 72 | decoder, err := createDecoderOfType(cfg, valType) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if cfg.readonlyDecode && decoder.HasPointer() { 77 | rootDecoder = &rootDecoderWithCopy{valType, decoder.Signature(), decoder} 78 | } else { 79 | rootDecoder = &rootDecoderWithoutCopy{valType, decoder.Signature(), decoder} 80 | } 81 | cfg.addDecoderToCache(cacheKey, rootDecoder) 82 | return rootDecoder, err 83 | } 84 | 85 | func createEncoderOfType(cfg *frozenConfig, valType reflect.Type) (ValEncoder, error) { 86 | valKind := valType.Kind() 87 | switch valKind { 88 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 89 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 90 | reflect.Uintptr, reflect.Float32, reflect.Float64: 91 | return &NoopCodec{BaseCodec: *newBaseCodec(valType, uint32(valKind))}, nil 92 | case reflect.String: 93 | return &stringCodec{BaseCodec: *newBaseCodec(valType, uint32(valKind))}, nil 94 | case reflect.Struct: 95 | signature := uint32(valKind) 96 | fields := make([]structFieldEncoder, 0, valType.NumField()) 97 | for i := 0; i < valType.NumField(); i++ { 98 | encoder, err := createEncoderOfType(cfg, valType.Field(i).Type) 99 | if err != nil { 100 | return nil, err 101 | } 102 | signature = 31*signature + encoder.Signature() 103 | if !encoder.IsNoop() { 104 | fields = append(fields, structFieldEncoder{ 105 | offset: valType.Field(i).Offset, 106 | encoder: encoder, 107 | }) 108 | } 109 | } 110 | encoder := &structEncoder{BaseCodec: *newBaseCodec(valType, signature), fields: fields} 111 | return encoder, nil 112 | case reflect.Array: 113 | signature := uint32(valKind) 114 | elemEncoder, err := createEncoderOfType(cfg, valType.Elem()) 115 | if err != nil { 116 | return nil, err 117 | } 118 | signature = 31*signature + elemEncoder.Signature() 119 | if elemEncoder.IsNoop() { 120 | elemEncoder = nil 121 | } 122 | encoder := &arrayEncoder{ 123 | BaseCodec: *newBaseCodec(valType, signature), 124 | arrayLength: valType.Len(), 125 | elementSize: valType.Elem().Size(), 126 | elemEncoder: elemEncoder, 127 | } 128 | return encoder, nil 129 | case reflect.Slice: 130 | signature := uint32(valKind) 131 | elemEncoder, err := createEncoderOfType(cfg, valType.Elem()) 132 | if err != nil { 133 | return nil, err 134 | } 135 | signature = 31*signature + elemEncoder.Signature() 136 | if elemEncoder.IsNoop() { 137 | elemEncoder = nil 138 | } 139 | return &sliceEncoder{BaseCodec: *newBaseCodec(valType, signature), 140 | elemSize: int(valType.Elem().Size()), elemEncoder: elemEncoder}, nil 141 | case reflect.Ptr: 142 | signature := uint32(valKind) 143 | elemEncoder, err := createEncoderOfType(cfg, valType.Elem()) 144 | if err != nil { 145 | return nil, err 146 | } 147 | signature = 31*signature + elemEncoder.Signature() 148 | encoder := &pointerEncoder{BaseCodec: *newBaseCodec(valType, signature), elemEncoder: elemEncoder} 149 | return encoder, nil 150 | } 151 | return nil, fmt.Errorf("unsupported type %s", valType.String()) 152 | } 153 | 154 | func createDecoderOfType(cfg *frozenConfig, valType reflect.Type) (ValDecoder, error) { 155 | valKind := valType.Kind() 156 | switch valKind { 157 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 158 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 159 | reflect.Uintptr, reflect.Float32, reflect.Float64: 160 | return &NoopCodec{BaseCodec: *newBaseCodec(valType, uint32(valKind))}, nil 161 | case reflect.String: 162 | return &stringCodec{BaseCodec: *newBaseCodec(valType, uint32(valKind))}, nil 163 | case reflect.Struct: 164 | fields := make([]structFieldDecoder, 0, valType.NumField()) 165 | signature := uint32(valKind) 166 | hasPointer := false 167 | for i := 0; i < valType.NumField(); i++ { 168 | decoder, err := createDecoderOfType(cfg, valType.Field(i).Type) 169 | if err != nil { 170 | return nil, err 171 | } 172 | if decoder.HasPointer() { 173 | hasPointer = true 174 | } 175 | signature = 31*signature + decoder.Signature() 176 | if !decoder.IsNoop() { 177 | fields = append(fields, structFieldDecoder{ 178 | offset: valType.Field(i).Offset, 179 | decoder: decoder, 180 | }) 181 | } 182 | } 183 | if hasPointer { 184 | return &structDecoderWithPointer{BaseCodec: *newBaseCodec(valType, signature), fields: fields}, nil 185 | } 186 | return &structDecoderWithoutPointer{BaseCodec: *newBaseCodec(valType, signature), fields: fields}, nil 187 | case reflect.Array: 188 | signature := uint32(valKind) 189 | elemDecoder, err := createDecoderOfType(cfg, valType.Elem()) 190 | if err != nil { 191 | return nil, err 192 | } 193 | signature = 31*signature + elemDecoder.Signature() 194 | hasPointer := elemDecoder.HasPointer() 195 | if elemDecoder.IsNoop() { 196 | elemDecoder = nil 197 | } 198 | if hasPointer { 199 | return &arrayDecoderWithPointer{ 200 | BaseCodec: *newBaseCodec(valType, signature), 201 | arrayLength: valType.Len(), 202 | elementSize: valType.Elem().Size(), 203 | elemDecoder: elemDecoder, 204 | }, nil 205 | } 206 | return &arrayDecoderWithoutPointer{ 207 | BaseCodec: *newBaseCodec(valType, signature), 208 | arrayLength: valType.Len(), 209 | elementSize: valType.Elem().Size(), 210 | elemDecoder: elemDecoder, 211 | }, nil 212 | case reflect.Slice: 213 | signature := uint32(valKind) 214 | elemDecoder, err := createDecoderOfType(cfg, valType.Elem()) 215 | if err != nil { 216 | return nil, err 217 | } 218 | signature = 31*signature + elemDecoder.Signature() 219 | shouldCopy := false 220 | if elemDecoder.HasPointer() && cfg.readonlyDecode { 221 | shouldCopy = true 222 | } 223 | if elemDecoder.IsNoop() { 224 | elemDecoder = nil 225 | } 226 | if shouldCopy { 227 | return &sliceDecoderWithCopy{BaseCodec: *newBaseCodec(valType, signature), 228 | elemSize: int(valType.Elem().Size()), elemDecoder: elemDecoder}, nil 229 | } 230 | return &sliceDecoderWithoutCopy{BaseCodec: *newBaseCodec(valType, signature), 231 | elemSize: int(valType.Elem().Size()), elemDecoder: elemDecoder}, nil 232 | case reflect.Ptr: 233 | signature := uint32(valKind) 234 | elemDecoder, err := createDecoderOfType(cfg, valType.Elem()) 235 | if err != nil { 236 | return nil, err 237 | } 238 | signature = 31*signature + elemDecoder.Signature() 239 | if elemDecoder.HasPointer() && cfg.readonlyDecode { 240 | return &pointerDecoderWithCopy{BaseCodec: *newBaseCodec(valType, signature), elemDecoder: elemDecoder}, nil 241 | } 242 | return &pointerDecoderWithoutCopy{BaseCodec: *newBaseCodec(valType, signature), elemDecoder: elemDecoder}, nil 243 | } 244 | return nil, fmt.Errorf("unsupported type %s", valType.String()) 245 | } 246 | -------------------------------------------------------------------------------- /gocodec_pointer.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "unsafe" 5 | "github.com/v2pro/plz/countlog" 6 | "errors" 7 | ) 8 | 9 | type pointerEncoder struct { 10 | BaseCodec 11 | elemEncoder ValEncoder 12 | } 13 | 14 | func (encoder *pointerEncoder) Encode(prPointer unsafe.Pointer, stream *Stream) { 15 | ptr := *(*unsafe.Pointer)(prPointer) 16 | if uintptr(ptr) == 0 { 17 | return 18 | } 19 | valAsBytes := ptrAsBytes(int(encoder.elemEncoder.Type().Size()), ptr) 20 | pwPointer := unsafe.Pointer(&stream.buf[stream.cursor]) 21 | *(*uintptr)(pwPointer) = uintptr(len(stream.buf)) - stream.cursor 22 | stream.cursor = uintptr(len(stream.buf)) 23 | stream.buf = append(stream.buf, valAsBytes...) 24 | encoder.elemEncoder.Encode(ptr, stream) 25 | } 26 | 27 | type pointerDecoderWithoutCopy struct { 28 | BaseCodec 29 | elemDecoder ValDecoder 30 | } 31 | 32 | func (decoder *pointerDecoderWithoutCopy) Decode(iter *Iterator) { 33 | if countlog.ShouldLog(countlog.LevelDebug) { 34 | defer func() { 35 | recovered := recover() 36 | countlog.LogPanic(recovered, "valueType", decoder.valType) 37 | if recovered != nil { 38 | iter.ReportError("pointerDecoder", errors.New( 39 | "decode failed at type: "+decoder.valType.String())) 40 | } 41 | }() 42 | } 43 | pPtr := unsafe.Pointer(&iter.cursor[0]) 44 | relOffset := *(*uintptr)(pPtr) 45 | if relOffset == 0 { 46 | return 47 | } 48 | iter.cursor = iter.cursor[relOffset:] 49 | *(*uintptr)(unsafe.Pointer(&iter.self[0])) = uintptr(unsafe.Pointer(&iter.cursor[0])) 50 | iter.self = iter.cursor 51 | decoder.elemDecoder.Decode(iter) 52 | } 53 | 54 | func (decoder *pointerDecoderWithoutCopy) HasPointer() bool { 55 | return true 56 | } 57 | 58 | type pointerDecoderWithCopy struct { 59 | BaseCodec 60 | elemDecoder ValDecoder 61 | } 62 | 63 | func (decoder *pointerDecoderWithCopy) Decode(iter *Iterator) { 64 | if countlog.ShouldLog(countlog.LevelDebug) { 65 | defer func() { 66 | recovered := recover() 67 | countlog.LogPanic(recovered, "valueType", decoder.valType) 68 | if recovered != nil { 69 | iter.ReportError("pointerDecoder", errors.New( 70 | "decode failed at type: "+decoder.valType.String())) 71 | } 72 | }() 73 | } 74 | pPtr := unsafe.Pointer(&iter.cursor[0]) 75 | relOffset := *(*uintptr)(pPtr) 76 | if relOffset == 0 { 77 | return 78 | } 79 | iter.cursor = iter.cursor[relOffset:] 80 | copied := iter.allocator.Allocate(iter.objectSeq, iter.cursor[:decoder.elemDecoder.Type().Size()]) 81 | *(*uintptr)(unsafe.Pointer(&iter.self[0])) = uintptr(unsafe.Pointer(&copied[0])) 82 | iter.self = copied 83 | decoder.elemDecoder.Decode(iter) 84 | } 85 | 86 | func (decoder *pointerDecoderWithCopy) HasPointer() bool { 87 | return true 88 | } 89 | -------------------------------------------------------------------------------- /gocodec_root.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "unsafe" 5 | "reflect" 6 | ) 7 | 8 | type rootEncoder struct { 9 | valType reflect.Type 10 | signature uint32 11 | encoder ValEncoder 12 | } 13 | 14 | func (encoder *rootEncoder) EncodeEmptyInterface(ptr unsafe.Pointer, stream *Stream) { 15 | stream.cursor = uintptr(len(stream.buf)) 16 | valAsSlice := ptrAsBytes(int(encoder.valType.Size()), ptr) 17 | stream.buf = append(stream.buf, valAsSlice...) 18 | encoder.encoder.Encode(ptr, stream) 19 | } 20 | 21 | func (encoder *rootEncoder) Signature() uint32 { 22 | return encoder.signature 23 | } 24 | 25 | func (encoder *rootEncoder) Type() reflect.Type { 26 | return encoder.valType 27 | } 28 | 29 | type rootDecoderWithCopy struct { 30 | valType reflect.Type 31 | signature uint32 32 | decoder ValDecoder 33 | } 34 | 35 | func (decoder *rootDecoderWithCopy) Signature() uint32 { 36 | return decoder.signature 37 | } 38 | 39 | func (decoder *rootDecoderWithCopy) Type() reflect.Type { 40 | return decoder.valType 41 | } 42 | 43 | func (decoder *rootDecoderWithCopy) DecodeEmptyInterface(ptr *emptyInterface, iter *Iterator) { 44 | iter.self = iter.allocator.Allocate(iter.objectSeq, iter.buf[8:8+decoder.Type().Size()]) 45 | ptr.word = unsafe.Pointer(&iter.self[0]) 46 | iter.cursor = iter.buf[8:] 47 | decoder.decoder.Decode(iter) 48 | } 49 | 50 | type rootDecoderWithoutCopy struct { 51 | valType reflect.Type 52 | signature uint32 53 | decoder ValDecoder 54 | } 55 | 56 | func (decoder *rootDecoderWithoutCopy) Signature() uint32 { 57 | return decoder.signature 58 | } 59 | 60 | func (decoder *rootDecoderWithoutCopy) Type() reflect.Type { 61 | return decoder.valType 62 | } 63 | 64 | func (decoder *rootDecoderWithoutCopy) DecodeEmptyInterface(ptr *emptyInterface, iter *Iterator) { 65 | ptr.word = unsafe.Pointer(&iter.buf[8]) 66 | iter.self = iter.buf[8:] 67 | iter.cursor = iter.buf[8:] 68 | decoder.decoder.Decode(iter) 69 | } 70 | -------------------------------------------------------------------------------- /gocodec_single_pointer_fix.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import "unsafe" 4 | 5 | type singlePointerFix struct { 6 | rootEncoder 7 | } 8 | 9 | func (encoder *singlePointerFix) EncodeEmptyInterface(ptr unsafe.Pointer, stream *Stream) { 10 | encoder.rootEncoder.EncodeEmptyInterface(unsafe.Pointer(&ptr), stream) 11 | } 12 | -------------------------------------------------------------------------------- /gocodec_slice.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import "unsafe" 4 | 5 | type sliceEncoder struct { 6 | BaseCodec 7 | elemSize int 8 | elemEncoder ValEncoder 9 | } 10 | 11 | func (encoder *sliceEncoder) Encode(prSlice unsafe.Pointer, stream *Stream) { 12 | rHeader := (*sliceReadonlyHeader)(prSlice) 13 | if rHeader.Len == 0 { 14 | return 15 | } 16 | pwSlice := unsafe.Pointer(&stream.buf[stream.cursor]) 17 | wHeader := (*sliceWritableHeader)(pwSlice) 18 | wHeader.Cap = rHeader.Len 19 | byteSlice := ptrAsBytes(encoder.elemSize*rHeader.Len, rHeader.Data) 20 | // replace actual pointer with relative offset 21 | wHeader.Data = uintptr(len(stream.buf)) - stream.cursor 22 | stream.cursor = uintptr(len(stream.buf)) // start of the bytes 23 | stream.buf = append(stream.buf, byteSlice...) 24 | if encoder.elemEncoder != nil { 25 | endCursor := uintptr(len(stream.buf)) // end of the bytes 26 | cursor := stream.cursor 27 | prElem := uintptr(rHeader.Data) 28 | for ; cursor < endCursor; cursor += uintptr(encoder.elemSize) { 29 | stream.cursor = cursor 30 | encoder.elemEncoder.Encode(unsafe.Pointer(prElem), stream) 31 | prElem += uintptr(encoder.elemSize) 32 | } 33 | } 34 | } 35 | 36 | type sliceDecoderWithoutCopy struct { 37 | BaseCodec 38 | elemSize int 39 | elemDecoder ValDecoder 40 | } 41 | 42 | func (decoder *sliceDecoderWithoutCopy) Decode(iter *Iterator) { 43 | pwSlice := unsafe.Pointer(&iter.self[0]) 44 | header := (*sliceWritableHeader)(pwSlice) 45 | if header.Len == 0 { 46 | return 47 | } 48 | relOffset := header.Data 49 | header.Data = uintptr(unsafe.Pointer(&iter.cursor[relOffset])) 50 | if decoder.elemDecoder != nil { 51 | cursor := iter.cursor[relOffset:] 52 | for i := 0; i < header.Len; i++ { 53 | if i > 0 { 54 | cursor = cursor[decoder.elemSize:] 55 | } 56 | iter.cursor = cursor 57 | iter.self = iter.cursor 58 | decoder.elemDecoder.Decode(iter) 59 | } 60 | } 61 | } 62 | 63 | func (decoder *sliceDecoderWithoutCopy) HasPointer() bool { 64 | return true 65 | } 66 | 67 | type sliceDecoderWithCopy struct { 68 | BaseCodec 69 | elemSize int 70 | elemDecoder ValDecoder 71 | } 72 | 73 | func (decoder *sliceDecoderWithCopy) Decode(iter *Iterator) { 74 | pwSlice := unsafe.Pointer(&iter.self[0]) 75 | header := (*sliceWritableHeader)(pwSlice) 76 | if header.Len == 0 { 77 | return 78 | } 79 | relOffset := header.Data 80 | cursor := iter.cursor[relOffset:] 81 | copied := iter.allocator.Allocate(iter.objectSeq, cursor[:decoder.elemSize*header.Len]) 82 | header.Data = uintptr(unsafe.Pointer(&copied[0])) 83 | for i := 0; i < header.Len; i++ { 84 | if i > 0 { 85 | cursor = cursor[decoder.elemSize:] 86 | copied = copied[decoder.elemSize:] 87 | } 88 | iter.cursor = cursor 89 | iter.self = copied 90 | decoder.elemDecoder.Decode(iter) 91 | } 92 | } 93 | 94 | func (decoder *sliceDecoderWithCopy) HasPointer() bool { 95 | return true 96 | } 97 | -------------------------------------------------------------------------------- /gocodec_string.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import "unsafe" 4 | 5 | type stringCodec struct { 6 | BaseCodec 7 | } 8 | 9 | func (codec *stringCodec) Encode(prStr unsafe.Pointer, stream *Stream) { 10 | pwStr := unsafe.Pointer(&stream.buf[stream.cursor]) 11 | str := *(*string)(prStr) 12 | offset := uintptr(len(stream.buf)) - stream.cursor 13 | header := (*stringWritableHeader)(pwStr) 14 | header.Data = offset 15 | stream.buf = append(stream.buf, str...) 16 | } 17 | 18 | func (codec *stringCodec) Decode(iter *Iterator) { 19 | prStr := unsafe.Pointer(&iter.cursor[0]) 20 | header := (*stringWritableHeader)(prStr) 21 | relOffset := header.Data 22 | pwStr := unsafe.Pointer(&iter.self[0]) 23 | header = (*stringWritableHeader)(pwStr) 24 | header.Data = uintptr(unsafe.Pointer(&iter.cursor[relOffset])) 25 | } 26 | 27 | func (codec *stringCodec) HasPointer() bool { 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /gocodec_struct.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import "unsafe" 4 | 5 | type structEncoder struct { 6 | BaseCodec 7 | fields []structFieldEncoder 8 | } 9 | 10 | type structFieldEncoder struct { 11 | offset uintptr 12 | encoder ValEncoder 13 | } 14 | 15 | func (encoder *structEncoder) Encode(prStruct unsafe.Pointer, stream *Stream) { 16 | baseCursor := stream.cursor 17 | prBase := uintptr(prStruct) 18 | for _, field := range encoder.fields { 19 | stream.cursor = baseCursor + field.offset 20 | field.encoder.Encode(unsafe.Pointer(prBase + field.offset), stream) 21 | } 22 | } 23 | 24 | type structDecoderWithoutPointer struct { 25 | BaseCodec 26 | fields []structFieldDecoder 27 | } 28 | 29 | type structFieldDecoder struct { 30 | offset uintptr 31 | decoder ValDecoder 32 | } 33 | 34 | func (decoder *structDecoderWithoutPointer) Decode(iter *Iterator) { 35 | baseCursor := iter.cursor 36 | for _, field := range decoder.fields { 37 | iter.cursor = baseCursor[field.offset:] 38 | field.decoder.Decode(iter) 39 | } 40 | } 41 | 42 | type structDecoderWithPointer struct { 43 | BaseCodec 44 | fields []structFieldDecoder 45 | } 46 | 47 | func (decoder *structDecoderWithPointer) Decode(iter *Iterator) { 48 | baseCursor := iter.cursor 49 | baseSelf := iter.self 50 | for _, field := range decoder.fields { 51 | iter.cursor = baseCursor[field.offset:] 52 | iter.self = baseSelf[field.offset:] 53 | field.decoder.Decode(iter) 54 | } 55 | } 56 | 57 | func (decoder *structDecoderWithPointer) HasPointer() bool { 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /level_0/array_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_array_of_int(t *testing.T) { 10 | should := require.New(t) 11 | type TestObject [2]int 12 | obj := TestObject{1, 2} 13 | encoded, err := gocodec.Marshal(obj) 14 | should.Nil(err) 15 | should.Equal([]byte{ 16 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 17 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 18 | }, encoded[8:]) 19 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 20 | should.Nil(err) 21 | should.Equal(obj, *decoded.(*TestObject)) 22 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 23 | should.Nil(err) 24 | should.Equal(obj, *decoded.(*TestObject)) 25 | } 26 | 27 | func Test_array_of_struct(t *testing.T) { 28 | should := require.New(t) 29 | type TestStruct struct { 30 | Field1 int 31 | Field2 int 32 | } 33 | type TestObject [1]TestStruct 34 | obj := TestObject{TestStruct{1, 2}} 35 | encoded, err := gocodec.Marshal(obj) 36 | should.Nil(err) 37 | should.Equal([]byte{ 38 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 39 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 40 | }, encoded[8:]) 41 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 42 | should.Nil(err) 43 | should.Equal(obj, *decoded.(*TestObject)) 44 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 45 | should.Nil(err) 46 | should.Equal(obj, *decoded.(*TestObject)) 47 | } -------------------------------------------------------------------------------- /level_0/float32_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_float32(t *testing.T) { 10 | should := require.New(t) 11 | encoded, err := gocodec.Marshal(float32(100)) 12 | should.Nil(err) 13 | should.Equal([]byte{0x0, 0x0, 0xc8, 0x42}, encoded[8:]) 14 | val, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*float32)(nil)) 15 | should.Nil(err) 16 | should.Equal(float32(100), *(val.(*float32))) 17 | val, err = gocodec.Unmarshal(encoded, (*float32)(nil)) 18 | should.Nil(err) 19 | should.Equal(float32(100), *(val.(*float32))) 20 | } 21 | -------------------------------------------------------------------------------- /level_0/float64_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_float64(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(float64(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x59, 0x40}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*float64)(nil)) 16 | should.Nil(err) 17 | should.Equal(float64(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/int16_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_int16(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(int16(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*int16)(nil)) 16 | should.Nil(err) 17 | should.Equal(int16(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/int32_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_int32(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(int32(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*int32)(nil)) 16 | should.Nil(err) 17 | should.Equal(int32(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/int64_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_int64(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(int64(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*int64)(nil)) 16 | should.Nil(err) 17 | should.Equal(int64(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/int8_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_int8(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(int8(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*int8)(nil)) 16 | should.Nil(err) 17 | should.Equal(int8(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/int_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_int(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(100) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*int)(nil)) 16 | should.Nil(err) 17 | should.Equal(int(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/struct_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_simple_struct(t *testing.T) { 10 | should := require.New(t) 11 | type TestObject struct { 12 | Field1 int 13 | Field2 int 14 | } 15 | obj := TestObject{1, 2} 16 | encoded, err := gocodec.Marshal(obj) 17 | should.Nil(err) 18 | should.Equal([]byte{ 19 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 20 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 21 | }, encoded[8:]) 22 | decoded, err := gocodec.Unmarshal(encoded, (*TestObject)(nil)) 23 | should.Nil(err) 24 | should.Equal(obj, *decoded.(*TestObject)) 25 | } 26 | 27 | func Test_struct_signature(t *testing.T) { 28 | should := require.New(t) 29 | type TestVersion1 struct { 30 | Field1 int 31 | } 32 | type TestVersion2 struct { 33 | Field1 uint 34 | Field2 uint 35 | } 36 | obj := TestVersion1{1} 37 | encoded, err := gocodec.Marshal(obj) 38 | should.Nil(err) 39 | should.Equal([]byte{ 40 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 41 | }, encoded[8:]) 42 | decoded, err := gocodec.UnmarshalCandidates(encoded, (*TestVersion2)(nil), (*TestVersion1)(nil)) 43 | should.Nil(err) 44 | should.Equal(obj, *decoded.(*TestVersion1)) 45 | } 46 | 47 | func Test_multiple_struct(t *testing.T) { 48 | should := require.New(t) 49 | type TestObj struct { 50 | Field int 51 | } 52 | stream := gocodec.NewStream(nil) 53 | stream.Marshal(TestObj{1}) 54 | stream.Marshal(TestObj{2}) 55 | should.Nil(stream.Error) 56 | iter := gocodec.NewIterator(stream.Buffer()) 57 | obj := iter.Unmarshal((*TestObj)(nil)) 58 | should.Nil(iter.Error) 59 | should.Equal(1, obj.(*TestObj).Field) 60 | obj = iter.Unmarshal((*TestObj)(nil)) 61 | should.Nil(iter.Error) 62 | should.Equal(2, obj.(*TestObj).Field) 63 | } 64 | -------------------------------------------------------------------------------- /level_0/uint16_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uint16(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uint16(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uint16)(nil)) 16 | should.Nil(err) 17 | should.Equal(uint16(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/uint32_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uint32(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uint32(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uint32)(nil)) 16 | should.Nil(err) 17 | should.Equal(uint32(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/uint64_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uint64(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uint64(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uint64)(nil)) 16 | should.Nil(err) 17 | should.Equal(uint64(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/uint8_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uint8(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uint8(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uint8)(nil)) 16 | should.Nil(err) 17 | should.Equal(uint8(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/uint_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uint(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uint(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uint)(nil)) 16 | should.Nil(err) 17 | should.Equal(uint(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_0/uintptr_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | "reflect" 8 | ) 9 | 10 | func Test_uintptr(t *testing.T) { 11 | should := require.New(t) 12 | encoded, err := gocodec.Marshal(uintptr(100)) 13 | should.Nil(err) 14 | should.Equal([]byte{100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.Unmarshal(encoded, (*uintptr)(nil)) 16 | should.Nil(err) 17 | should.Equal(uintptr(100), reflect.ValueOf(decoded).Elem().Interface()) 18 | } -------------------------------------------------------------------------------- /level_1/array_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_single_ptr_int_array(t *testing.T) { 10 | should := require.New(t) 11 | type TestObject [1]*uint8 12 | one := uint8(1) 13 | obj := TestObject{&one} 14 | encoded, err := gocodec.Marshal(obj) 15 | should.Nil(err) 16 | should.Equal([]byte{ 17 | 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 18 | 0x1, 19 | }, encoded[8:]) 20 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 21 | should.Nil(err) 22 | should.Equal(obj, *decoded.(*TestObject)) 23 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 24 | should.Nil(err) 25 | should.Equal(obj, *decoded.(*TestObject)) 26 | } 27 | 28 | func Test_two_ptrs_in_array(t *testing.T) { 29 | should := require.New(t) 30 | type TestObject [2]*uint8 31 | one := uint8(1) 32 | obj := TestObject{&one, &one} 33 | encoded, err := gocodec.Marshal(obj) 34 | should.Nil(err) 35 | should.Equal([]byte{ 36 | 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 37 | 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 38 | 1, 39 | 1, 40 | }, encoded[8:]) 41 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 42 | should.Nil(err) 43 | should.Equal(obj, *decoded.(*TestObject)) 44 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 45 | should.Nil(err) 46 | should.Equal(obj, *decoded.(*TestObject)) 47 | } -------------------------------------------------------------------------------- /level_1/byte_slice_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_byte_slice(t *testing.T) { 10 | should := require.New(t) 11 | encoded, err := gocodec.Marshal([]byte("hello")) 12 | should.Nil(err) 13 | should.Equal([]byte{ 14 | 0x18, 0, 0, 0, 0, 0, 0, 0, 15 | 5, 0, 0, 0, 0, 0, 0, 0, 16 | 5, 0, 0, 0, 0, 0, 0, 0, 17 | 'h', 'e', 'l', 'l', 'o'}, encoded[8:]) 18 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*[]byte)(nil)) 19 | should.Nil(err) 20 | should.Equal([]byte("hello"), *decoded.(*[]byte)) 21 | decoded, err = gocodec.Unmarshal(encoded, (*[]byte)(nil)) 22 | should.Nil(err) 23 | should.Equal([]byte("hello"), *decoded.(*[]byte)) 24 | } -------------------------------------------------------------------------------- /level_1/int_slice_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_int_slice(t *testing.T) { 10 | should := require.New(t) 11 | encoded, err := gocodec.Marshal([]int{1, 2, 3}) 12 | should.Nil(err) 13 | should.Equal([]byte{ 14 | 0x18, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 15 | 1, 0, 0, 0, 0, 0, 0, 0, 16 | 2, 0, 0, 0, 0, 0, 0, 0, 17 | 3, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 18 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*[]int)(nil)) 19 | should.Equal([]int{1, 2, 3}, *decoded.(*[]int)) 20 | decoded, err = gocodec.Unmarshal(encoded, (*[]int)(nil)) 21 | should.Equal([]int{1, 2, 3}, *decoded.(*[]int)) 22 | } 23 | -------------------------------------------------------------------------------- /level_1/ptr_int_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "github.com/esdb/gocodec" 6 | "testing" 7 | ) 8 | 9 | func Test_ptr_int(t *testing.T) { 10 | should := require.New(t) 11 | val := 100 12 | encoded, err := gocodec.Marshal(&val) 13 | should.Nil(err) 14 | should.Equal([]byte{0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 100, 0, 0, 0, 0, 0, 0, 0}, encoded[8:]) 15 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (**int)(nil)) 16 | should.Nil(err) 17 | should.Equal(100, **decoded.(**int)) 18 | decoded, err = gocodec.Unmarshal(encoded, (**int)(nil)) 19 | should.Nil(err) 20 | should.Equal(100, **decoded.(**int)) 21 | } 22 | -------------------------------------------------------------------------------- /level_1/string_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_string(t *testing.T) { 10 | should := require.New(t) 11 | encoded, err := gocodec.Marshal("hello") 12 | should.Nil(err) 13 | should.Equal([]byte{0x10, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 'h', 'e', 'l', 'l', 'o'}, encoded[8:]) 14 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*string)(nil)) 15 | should.Nil(err) 16 | should.Equal("hello", *decoded.(*string)) 17 | decoded, err = gocodec.Unmarshal(encoded, (*string)(nil)) 18 | should.Nil(err) 19 | should.Equal("hello", *decoded.(*string)) 20 | } -------------------------------------------------------------------------------- /level_1/struct_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | ) 8 | 9 | func Test_single_ptr_in_struct(t *testing.T) { 10 | should := require.New(t) 11 | type TestObject struct { 12 | Field1 *uint8 13 | } 14 | one := uint8(1) 15 | obj := TestObject{&one} 16 | encoded, err := gocodec.Marshal(obj) 17 | should.Nil(err) 18 | should.Equal([]byte{ 19 | 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 20 | 0x1, 21 | }, encoded[8:]) 22 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 23 | should.Nil(err) 24 | should.Equal(obj, *decoded.(*TestObject)) 25 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 26 | should.Nil(err) 27 | should.Equal(obj, *decoded.(*TestObject)) 28 | } 29 | 30 | func Test_two_ptrs_in_struct(t *testing.T) { 31 | should := require.New(t) 32 | type TestObject struct { 33 | Field1 *uint8 34 | Field2 *uint8 35 | } 36 | one := uint8(1) 37 | obj := TestObject{&one, &one} 38 | encoded, err := gocodec.Marshal(obj) 39 | should.Nil(err) 40 | should.Equal([]byte{ 41 | 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 42 | 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 43 | 1, 44 | 1, 45 | }, encoded[8:]) 46 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 47 | should.Nil(err) 48 | should.Equal(obj, *decoded.(*TestObject)) 49 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 50 | should.Nil(err) 51 | should.Equal(obj, *decoded.(*TestObject)) 52 | } 53 | 54 | func Test_slice_in_struct(t *testing.T) { 55 | should := require.New(t) 56 | type TestObject struct { 57 | Field1 uint 58 | Field2 []uint 59 | } 60 | obj := TestObject{Field1: 1, Field2: []uint{2,3}} 61 | encoded, err := gocodec.Marshal(obj) 62 | should.Nil(err) 63 | should.Equal([]byte{ 64 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 65 | 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 66 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 67 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 68 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 69 | 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 70 | }, encoded[8:]) 71 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 72 | should.Nil(err) 73 | should.Equal(obj, *decoded.(*TestObject)) 74 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 75 | should.Nil(err) 76 | should.Equal(obj, *decoded.(*TestObject)) 77 | } 78 | 79 | func Test_multiple_struct(t *testing.T) { 80 | should := require.New(t) 81 | type TestObj struct { 82 | Field []int 83 | } 84 | stream := gocodec.NewStream(nil) 85 | stream.Marshal(TestObj{[]int{1}}) 86 | stream.Marshal(TestObj{[]int{2}}) 87 | should.Nil(stream.Error) 88 | iter := gocodec.NewIterator(stream.Buffer()) 89 | obj := iter.Unmarshal((*TestObj)(nil)) 90 | should.Nil(iter.Error) 91 | should.Equal([]int{1}, obj.(*TestObj).Field) 92 | obj = iter.Unmarshal((*TestObj)(nil)) 93 | should.Nil(iter.Error) 94 | should.Equal([]int{2}, obj.(*TestObj).Field) 95 | } 96 | -------------------------------------------------------------------------------- /level_2/ptr_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_ptr_of_string(t *testing.T) { 10 | should := require.New(t) 11 | obj := "hello" 12 | encoded, err := gocodec.Marshal(&obj) 13 | should.Nil(err) 14 | should.Equal([]byte{ 15 | 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 16 | 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 17 | 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 18 | 0x68, 0x65, 0x6c, 0x6c, 0x6f, 19 | }, encoded[8:]) 20 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (**string)(nil)) 21 | should.Nil(err) 22 | should.Equal("hello", **decoded.(**string)) 23 | decoded, err = gocodec.Unmarshal(encoded, (**string)(nil)) 24 | should.Nil(err) 25 | should.Equal("hello", **decoded.(**string)) 26 | } 27 | 28 | func Test_ptr_of_slice(t *testing.T) { 29 | should := require.New(t) 30 | obj := []byte("hello") 31 | encoded, err := gocodec.Marshal(&obj) 32 | should.Nil(err) 33 | should.Equal([]byte{ 34 | 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 35 | 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 36 | 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 37 | 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 38 | 0x68, 0x65, 0x6c, 0x6c, 0x6f, 39 | }, encoded[8:]) 40 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (**[]byte)(nil)) 41 | should.Nil(err) 42 | should.Equal("hello", string(**decoded.(**[]byte))) 43 | decoded, err = gocodec.Unmarshal(encoded, (**[]byte)(nil)) 44 | should.Nil(err) 45 | should.Equal("hello", string(**decoded.(**[]byte))) 46 | } 47 | -------------------------------------------------------------------------------- /level_2/slice_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/stretchr/testify/require" 6 | "github.com/esdb/gocodec" 7 | "github.com/json-iterator/go" 8 | "encoding/json" 9 | ) 10 | 11 | func Test_string_slice(t *testing.T) { 12 | should := require.New(t) 13 | encoded, err := gocodec.Marshal([]string{"h", "i"}) 14 | should.Nil(err) 15 | should.Equal([]byte{ 16 | 0x18, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, // sliceHeader 17 | 0x20, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // string header 18 | 0x11, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // string header 19 | 'h', 'i'}, encoded[8:]) 20 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*[]string)(nil)) 21 | should.Nil(err) 22 | should.Equal([]string{"h", "i"}, *decoded.(*[]string)) 23 | decoded, err = gocodec.Unmarshal(encoded, (*[]string)(nil)) 24 | should.Nil(err) 25 | should.Equal([]string{"h", "i"}, *decoded.(*[]string)) 26 | } 27 | 28 | func Test_ptr_slice(t *testing.T) { 29 | type TestObject struct { 30 | Field1 int 31 | Field2 int 32 | } 33 | should := require.New(t) 34 | encoded, err := gocodec.Marshal([]*TestObject{{1, 2}, {3, 4}}) 35 | should.Nil(err) 36 | should.Equal([]byte{ 37 | 24, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 38 | 16, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 39 | 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, // [0] 40 | 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, // [1] 41 | }, encoded[8:]) 42 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*[]*TestObject)(nil)) 43 | should.Nil(err) 44 | should.Equal([]*TestObject{{1, 2}, {3, 4}}, *decoded.(*[]*TestObject)) 45 | decoded, err = gocodec.Unmarshal(encoded, (*[]*TestObject)(nil)) 46 | should.Nil(err) 47 | should.Equal([]*TestObject{{1, 2}, {3, 4}}, *decoded.(*[]*TestObject)) 48 | } 49 | 50 | func Benchmark_string_slice(b *testing.B) { 51 | data := []string{"hello", "world"} 52 | gocEncoded, err := gocodec.Marshal(data) 53 | if err != nil { 54 | b.Error(err) 55 | } 56 | jsonEncoded, _ := jsoniter.Marshal(data) 57 | b.Run("goc encode", func(b *testing.B) { 58 | b.ReportAllocs() 59 | stream := gocodec.DefaultConfig.NewStream(nil) 60 | for i := 0; i < b.N; i++ { 61 | stream.Reset(stream.Buffer()[:0]) 62 | stream.Marshal(data) 63 | } 64 | }) 65 | b.Run("goc decode", func(b *testing.B) { 66 | b.ReportAllocs() 67 | iter := gocodec.DefaultConfig.NewIterator(gocEncoded) 68 | for i := 0; i < b.N; i++ { 69 | iter.Reset(append(([]byte)(nil), gocEncoded...)) 70 | iter.Unmarshal(&data) 71 | } 72 | }) 73 | b.Run("json encode", func(b *testing.B) { 74 | b.ReportAllocs() 75 | jsonEncoder := jsoniter.ConfigFastest.BorrowStream(nil) 76 | for i := 0; i < b.N; i++ { 77 | jsonEncoder.Reset(nil) 78 | jsonEncoder.WriteVal(data) 79 | } 80 | }) 81 | b.Run("json decode", func(b *testing.B) { 82 | b.ReportAllocs() 83 | jsonDecoder := jsoniter.ConfigFastest.BorrowIterator(nil) 84 | for i := 0; i < b.N; i++ { 85 | jsonDecoder.ResetBytes(jsonEncoded) 86 | jsonDecoder.ReadVal(&data) 87 | } 88 | }) 89 | b.Run("encoding/json decode", func(b *testing.B) { 90 | b.ReportAllocs() 91 | for i := 0; i < b.N; i++ { 92 | json.Unmarshal(jsonEncoded, &data) 93 | } 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /level_2/struct_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | "github.com/esdb/gocodec" 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_nil_struct_within_struct(t *testing.T) { 10 | should := require.New(t) 11 | type SubObject struct { 12 | length uint 13 | set []uint64 14 | } 15 | type TestObject struct { 16 | f1 uint 17 | f2 uint 18 | f3 *SubObject 19 | } 20 | obj := TestObject{} 21 | encoded, err := gocodec.Marshal(obj) 22 | should.Nil(err) 23 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 24 | should.Nil(err) 25 | should.Equal(obj, *decoded.(*TestObject)) 26 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 27 | should.Nil(err) 28 | should.Equal(obj, *decoded.(*TestObject)) 29 | } 30 | 31 | func Test_struct_within_struct(t *testing.T) { 32 | should := require.New(t) 33 | type SubObject struct { 34 | length uint 35 | set []uint64 36 | } 37 | type TestObject struct { 38 | f1 uint 39 | f2 uint 40 | f3 *SubObject 41 | } 42 | obj := TestObject{f1: 1, f2: 2, f3: &SubObject{length: 3, set: []uint64{100}}} 43 | encoded, err := gocodec.Marshal(obj) 44 | should.Nil(err) 45 | should.Equal([]byte{ 46 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 47 | 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 48 | 8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 49 | 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 50 | 24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 51 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 52 | 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 53 | 0x64, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 54 | }, encoded[8:]) 55 | decoded, err := gocodec.ReadonlyConfig.Unmarshal(encoded, (*TestObject)(nil)) 56 | should.Nil(err) 57 | should.Equal(obj, *decoded.(*TestObject)) 58 | decoded, err = gocodec.Unmarshal(encoded, (*TestObject)(nil)) 59 | should.Nil(err) 60 | should.Equal(obj, *decoded.(*TestObject)) 61 | } 62 | 63 | func Test_multiple_struct(t *testing.T) { 64 | should := require.New(t) 65 | type TestObj struct { 66 | Field1 []int 67 | Field2 [][]byte 68 | } 69 | stream := gocodec.NewStream(nil) 70 | stream.Marshal(TestObj{Field2: [][]byte{[]byte("hello")}}) 71 | stream.Marshal(TestObj{Field2: [][]byte{[]byte("world")}}) 72 | should.Nil(stream.Error) 73 | iter := gocodec.NewIterator(stream.Buffer()) 74 | obj := iter.Unmarshal((*TestObj)(nil)) 75 | should.Nil(iter.Error) 76 | should.Equal([][]byte{[]byte("hello")}, obj.(*TestObj).Field2) 77 | obj = iter.Unmarshal((*TestObj)(nil)) 78 | should.Nil(iter.Error) 79 | should.Equal([][]byte{[]byte("world")}, obj.(*TestObj).Field2) 80 | } 81 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -coverprofile=profile.out $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /unsafe.go: -------------------------------------------------------------------------------- 1 | package gocodec 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // emptyInterface is the header for an interface{} value. 8 | type emptyInterface struct { 9 | typ unsafe.Pointer 10 | word unsafe.Pointer 11 | } 12 | 13 | type stringWritableHeader struct { 14 | Data uintptr 15 | Len int 16 | } 17 | 18 | type sliceReadonlyHeader struct { 19 | Data unsafe.Pointer 20 | Len int 21 | Cap int 22 | } 23 | 24 | type sliceWritableHeader struct { 25 | Data uintptr 26 | Len int 27 | Cap int 28 | } 29 | 30 | func ptrOfEmptyInterface(obj interface{}) unsafe.Pointer { 31 | return unsafe.Pointer((*emptyInterface)(unsafe.Pointer(&obj)).word) 32 | } 33 | 34 | func ptrAsBytes(size int, ptr unsafe.Pointer) []byte { 35 | valAsSlice := *(*[]byte)((unsafe.Pointer)(&sliceReadonlyHeader{ 36 | Data: ptr, Len: size, Cap: size})) 37 | return valAsSlice 38 | } 39 | --------------------------------------------------------------------------------