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

mlange-42 / ark / 13593874277

28 Feb 2025 06:06PM CUT coverage: 96.872% (+2.1%) from 94.795%
13593874277

Pull #79

github

web-flow
Merge 94da96c6d into 10315654e
Pull Request #79: Relation getters for queries and maps

118 of 118 new or added lines in 6 files covered. (100.0%)

8 existing lines in 1 file now uncovered.

3562 of 3677 relevant lines covered (96.87%)

50530.93 hits per line

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

93.97
/ecs/storage.go
1
package ecs
2

3
import "fmt"
4

5
type storage struct {
6
        registry   componentRegistry
7
        entities   entities
8
        isTarget   []bool
9
        entityPool entityPool
10

11
        archetypes         []archetype
12
        relationArchetypes []archetypeID
13
        tables             []table
14
        components         []componentStorage
15
        initialCapacity    uint32
16
}
17

18
type componentStorage struct {
19
        columns []*column
20
}
21

22
func newStorage(capacity uint32) storage {
101✔
23
        reg := newComponentRegistry()
101✔
24
        entities := make([]entityIndex, reservedEntities, capacity+reservedEntities)
101✔
25
        isTarget := make([]bool, reservedEntities, capacity+reservedEntities)
101✔
26
        // Reserved zero and wildcard entities
101✔
27
        for i := range reservedEntities {
303✔
28
                entities[i] = entityIndex{table: maxTableID, row: 0}
202✔
29
        }
202✔
30

31
        tables := make([]table, 0, 128)
101✔
32
        tables = append(tables, newTable(0, 0, capacity, &reg, []ID{}, make([]int16, MaskTotalBits), []bool{}, []Entity{}, []relationID{}))
101✔
33
        archetypes := make([]archetype, 0, 128)
101✔
34
        archetypes = append(archetypes, newArchetype(0, &Mask{}, []ID{}, []tableID{0}, &reg))
101✔
35
        return storage{
101✔
36
                registry:        reg,
101✔
37
                entities:        entities,
101✔
38
                isTarget:        isTarget,
101✔
39
                entityPool:      newEntityPool(capacity, reservedEntities),
101✔
40
                archetypes:      archetypes,
101✔
41
                tables:          tables,
101✔
42
                initialCapacity: capacity,
101✔
43
                components:      make([]componentStorage, 0, MaskTotalBits),
101✔
44
        }
101✔
45
}
46

47
func (s *storage) findOrCreateTable(oldTable *table, mask *Mask, relations []relationID) *table {
497,960✔
48
        // TODO: use archetype graph
497,960✔
49
        var arch *archetype
497,960✔
50
        for i := range s.archetypes {
1,493,785✔
51
                if s.archetypes[i].mask.Equals(mask) {
1,493,655✔
52
                        arch = &s.archetypes[i]
497,830✔
53
                        break
497,830✔
54
                }
55
        }
56
        if arch == nil {
498,090✔
57
                arch = s.createArchetype(mask)
130✔
58
        }
130✔
59
        allRelation := appendNew(oldTable.relationIDs, relations...)
497,960✔
60
        table, ok := arch.GetTable(s, allRelation)
497,960✔
61
        if !ok {
498,112✔
62
                table = s.createTable(arch, allRelation)
152✔
63
        }
152✔
64
        return table
497,960✔
65
}
66

67
func (s *storage) AddComponent(id uint8) {
455✔
68
        if len(s.components) != int(id) {
455✔
69
                panic("components can only be added to a storage sequentially")
×
70
        }
71
        s.components = append(s.components, componentStorage{columns: make([]*column, len(s.tables))})
455✔
72
}
73

74
// RemoveEntity removes the given entity from the world.
75
func (s *storage) RemoveEntity(entity Entity) {
496,526✔
76
        if !s.entityPool.Alive(entity) {
496,526✔
77
                panic("can't remove a dead entity")
×
78
        }
79
        index := &s.entities[entity.id]
496,526✔
80
        table := &s.tables[index.table]
496,526✔
81

496,526✔
82
        swapped := table.Remove(index.row)
496,526✔
83

496,526✔
84
        s.entityPool.Recycle(entity)
496,526✔
85

496,526✔
86
        if swapped {
991,895✔
87
                swapEntity := table.GetEntity(uintptr(index.row))
495,369✔
88
                s.entities[swapEntity.id].row = index.row
495,369✔
89
        }
495,369✔
90
        index.table = maxTableID
496,526✔
91

496,526✔
92
        if s.isTarget[entity.id] {
496,527✔
93
                s.cleanupArchetypes(entity)
1✔
94
                s.isTarget[entity.id] = false
1✔
95
        }
1✔
96
}
97

98
func (s *storage) getRelation(entity Entity, comp ID) Entity {
125✔
99
        if !s.entityPool.Alive(entity) {
125✔
100
                panic("can't get relation for a dead entity")
×
101
        }
102
        return s.tables[s.entities[entity.id].table].GetRelation(comp)
125✔
103
}
104

105
func (s *storage) getRelationUnchecked(entity Entity, comp ID) Entity {
2✔
106
        return s.tables[s.entities[entity.id].table].GetRelation(comp)
2✔
107
}
2✔
108

109
func (s *storage) registerTargets(relations []relationID) {
498,002✔
110
        for _, rel := range relations {
498,440✔
111
                s.isTarget[rel.target.id] = true
438✔
112
        }
438✔
113
}
114

115
func (s *storage) createEntity(table tableID) (Entity, uint32) {
497,636✔
116
        entity := s.entityPool.Get()
497,636✔
117

497,636✔
118
        idx := s.tables[table].Add(entity)
497,636✔
119
        len := len(s.entities)
497,636✔
120
        if int(entity.id) == len {
501,807✔
121
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
4,171✔
122
                s.isTarget = append(s.isTarget, false)
4,171✔
123
        } else {
497,636✔
124
                s.entities[entity.id] = entityIndex{table: table, row: idx}
493,465✔
125
        }
493,465✔
126
        return entity, idx
497,636✔
127
}
128

129
func (s *storage) createEntities(table *table, count int) {
16✔
130
        startIdx := table.Len()
16✔
131
        table.Alloc(uint32(count))
16✔
132

16✔
133
        len := len(s.entities)
16✔
134
        for i := range count {
400✔
135
                index := uint32(startIdx + i)
384✔
136
                entity := s.entityPool.Get()
384✔
137
                table.SetEntity(index, entity)
384✔
138

384✔
139
                if int(entity.id) == len {
768✔
140
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
384✔
141
                        s.isTarget = append(s.isTarget, false)
384✔
142
                        len++
384✔
143
                } else {
384✔
144
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
×
145
                }
×
146
        }
147
}
148

149
func (s *storage) createArchetype(mask *Mask) *archetype {
130✔
150
        comps := mask.toTypes(&s.registry.registry)
130✔
151
        index := len(s.archetypes)
130✔
152
        s.archetypes = append(s.archetypes, newArchetype(archetypeID(index), mask, comps, nil, &s.registry))
130✔
153
        archetype := &s.archetypes[index]
130✔
154
        if archetype.HasRelations() {
153✔
155
                s.relationArchetypes = append(s.relationArchetypes, archetype.id)
23✔
156
        }
23✔
157
        return archetype
130✔
158
}
159

160
func (s *storage) createTable(archetype *archetype, relations []relationID) *table {
164✔
161
        targets := make([]Entity, len(archetype.components))
164✔
162
        numRelations := uint8(0)
164✔
163
        for _, rel := range relations {
227✔
164
                idx := archetype.componentsMap[rel.component.id]
63✔
165
                targets[idx] = rel.target
63✔
166
                numRelations++
63✔
167
        }
63✔
168
        if numRelations != archetype.numRelations {
164✔
169
                panic("relations must be fully specified")
×
170
        }
171

172
        var newTableID tableID
164✔
173
        if id, ok := archetype.GetFreeTable(); ok {
165✔
174
                newTableID = id
1✔
175
                s.tables[newTableID].recycle(targets, relations)
1✔
176
        } else {
164✔
177
                newTableID = tableID(len(s.tables))
163✔
178
                s.tables = append(s.tables, newTable(
163✔
179
                        newTableID, archetype.id, s.initialCapacity, &s.registry,
163✔
180
                        archetype.components, archetype.componentsMap,
163✔
181
                        archetype.isRelation, targets, relations))
163✔
182
        }
163✔
183
        archetype.AddTable(&s.tables[newTableID])
164✔
184

164✔
185
        table := &s.tables[newTableID]
164✔
186
        for i := range s.components {
927✔
187
                id := ID{id: uint8(i)}
763✔
188
                comps := &s.components[i]
763✔
189
                if archetype.mask.Get(id) {
1,350✔
190
                        comps.columns = append(comps.columns, table.GetColumn(id))
587✔
191
                } else {
763✔
192
                        comps.columns = append(comps.columns, nil)
176✔
193
                }
176✔
194
        }
195
        return table
164✔
196
}
197

198
func (s *storage) getExchangeMask(mask *Mask, add []ID, rem []ID) {
612✔
199
        for _, comp := range rem {
1,590✔
200
                if !mask.Get(comp) {
978✔
201
                        panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", s.registry.Types[comp.id]))
×
202
                }
203
                mask.Set(comp, false)
978✔
204
        }
205
        for _, comp := range add {
1,336✔
206
                if mask.Get(comp) {
724✔
207
                        panic(fmt.Sprintf("entity already has component of type %v, can't add", s.registry.Types[comp.id]))
×
208
                }
209
                mask.Set(comp, true)
724✔
210
        }
211
}
212

213
// Removes empty archetypes that have a target relation to the given entity.
214
func (s *storage) cleanupArchetypes(target Entity) {
1✔
215
        newRelations := []relationID{}
1✔
216
        for _, arch := range s.relationArchetypes {
2✔
217
                archetype := &s.archetypes[arch]
1✔
218
                for _, t := range archetype.tables {
3✔
219
                        table := &s.tables[t]
2✔
220

2✔
221
                        foundTarget := false
2✔
222
                        for _, rel := range table.relationIDs {
4✔
223
                                if rel.target.id == target.id {
3✔
224
                                        newRelations = append(newRelations, relationID{component: rel.component, target: Entity{}})
1✔
225
                                        foundTarget = true
1✔
226
                                }
1✔
227
                        }
228
                        if !foundTarget {
3✔
229
                                continue
1✔
230
                        }
231

232
                        allRelations := s.getExchangeTargetsUnchecked(table, newRelations)
1✔
233
                        newTable, ok := archetype.GetTable(s, allRelations)
1✔
234
                        if !ok {
2✔
235
                                newTable = s.createTable(archetype, newRelations)
1✔
236
                        }
1✔
237
                        s.moveEntities(table, newTable)
1✔
238
                        archetype.FreeTable(table.id)
1✔
239

1✔
240
                        newRelations = newRelations[:0]
1✔
241
                }
242
                archetype.RemoveTarget(target)
1✔
243
        }
244
}
245

246
// moveEntities moves all entities from src to dst.
247
func (s *storage) moveEntities(src, dst *table) {
1✔
248
        oldLen := dst.Len()
1✔
249
        dst.AddAll(src)
1✔
250

1✔
251
        newLen := dst.Len()
1✔
252
        newTable := dst.id
1✔
253
        for i := oldLen; i < newLen; i++ {
33✔
254
                entity := dst.GetEntity(uintptr(i))
32✔
255
                s.entities[entity.id] = entityIndex{table: newTable, row: uint32(i)}
32✔
256
        }
32✔
257
        src.Reset()
1✔
258
}
259

260
func (s *storage) getExchangeTargetsUnchecked(oldTable *table, relations []relationID) []relationID {
1✔
261
        targets := make([]Entity, len(oldTable.columns))
1✔
262
        for i := range oldTable.columns {
3✔
263
                targets[i] = oldTable.columns[i].target
2✔
264
        }
2✔
265
        for _, rel := range relations {
2✔
266
                index := oldTable.components[rel.component.id]
1✔
267
                if rel.target == targets[index] {
1✔
268
                        continue
×
269
                }
270
                targets[index] = rel.target
1✔
271
        }
272

273
        result := make([]relationID, 0, len(oldTable.relationIDs))
1✔
274
        for i, e := range targets {
3✔
275
                if !oldTable.columns[i].isRelation {
3✔
276
                        continue
1✔
277
                }
278
                id := oldTable.ids[i]
1✔
279
                result = append(result, relationID{component: id, target: e})
1✔
280
        }
281

282
        return result
1✔
283
}
284

285
func (s *storage) getExchangeTargets(oldTable *table, relations []relationID) ([]relationID, bool) {
42✔
286
        changed := false
42✔
287
        targets := make([]Entity, len(oldTable.columns))
42✔
288
        for i := range oldTable.columns {
145✔
289
                targets[i] = oldTable.columns[i].target
103✔
290
        }
103✔
291
        for _, rel := range relations {
84✔
292
                if !rel.target.IsZero() && !s.entityPool.Alive(rel.target) {
42✔
293
                        panic("can't make a dead entity a relation target")
×
294
                }
295
                index := oldTable.components[rel.component.id]
42✔
296
                if !oldTable.columns[index].isRelation {
42✔
297
                        panic(fmt.Sprintf("component %d is not a relation component", rel.component.id))
×
298
                }
299
                if rel.target == targets[index] {
42✔
300
                        continue
×
301
                }
302
                targets[index] = rel.target
42✔
303
                changed = true
42✔
304
        }
305
        if !changed {
42✔
306
                return nil, false
×
307
        }
×
308

309
        result := make([]relationID, 0, len(oldTable.relationIDs))
42✔
310
        for i, e := range targets {
145✔
311
                if !oldTable.columns[i].isRelation {
163✔
312
                        continue
60✔
313
                }
314
                id := oldTable.ids[i]
43✔
315
                result = append(result, relationID{component: id, target: e})
43✔
316
        }
317

318
        return result, true
42✔
319
}
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