Skip to content

Commit fac626c

Browse files
authored
Binary entity (de)-serialization (#453)
1 parent fbf486d commit fac626c

File tree

3 files changed

+142
-6
lines changed

3 files changed

+142
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [[unpublished]](https://github.com/mlange-42/ark/compare/v0.6.4...v0.6.5)
4+
5+
### Features
6+
7+
- Provides binary serialization and de-serialization of entities for networking (#453)
8+
39
## [[v0.6.4]](https://github.com/mlange-42/ark/compare/v0.6.3...v0.6.4)
410

511
### Bugfixes

ecs/entity.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ecs
22

33
import (
4+
"encoding/binary"
45
"encoding/json"
6+
"fmt"
57
"reflect"
68
)
79

@@ -82,6 +84,34 @@ func (e *Entity) UnmarshalJSON(data []byte) error {
8284
return nil
8385
}
8486

87+
// MarshalBinary returns a binary representation of the entity, for serialization and networking purposes.
88+
func (e *Entity) MarshalBinary() (data []byte, err error) {
89+
buf := make([]byte, 8)
90+
binary.BigEndian.PutUint32(buf[0:4], uint32(e.id))
91+
binary.BigEndian.PutUint32(buf[4:8], e.gen)
92+
return buf, nil
93+
}
94+
95+
// AppendBinary appends the binary representation of the entity to the given slice,
96+
// for serialization and networking purposes.
97+
func (e *Entity) AppendBinary(buf []byte) ([]byte, error) {
98+
buf = binary.BigEndian.AppendUint32(buf, uint32(e.id))
99+
buf = binary.BigEndian.AppendUint32(buf, e.gen)
100+
return buf, nil
101+
}
102+
103+
// UnmarshalBinary into an entity.
104+
//
105+
// For serialization and networking purposes only. Do not use this to create entities!
106+
func (e *Entity) UnmarshalBinary(data []byte) error {
107+
if len(data) != 8 {
108+
return fmt.Errorf("invalid data length: expected 8 bytes, got %d", len(data))
109+
}
110+
e.id = entityID(binary.BigEndian.Uint32(data[0:4]))
111+
e.gen = binary.BigEndian.Uint32(data[4:8])
112+
return nil
113+
}
114+
85115
// entityIndex denotes an entity's location by table and row index.
86116
type entityIndex struct {
87117
table tableID

ecs/entity_test.go

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ecs
22

33
import (
4+
"encoding"
45
"encoding/json"
6+
"runtime"
57
"testing"
68
)
79

@@ -34,19 +36,117 @@ func TestReservedEntities(t *testing.T) {
3436
func TestEntityMarshal(t *testing.T) {
3537
e := Entity{2, 3}
3638

39+
var _ json.Marshaler = &e
40+
var _ json.Unmarshaler = &e
41+
3742
jsonData, err := json.Marshal(&e)
38-
if err != nil {
39-
t.Fatal(err)
40-
}
43+
expectNil(t, err)
4144

4245
e2 := Entity{}
4346
err = json.Unmarshal(jsonData, &e2)
44-
if err != nil {
45-
t.Fatal(err)
46-
}
47+
expectNil(t, err)
4748

4849
expectEqual(t, e2, e)
4950

5051
err = e2.UnmarshalJSON([]byte("pft"))
5152
expectNotNil(t, err)
5253
}
54+
55+
func TestEntityMarshalBinary(t *testing.T) {
56+
e := Entity{2, 3}
57+
58+
var _ encoding.BinaryMarshaler = &e
59+
var _ encoding.BinaryUnmarshaler = &e
60+
var _ encoding.BinaryAppender = &e
61+
62+
binData, err := e.MarshalBinary()
63+
expectNil(t, err)
64+
65+
e2 := Entity{}
66+
err = e2.UnmarshalBinary(binData)
67+
expectNil(t, err)
68+
expectEqual(t, 8, len(binData))
69+
70+
expectEqual(t, e2, e)
71+
72+
err = e2.UnmarshalBinary(make([]byte, 9))
73+
expectNotNil(t, err)
74+
75+
e = Entity{4, 5}
76+
binData, err = e.AppendBinary(binData)
77+
expectNil(t, err)
78+
expectEqual(t, 16, len(binData))
79+
80+
err = e2.UnmarshalBinary(binData[8:])
81+
expectNil(t, err)
82+
expectEqual(t, e2, e)
83+
84+
err = e2.UnmarshalBinary(binData[:8])
85+
expectNil(t, err)
86+
expectEqual(t, e2, Entity{2, 3})
87+
}
88+
89+
func BenchmarkEntityMarshalBinary_1000(b *testing.B) {
90+
w := NewWorld()
91+
92+
entities := make([]Entity, 0, 1000)
93+
w.NewEntities(1000, func(e Entity) {
94+
entities = append(entities, e)
95+
})
96+
97+
var binData []byte
98+
loop := func() {
99+
for _, e := range entities {
100+
binData, _ = e.MarshalBinary()
101+
}
102+
}
103+
for b.Loop() {
104+
loop()
105+
}
106+
107+
runtime.KeepAlive(binData)
108+
}
109+
110+
func BenchmarkEntityAppendBinary_1000(b *testing.B) {
111+
w := NewWorld()
112+
113+
entities := make([]Entity, 0, 1000)
114+
w.NewEntities(1000, func(e Entity) {
115+
entities = append(entities, e)
116+
})
117+
118+
binData := make([]byte, 0, 8000)
119+
loop := func() {
120+
binData = binData[:0]
121+
for _, e := range entities {
122+
binData, _ = e.AppendBinary(binData)
123+
}
124+
}
125+
for b.Loop() {
126+
loop()
127+
}
128+
129+
runtime.KeepAlive(binData)
130+
}
131+
132+
func BenchmarkEntityUnmarshalBinary_1000(b *testing.B) {
133+
w := NewWorld()
134+
135+
entities := make([][]byte, 0, 1000)
136+
w.NewEntities(1000, func(e Entity) {
137+
binData, _ := e.MarshalBinary()
138+
entities = append(entities, binData)
139+
})
140+
141+
var entity Entity
142+
loop := func() {
143+
for _, e := range entities {
144+
_ = entity.UnmarshalBinary(e)
145+
}
146+
}
147+
for b.Loop() {
148+
loop()
149+
}
150+
151+
runtime.KeepAlive(entity)
152+
}

0 commit comments

Comments
 (0)