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

mlange-42 / arche-serde / 8032178830

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

Pull #13

github

web-flow
Merge c777cef84 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

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
// # Query iteration order
20
//
21
// After deserialization, it is not guaranteed that entity iteration order in queries is the same as before.
22
// More precisely, it should at first be the same as before, but will likely deviate over time from what would
23
// happen when continuing the original, serialized run. Multiple worlds deserialized from the same source should,
24
// however, behave exactly the same.
25
func Deserialize(jsonData []byte, world *ecs.World, options ...Option) error {
25✔
26
        opts := newSerdeOptions(options...)
25✔
27

25✔
28
        deserial := deserializer{}
25✔
29
        if err := json.Unmarshal(jsonData, &deserial); err != nil {
27✔
30
                return err
2✔
31
        }
2✔
32

33
        if !opts.skipEntities {
45✔
34
                world.LoadEntities(&deserial.World)
22✔
35
        }
22✔
36

37
        if err := deserializeComponents(world, &deserial, &opts); err != nil {
28✔
38
                return err
5✔
39
        }
5✔
40
        if err := deserializeResources(world, &deserial, &opts); err != nil {
21✔
41
                return err
3✔
42
        }
3✔
43

44
        return nil
15✔
45
}
46

47
func deserializeComponents(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
23✔
48
        if opts.skipEntities {
24✔
49
                return nil
1✔
50
        }
1✔
51

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

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

68
        if len(deserial.Components) != len(deserial.World.Alive) {
22✔
69
                return fmt.Errorf("found components for %d entities, but world has %d alive entities", len(deserial.Components), len(deserial.World.Alive))
1✔
70
        }
1✔
71

72
        skipComponents := ecs.Mask{}
20✔
73
        for _, tp := range opts.skipComponents {
21✔
74
                id := ecs.TypeID(world, tp)
1✔
75
                skipComponents.Set(id, true)
1✔
76
        }
1✔
77

78
        for i, comps := range deserial.Components {
123✔
79
                entity := deserial.World.Entities[deserial.World.Alive[i]]
103✔
80

103✔
81
                mp := map[string]entry{}
103✔
82

103✔
83
                if err := json.Unmarshal(comps.Bytes, &mp); err != nil {
104✔
84
                        return err
1✔
85
                }
1✔
86

87
                target := ecs.Entity{}
102✔
88
                var targetComp ecs.ID
102✔
89
                hasRelation := false
102✔
90
                components := []ecs.Component{}
102✔
91
                for tpName, value := range mp {
219✔
92
                        if tpName == targetTag {
120✔
93
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
4✔
94
                                        return err
1✔
95
                                }
1✔
96
                                continue
2✔
97
                        }
98

99
                        id := ids[tpName]
114✔
100
                        if skipComponents.Get(id) {
116✔
101
                                continue
2✔
102
                        }
103

104
                        info := infos[id]
112✔
105

112✔
106
                        if info.IsRelation {
114✔
107
                                targetComp = id
2✔
108
                                hasRelation = true
2✔
109
                        }
2✔
110

111
                        component := reflect.New(info.Type).Interface()
112✔
112
                        if err := json.Unmarshal(value.Bytes, &component); err != nil {
113✔
113
                                return err
1✔
114
                        }
1✔
115
                        components = append(components, ecs.Component{
111✔
116
                                ID:   id,
111✔
117
                                Comp: component,
111✔
118
                        })
111✔
119
                }
120

121
                if len(components) == 0 {
113✔
122
                        continue
13✔
123
                }
124

125
                if !hasRelation {
172✔
126
                        target = ecs.Entity{}
85✔
127
                }
85✔
128

129
                builder := ecs.NewBuilderWith(world, components...)
87✔
130
                if target.IsZero() {
173✔
131
                        builder.Add(entity)
86✔
132
                } else {
87✔
133
                        builder = builder.WithRelation(targetComp)
1✔
134
                        builder.Add(entity, target)
1✔
135
                }
1✔
136
        }
137
        return nil
17✔
138
}
139

140
func deserializeResources(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
18✔
141
        if opts.skipAllResources {
19✔
142
                return nil
1✔
143
        }
1✔
144

145
        resTypes := map[ecs.ResID]reflect.Type{}
17✔
146
        resIds := map[string]ecs.ResID{}
17✔
147
        allRes := ecs.ResourceIDs(world)
17✔
148
        skipResources := ecs.Mask{}
17✔
149
        for _, id := range allRes {
37✔
150
                if tp, ok := ecs.ResourceType(world, id); ok {
40✔
151
                        resTypes[id] = tp
20✔
152
                        resIds[tp.String()] = id
20✔
153

20✔
154
                        if slices.Contains(opts.skipResources, tp) {
21✔
155
                                skipResources.Set(ecs.ID(id), true)
1✔
156
                        }
1✔
157
                }
158
        }
159

160
        for tpName, res := range deserial.Resources {
38✔
161
                resID, ok := resIds[tpName]
21✔
162
                if !ok {
22✔
163
                        return fmt.Errorf("resource type is not registered: %s", tpName)
1✔
164
                }
1✔
165
                if skipResources.Get(ecs.ID(resID)) {
21✔
166
                        continue
1✔
167
                }
168

169
                tp := resTypes[resID]
19✔
170

19✔
171
                resLoc := world.Resources().Get(resID)
19✔
172
                if resLoc == nil {
20✔
173
                        return fmt.Errorf("resource type registered but nil: %s", tpName)
1✔
174
                }
1✔
175

176
                ptr := reflect.ValueOf(resLoc).UnsafePointer()
18✔
177
                value := reflect.NewAt(tp, ptr).Interface()
18✔
178

18✔
179
                if err := json.Unmarshal(res.Bytes, &value); err != nil {
19✔
180
                        return err
1✔
181
                }
1✔
182
        }
183
        return nil
14✔
184
}
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