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

mlange-42 / ark / 13752743521

09 Mar 2025 09:34PM CUT coverage: 99.43%. Remained the same
13752743521

Pull #169

github

web-flow
Merge 6a3e26b95 into c98324113
Pull Request #169: Useful error on incomplete relations

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

6458 of 6495 relevant lines covered (99.43%)

29592.87 hits per line

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

97.8
/ecs/storage.go
1
package ecs
2

3
import (
4
        "unsafe"
5
)
6

7
type storage struct {
8
        config     config
9
        registry   componentRegistry
10
        entities   []entityIndex
11
        isTarget   []bool
12
        entityPool entityPool
13
        graph      graph
14
        cache      cache
15

16
        archetypes         []archetype
17
        relationArchetypes []archetypeID
18
        tables             []table
19
        components         []componentStorage
20
}
21

22
type componentStorage struct {
23
        columns []*column
24
}
25

26
func newStorage(capacity ...int) storage {
208✔
27
        config := newConfig(capacity...)
208✔
28

208✔
29
        reg := newComponentRegistry()
208✔
30
        entities := make([]entityIndex, reservedEntities, config.initialCapacity+reservedEntities)
208✔
31
        isTarget := make([]bool, reservedEntities, config.initialCapacity+reservedEntities)
208✔
32
        // Reserved zero and wildcard entities
208✔
33
        for i := range reservedEntities {
624✔
34
                entities[i] = entityIndex{table: maxTableID, row: 0}
416✔
35
        }
416✔
36
        componentsMap := make([]int16, maskTotalBits)
208✔
37
        for i := range maskTotalBits {
53,456✔
38
                componentsMap[i] = -1
53,248✔
39
        }
53,248✔
40

41
        archetypes := make([]archetype, 0, 128)
208✔
42
        archetypes = append(archetypes, newArchetype(0, 0, &bitMask{}, []ID{}, []tableID{0}, &reg))
208✔
43
        tables := make([]table, 0, 128)
208✔
44
        tables = append(tables, newTable(0, &archetypes[0], uint32(config.initialCapacity), &reg, []Entity{}, []RelationID{}))
208✔
45
        return storage{
208✔
46
                config:     config,
208✔
47
                registry:   reg,
208✔
48
                cache:      newCache(),
208✔
49
                entities:   entities,
208✔
50
                isTarget:   isTarget,
208✔
51
                entityPool: newEntityPool(uint32(config.initialCapacity), reservedEntities),
208✔
52
                graph:      newGraph(),
208✔
53
                archetypes: archetypes,
208✔
54
                tables:     tables,
208✔
55
                components: make([]componentStorage, 0, maskTotalBits),
208✔
56
        }
208✔
57
}
58

59
func (s *storage) findOrCreateTable(oldTable *table, add []ID, remove []ID, relations []RelationID, outMask *bitMask) *table {
528,474✔
60
        startNode := s.archetypes[oldTable.archetype].node
528,474✔
61

528,474✔
62
        node := s.graph.Find(startNode, add, remove, outMask)
528,474✔
63
        var arch *archetype
528,474✔
64
        if archID, ok := node.GetArchetype(); ok {
1,056,537✔
65
                arch = &s.archetypes[archID]
528,063✔
66
        } else {
528,474✔
67
                arch = s.createArchetype(node)
411✔
68
                node.archetype = arch.id
411✔
69
        }
411✔
70

71
        var allRelations []RelationID
528,474✔
72
        if len(relations) > 0 {
529,135✔
73
                allRelations = appendNew(oldTable.relationIDs, relations...)
661✔
74
        } else {
528,474✔
75
                allRelations = oldTable.relationIDs
527,813✔
76
        }
527,813✔
77
        table, ok := arch.GetTable(s, allRelations)
528,474✔
78
        if !ok {
528,942✔
79
                table = s.createTable(arch, allRelations)
468✔
80
        }
468✔
81
        return table
528,473✔
82
}
83

84
func (s *storage) AddComponent(id uint8) {
922✔
85
        if len(s.components) != int(id) {
923✔
86
                panic("components can only be added to a storage sequentially")
1✔
87
        }
88
        s.components = append(s.components, componentStorage{columns: make([]*column, len(s.tables))})
921✔
89
}
90

91
// RemoveEntity removes the given entity from the world.
92
func (s *storage) RemoveEntity(entity Entity) {
525,371✔
93
        if !s.entityPool.Alive(entity) {
525,372✔
94
                panic("can't remove a dead entity")
1✔
95
        }
96
        index := &s.entities[entity.id]
525,370✔
97
        table := &s.tables[index.table]
525,370✔
98

525,370✔
99
        swapped := table.Remove(index.row)
525,370✔
100

525,370✔
101
        s.entityPool.Recycle(entity)
525,370✔
102

525,370✔
103
        if swapped {
1,049,298✔
104
                swapEntity := table.GetEntity(uintptr(index.row))
523,928✔
105
                s.entities[swapEntity.id].row = index.row
523,928✔
106
        }
523,928✔
107
        index.table = maxTableID
525,370✔
108

525,370✔
109
        if s.isTarget[entity.id] {
525,373✔
110
                s.cleanupArchetypes(entity)
3✔
111
                s.isTarget[entity.id] = false
3✔
112
        }
3✔
113
}
114

115
func (s *storage) Reset() {
1✔
116
        s.entities = s.entities[:reservedEntities]
1✔
117
        s.entityPool.Reset()
1✔
118
        s.isTarget = s.isTarget[:reservedEntities]
1✔
119
        s.cache.Reset()
1✔
120

1✔
121
        for i := range s.archetypes {
5✔
122
                s.archetypes[i].Reset(s)
4✔
123
        }
4✔
124
}
125

126
func (s *storage) get(entity Entity, component ID) unsafe.Pointer {
5✔
127
        if !s.entityPool.Alive(entity) {
6✔
128
                panic("can't get component of a dead entity")
1✔
129
        }
130
        return s.getUnchecked(entity, component)
4✔
131
}
132

133
func (s *storage) getUnchecked(entity Entity, component ID) unsafe.Pointer {
5✔
134
        s.checkHasComponent(entity, component)
5✔
135
        index := s.entities[entity.id]
5✔
136
        return s.tables[index.table].Get(component, uintptr(index.row))
5✔
137
}
5✔
138

139
func (s *storage) has(entity Entity, component ID) bool {
17✔
140
        if !s.entityPool.Alive(entity) {
18✔
141
                panic("can't get component of a dead entity")
1✔
142
        }
143
        return s.hasUnchecked(entity, component)
16✔
144
}
145

146
func (s *storage) hasUnchecked(entity Entity, component ID) bool {
18✔
147
        s.checkHasComponent(entity, component)
18✔
148
        index := s.entities[entity.id]
18✔
149
        return s.tables[index.table].Has(component)
18✔
150
}
18✔
151

152
func (s *storage) getRelation(entity Entity, comp ID) Entity {
347✔
153
        if !s.entityPool.Alive(entity) {
348✔
154
                panic("can't get relation for a dead entity")
1✔
155
        }
156
        return s.getRelationUnchecked(entity, comp)
346✔
157
}
158

159
func (s *storage) getRelationUnchecked(entity Entity, comp ID) Entity {
351✔
160
        s.checkHasComponent(entity, comp)
351✔
161
        return s.tables[s.entities[entity.id].table].GetRelation(comp)
351✔
162
}
351✔
163

164
func (s *storage) registerTargets(relations []RelationID) {
528,527✔
165
        for _, rel := range relations {
529,287✔
166
                s.isTarget[rel.target.id] = true
760✔
167
        }
760✔
168
}
169

170
func (s *storage) registerFilter(batch *Batch) cacheID {
32✔
171
        return s.cache.register(s, batch)
32✔
172
}
32✔
173

174
func (s *storage) unregisterFilter(entry cacheID) {
28✔
175
        s.cache.unregister(entry)
28✔
176
}
28✔
177

178
func (s *storage) getRegisteredFilter(id cacheID) *cacheEntry {
28✔
179
        return s.cache.getEntry(id)
28✔
180
}
28✔
181

182
func (s *storage) createEntity(table tableID) (Entity, uint32) {
527,890✔
183
        entity := s.entityPool.Get()
527,890✔
184

527,890✔
185
        idx := s.tables[table].Add(entity)
527,890✔
186
        len := len(s.entities)
527,890✔
187
        if int(entity.id) == len {
532,905✔
188
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
5,015✔
189
                s.isTarget = append(s.isTarget, false)
5,015✔
190
        } else {
527,890✔
191
                s.entities[entity.id] = entityIndex{table: table, row: idx}
522,875✔
192
        }
522,875✔
193
        return entity, idx
527,890✔
194
}
195

196
func (s *storage) createEntities(table *table, count int) {
156✔
197
        startIdx := table.Len()
156✔
198
        table.Alloc(uint32(count))
156✔
199

156✔
200
        len := len(s.entities)
156✔
201
        for i := range count {
3,011✔
202
                index := uint32(startIdx + i)
2,855✔
203
                entity := s.entityPool.Get()
2,855✔
204
                table.SetEntity(index, entity)
2,855✔
205

2,855✔
206
                if int(entity.id) == len {
5,692✔
207
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
2,837✔
208
                        s.isTarget = append(s.isTarget, false)
2,837✔
209
                        len++
2,837✔
210
                } else {
2,855✔
211
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
18✔
212
                }
18✔
213
        }
214
}
215

216
func (s *storage) createArchetype(node *node) *archetype {
411✔
217
        comps := node.mask.toTypes(&s.registry.registry)
411✔
218
        index := len(s.archetypes)
411✔
219
        s.archetypes = append(s.archetypes, newArchetype(archetypeID(index), node.id, &node.mask, comps, nil, &s.registry))
411✔
220
        archetype := &s.archetypes[index]
411✔
221
        if archetype.HasRelations() {
461✔
222
                s.relationArchetypes = append(s.relationArchetypes, archetype.id)
50✔
223
        }
50✔
224
        return archetype
411✔
225
}
226

227
func (s *storage) createTable(archetype *archetype, relations []RelationID) *table {
491✔
228
        targets := make([]Entity, len(archetype.components))
491✔
229
        numRelations := uint8(0)
491✔
230
        for _, rel := range relations {
632✔
231
                idx := archetype.componentsMap[rel.component.id]
141✔
232
                targets[idx] = rel.target
141✔
233
                numRelations++
141✔
234
        }
141✔
235
        if numRelations != archetype.numRelations {
491✔
236
                panic("relations must be fully specified")
×
237
        }
238
        for i := range relations {
632✔
239
                rel := &relations[i]
141✔
240
                s.checkRelationComponent(rel.component)
141✔
241
                s.checkRelationTarget(rel.target)
141✔
242
        }
141✔
243

244
        var newTableID tableID
491✔
245
        if id, ok := archetype.GetFreeTable(); ok {
493✔
246
                newTableID = id
2✔
247
                s.tables[newTableID].recycle(targets, relations)
2✔
248
        } else {
491✔
249
                newTableID = tableID(len(s.tables))
489✔
250
                cap := s.config.initialCapacity
489✔
251
                if archetype.HasRelations() {
617✔
252
                        cap = s.config.initialCapacityRelations
128✔
253
                }
128✔
254
                s.tables = append(s.tables, newTable(
489✔
255
                        newTableID, archetype, uint32(cap), &s.registry,
489✔
256
                        targets, relations))
489✔
257
        }
258
        archetype.AddTable(&s.tables[newTableID])
491✔
259

491✔
260
        table := &s.tables[newTableID]
491✔
261
        for i := range s.components {
3,082✔
262
                id := ID{id: uint8(i)}
2,591✔
263
                comps := &s.components[i]
2,591✔
264
                if archetype.mask.Get(id) {
4,349✔
265
                        comps.columns = append(comps.columns, table.GetColumn(id))
1,758✔
266
                } else {
2,591✔
267
                        comps.columns = append(comps.columns, nil)
833✔
268
                }
833✔
269
        }
270

271
        s.cache.addTable(s, table)
491✔
272
        return table
491✔
273
}
274

275
// Removes empty archetypes that have a target relation to the given entity.
276
func (s *storage) cleanupArchetypes(target Entity) {
9✔
277
        newRelations := []RelationID{}
9✔
278
        for _, arch := range s.relationArchetypes {
25✔
279
                archetype := &s.archetypes[arch]
16✔
280
                len := len(archetype.tables)
16✔
281
                for i := len - 1; i >= 0; i-- {
41✔
282
                        table := &s.tables[archetype.tables[i]]
25✔
283

25✔
284
                        foundTarget := false
25✔
285
                        for _, rel := range table.relationIDs {
55✔
286
                                if rel.target.id == target.id {
42✔
287
                                        newRelations = append(newRelations, RelationID{component: rel.component, target: Entity{}})
12✔
288
                                        foundTarget = true
12✔
289
                                }
12✔
290
                        }
291
                        if !foundTarget {
38✔
292
                                continue
13✔
293
                        }
294

295
                        if table.Len() > 0 {
13✔
296
                                allRelations := s.getExchangeTargetsUnchecked(table, newRelations)
1✔
297
                                newTable, ok := archetype.GetTable(s, allRelations)
1✔
298
                                if !ok {
2✔
299
                                        newTable = s.createTable(archetype, newRelations)
1✔
300
                                }
1✔
301
                                s.moveEntities(table, newTable, uint32(table.Len()))
1✔
302
                        }
303
                        archetype.FreeTable(table.id)
12✔
304
                        s.cache.removeTable(s, table)
12✔
305

12✔
306
                        newRelations = newRelations[:0]
12✔
307
                }
308
                archetype.RemoveTarget(target)
16✔
309
        }
310
}
311

312
// moveEntities moves all entities from src to dst.
313
func (s *storage) moveEntities(src, dst *table, count uint32) {
10✔
314
        oldLen := dst.Len()
10✔
315
        dst.AddAll(src, count)
10✔
316

10✔
317
        newLen := dst.Len()
10✔
318
        newTable := dst.id
10✔
319
        for i := oldLen; i < newLen; i++ {
258✔
320
                entity := dst.GetEntity(uintptr(i))
248✔
321
                s.entities[entity.id] = entityIndex{table: newTable, row: uint32(i)}
248✔
322
        }
248✔
323
        src.Reset()
10✔
324
}
325

326
func (s *storage) getExchangeTargetsUnchecked(oldTable *table, relations []RelationID) []RelationID {
1✔
327
        targets := make([]Entity, len(oldTable.columns))
1✔
328
        for i := range oldTable.columns {
3✔
329
                targets[i] = oldTable.columns[i].target
2✔
330
        }
2✔
331
        for _, rel := range relations {
2✔
332
                index := oldTable.components[rel.component.id]
1✔
333
                if rel.target == targets[index] {
1✔
334
                        continue
×
335
                }
336
                targets[index] = rel.target
1✔
337
        }
338

339
        result := make([]RelationID, 0, len(oldTable.relationIDs))
1✔
340
        for i, e := range targets {
3✔
341
                if !oldTable.columns[i].isRelation {
3✔
342
                        continue
1✔
343
                }
344
                id := oldTable.ids[i]
1✔
345
                result = append(result, RelationID{component: id, target: e})
1✔
346
        }
347

348
        return result
1✔
349
}
350

351
func (s *storage) getExchangeTargets(oldTable *table, relations []RelationID) ([]RelationID, bool) {
54✔
352
        changed := false
54✔
353
        targets := make([]Entity, len(oldTable.columns))
54✔
354
        for i := range oldTable.columns {
201✔
355
                targets[i] = oldTable.columns[i].target
147✔
356
        }
147✔
357
        for _, rel := range relations {
109✔
358
                // Validity of the target is checked when creating a new table.
55✔
359
                // Whether the component is a relation is checked when creating a new table.
55✔
360
                index := oldTable.components[rel.component.id]
55✔
361
                if rel.target == targets[index] {
55✔
362
                        continue
×
363
                }
364
                targets[index] = rel.target
55✔
365
                changed = true
55✔
366
        }
367
        if !changed {
54✔
368
                return nil, false
×
369
        }
×
370

371
        result := make([]RelationID, 0, len(oldTable.relationIDs))
54✔
372
        for i, e := range targets {
201✔
373
                if !oldTable.columns[i].isRelation {
238✔
374
                        continue
91✔
375
                }
376
                id := oldTable.ids[i]
56✔
377
                result = append(result, RelationID{component: id, target: e})
56✔
378
        }
379

380
        return result, true
54✔
381
}
382

383
func (s *storage) getTables(batch *Batch) []*table {
96✔
384
        tables := []*table{}
96✔
385

96✔
386
        for i := range s.archetypes {
447✔
387
                archetype := &s.archetypes[i]
351✔
388
                if !batch.filter.matches(&archetype.mask) {
514✔
389
                        continue
163✔
390
                }
391

392
                if !archetype.HasRelations() {
363✔
393
                        table := &s.tables[archetype.tables[0]]
175✔
394
                        tables = append(tables, table)
175✔
395
                        continue
175✔
396
                }
397

398
                tableIDs := archetype.GetTables(batch.relations)
13✔
399
                for _, tab := range tableIDs {
31✔
400
                        table := &s.tables[tab]
18✔
401
                        if !table.Matches(batch.relations) {
18✔
402
                                continue
×
403
                        }
404
                        tables = append(tables, table)
18✔
405
                }
406
        }
407
        return tables
96✔
408
}
409

410
func (s *storage) getTableIDs(batch *Batch) []tableID {
34✔
411
        tables := []tableID{}
34✔
412

34✔
413
        for i := range s.archetypes {
101✔
414
                archetype := &s.archetypes[i]
67✔
415
                if !batch.filter.matches(&archetype.mask) {
102✔
416
                        continue
35✔
417
                }
418

419
                if !archetype.HasRelations() {
39✔
420
                        tables = append(tables, archetype.tables[0])
7✔
421
                        continue
7✔
422
                }
423

424
                tableIDs := archetype.GetTables(batch.relations)
25✔
425
                for _, tab := range tableIDs {
84✔
426
                        table := &s.tables[tab]
59✔
427
                        if !table.Matches(batch.relations) {
59✔
428
                                continue
×
429
                        }
430
                        tables = append(tables, tab)
59✔
431
                }
432
        }
433
        return tables
34✔
434
}
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