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

mlange-42 / ark / 13860462434

14 Mar 2025 03:57PM CUT coverage: 99.785%. Remained the same
13860462434

Pull #200

github

web-flow
Merge 6a45aff71 into c4a070bbd
Pull Request #200: Sort struct fields to save some memory

8343 of 8361 relevant lines covered (99.78%)

20356.63 hits per line

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

97.55
/ecs/storage.go
1
package ecs
2

3
import (
4
        "unsafe"
5
)
6

7
type storage struct {
8
        entities           []entityIndex
9
        isTarget           []bool
10
        graph              graph
11
        archetypes         []archetype
12
        relationArchetypes []archetypeID
13
        tables             []table
14
        components         []componentStorage
15
        cache              cache
16
        entityPool         entityPool
17
        registry           componentRegistry
18
        config             config
19
}
20

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

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

252✔
28
        reg := newComponentRegistry()
252✔
29
        entities := make([]entityIndex, reservedEntities, config.initialCapacity+reservedEntities)
252✔
30
        isTarget := make([]bool, reservedEntities, config.initialCapacity+reservedEntities)
252✔
31
        // Reserved zero and wildcard entities
252✔
32
        for i := range reservedEntities {
756✔
33
                entities[i] = entityIndex{table: maxTableID, row: 0}
504✔
34
        }
504✔
35
        componentsMap := make([]int16, maskTotalBits)
252✔
36
        for i := range maskTotalBits {
64,764✔
37
                componentsMap[i] = -1
64,512✔
38
        }
64,512✔
39

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

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

493,503✔
61
        node := s.graph.Find(startNode, add, remove, outMask)
493,503✔
62
        var arch *archetype
493,503✔
63
        if archID, ok := node.GetArchetype(); ok {
986,526✔
64
                arch = &s.archetypes[archID]
493,023✔
65
        } else {
493,503✔
66
                arch = s.createArchetype(node)
480✔
67
                node.archetype = arch.id
480✔
68
        }
480✔
69

70
        var allRelations []RelationID
493,503✔
71
        if len(relations) > 0 {
494,188✔
72
                allRelations = appendNew(oldTable.relationIDs, relations...)
685✔
73
        } else {
493,503✔
74
                allRelations = oldTable.relationIDs
492,818✔
75
        }
492,818✔
76
        table, ok := arch.GetTable(s, allRelations)
493,503✔
77
        if !ok {
494,047✔
78
                table = s.createTable(arch, allRelations)
544✔
79
        }
544✔
80
        return table
493,502✔
81
}
82

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

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

490,787✔
98
        swapped := table.Remove(index.row)
490,787✔
99

490,787✔
100
        s.entityPool.Recycle(entity)
490,787✔
101

490,787✔
102
        if swapped {
980,027✔
103
                swapEntity := table.GetEntity(uintptr(index.row))
489,240✔
104
                s.entities[swapEntity.id].row = index.row
489,240✔
105
        }
489,240✔
106
        index.table = maxTableID
490,787✔
107

490,787✔
108
        if s.isTarget[entity.id] {
490,790✔
109
                s.cleanupArchetypes(entity)
3✔
110
                s.isTarget[entity.id] = false
3✔
111
        }
3✔
112
}
113

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

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

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

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

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

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

150
func (s *storage) getRelation(entity Entity, comp ID) Entity {
455✔
151
        if !s.entityPool.Alive(entity) {
456✔
152
                panic("can't get relation for a dead entity")
1✔
153
        }
154
        return s.getRelationUnchecked(entity, comp)
454✔
155
}
156

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

162
func (s *storage) registerTargets(relations []RelationID) {
493,560✔
163
        for _, rel := range relations {
494,364✔
164
                s.isTarget[rel.target.id] = true
804✔
165
        }
804✔
166
}
167

168
func (s *storage) registerFilter(filter *filter, relations []RelationID) cacheID {
34✔
169
        return s.cache.register(s, filter, relations)
34✔
170
}
34✔
171

172
func (s *storage) unregisterFilter(entry cacheID) {
29✔
173
        s.cache.unregister(entry)
29✔
174
}
29✔
175

176
func (s *storage) getRegisteredFilter(id cacheID) *cacheEntry {
30✔
177
        return s.cache.getEntry(id)
30✔
178
}
30✔
179

180
func (s *storage) createEntity(table tableID) (Entity, uint32) {
492,819✔
181
        entity := s.entityPool.Get()
492,819✔
182

492,819✔
183
        idx := s.tables[table].Add(entity)
492,819✔
184
        len := len(s.entities)
492,819✔
185
        if int(entity.id) == len {
498,447✔
186
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
5,628✔
187
                s.isTarget = append(s.isTarget, false)
5,628✔
188
        } else {
492,819✔
189
                s.entities[entity.id] = entityIndex{table: table, row: idx}
487,191✔
190
        }
487,191✔
191
        return entity, idx
492,819✔
192
}
193

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

196✔
198
        len := len(s.entities)
196✔
199
        for i := range count {
3,857✔
200
                index := uint32(startIdx + i)
3,661✔
201
                entity := s.entityPool.Get()
3,661✔
202
                table.SetEntity(index, entity)
3,661✔
203

3,661✔
204
                if int(entity.id) == len {
7,096✔
205
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
3,435✔
206
                        s.isTarget = append(s.isTarget, false)
3,435✔
207
                        len++
3,435✔
208
                } else {
3,661✔
209
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
226✔
210
                }
226✔
211
        }
212
}
213

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

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

242
        var newTableID tableID
575✔
243
        if id, ok := archetype.GetFreeTable(); ok {
579✔
244
                newTableID = id
4✔
245
                s.tables[newTableID].recycle(targets, relations)
4✔
246
        } else {
575✔
247
                newTableID = tableID(len(s.tables))
571✔
248
                cap := s.config.initialCapacity
571✔
249
                if archetype.HasRelations() {
720✔
250
                        cap = s.config.initialCapacityRelations
149✔
251
                }
149✔
252
                s.tables = append(s.tables, newTable(
571✔
253
                        newTableID, archetype, uint32(cap), &s.registry,
571✔
254
                        targets, relations))
571✔
255
        }
256
        archetype.AddTable(&s.tables[newTableID])
575✔
257

575✔
258
        table := &s.tables[newTableID]
575✔
259
        for i := range s.components {
4,075✔
260
                id := ID{id: uint8(i)}
3,500✔
261
                comps := &s.components[i]
3,500✔
262
                if archetype.mask.Get(id) {
5,873✔
263
                        comps.columns = append(comps.columns, table.GetColumn(id))
2,373✔
264
                } else {
3,500✔
265
                        comps.columns = append(comps.columns, nil)
1,127✔
266
                }
1,127✔
267
        }
268

269
        s.cache.addTable(s, table)
575✔
270
        return table
575✔
271
}
272

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

28✔
282
                        foundTarget := false
28✔
283
                        for _, rel := range table.relationIDs {
64✔
284
                                if rel.target.id == target.id {
50✔
285
                                        newRelations = append(newRelations, RelationID{component: rel.component, target: Entity{}})
14✔
286
                                        foundTarget = true
14✔
287
                                }
14✔
288
                        }
289
                        if !foundTarget {
42✔
290
                                continue
14✔
291
                        }
292

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

14✔
304
                        newRelations = newRelations[:0]
14✔
305
                }
306
                archetype.RemoveTarget(target)
19✔
307
        }
308
}
309

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

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

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

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

346
        return result
1✔
347
}
348

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

369
        result := make([]RelationID, 0, len(oldTable.relationIDs))
62✔
370
        for i, e := range targets {
293✔
371
                if !oldTable.columns[i].isRelation {
398✔
372
                        continue
167✔
373
                }
374
                id := oldTable.ids[i]
64✔
375
                result = append(result, RelationID{component: id, target: e})
64✔
376
        }
377

378
        return result, true
62✔
379
}
380

381
func (s *storage) getTables(batch *Batch) []*table {
119✔
382
        tables := []*table{}
119✔
383

119✔
384
        if batch.cache != maxCacheID {
120✔
385
                cache := s.getRegisteredFilter(batch.cache)
1✔
386
                for _, tableID := range cache.tables {
3✔
387
                        table := &s.tables[tableID]
2✔
388
                        if table.Len() == 0 {
3✔
389
                                continue
1✔
390
                        }
391
                        if !table.Matches(batch.relations) {
1✔
392
                                continue
×
393
                        }
394
                        tables = append(tables, table)
1✔
395
                }
396
                return tables
1✔
397
        }
398

399
        for i := range s.archetypes {
546✔
400
                archetype := &s.archetypes[i]
428✔
401
                if !batch.filter.matches(&archetype.mask) {
628✔
402
                        continue
200✔
403
                }
404

405
                if !archetype.HasRelations() {
438✔
406
                        table := &s.tables[archetype.tables[0]]
210✔
407
                        tables = append(tables, table)
210✔
408
                        continue
210✔
409
                }
410

411
                tableIDs := archetype.GetTables(batch.relations)
18✔
412
                for _, tab := range tableIDs {
42✔
413
                        table := &s.tables[tab]
24✔
414
                        if !table.Matches(batch.relations) {
24✔
415
                                continue
×
416
                        }
417
                        tables = append(tables, table)
24✔
418
                }
419
        }
420
        return tables
118✔
421
}
422

423
func (s *storage) getTableIDs(filter *filter, relations []RelationID) []tableID {
36✔
424
        tables := []tableID{}
36✔
425

36✔
426
        for i := range s.archetypes {
108✔
427
                archetype := &s.archetypes[i]
72✔
428
                if !filter.matches(&archetype.mask) {
109✔
429
                        continue
37✔
430
                }
431

432
                if !archetype.HasRelations() {
45✔
433
                        tables = append(tables, archetype.tables[0])
10✔
434
                        continue
10✔
435
                }
436

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