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

mlange-42 / arche-serde / 12457234164

22 Dec 2024 07:57PM CUT coverage: 95.808% (+0.1%) from 95.679%
12457234164

Pull #15

github

web-flow
Merge 13e02ce48 into e0568ef0d
Pull Request #15: Get rid of the use of deprecated methods

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

320 of 334 relevant lines covered (95.81%)

39.52 hits per line

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

100.0
/deserialize.go
1
package archeserde
2

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

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

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

25✔
33
        deserial := deserializer{}
25✔
34
        if err := json.Unmarshal(jsonData, &deserial); err != nil {
27✔
35
                return err
2✔
36
        }
2✔
37

38
        if !opts.skipEntities {
45✔
39
                world.LoadEntities(&deserial.World)
22✔
40
        }
22✔
41

42
        if err := deserializeComponents(world, &deserial, &opts); err != nil {
28✔
43
                return err
5✔
44
        }
5✔
45
        if err := deserializeResources(world, &deserial, &opts); err != nil {
21✔
46
                return err
3✔
47
        }
3✔
48

49
        return nil
15✔
50
}
51

52
func deserializeComponents(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
23✔
53
        if opts.skipEntities {
24✔
54
                return nil
1✔
55
        }
1✔
56

57
        infos := map[ecs.ID]ecs.CompInfo{}
22✔
58
        ids := map[string]ecs.ID{}
22✔
59
        allComps := ecs.ComponentIDs(world)
22✔
60
        for _, id := range allComps {
85✔
61
                if info, ok := ecs.ComponentInfo(world, id); ok {
126✔
62
                        infos[id] = info
63✔
63
                        ids[info.Type.String()] = id
63✔
64
                }
63✔
65
        }
66

67
        for _, tp := range deserial.Types {
71✔
68
                if _, ok := ids[tp]; !ok {
50✔
69
                        return fmt.Errorf("component type is not registered: %s", tp)
1✔
70
                }
1✔
71
        }
72

73
        if len(deserial.Components) != len(deserial.World.Alive) {
22✔
74
                return fmt.Errorf("found components for %d entities, but world has %d alive entities", len(deserial.Components), len(deserial.World.Alive))
1✔
75
        }
1✔
76

77
        skipComponents := ecs.Mask{}
20✔
78
        for _, tp := range opts.skipComponents {
21✔
79
                id := ecs.TypeID(world, tp)
1✔
80
                skipComponents.Set(id, true)
1✔
81
        }
1✔
82

83
        for i, comps := range deserial.Components {
123✔
84
                entity := deserial.World.Entities[deserial.World.Alive[i]]
103✔
85

103✔
86
                mp := map[string]entry{}
103✔
87

103✔
88
                if err := json.Unmarshal(comps.Bytes, &mp); err != nil {
104✔
89
                        return err
1✔
90
                }
1✔
91

92
                target := ecs.Entity{}
102✔
93
                var targetComp ecs.ID
102✔
94
                hasRelation := false
102✔
95
                components := make([]ecs.Component, 0, len(mp))
102✔
96
                compIDs := make([]ecs.ID, 0, len(mp))
102✔
97
                for tpName, value := range mp {
219✔
98
                        if tpName == targetTag {
120✔
99
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
4✔
100
                                        return err
1✔
101
                                }
1✔
102
                                continue
2✔
103
                        }
104

105
                        id := ids[tpName]
114✔
106
                        if skipComponents.Get(id) {
116✔
107
                                continue
2✔
108
                        }
109

110
                        info := infos[id]
112✔
111

112✔
112
                        if info.IsRelation {
114✔
113
                                targetComp = id
2✔
114
                                hasRelation = true
2✔
115
                        }
2✔
116

117
                        component := reflect.New(info.Type).Interface()
112✔
118
                        if err := json.Unmarshal(value.Bytes, &component); err != nil {
113✔
119
                                return err
1✔
120
                        }
1✔
121
                        compIDs = append(compIDs, id)
111✔
122
                        components = append(components, ecs.Component{
111✔
123
                                ID:   id,
111✔
124
                                Comp: component,
111✔
125
                        })
111✔
126
                }
127

128
                if len(components) == 0 {
113✔
129
                        continue
13✔
130
                }
131

132
                if !hasRelation {
172✔
133
                        target = ecs.Entity{}
85✔
134
                }
85✔
135

136
                world.Add(entity, compIDs...)
87✔
137
                for _, comp := range components {
197✔
138
                        assign(world, entity, comp.ID, comp.Comp)
110✔
139
                }
110✔
140
                if !target.IsZero() {
88✔
141
                        world.Relations().Set(entity, targetComp, target)
1✔
142
                }
1✔
143
        }
144
        return nil
17✔
145
}
146

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

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

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

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

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

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

185
                tp := resTypes[resID]
19✔
186

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

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

18✔
195
                if err := json.Unmarshal(res.Bytes, &value); err != nil {
19✔
196
                        return err
1✔
197
                }
1✔
198
        }
199
        return nil
14✔
200
}
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