Skip to content

Commit

Permalink
feat: add deep/copy
Browse files Browse the repository at this point in the history
  • Loading branch information
delichik committed Aug 12, 2024
1 parent 952699d commit 9863c8f
Show file tree
Hide file tree
Showing 2 changed files with 293 additions and 0 deletions.
165 changes: 165 additions & 0 deletions deep/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package deep

import (
"reflect"
)

func Copy[T any](src, dst *T) {
srv := reflect.ValueOf(src)
srv = srv.Elem()
drv := reflect.ValueOf(dst)
drv = drv.Elem()
h := copyHandler{addrMap: map[uint64]reflect.Value{}}
h.handle(srv, drv)
clear(h.addrMap)
}

type copyHandler struct {
addrMap map[uint64]reflect.Value
}

func (copyHandler) genKey(src reflect.Value) uint64 {
switch src.Kind() {
case reflect.Pointer, reflect.Chan, reflect.Map, reflect.UnsafePointer, reflect.Func, reflect.Slice:
return uint64(src.Pointer())<<6 + uint64(src.Kind())<<1
case reflect.Struct, reflect.Interface:
return uint64(src.UnsafeAddr())<<6 + uint64(src.Kind())<<1 + 1
default:
return 0
}
}

func (h copyHandler) handle(src, dst reflect.Value) {
switch src.Kind() {
case reflect.Struct:
h.handleStruct(src, dst)
case reflect.Interface:
h.handleInterface(src, dst)
case reflect.Pointer:
h.handlePointer(src, dst)
case reflect.Array:
h.handleArray(src, dst)
case reflect.Slice:
h.handleSlice(src, dst)
case reflect.Chan:
dst.Set(reflect.MakeChan(src.Type(), src.Cap()))
case reflect.Map:
dst.Set(reflect.MakeMap(src.Type()))
h.handleMap(src, dst)
default:
dst.Set(src)
}
}

func (h copyHandler) handlePointer(src, dst reflect.Value) {
src = src.Elem()
addr := h.genKey(src)
ndst, ok := h.addrMap[addr]
if !ok {
ndst = reflect.New(src.Type()).Elem()
if addr > 0 {
h.addrMap[addr] = ndst
}
h.handle(src, ndst)
}
dst.Set(ndst.Addr())
}

func (h copyHandler) handleStruct(src, dst reflect.Value) {
srcAddr := h.genKey(src)
if srcAddr > 0 {
h.addrMap[srcAddr] = dst
}
for i := 0; i < src.NumField(); i++ {
srcf := src.Field(i)
addr := h.genKey(srcf)
dstf, ok := h.addrMap[addr]
if !ok {
ndstf := dst.Field(i)
if addr > 0 {
h.addrMap[addr] = ndstf
}
h.handle(srcf, ndstf)
} else {
dst.Field(i).Set(dstf)
}
}
}

func (h copyHandler) handleInterface(src, dst reflect.Value) {
src = src.Elem()
h.handle(src, dst)
}

func (h copyHandler) handleArray(src, dst reflect.Value) {
for i := 0; i < src.Len(); i++ {
srcf := src.Index(i)
addr := h.genKey(srcf)
dstf, ok := h.addrMap[addr]
if !ok {
ndstf := dst.Index(i)
if addr > 0 {
h.addrMap[addr] = ndstf
}
h.handle(srcf, ndstf)
} else {
dst.Index(i).Set(dstf)
}
}
}

func (h copyHandler) handleSlice(src, dst reflect.Value) {
srcAddr := h.genKey(src)
if srcAddr > 0 {
h.addrMap[srcAddr] = dst
}
dst.Grow(src.Len() - dst.Len())
dst.SetLen(src.Len())
for i := 0; i < src.Len(); i++ {
srcf := src.Index(i)
addr := h.genKey(srcf)
dstf, ok := h.addrMap[addr]
if !ok {
ndstf := dst.Index(i)
if addr > 0 {
h.addrMap[addr] = ndstf
}
h.handle(srcf, ndstf)
} else {
dst.Index(i).Set(dstf)
}
}
}

func (h copyHandler) handleMap(src, dst reflect.Value) {
srcAddr := h.genKey(src)
if srcAddr > 0 {
h.addrMap[srcAddr] = dst
}
iter := src.MapRange()
for iter.Next() {
k := iter.Key()
v := iter.Value()

kAddr := h.genKey(k)
kdst, ok := h.addrMap[kAddr]
if !ok {
kdst = reflect.New(k.Type()).Elem()
if kAddr > 0 {
h.addrMap[kAddr] = kdst
}
h.handle(k, kdst)
}

vAddr := h.genKey(v)
vdst, ok := h.addrMap[vAddr]
if !ok {
vdst = reflect.New(v.Type()).Elem()
if vAddr > 0 {
h.addrMap[vAddr] = vdst
}
h.handle(v, vdst)
}
dst.SetMapIndex(kdst, vdst)
}
}
128 changes: 128 additions & 0 deletions deep/copy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package deep

import (
"encoding/json"
"testing"

"github.com/bytedance/sonic"
)

type _T1 struct {
T1V int `json:"t_1_v"`
T1P2 *_T2 `json:"t_1_p_2"`
T1P3 any `json:"t_1_p_3"`
T1P4 [3]int `json:"t_1_p_4"`
T1P5 [3]int `json:"t_1_p_5"`
T1P6 *_T1 `json:"t_1_p_6"`
T1P7 []int `json:"t_1_p_7"`
T1P8 []int `json:"t_1_p_8"`
T1P9 []*_T2 `json:"t_1_p_9"`
T1P10 map[string]int `json:"t_1_p_10"`
T1P11 map[string]int `json:"t_1_p_11"`
T1P12 []int `json:"t_1_p_12"`
}

type _T2 struct {
T2P1 *_T1 `json:"t_2_p_1"`
T2V int `json:"t_2_v"`
}

func _assert(t *testing.T, a bool) {
if !a {
t.Fail()
panic("deepcopy failed")
}
}

func newSrc() *_T1 {
src := &_T1{
T1V: 1,
T1P2: nil,
T1P3: nil,
}
src.T1P2 = &_T2{
T2P1: src,
T2V: 2,
}
src.T1P3 = src.T1P2
src.T1P4 = [3]int{1, 2, 3}
src.T1P5 = src.T1P4
src.T1P6 = src
src.T1P7 = []int{1, 2, 3}
src.T1P8 = src.T1P7
src.T1P9 = []*_T2{src.T1P2}
src.T1P10 = map[string]int{"a": 1, "b": 2}
src.T1P11 = src.T1P10
src.T1P12 = src.T1P4[:]
return src
}

func Test_deep_copy(t *testing.T) {
src := newSrc()
dst := &_T1{}
Copy(src, dst)
_assert(t, dst.T1V == src.T1V)
_assert(t, dst.T1P2.T2V == src.T1P2.T2V)
_assert(t, dst.T1P2.T2P1.T1V == src.T1P2.T2P1.T1V)
_assert(t, dst.T1P3.(*_T2).T2V == src.T1P3.(*_T2).T2V)
_assert(t, dst.T1P3.(*_T2).T2P1.T1V == src.T1P3.(*_T2).T2P1.T1V)
_assert(t, dst.T1P4[0] == src.T1P4[0])
_assert(t, dst.T1P4[1] == src.T1P4[1])
_assert(t, dst.T1P4[2] == src.T1P4[2])
_assert(t, dst.T1P5[0] == src.T1P5[0])
_assert(t, dst.T1P5[1] == src.T1P5[1])
_assert(t, dst.T1P5[2] == src.T1P5[2])
_assert(t, dst.T1P7[0] == src.T1P7[0])
_assert(t, dst.T1P7[1] == src.T1P7[1])
_assert(t, dst.T1P7[2] == src.T1P7[2])
_assert(t, dst.T1P8[0] == src.T1P8[0])
_assert(t, dst.T1P8[1] == src.T1P8[1])
_assert(t, dst.T1P8[2] == src.T1P8[2])
_assert(t, dst.T1P10["a"] == src.T1P10["a"])
_assert(t, dst.T1P10["b"] == src.T1P10["b"])
_assert(t, dst.T1P11["a"] == src.T1P11["a"])
_assert(t, dst.T1P11["b"] == src.T1P11["b"])

dst.T1V = 3
_assert(t, dst.T1V == dst.T1P2.T2P1.T1V)
_assert(t, dst.T1V == dst.T1P3.(*_T2).T2P1.T1V)
dst.T1P4[1] = 6
_assert(t, dst.T1P5[0] == dst.T1P4[0])
_assert(t, dst.T1P5[1] != dst.T1P4[1])
_assert(t, dst.T1P5[2] == dst.T1P4[2])
_assert(t, dst.T1P12[1] != dst.T1P4[1])
dst.T1P7[1] = 6
_assert(t, dst.T1P7[0] == dst.T1P8[0])
_assert(t, dst.T1P7[1] == dst.T1P8[1])
_assert(t, dst.T1P7[2] == dst.T1P8[2])

dst.T1P10["b"] = 3
_assert(t, dst.T1P11["a"] == 1)
_assert(t, dst.T1P11["b"] == 3)
}

func Benchmark_copy_json(b *testing.B) {
src := newSrc()
for i := 0; i < b.N; i++ {
dst := &_T1{}
mid, _ := json.Marshal(src)
_ = json.Unmarshal(mid, dst)
}
}

func Benchmark_copy_sonic(b *testing.B) {
src := newSrc()
for i := 0; i < b.N; i++ {
dst := &_T1{}
mid, _ := sonic.Marshal(src)
_ = sonic.Unmarshal(mid, dst)
}
}

func Benchmark_copy_deep(b *testing.B) {
src := newSrc()
for i := 0; i < b.N; i++ {
dst := &_T1{}
Copy(src, dst)
}
}

0 comments on commit 9863c8f

Please sign in to comment.