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

mlange-42 / ark-serde / 14019558887

23 Mar 2025 01:38PM CUT coverage: 96.023%. Remained the same
14019558887

push

github

web-flow
Profiling and optimizations (#13)

* use slice for component infos
* add profiling projects
* update changelog

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

338 of 352 relevant lines covered (96.02%)

42.14 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
        "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 {
25✔
32
        opts := newSerdeOptions(options...)
25✔
33

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

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

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

50
        return nil
15✔
51
}
52

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

23✔
56
        if opts.skipEntities {
24✔
57
                return nil
1✔
58
        }
1✔
59

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

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

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

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

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

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

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

95
                // TODO: check that multiple relations work properly
96
                idsMap := map[string]ecs.ID{}
102✔
97
                targetsMap := map[string]ecs.Entity{}
102✔
98

102✔
99
                components := make([]component, 0, len(mp))
102✔
100
                compIDs := make([]ecs.ID, 0, len(mp))
102✔
101
                for tpName, value := range mp {
219✔
102
                        if strings.HasSuffix(tpName, targetTag) {
120✔
103
                                var target ecs.Entity
3✔
104
                                if err := json.Unmarshal(value.Bytes, &target); err != nil {
4✔
105
                                        return err
1✔
106
                                }
1✔
107
                                targetsMap[strings.TrimSuffix(tpName, targetTag)] = target
2✔
108
                                continue
2✔
109
                        }
110

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

116
                        info := infos[id.Index()]
112✔
117

112✔
118
                        if info.IsRelation {
114✔
119
                                idsMap[tpName] = id
2✔
120
                        }
2✔
121

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

133
                if len(components) == 0 {
113✔
134
                        continue
13✔
135
                }
136

137
                relations := []ecs.RelationID{}
87✔
138
                for name, id := range idsMap {
89✔
139
                        relations = append(relations, ecs.RelID(id, targetsMap[name]))
2✔
140
                }
2✔
141
                u.AddRel(entity, compIDs, relations...)
87✔
142
                for _, comp := range components {
197✔
143
                        assign(world, entity, comp.ID, comp.Comp)
110✔
144
                }
110✔
145
        }
146
        return nil
17✔
147
}
148

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

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

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

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

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

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

187
                tp := resTypes[resID]
19✔
188

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

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

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