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

mlange-42 / ark / 13700334370

06 Mar 2025 01:56PM CUT coverage: 99.317% (-0.06%) from 99.381%
13700334370

Pull #146

github

web-flow
Merge 3975a48d8 into 30636e28f
Pull Request #146: Optimize table operations

87 of 89 new or added lines in 7 files covered. (97.75%)

21 existing lines in 3 files now uncovered.

6252 of 6295 relevant lines covered (99.32%)

29953.43 hits per line

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

97.87
/ecs/storage.go
1
package ecs
2

3
import (
4
        "fmt"
5
        "unsafe"
6
)
7

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

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

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

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

196✔
30
        reg := newComponentRegistry()
196✔
31
        entities := make([]entityIndex, reservedEntities, config.initialCapacity+reservedEntities)
196✔
32
        isTarget := make([]bool, reservedEntities, config.initialCapacity+reservedEntities)
196✔
33
        // Reserved zero and wildcard entities
196✔
34
        for i := range reservedEntities {
588✔
35
                entities[i] = entityIndex{table: maxTableID, row: 0}
392✔
36
        }
392✔
37
        componentsMap := make([]int16, MaskTotalBits)
196✔
38
        for i := range MaskTotalBits {
50,372✔
39
                componentsMap[i] = -1
50,176✔
40
        }
50,176✔
41

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

60
func (s *storage) findOrCreateTable(oldTable *table, add []ID, remove []ID, relations []RelationID) *table {
507,718✔
61
        startNode := s.archetypes[oldTable.archetype].node
507,718✔
62

507,718✔
63
        node := s.graph.Find(startNode, add, remove)
507,718✔
64
        var arch *archetype
507,718✔
65
        if archID, ok := node.GetArchetype(); ok {
1,015,043✔
66
                arch = &s.archetypes[archID]
507,325✔
67
        } else {
507,718✔
68
                arch = s.createArchetype(node)
393✔
69
                node.archetype = arch.id
393✔
70
        }
393✔
71

72
        var allRelations []RelationID
507,718✔
73
        if len(relations) > 0 {
508,376✔
74
                allRelations = appendNew(oldTable.relationIDs, relations...)
658✔
75
        } else {
507,718✔
76
                allRelations = oldTable.relationIDs
507,060✔
77
        }
507,060✔
78
        table, ok := arch.GetTable(s, allRelations)
507,718✔
79
        if !ok {
508,166✔
80
                table = s.createTable(arch, allRelations)
448✔
81
        }
448✔
82
        return table
507,718✔
83
}
84

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

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

505,327✔
100
        swapped := table.Remove(index.row)
505,327✔
101

505,327✔
102
        s.entityPool.Recycle(entity)
505,327✔
103

505,327✔
104
        if swapped {
1,009,171✔
105
                swapEntity := table.GetEntity(uintptr(index.row))
503,844✔
106
                s.entities[swapEntity.id].row = index.row
503,844✔
107
        }
503,844✔
108
        index.table = maxTableID
505,327✔
109

505,327✔
110
        if s.isTarget[entity.id] {
505,330✔
111
                s.cleanupArchetypes(entity)
3✔
112
                s.isTarget[entity.id] = false
3✔
113
        }
3✔
114
}
115

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

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

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

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

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

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

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

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

165
func (s *storage) registerTargets(relations []RelationID) {
507,772✔
166
        for _, rel := range relations {
508,526✔
167
                s.isTarget[rel.target.id] = true
754✔
168
        }
754✔
169
}
170

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

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

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

183
func (s *storage) createEntity(table tableID) (Entity, uint32) {
507,157✔
184
        entity := s.entityPool.Get()
507,157✔
185

507,157✔
186
        idx := s.tables[table].Add(entity)
507,157✔
187
        len := len(s.entities)
507,157✔
188
        if int(entity.id) == len {
512,017✔
189
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
4,860✔
190
                s.isTarget = append(s.isTarget, false)
4,860✔
191
        } else {
507,157✔
192
                s.entities[entity.id] = entityIndex{table: table, row: idx}
502,297✔
193
        }
502,297✔
194
        return entity, idx
507,157✔
195
}
196

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

144✔
201
        len := len(s.entities)
144✔
202
        for i := range count {
2,398✔
203
                index := uint32(startIdx + i)
2,254✔
204
                entity := s.entityPool.Get()
2,254✔
205
                table.SetEntity(index, entity)
2,254✔
206

2,254✔
207
                if int(entity.id) == len {
4,492✔
208
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
2,238✔
209
                        s.isTarget = append(s.isTarget, false)
2,238✔
210
                        len++
2,238✔
211
                } else {
2,254✔
212
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
16✔
213
                }
16✔
214
        }
215
}
216

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

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

245
        var newTableID tableID
471✔
246
        if id, ok := archetype.GetFreeTable(); ok {
473✔
247
                newTableID = id
2✔
248
                s.tables[newTableID].recycle(targets, relations)
2✔
249
        } else {
471✔
250
                newTableID = tableID(len(s.tables))
469✔
251
                cap := s.config.initialCapacity
469✔
252
                if archetype.HasRelations() {
594✔
253
                        cap = s.config.initialCapacityRelations
125✔
254
                }
125✔
255
                s.tables = append(s.tables, newTable(
469✔
256
                        newTableID, archetype.id, uint32(cap), &s.registry,
469✔
257
                        archetype.components, archetype.componentsMap,
469✔
258
                        archetype.isRelation, targets, relations))
469✔
259
        }
260
        archetype.AddTable(&s.tables[newTableID])
471✔
261

471✔
262
        table := &s.tables[newTableID]
471✔
263
        for i := range s.components {
3,000✔
264
                id := ID{id: uint8(i)}
2,529✔
265
                comps := &s.components[i]
2,529✔
266
                if archetype.mask.Get(id) {
4,247✔
267
                        comps.columns = append(comps.columns, table.GetColumn(id))
1,718✔
268
                } else {
2,529✔
269
                        comps.columns = append(comps.columns, nil)
811✔
270
                }
811✔
271
        }
272

273
        s.cache.addTable(s, table)
471✔
274
        return table
471✔
275
}
276

277
func (s *storage) getExchangeMask(mask *Mask, add []ID, rem []ID) {
923✔
278
        for _, comp := range rem {
2,140✔
279
                if !mask.Get(comp) {
1,218✔
280
                        panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", s.registry.Types[comp.id]))
1✔
281
                }
282
                mask.Set(comp, false)
1,216✔
283
        }
284
        for _, comp := range add {
2,609✔
285
                if mask.Get(comp) {
1,688✔
286
                        panic(fmt.Sprintf("entity already has component of type %v, can't add", s.registry.Types[comp.id]))
1✔
287
                }
288
                mask.Set(comp, true)
1,686✔
289
        }
290
}
291

292
// Removes empty archetypes that have a target relation to the given entity.
293
func (s *storage) cleanupArchetypes(target Entity) {
6✔
294
        newRelations := []RelationID{}
6✔
295
        for _, arch := range s.relationArchetypes {
19✔
296
                archetype := &s.archetypes[arch]
13✔
297
                len := len(archetype.tables)
13✔
298
                for i := len - 1; i >= 0; i-- {
33✔
299
                        table := &s.tables[archetype.tables[i]]
20✔
300

20✔
301
                        foundTarget := false
20✔
302
                        for _, rel := range table.relationIDs {
40✔
303
                                if rel.target.id == target.id {
29✔
304
                                        newRelations = append(newRelations, RelationID{component: rel.component, target: Entity{}})
9✔
305
                                        foundTarget = true
9✔
306
                                }
9✔
307
                        }
308
                        if !foundTarget {
31✔
309
                                continue
11✔
310
                        }
311

312
                        if table.Len() > 0 {
10✔
313
                                allRelations := s.getExchangeTargetsUnchecked(table, newRelations)
1✔
314
                                newTable, ok := archetype.GetTable(s, allRelations)
1✔
315
                                if !ok {
2✔
316
                                        newTable = s.createTable(archetype, newRelations)
1✔
317
                                }
1✔
318
                                s.moveEntities(table, newTable, uint32(table.Len()))
1✔
319
                        }
320
                        archetype.FreeTable(table.id)
9✔
321
                        s.cache.removeTable(s, table)
9✔
322

9✔
323
                        newRelations = newRelations[:0]
9✔
324
                }
325
                archetype.RemoveTarget(target)
13✔
326
        }
327
}
328

329
// moveEntities moves all entities from src to dst.
330
func (s *storage) moveEntities(src, dst *table, count uint32) {
10✔
331
        oldLen := dst.Len()
10✔
332
        dst.AddAll(src, count)
10✔
333

10✔
334
        newLen := dst.Len()
10✔
335
        newTable := dst.id
10✔
336
        for i := oldLen; i < newLen; i++ {
258✔
337
                entity := dst.GetEntity(uintptr(i))
248✔
338
                s.entities[entity.id] = entityIndex{table: newTable, row: uint32(i)}
248✔
339
        }
248✔
340
        src.Reset()
10✔
341
}
342

343
func (s *storage) getExchangeTargetsUnchecked(oldTable *table, relations []RelationID) []RelationID {
1✔
344
        targets := make([]Entity, len(oldTable.columns))
1✔
345
        for i := range oldTable.columns {
3✔
346
                targets[i] = oldTable.columns[i].target
2✔
347
        }
2✔
348
        for _, rel := range relations {
2✔
349
                index := oldTable.components[rel.component.id]
1✔
350
                if rel.target == targets[index] {
1✔
UNCOV
351
                        continue
×
352
                }
353
                targets[index] = rel.target
1✔
354
        }
355

356
        result := make([]RelationID, 0, len(oldTable.relationIDs))
1✔
357
        for i, e := range targets {
3✔
358
                if !oldTable.columns[i].isRelation {
3✔
359
                        continue
1✔
360
                }
361
                id := oldTable.ids[i]
1✔
362
                result = append(result, RelationID{component: id, target: e})
1✔
363
        }
364

365
        return result
1✔
366
}
367

368
func (s *storage) getExchangeTargets(oldTable *table, relations []RelationID) ([]RelationID, bool) {
54✔
369
        changed := false
54✔
370
        targets := make([]Entity, len(oldTable.columns))
54✔
371
        for i := range oldTable.columns {
201✔
372
                targets[i] = oldTable.columns[i].target
147✔
373
        }
147✔
374
        for _, rel := range relations {
109✔
375
                // Validity of the target is checked when creating a new table.
55✔
376
                // Whether the component is a relation is checked when creating a new table.
55✔
377
                index := oldTable.components[rel.component.id]
55✔
378
                if rel.target == targets[index] {
55✔
UNCOV
379
                        continue
×
380
                }
381
                targets[index] = rel.target
55✔
382
                changed = true
55✔
383
        }
384
        if !changed {
54✔
UNCOV
385
                return nil, false
×
UNCOV
386
        }
×
387

388
        result := make([]RelationID, 0, len(oldTable.relationIDs))
54✔
389
        for i, e := range targets {
201✔
390
                if !oldTable.columns[i].isRelation {
238✔
391
                        continue
91✔
392
                }
393
                id := oldTable.ids[i]
56✔
394
                result = append(result, RelationID{component: id, target: e})
56✔
395
        }
396

397
        return result, true
54✔
398
}
399

400
func (s *storage) getTables(batch *Batch) []*table {
91✔
401
        tables := []*table{}
91✔
402

91✔
403
        for i := range s.archetypes {
422✔
404
                archetype := &s.archetypes[i]
331✔
405
                if !batch.filter.matches(&archetype.mask) {
486✔
406
                        continue
155✔
407
                }
408

409
                if !archetype.HasRelations() {
340✔
410
                        table := &s.tables[archetype.tables[0]]
164✔
411
                        tables = append(tables, table)
164✔
412
                        continue
164✔
413
                }
414

415
                tableIDs := archetype.GetTables(batch.relations)
12✔
416
                for _, tab := range tableIDs {
27✔
417
                        table := &s.tables[tab]
15✔
418
                        if !table.Matches(batch.relations) {
15✔
UNCOV
419
                                continue
×
420
                        }
421
                        tables = append(tables, table)
15✔
422
                }
423
        }
424
        return tables
91✔
425
}
426

427
func (s *storage) getTableIDs(batch *Batch) []tableID {
33✔
428
        tables := []tableID{}
33✔
429

33✔
430
        for i := range s.archetypes {
99✔
431
                archetype := &s.archetypes[i]
66✔
432
                if !batch.filter.matches(&archetype.mask) {
100✔
433
                        continue
34✔
434
                }
435

436
                if !archetype.HasRelations() {
39✔
437
                        tables = append(tables, archetype.tables[0])
7✔
438
                        continue
7✔
439
                }
440

441
                tableIDs := archetype.GetTables(batch.relations)
25✔
442
                for _, tab := range tableIDs {
84✔
443
                        table := &s.tables[tab]
59✔
444
                        if !table.Matches(batch.relations) {
59✔
UNCOV
445
                                continue
×
446
                        }
447
                        tables = append(tables, tab)
59✔
448
                }
449
        }
450
        return tables
33✔
451
}
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