Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [[unpublished]](https://github.com/mlange-42/ark/compare/v0.6.4...v0.6.5)

### Features

- Provides binary serialization and de-serialization of entities for networking (#453)

## [[v0.6.4]](https://github.com/mlange-42/ark/compare/v0.6.3...v0.6.4)

### Bugfixes
Expand Down
30 changes: 30 additions & 0 deletions ecs/entity.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ecs

import (
"encoding/binary"
"encoding/json"
"fmt"
"reflect"
)

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

// MarshalBinary returns a binary representation of the entity, for serialization and networking purposes.
func (e *Entity) MarshalBinary() (data []byte, err error) {
buf := make([]byte, 8)
binary.BigEndian.PutUint32(buf[0:4], uint32(e.id))
binary.BigEndian.PutUint32(buf[4:8], e.gen)
return buf, nil
}

// AppendBinary appends the binary representation of the entity to the given slice,
// for serialization and networking purposes.
func (e *Entity) AppendBinary(buf []byte) ([]byte, error) {
buf = binary.BigEndian.AppendUint32(buf, uint32(e.id))
buf = binary.BigEndian.AppendUint32(buf, e.gen)
return buf, nil
}

// UnmarshalBinary into an entity.
//
// For serialization and networking purposes only. Do not use this to create entities!
func (e *Entity) UnmarshalBinary(data []byte) error {
if len(data) != 8 {
return fmt.Errorf("invalid data length: expected 8 bytes, got %d", len(data))
}
e.id = entityID(binary.BigEndian.Uint32(data[0:4]))
e.gen = binary.BigEndian.Uint32(data[4:8])
return nil
}

// entityIndex denotes an entity's location by table and row index.
type entityIndex struct {
table tableID
Expand Down
112 changes: 106 additions & 6 deletions ecs/entity_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package ecs

import (
"encoding"
"encoding/json"
"runtime"
"testing"
)

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

var _ json.Marshaler = &e
var _ json.Unmarshaler = &e

jsonData, err := json.Marshal(&e)
if err != nil {
t.Fatal(err)
}
expectNil(t, err)

e2 := Entity{}
err = json.Unmarshal(jsonData, &e2)
if err != nil {
t.Fatal(err)
}
expectNil(t, err)

expectEqual(t, e2, e)

err = e2.UnmarshalJSON([]byte("pft"))
expectNotNil(t, err)
}

func TestEntityMarshalBinary(t *testing.T) {
e := Entity{2, 3}

var _ encoding.BinaryMarshaler = &e
var _ encoding.BinaryUnmarshaler = &e
var _ encoding.BinaryAppender = &e

binData, err := e.MarshalBinary()
expectNil(t, err)

e2 := Entity{}
err = e2.UnmarshalBinary(binData)
expectNil(t, err)
expectEqual(t, 8, len(binData))

expectEqual(t, e2, e)

err = e2.UnmarshalBinary(make([]byte, 9))
expectNotNil(t, err)

e = Entity{4, 5}
binData, err = e.AppendBinary(binData)
expectNil(t, err)
expectEqual(t, 16, len(binData))

err = e2.UnmarshalBinary(binData[8:])
expectNil(t, err)
expectEqual(t, e2, e)

err = e2.UnmarshalBinary(binData[:8])
expectNil(t, err)
expectEqual(t, e2, Entity{2, 3})
}

func BenchmarkEntityMarshalBinary_1000(b *testing.B) {
w := NewWorld()

entities := make([]Entity, 0, 1000)
w.NewEntities(1000, func(e Entity) {
entities = append(entities, e)
})

var binData []byte
loop := func() {
for _, e := range entities {
binData, _ = e.MarshalBinary()
}
}
for b.Loop() {
loop()
}

runtime.KeepAlive(binData)
}

func BenchmarkEntityAppendBinary_1000(b *testing.B) {
w := NewWorld()

entities := make([]Entity, 0, 1000)
w.NewEntities(1000, func(e Entity) {
entities = append(entities, e)
})

binData := make([]byte, 0, 8000)
loop := func() {
binData = binData[:0]
for _, e := range entities {
binData, _ = e.AppendBinary(binData)
}
}
for b.Loop() {
loop()
}

runtime.KeepAlive(binData)
}

func BenchmarkEntityUnmarshalBinary_1000(b *testing.B) {
w := NewWorld()

entities := make([][]byte, 0, 1000)
w.NewEntities(1000, func(e Entity) {
binData, _ := e.MarshalBinary()
entities = append(entities, binData)
})

var entity Entity
loop := func() {
for _, e := range entities {
_ = entity.UnmarshalBinary(e)
}
}
for b.Loop() {
loop()
}

runtime.KeepAlive(entity)
}
Loading