• 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

100.0
/deserialize.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
// Deserialize an Ark [ecs.World] from JSON.
14
//
15
// The world must be prepared the following way:
16
//   - The world must not contain any alive or dead entities (i.e. a new or [ecs.World.Reset] world)
17
//   - All required component types must be registered using [ecs.ComponentID]
18
//   - All required resources must be added as dummies using [ecs.AddResource]
19
//
20
// The options can be used to skip some or all components,
21
// entities entirely, and/or some or all resources.
22
// It only some components or resources are skipped,
23
// they still need to be registered to the world.
24
//
25
// # Query iteration order
26
//
27
// After deserialization, it is not guaranteed that entity iteration order in queries is the same as before.
28
// More precisely, it should at first be the same as before, but will likely deviate over time from what would
29
// happen when continuing the original, serialized run. Multiple worlds deserialized from the same source should,
30
// however, behave exactly the same.
31
func Deserialize(jsonData []byte, world *ecs.World, options ...Option) error {
27✔
32
        opts := newSerdeOptions(options...)
27✔
33

27✔
34
        if opts.compressed {
29✔
35
                var err error
2✔
36
                jsonData, err = uncompressGZip(jsonData)
2✔
37
                if err != nil {
3✔
38
                        return err
1✔
39
                }
1✔
40
        }
41

42
        deserial := deserializer{}
26✔
43
        if err := json.Unmarshal(jsonData, &deserial); err != nil {
28✔
44
                return err
2✔
45
        }
2✔
46

47
        if !opts.skipEntities {
47✔
48
                world.Unsafe().LoadEntities(&deserial.World)
23✔
49
        }
23✔
50

51
        if err := deserializeComponents(world, &deserial, &opts); err != nil {
29✔
52
                return err
5✔
53
        }
5✔
54
        if err := deserializeResources(world, &deserial, &opts); err != nil {
22✔
55
                return err
3✔
56
        }
3✔
57

58
        return nil
16✔
59
}
60

61
func deserializeComponents(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
24✔
62
        u := world.Unsafe()
24✔
63

24✔
64
        if opts.skipEntities {
25✔
65
                return nil
1✔
66
        }
1✔
67

68
        ids := map[string]ecs.ID{}
23✔
69
        allComps := ecs.ComponentIDs(world)
23✔
70
        infos := make([]ecs.CompInfo, len(allComps))
23✔
71
        for _, id := range allComps {
88✔
72
                if info, ok := ecs.ComponentInfo(world, id); ok {
130✔
73
                        infos[id.Index()] = info
65✔
74
                        ids[info.Type.String()] = id
65✔
75
                }
65✔
76
        }
77

78
        for _, tp := range deserial.Types {
74✔
79
                if _, ok := ids[tp]; !ok {
52✔
80
                        return fmt.Errorf("component type is not registered: %s", tp)
1✔
81
                }
1✔
82
        }
83

84
        if len(deserial.Components) != len(deserial.World.Alive) {
23✔
85
                return fmt.Errorf("found components for %d entities, but world has %d alive entities", len(deserial.Components), len(deserial.World.Alive))
1✔
86
        }
1✔
87

88
        skipComponents := bitMask{}
21✔
89
        for _, tp := range opts.skipComponents {
22✔
90
                id := ecs.TypeID(world, tp)
1✔
91
                skipComponents.Set(id, true)
1✔
92
        }
1✔
93

94
        for i, comps := range deserial.Components {
224✔
95
                entity := deserial.World.Entities[deserial.World.Alive[i]]
203✔
96

203✔
97
                mp := map[string]entry{}
203✔
98

203✔
99
                if err := json.Unmarshal(comps.Bytes, &mp); err != nil {
204✔
100
                        return err
1✔
101
                }
1✔
102

103
                // TODO: check that multiple relations work properly
104
                idsMap := map[string]ecs.ID{}
202✔
105
                targetsMap := map[string]ecs.Entity{}
202✔
106

202✔
107
                components := make([]component, 0, len(mp))
202✔
108
                compIDs := make([]ecs.ID, 0, len(mp))
202✔
109
                for tpName, value := range mp {
519✔
110
                        if strings.HasSuffix(tpName, targetTag) {
320✔
111
                                var target ecs.Entity
3✔
112
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
4✔
113
                                        return err
1✔
114
                                }
1✔
115
                                targetsMap[strings.TrimSuffix(tpName, targetTag)] = target
2✔
116
                                continue
2✔
117
                        }
118

119
                        id := ids[tpName]
314✔
120
                        if skipComponents.Get(id) {
316✔
121
                                continue
2✔
122
                        }
123

124
                        info := infos[id.Index()]
312✔
125

312✔
126
                        if info.IsRelation {
314✔
127
                                idsMap[tpName] = id
2✔
128
                        }
2✔
129

130
                        comp := reflect.New(info.Type).Interface()
312✔
131
                        if err := json.Unmarshal(value.Bytes, &comp); err != nil {
313✔
132
                                return err
1✔
133
                        }
1✔
134
                        compIDs = append(compIDs, id)
311✔
135
                        components = append(components, component{
311✔
136
                                ID:   id,
311✔
137
                                Comp: comp,
311✔
138
                        })
311✔
139
                }
140

141
                if len(components) == 0 {
213✔
142
                        continue
13✔
143
                }
144

145
                relations := []ecs.RelationID{}
187✔
146
                for name, id := range idsMap {
189✔
147
                        relations = append(relations, ecs.RelID(id, targetsMap[name]))
2✔
148
                }
2✔
149
                u.AddRel(entity, compIDs, relations...)
187✔
150
                for _, comp := range components {
497✔
151
                        assign(world, entity, comp.ID, comp.Comp)
310✔
152
                }
310✔
153
        }
154
        return nil
18✔
155
}
156

157
func assign(world *ecs.World, entity ecs.Entity, id ecs.ID, comp interface{}) {
310✔
158
        dst := world.Unsafe().Get(entity, id)
310✔
159
        rValue := reflect.ValueOf(comp).Elem()
310✔
160

310✔
161
        valueType := rValue.Type()
310✔
162
        valuePtr := reflect.NewAt(valueType, dst)
310✔
163
        valuePtr.Elem().Set(rValue)
310✔
164
}
310✔
165

166
func deserializeResources(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
19✔
167
        if opts.skipAllResources {
20✔
168
                return nil
1✔
169
        }
1✔
170

171
        resTypes := map[ecs.ResID]reflect.Type{}
18✔
172
        resIds := map[string]ecs.ResID{}
18✔
173
        allRes := ecs.ResourceIDs(world)
18✔
174
        skipResources := bitMask{}
18✔
175
        for _, id := range allRes {
38✔
176
                if tp, ok := ecs.ResourceType(world, id); ok {
40✔
177
                        resTypes[id] = tp
20✔
178
                        resIds[tp.String()] = id
20✔
179

20✔
180
                        if slices.Contains(opts.skipResources, tp) {
21✔
181
                                skipResources.Set(ecs.ID(id), true)
1✔
182
                        }
1✔
183
                }
184
        }
185

186
        for tpName, res := range deserial.Resources {
39✔
187
                resID, ok := resIds[tpName]
21✔
188
                if !ok {
22✔
189
                        return fmt.Errorf("resource type is not registered: %s", tpName)
1✔
190
                }
1✔
191
                if skipResources.Get(ecs.ID(resID)) {
21✔
192
                        continue
1✔
193
                }
194

195
                tp := resTypes[resID]
19✔
196

19✔
197
                resLoc := world.Resources().Get(resID)
19✔
198
                if resLoc == nil {
20✔
199
                        return fmt.Errorf("resource type registered but nil: %s", tpName)
1✔
200
                }
1✔
201

202
                ptr := reflect.ValueOf(resLoc).UnsafePointer()
18✔
203
                value := reflect.NewAt(tp, ptr).Interface()
18✔
204

18✔
205
                if err := json.Unmarshal(res.Bytes, &value); err != nil {
19✔
206
                        return err
1✔
207
                }
1✔
208
        }
209
        return nil
15✔
210
}
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