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

mlange-42 / ark-serde / 13605188436

01 Mar 2025 01:17PM CUT coverage: 89.222%. First build
13605188436

Pull #1

github

web-flow
Merge dfffb02a6 into 6d7682df1
Pull Request #1: Make it work properly with Ark

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

298 of 334 relevant lines covered (89.22%)

34.64 hits per line

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

83.7
/deserialize.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
// 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 {
14✔
32
        opts := newSerdeOptions(options...)
14✔
33

14✔
34
        deserial := deserializer{}
14✔
35
        if err := json.Unmarshal(jsonData, &deserial); err != nil {
14✔
36
                return err
×
37
        }
×
38

39
        if !opts.skipEntities {
27✔
40
                world.LoadEntities(&deserial.World)
13✔
41
        }
13✔
42

43
        if err := deserializeComponents(world, &deserial, &opts); err != nil {
14✔
44
                return err
×
45
        }
×
46
        if err := deserializeResources(world, &deserial, &opts); err != nil {
14✔
47
                return err
×
48
        }
×
49

50
        return nil
14✔
51
}
52

53
func deserializeComponents(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
14✔
54
        u := world.Unsafe()
14✔
55

14✔
56
        if opts.skipEntities {
15✔
57
                return nil
1✔
58
        }
1✔
59

60
        infos := map[ecs.ID]ecs.CompInfo{}
13✔
61
        ids := map[string]ecs.ID{}
13✔
62
        allComps := ecs.ComponentIDs(world)
13✔
63
        for _, id := range allComps {
41✔
64
                if info, ok := ecs.ComponentInfo(world, id); ok {
56✔
65
                        infos[id] = info
28✔
66
                        ids[info.Type.String()] = id
28✔
67
                }
28✔
68
        }
69

70
        for _, tp := range deserial.Types {
41✔
71
                if _, ok := ids[tp]; !ok {
28✔
72
                        return fmt.Errorf("component type is not registered: %s", tp)
×
73
                }
×
74
        }
75

76
        if len(deserial.Components) != len(deserial.World.Alive) {
13✔
77
                return fmt.Errorf("found components for %d entities, but world has %d alive entities", len(deserial.Components), len(deserial.World.Alive))
×
78
        }
×
79

80
        skipComponents := ecs.Mask{}
13✔
81
        for _, tp := range opts.skipComponents {
14✔
82
                id := ecs.TypeID(world, tp)
1✔
83
                skipComponents.Set(id, true)
1✔
84
        }
1✔
85

86
        for i, comps := range deserial.Components {
106✔
87
                entity := deserial.World.Entities[deserial.World.Alive[i]]
93✔
88

93✔
89
                mp := map[string]entry{}
93✔
90

93✔
91
                if err := json.Unmarshal(comps.Bytes, &mp); err != nil {
93✔
92
                        return err
×
93
                }
×
94

95
                // TODO: check that multiple relations work properly
96
                targets := []ecs.Entity{}
93✔
97
                targetComps := []ecs.ID{}
93✔
98
                components := make([]component, 0, len(mp))
93✔
99
                compIDs := make([]ecs.ID, 0, len(mp))
93✔
100
                for tpName, value := range mp {
194✔
101
                        if strings.HasSuffix(tpName, targetTag) {
103✔
102
                                var target ecs.Entity
2✔
103
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
2✔
104
                                        return err
×
105
                                }
×
106
                                targets = append(targets, target)
2✔
107
                                continue
2✔
108
                        }
109

110
                        id := ids[tpName]
99✔
111
                        if skipComponents.Get(id) {
101✔
112
                                continue
2✔
113
                        }
114

115
                        info := infos[id]
97✔
116

97✔
117
                        if info.IsRelation {
99✔
118
                                targetComps = append(targetComps, id)
2✔
119
                        }
2✔
120

121
                        comp := reflect.New(info.Type).Interface()
97✔
122
                        if err := json.Unmarshal(value.Bytes, &comp); err != nil {
97✔
123
                                return err
×
124
                        }
×
125
                        compIDs = append(compIDs, id)
97✔
126
                        components = append(components, component{
97✔
127
                                ID:   id,
97✔
128
                                Comp: comp,
97✔
129
                        })
97✔
130
                }
131

132
                if len(components) == 0 {
106✔
133
                        continue
13✔
134
                }
135

136
                relations := []ecs.RelationID{}
80✔
137
                for i := range targetComps {
82✔
138
                        relations = append(relations, ecs.RelID(targetComps[i], targets[i]))
2✔
139
                }
2✔
140
                u.AddRel(entity, compIDs, relations...)
80✔
141
                for _, comp := range components {
177✔
142
                        assign(world, entity, comp.ID, comp.Comp)
97✔
143
                }
97✔
144
        }
145
        return nil
13✔
146
}
147

148
func assign(world *ecs.World, entity ecs.Entity, id ecs.ID, comp interface{}) {
97✔
149
        dst := world.Unsafe().Get(entity, id)
97✔
150
        rValue := reflect.ValueOf(comp).Elem()
97✔
151

97✔
152
        valueType := rValue.Type()
97✔
153
        valuePtr := reflect.NewAt(valueType, dst)
97✔
154
        valuePtr.Elem().Set(rValue)
97✔
155
}
97✔
156

157
func deserializeResources(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
14✔
158
        if opts.skipAllResources {
15✔
159
                return nil
1✔
160
        }
1✔
161

162
        resTypes := map[ecs.ResID]reflect.Type{}
13✔
163
        resIds := map[string]ecs.ResID{}
13✔
164
        allRes := ecs.ResourceIDs(world)
13✔
165
        skipResources := ecs.Mask{}
13✔
166
        for _, id := range allRes {
30✔
167
                if tp, ok := ecs.ResourceType(world, id); ok {
34✔
168
                        resTypes[id] = tp
17✔
169
                        resIds[tp.String()] = id
17✔
170

17✔
171
                        if slices.Contains(opts.skipResources, tp) {
18✔
172
                                skipResources.Set(ecs.ID(id), true)
1✔
173
                        }
1✔
174
                }
175
        }
176

177
        for tpName, res := range deserial.Resources {
30✔
178
                resID, ok := resIds[tpName]
17✔
179
                if !ok {
17✔
180
                        return fmt.Errorf("resource type is not registered: %s", tpName)
×
181
                }
×
182
                if skipResources.Get(ecs.ID(resID)) {
18✔
183
                        continue
1✔
184
                }
185

186
                tp := resTypes[resID]
16✔
187

16✔
188
                resLoc := world.Resources().Get(resID)
16✔
189
                if resLoc == nil {
16✔
190
                        return fmt.Errorf("resource type registered but nil: %s", tpName)
×
191
                }
×
192

193
                ptr := reflect.ValueOf(resLoc).UnsafePointer()
16✔
194
                value := reflect.NewAt(tp, ptr).Interface()
16✔
195

16✔
196
                if err := json.Unmarshal(res.Bytes, &value); err != nil {
16✔
197
                        return err
×
198
                }
×
199
        }
200
        return nil
13✔
201
}
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