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

mlange-42 / arche-serde / 8032168232

24 Feb 2024 05:36PM CUT coverage: 95.679% (+2.1%) from 93.562%
8032168232

Pull #13

github

web-flow
Merge ff18721ad into c2c523fde
Pull Request #13: Add options to skip stuff when serializing or deserializing

135 of 137 new or added lines in 3 files covered. (98.54%)

10 existing lines in 1 file now uncovered.

310 of 324 relevant lines covered (95.68%)

36.72 hits per line

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

91.52
/serialize.go
1
package archeserde
2

3
import (
4
        "encoding/json"
5
        "fmt"
6
        "reflect"
7
        "slices"
8
        "strings"
9

10
        "github.com/mlange-42/arche/ecs"
11
)
12

13
const targetTag = "arche.relation.Target"
14

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

14✔
26
        builder := strings.Builder{}
14✔
27

14✔
28
        builder.WriteString("{\n")
14✔
29

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

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

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

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

14✔
50
        return []byte(builder.String()), nil
14✔
51
}
52

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

58
        entities := world.DumpEntities()
13✔
59

13✔
60
        jsonData, err := json.Marshal(entities)
13✔
61
        if err != nil {
13✔
UNCOV
62
                return err
×
UNCOV
63
        }
×
64
        builder.WriteString(fmt.Sprintf("\"World\" : %s", string(jsonData)))
13✔
65
        return nil
13✔
66
}
67

68
func serializeTypes(world *ecs.World, builder *strings.Builder, opts *serdeOptions) {
14✔
69
        if opts.skipEntities || opts.skipAllComponents {
16✔
70
                builder.WriteString("\"Types\" : []")
2✔
71
                return
2✔
72
        }
2✔
73

74
        builder.WriteString("\"Types\" : [\n")
12✔
75

12✔
76
        types := map[ecs.ID]reflect.Type{}
12✔
77

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

97
        builder.WriteString("]")
12✔
98
}
99

100
func serializeComponents(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
14✔
101
        if opts.skipEntities {
15✔
102
                builder.WriteString("\"Components\" : []")
1✔
103
                return nil
1✔
104
        }
1✔
105

106
        skipComponents := ecs.Mask{}
13✔
107
        for _, tp := range opts.skipComponents {
14✔
108
                id := ecs.TypeID(world, tp)
1✔
109
                skipComponents.Set(id, true)
1✔
110
        }
1✔
111

112
        builder.WriteString("\"Components\" : [\n")
13✔
113

13✔
114
        query := world.Query(ecs.All())
13✔
115
        lastEntity := query.Count() - 1
13✔
116
        counter := 0
13✔
117
        tempIDs := []ecs.ID{}
13✔
118
        for query.Next() {
109✔
119
                if opts.skipAllComponents {
99✔
120
                        builder.WriteString("  {")
3✔
121
                } else {
96✔
122
                        builder.WriteString("  {\n")
93✔
123

93✔
124
                        ids := query.Ids()
93✔
125

93✔
126
                        tempIDs = tempIDs[:0]
93✔
127
                        for _, id := range ids {
198✔
128
                                if !skipComponents.Get(id) {
208✔
129
                                        tempIDs = append(tempIDs, id)
103✔
130
                                }
103✔
131
                        }
132
                        last := len(tempIDs) - 1
93✔
133

93✔
134
                        for i, id := range tempIDs {
196✔
135
                                info, _ := ecs.ComponentInfo(world, id)
103✔
136

103✔
137
                                if info.IsRelation {
105✔
138
                                        target := query.Relation(id)
2✔
139
                                        eJSON, err := target.MarshalJSON()
2✔
140
                                        if err != nil {
2✔
NEW
141
                                                return err
×
NEW
142
                                        }
×
143
                                        builder.WriteString(fmt.Sprintf("    \"%s\" : %s,\n", targetTag, eJSON))
2✔
144
                                }
145

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

96✔
166
                counter++
96✔
167
        }
168
        builder.WriteString("]")
13✔
169

13✔
170
        return nil
13✔
171
}
172

173
func serializeResources(world *ecs.World, builder *strings.Builder, opts *serdeOptions) error {
14✔
174
        if opts.skipAllResources {
15✔
175
                builder.WriteString("\"Resources\" : {}")
1✔
176
                return nil
1✔
177
        }
1✔
178

179
        builder.WriteString("\"Resources\" : {\n")
13✔
180

13✔
181
        resTypes := map[ecs.ResID]reflect.Type{}
13✔
182
        allRes := ecs.ResourceIDs(world)
13✔
183
        for _, id := range allRes {
33✔
184
                if tp, ok := ecs.ResourceType(world, id); ok {
40✔
185
                        if !slices.Contains(opts.skipResources, tp) {
39✔
186
                                resTypes[id] = tp
19✔
187
                        }
19✔
188
                }
189
        }
190

191
        last := len(resTypes) - 1
13✔
192
        counter := 0
13✔
193
        for id, tp := range resTypes {
32✔
194
                res := world.Resources().Get(id)
19✔
195
                rValue := reflect.ValueOf(res)
19✔
196
                ptr := rValue.UnsafePointer()
19✔
197

19✔
198
                value := reflect.NewAt(tp, ptr).Interface()
19✔
199
                jsonData, err := json.Marshal(value)
19✔
200
                if err != nil {
19✔
UNCOV
201
                        return err
×
UNCOV
202
                }
×
203

204
                builder.WriteString("    ")
19✔
205
                builder.WriteString(fmt.Sprintf("\"%s\" : ", tp.String()))
19✔
206
                builder.WriteString(string(jsonData))
19✔
207

19✔
208
                if counter < last {
28✔
209
                        builder.WriteString(",")
9✔
210
                }
9✔
211
                builder.WriteString("\n")
19✔
212
                counter++
19✔
213
        }
214

215
        builder.WriteString("}")
13✔
216

13✔
217
        return nil
13✔
218
}
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