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

mlange-42 / ark-serde / 14021510664

23 Mar 2025 05:52PM CUT coverage: 94.045%. Remained the same
14021510664

push

github

web-flow
Add GZIP to README features (#20)

379 of 403 relevant lines covered (94.04%)

97.1 hits per line

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

90.75
/serialize.go
1
package arkserde
2

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

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

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

16✔
27
        builder := strings.Builder{}
16✔
28

16✔
29
        builder.WriteString("{\n")
16✔
30

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

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

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

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

16✔
51
        data := []byte(builder.String())
16✔
52
        if opts.compressed {
17✔
53
                var err error
1✔
54
                data, err = compressGZip(data, opts.compressionLevel)
1✔
55
                if err != nil {
1✔
56
                        return nil, err
×
57
                }
×
58
        }
59

60
        return data, nil
16✔
61
}
62

63
func serializeWorld(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
16✔
64
        if opts.skipEntities {
17✔
65
                return nil
1✔
66
        }
1✔
67

68
        entities := world.Unsafe().DumpEntities()
15✔
69

15✔
70
        jsonData, err := json.Marshal(entities)
15✔
71
        if err != nil {
15✔
72
                return err
×
73
        }
×
74
        builder.WriteString(fmt.Sprintf("\"World\" : %s", string(jsonData)))
15✔
75
        return nil
15✔
76
}
77

78
func serializeTypes(world *ecs.World, builder *strings.Builder, opts *serdeOptions) {
16✔
79
        if opts.skipEntities || opts.skipAllComponents {
18✔
80
                builder.WriteString("\"Types\" : []")
2✔
81
                return
2✔
82
        }
2✔
83

84
        builder.WriteString("\"Types\" : [\n")
14✔
85

14✔
86
        types := map[ecs.ID]reflect.Type{}
14✔
87

14✔
88
        allComps := ecs.ComponentIDs(world)
14✔
89
        for _, id := range allComps {
50✔
90
                if info, ok := ecs.ComponentInfo(world, id); ok {
72✔
91
                        if !slices.Contains(opts.skipComponents, info.Type) {
71✔
92
                                types[id] = info.Type
35✔
93
                        }
35✔
94
                }
95
        }
96
        maxComp := len(types) - 1
14✔
97
        counter := 0
14✔
98
        for _, tp := range types {
49✔
99
                builder.WriteString(fmt.Sprintf("  \"%s\"", tp.String()))
35✔
100
                if counter < maxComp {
56✔
101
                        builder.WriteString(",")
21✔
102
                }
21✔
103
                builder.WriteString("\n")
35✔
104
                counter++
35✔
105
        }
106

107
        builder.WriteString("]")
14✔
108
}
109

110
func serializeComponents(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
16✔
111
        if opts.skipEntities {
17✔
112
                builder.WriteString("\"Components\" : []")
1✔
113
                return nil
1✔
114
        }
1✔
115

116
        skipComponents := bitMask{}
15✔
117
        for _, tp := range opts.skipComponents {
16✔
118
                id := ecs.TypeID(world, tp)
1✔
119
                skipComponents.Set(id, true)
1✔
120
        }
1✔
121

122
        builder.WriteString("\"Components\" : [\n")
15✔
123

15✔
124
        query := ecs.NewUnsafeFilter(world).Query()
15✔
125
        lastEntity := query.Count() - 1
15✔
126
        counter := 0
15✔
127
        tempIDs := []ecs.ID{}
15✔
128
        for query.Next() {
311✔
129
                if opts.skipAllComponents {
299✔
130
                        builder.WriteString("  {")
3✔
131
                } else {
296✔
132
                        builder.WriteString("  {\n")
293✔
133

293✔
134
                        ids := query.IDs()
293✔
135

293✔
136
                        tempIDs = tempIDs[:0]
293✔
137
                        for i := range ids.Len() {
798✔
138
                                id := ids.Get(i)
505✔
139
                                if !skipComponents.Get(id) {
1,008✔
140
                                        tempIDs = append(tempIDs, id)
503✔
141
                                }
503✔
142
                        }
143
                        last := len(tempIDs) - 1
293✔
144

293✔
145
                        for i, id := range tempIDs {
796✔
146
                                info, _ := ecs.ComponentInfo(world, id)
503✔
147

503✔
148
                                if info.IsRelation {
505✔
149
                                        target := query.GetRelation(id)
2✔
150
                                        eJSON, err := target.MarshalJSON()
2✔
151
                                        if err != nil {
2✔
152
                                                return err
×
153
                                        }
×
154
                                        builder.WriteString(fmt.Sprintf("    \"%s%s\" : %s,\n", info.Type.String(), targetTag, eJSON))
2✔
155
                                }
156

157
                                comp := query.Get(id)
503✔
158
                                value := reflect.NewAt(info.Type, comp).Interface()
503✔
159
                                jsonData, err := json.Marshal(value)
503✔
160
                                if err != nil {
503✔
161
                                        return err
×
162
                                }
×
163
                                builder.WriteString(fmt.Sprintf("    \"%s\" : ", info.Type.String()))
503✔
164
                                builder.WriteString(string(jsonData))
503✔
165
                                if i < last {
723✔
166
                                        builder.WriteString(",")
220✔
167
                                }
220✔
168
                                builder.WriteString("\n")
503✔
169
                        }
170
                }
171
                builder.WriteString("  }")
296✔
172
                if counter < lastEntity {
577✔
173
                        builder.WriteString(",")
281✔
174
                }
281✔
175
                builder.WriteString("\n")
296✔
176

296✔
177
                counter++
296✔
178
        }
179
        builder.WriteString("]")
15✔
180

15✔
181
        return nil
15✔
182
}
183

184
func serializeResources(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
16✔
185
        if opts.skipAllResources {
17✔
186
                builder.WriteString("\"Resources\" : {}")
1✔
187
                return nil
1✔
188
        }
1✔
189

190
        builder.WriteString("\"Resources\" : {\n")
15✔
191

15✔
192
        resTypes := map[ecs.ResID]reflect.Type{}
15✔
193
        allRes := ecs.ResourceIDs(world)
15✔
194
        for _, id := range allRes {
35✔
195
                if tp, ok := ecs.ResourceType(world, id); ok {
40✔
196
                        if !slices.Contains(opts.skipResources, tp) {
39✔
197
                                resTypes[id] = tp
19✔
198
                        }
19✔
199
                }
200
        }
201

202
        last := len(resTypes) - 1
15✔
203
        counter := 0
15✔
204
        for id, tp := range resTypes {
34✔
205
                res := world.Resources().Get(id)
19✔
206
                rValue := reflect.ValueOf(res)
19✔
207
                ptr := rValue.UnsafePointer()
19✔
208

19✔
209
                value := reflect.NewAt(tp, ptr).Interface()
19✔
210
                jsonData, err := json.Marshal(value)
19✔
211
                if err != nil {
19✔
212
                        return err
×
213
                }
×
214

215
                builder.WriteString("    ")
19✔
216
                builder.WriteString(fmt.Sprintf("\"%s\" : ", tp.String()))
19✔
217
                builder.WriteString(string(jsonData))
19✔
218

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

226
        builder.WriteString("}")
15✔
227

15✔
228
        return nil
15✔
229
}
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