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

mlange-42 / arche-serde / 8032871356

24 Feb 2024 08:02PM CUT coverage: 95.679%. Remained the same
8032871356

Pull #14

github

web-flow
Merge a9aeebe06 into 27e3e9f0f
Pull Request #14: Prepare release v0.2.0

310 of 324 relevant lines covered (95.68%)

36.88 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 := []ecs.Component{}
102✔
96
                for tpName, value := range mp {
219✔
97
                        if tpName == targetTag {
120✔
98
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
4✔
99
                                        return err
1✔
100
                                }
1✔
101
                                continue
2✔
102
                        }
103

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

109
                        info := infos[id]
112✔
110

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

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

126
                if len(components) == 0 {
113✔
127
                        continue
13✔
128
                }
129

130
                if !hasRelation {
172✔
131
                        target = ecs.Entity{}
85✔
132
                }
85✔
133

134
                builder := ecs.NewBuilderWith(world, components...)
87✔
135
                if target.IsZero() {
173✔
136
                        builder.Add(entity)
86✔
137
                } else {
87✔
138
                        builder = builder.WithRelation(targetComp)
1✔
139
                        builder.Add(entity, target)
1✔
140
                }
1✔
141
        }
142
        return nil
17✔
143
}
144

145
func deserializeResources(world *ecs.World, deserial *deserializer, opts *serdeOptions) error {
18✔
146
        if opts.skipAllResources {
19✔
147
                return nil
1✔
148
        }
1✔
149

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

20✔
159
                        if slices.Contains(opts.skipResources, tp) {
21✔
160
                                skipResources.Set(ecs.ID(id), true)
1✔
161
                        }
1✔
162
                }
163
        }
164

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

174
                tp := resTypes[resID]
19✔
175

19✔
176
                resLoc := world.Resources().Get(resID)
19✔
177
                if resLoc == nil {
20✔
178
                        return fmt.Errorf("resource type registered but nil: %s", tpName)
1✔
179
                }
1✔
180

181
                ptr := reflect.ValueOf(resLoc).UnsafePointer()
18✔
182
                value := reflect.NewAt(tp, ptr).Interface()
18✔
183

18✔
184
                if err := json.Unmarshal(res.Bytes, &value); err != nil {
19✔
185
                        return err
1✔
186
                }
1✔
187
        }
188
        return nil
14✔
189
}
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