• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlange-42 / ark-serde / 14020213379

23 Mar 2025 03:04PM CUT coverage: 96.185% (+0.2%) from 96.023%
14020213379

Pull #17

github

web-flow
Merge 2843cd0d1 into cdaf785e5
Pull Request #17: Use encoder and decoder to prepare formats

22 of 22 new or added lines in 2 files covered. (100.0%)

353 of 367 relevant lines covered (96.19%)

42.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.86
/serialize.go
1
package arkserde
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "reflect"
7
        "slices"
8
        "strings"
9

10
        "github.com/goccy/go-json"
11
        "github.com/mlange-42/ark/ecs"
12
)
13

14
// Serialize an Ark [ecs.World] to JSON.
15
//
16
// Serializes the following:
17
//   - Entities and the entity pool
18
//   - All components of all entities
19
//   - All resources
20
//
21
// All components and resources must be "JSON-able" with [encoding/json].
22
//
23
// The options can be used to skip some or all components,
24
// entities entirely, and/or some or all resources.
25
func Serialize(world *ecs.World, options ...Option) ([]byte, error) {
14✔
26
        opts := newSerdeOptions(options...)
14✔
27

14✔
28
        builder := strings.Builder{}
14✔
29

14✔
30
        builder.WriteString("{\n")
14✔
31

14✔
32
        if err := serializeWorld(world, &builder, &opts); err != nil {
14✔
33
                return nil, err
×
34
        }
×
35
        if !opts.skipEntities {
27✔
36
                builder.WriteString(",\n")
13✔
37
        }
13✔
38

39
        serializeTypes(world, &builder, &opts)
14✔
40
        builder.WriteString(",\n")
14✔
41

14✔
42
        if err := serializeComponents(world, &builder, &opts); err != nil {
14✔
43
                return nil, err
×
44
        }
×
45
        builder.WriteString(",\n")
14✔
46

14✔
47
        if err := serializeResources(world, &builder, &opts); err != nil {
14✔
48
                return nil, err
×
49
        }
×
50
        builder.WriteString("}\n")
14✔
51

14✔
52
        return []byte(builder.String()), nil
14✔
53
}
54

55
func serializeWorld(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
14✔
56
        if opts.skipEntities {
15✔
57
                return nil
1✔
58
        }
1✔
59

60
        entities := world.Unsafe().DumpEntities()
13✔
61

13✔
62
        buffer := bytes.NewBuffer(nil)
13✔
63
        encoder := json.NewEncoder(buffer)
13✔
64

13✔
65
        err := encoder.Encode(entities)
13✔
66
        if err != nil {
13✔
67
                return err
×
68
        }
×
69
        builder.WriteString(fmt.Sprintf("\"World\" : %s", string(buffer.Bytes())))
13✔
70
        return nil
13✔
71
}
72

73
func serializeTypes(world *ecs.World, builder *strings.Builder, opts *serdeOptions) {
14✔
74
        if opts.skipEntities || opts.skipAllComponents {
16✔
75
                builder.WriteString("\"Types\" : []")
2✔
76
                return
2✔
77
        }
2✔
78

79
        builder.WriteString("\"Types\" : [\n")
12✔
80

12✔
81
        types := map[ecs.ID]reflect.Type{}
12✔
82

12✔
83
        allComps := ecs.ComponentIDs(world)
12✔
84
        for _, id := range allComps {
44✔
85
                if info, ok := ecs.ComponentInfo(world, id); ok {
64✔
86
                        if !slices.Contains(opts.skipComponents, info.Type) {
63✔
87
                                types[id] = info.Type
31✔
88
                        }
31✔
89
                }
90
        }
91
        maxComp := len(types) - 1
12✔
92
        counter := 0
12✔
93
        for _, tp := range types {
43✔
94
                builder.WriteString(fmt.Sprintf("  \"%s\"", tp.String()))
31✔
95
                if counter < maxComp {
50✔
96
                        builder.WriteString(",")
19✔
97
                }
19✔
98
                builder.WriteString("\n")
31✔
99
                counter++
31✔
100
        }
101

102
        builder.WriteString("]")
12✔
103
}
104

105
func serializeComponents(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
14✔
106
        if opts.skipEntities {
15✔
107
                builder.WriteString("\"Components\" : []")
1✔
108
                return nil
1✔
109
        }
1✔
110

111
        skipComponents := bitMask{}
13✔
112
        for _, tp := range opts.skipComponents {
14✔
113
                id := ecs.TypeID(world, tp)
1✔
114
                skipComponents.Set(id, true)
1✔
115
        }
1✔
116

117
        builder.WriteString("\"Components\" : [\n")
13✔
118

13✔
119
        query := ecs.NewUnsafeFilter(world).Query()
13✔
120
        lastEntity := query.Count() - 1
13✔
121
        counter := 0
13✔
122
        tempIDs := []ecs.ID{}
13✔
123
        for query.Next() {
109✔
124
                if opts.skipAllComponents {
99✔
125
                        builder.WriteString("  {")
3✔
126
                } else {
96✔
127
                        builder.WriteString("  {\n")
93✔
128

93✔
129
                        ids := query.IDs()
93✔
130

93✔
131
                        tempIDs = tempIDs[:0]
93✔
132
                        for i := range ids.Len() {
198✔
133
                                id := ids.Get(i)
105✔
134
                                if !skipComponents.Get(id) {
208✔
135
                                        tempIDs = append(tempIDs, id)
103✔
136
                                }
103✔
137
                        }
138
                        last := len(tempIDs) - 1
93✔
139

93✔
140
                        for i, id := range tempIDs {
196✔
141
                                info, _ := ecs.ComponentInfo(world, id)
103✔
142

103✔
143
                                if info.IsRelation {
105✔
144
                                        target := query.GetRelation(id)
2✔
145
                                        eJSON, err := target.MarshalJSON()
2✔
146
                                        if err != nil {
2✔
147
                                                return err
×
148
                                        }
×
149
                                        builder.WriteString(fmt.Sprintf("    \"%s%s\" : %s,\n", info.Type.String(), targetTag, eJSON))
2✔
150
                                }
151

152
                                comp := query.Get(id)
103✔
153
                                value := reflect.NewAt(info.Type, comp).Interface()
103✔
154
                                jsonData, err := json.Marshal(value)
103✔
155
                                if err != nil {
103✔
156
                                        return err
×
157
                                }
×
158
                                builder.WriteString(fmt.Sprintf("    \"%s\" : ", info.Type.String()))
103✔
159
                                builder.WriteString(string(jsonData))
103✔
160
                                if i < last {
123✔
161
                                        builder.WriteString(",")
20✔
162
                                }
20✔
163
                                builder.WriteString("\n")
103✔
164
                        }
165
                }
166
                builder.WriteString("  }")
96✔
167
                if counter < lastEntity {
179✔
168
                        builder.WriteString(",")
83✔
169
                }
83✔
170
                builder.WriteString("\n")
96✔
171

96✔
172
                counter++
96✔
173
        }
174
        builder.WriteString("]")
13✔
175

13✔
176
        return nil
13✔
177
}
178

179
func serializeResources(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
14✔
180
        if opts.skipAllResources {
15✔
181
                builder.WriteString("\"Resources\" : {}")
1✔
182
                return nil
1✔
183
        }
1✔
184

185
        builder.WriteString("\"Resources\" : {\n")
13✔
186

13✔
187
        resTypes := map[ecs.ResID]reflect.Type{}
13✔
188
        allRes := ecs.ResourceIDs(world)
13✔
189
        for _, id := range allRes {
33✔
190
                if tp, ok := ecs.ResourceType(world, id); ok {
40✔
191
                        if !slices.Contains(opts.skipResources, tp) {
39✔
192
                                resTypes[id] = tp
19✔
193
                        }
19✔
194
                }
195
        }
196

197
        last := len(resTypes) - 1
13✔
198
        counter := 0
13✔
199
        for id, tp := range resTypes {
32✔
200
                res := world.Resources().Get(id)
19✔
201
                rValue := reflect.ValueOf(res)
19✔
202
                ptr := rValue.UnsafePointer()
19✔
203

19✔
204
                buffer := bytes.NewBuffer(nil)
19✔
205
                encoder := json.NewEncoder(buffer)
19✔
206

19✔
207
                value := reflect.NewAt(tp, ptr).Interface()
19✔
208
                err := encoder.Encode(value)
19✔
209
                if err != nil {
19✔
210
                        return err
×
211
                }
×
212

213
                builder.WriteString("    ")
19✔
214
                builder.WriteString(fmt.Sprintf("\"%s\" : ", tp.String()))
19✔
215
                builder.WriteString(string(buffer.Bytes()))
19✔
216

19✔
217
                if counter < last {
28✔
218
                        builder.WriteString(",")
9✔
219
                }
9✔
220
                builder.WriteString("\n")
19✔
221
                counter++
19✔
222
        }
223

224
        builder.WriteString("}")
13✔
225

13✔
226
        return nil
13✔
227
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc