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

mlange-42 / ark-serde / 13877816648

15 Mar 2025 11:59PM CUT coverage: 96.023%. Remained the same
13877816648

push

github

web-flow
Upgrade Ark for renamed unsafe filter (#9)

1 of 1 new or added line in 1 file covered. (100.0%)

338 of 352 relevant lines covered (96.02%)

42.14 hits per line

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

91.57
/serialize.go
1
package arkserde
2

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

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) {
14✔
25
        opts := newSerdeOptions(options...)
14✔
26

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

93✔
125
                        ids := query.IDs()
93✔
126

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

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

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

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

96✔
168
                counter++
96✔
169
        }
170
        builder.WriteString("]")
13✔
171

13✔
172
        return nil
13✔
173
}
174

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

181
        builder.WriteString("\"Resources\" : {\n")
13✔
182

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

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

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

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

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

217
        builder.WriteString("}")
13✔
218

13✔
219
        return nil
13✔
220
}
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