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

mlange-42 / ark / 13621056130

03 Mar 2025 12:46AM CUT coverage: 97.771%. Remained the same
13621056130

Pull #110

github

web-flow
Merge 04fb38b3d into 7f872f0ad
Pull Request #110: Remove initial capacity from examples

5219 of 5338 relevant lines covered (97.77%)

36912.88 hits per line

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

94.81
/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

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 {
179✔
27
        config := newConfig(capacity...)
179✔
28

179✔
29
        reg := newComponentRegistry()
179✔
30
        entities := make([]entityIndex, reservedEntities, config.initialCapacity+reservedEntities)
179✔
31
        isTarget := make([]bool, reservedEntities, config.initialCapacity+reservedEntities)
179✔
32
        // Reserved zero and wildcard entities
179✔
33
        for i := range reservedEntities {
537✔
34
                entities[i] = entityIndex{table: maxTableID, row: 0}
358✔
35
        }
358✔
36
        componentsMap := make([]int16, MaskTotalBits)
179✔
37
        for i := range MaskTotalBits {
46,003✔
38
                componentsMap[i] = -1
45,824✔
39
        }
45,824✔
40

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

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

494,138✔
61
        node := s.graph.Find(startNode, add, remove)
494,138✔
62
        var arch *archetype
494,138✔
63
        if archID, ok := node.GetArchetype(); ok {
987,898✔
64
                arch = &s.archetypes[archID]
493,760✔
65
        } else {
494,138✔
66
                arch = s.createArchetype(node)
378✔
67
                node.archetype = arch.id
378✔
68
        }
378✔
69

70
        allRelation := appendNew(oldTable.relationIDs, relations...)
494,138✔
71
        table, ok := arch.GetTable(s, allRelation)
494,138✔
72
        if !ok {
494,550✔
73
                table = s.createTable(arch, allRelation)
412✔
74
        }
412✔
75
        return table
494,138✔
76
}
77

78
func (s *storage) AddComponent(id uint8) {
839✔
79
        if len(s.components) != int(id) {
839✔
80
                panic("components can only be added to a storage sequentially")
×
81
        }
82
        s.components = append(s.components, componentStorage{columns: make([]*column, len(s.tables))})
839✔
83
}
84

85
// RemoveEntity removes the given entity from the world.
86
func (s *storage) RemoveEntity(entity Entity) {
491,984✔
87
        if !s.entityPool.Alive(entity) {
491,984✔
88
                panic("can't remove a dead entity")
×
89
        }
90
        index := &s.entities[entity.id]
491,984✔
91
        table := &s.tables[index.table]
491,984✔
92

491,984✔
93
        swapped := table.Remove(index.row)
491,984✔
94

491,984✔
95
        s.entityPool.Recycle(entity)
491,984✔
96

491,984✔
97
        if swapped {
982,801✔
98
                swapEntity := table.GetEntity(uintptr(index.row))
490,817✔
99
                s.entities[swapEntity.id].row = index.row
490,817✔
100
        }
490,817✔
101
        index.table = maxTableID
491,984✔
102

491,984✔
103
        if s.isTarget[entity.id] {
491,986✔
104
                s.cleanupArchetypes(entity)
2✔
105
                s.isTarget[entity.id] = false
2✔
106
        }
2✔
107
}
108

109
func (s *storage) Reset() {
1✔
110
        s.entities = s.entities[:reservedEntities]
1✔
111
        s.entityPool.Reset()
1✔
112
        s.isTarget = s.isTarget[:reservedEntities]
1✔
113

1✔
114
        for i := range s.archetypes {
5✔
115
                s.archetypes[i].Reset(s)
4✔
116
        }
4✔
117
}
118

119
func (s *storage) get(entity Entity, component ID) unsafe.Pointer {
5✔
120
        if !s.entityPool.Alive(entity) {
6✔
121
                panic("can't get component of a dead entity")
1✔
122
        }
123
        return s.getUnchecked(entity, component)
4✔
124
}
125

126
func (s *storage) getUnchecked(entity Entity, component ID) unsafe.Pointer {
5✔
127
        s.checkHasComponent(entity, component)
5✔
128
        index := s.entities[entity.id]
5✔
129
        return s.tables[index.table].Get(component, uintptr(index.row))
5✔
130
}
5✔
131

132
func (s *storage) has(entity Entity, component ID) bool {
17✔
133
        if !s.entityPool.Alive(entity) {
18✔
134
                panic("can't get component of a dead entity")
1✔
135
        }
136
        return s.hasUnchecked(entity, component)
16✔
137
}
138

139
func (s *storage) hasUnchecked(entity Entity, component ID) bool {
18✔
140
        s.checkHasComponent(entity, component)
18✔
141
        index := s.entities[entity.id]
18✔
142
        return s.tables[index.table].Has(component)
18✔
143
}
18✔
144

145
func (s *storage) getRelation(entity Entity, comp ID) Entity {
345✔
146
        if !s.entityPool.Alive(entity) {
345✔
147
                panic("can't get relation for a dead entity")
×
148
        }
149
        return s.getRelationUnchecked(entity, comp)
345✔
150
}
151

152
func (s *storage) getRelationUnchecked(entity Entity, comp ID) Entity {
349✔
153
        s.checkHasComponent(entity, comp)
349✔
154
        return s.tables[s.entities[entity.id].table].GetRelation(comp)
349✔
155
}
349✔
156

157
func (s *storage) registerTargets(relations []RelationID) {
494,190✔
158
        for _, rel := range relations {
494,693✔
159
                s.isTarget[rel.target.id] = true
503✔
160
        }
503✔
161
}
162

163
func (s *storage) createEntity(table tableID) (Entity, uint32) {
493,558✔
164
        entity := s.entityPool.Get()
493,558✔
165

493,558✔
166
        idx := s.tables[table].Add(entity)
493,558✔
167
        len := len(s.entities)
493,558✔
168
        if int(entity.id) == len {
498,485✔
169
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
4,927✔
170
                s.isTarget = append(s.isTarget, false)
4,927✔
171
        } else {
493,558✔
172
                s.entities[entity.id] = entityIndex{table: table, row: idx}
488,631✔
173
        }
488,631✔
174
        return entity, idx
493,558✔
175
}
176

177
func (s *storage) createEntities(table *table, count int) {
133✔
178
        startIdx := table.Len()
133✔
179
        table.Alloc(uint32(count))
133✔
180

133✔
181
        len := len(s.entities)
133✔
182
        for i := range count {
2,137✔
183
                index := uint32(startIdx + i)
2,004✔
184
                entity := s.entityPool.Get()
2,004✔
185
                table.SetEntity(index, entity)
2,004✔
186

2,004✔
187
                if int(entity.id) == len {
4,008✔
188
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
2,004✔
189
                        s.isTarget = append(s.isTarget, false)
2,004✔
190
                        len++
2,004✔
191
                } else {
2,004✔
192
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
×
193
                }
×
194
        }
195
}
196

197
func (s *storage) createArchetype(node *node) *archetype {
378✔
198
        comps := node.mask.toTypes(&s.registry.registry)
378✔
199
        index := len(s.archetypes)
378✔
200
        s.archetypes = append(s.archetypes, newArchetype(archetypeID(index), node.id, &node.mask, comps, nil, &s.registry))
378✔
201
        archetype := &s.archetypes[index]
378✔
202
        if archetype.HasRelations() {
415✔
203
                s.relationArchetypes = append(s.relationArchetypes, archetype.id)
37✔
204
        }
37✔
205
        return archetype
378✔
206
}
207

208
func (s *storage) createTable(archetype *archetype, relations []RelationID) *table {
435✔
209
        targets := make([]Entity, len(archetype.components))
435✔
210
        numRelations := uint8(0)
435✔
211
        for _, rel := range relations {
537✔
212
                idx := archetype.componentsMap[rel.component.id]
102✔
213
                targets[idx] = rel.target
102✔
214
                numRelations++
102✔
215
        }
102✔
216
        if numRelations != archetype.numRelations {
435✔
217
                panic("relations must be fully specified")
×
218
        }
219

220
        var newTableID tableID
435✔
221
        if id, ok := archetype.GetFreeTable(); ok {
436✔
222
                newTableID = id
1✔
223
                s.tables[newTableID].recycle(targets, relations)
1✔
224
        } else {
435✔
225
                newTableID = tableID(len(s.tables))
434✔
226
                cap := s.config.initialCapacity
434✔
227
                if archetype.HasRelations() {
527✔
228
                        cap = s.config.initialCapacityRelations
93✔
229
                }
93✔
230
                s.tables = append(s.tables, newTable(
434✔
231
                        newTableID, archetype.id, uint32(cap), &s.registry,
434✔
232
                        archetype.components, archetype.componentsMap,
434✔
233
                        archetype.isRelation, targets, relations))
434✔
234
        }
235
        archetype.AddTable(&s.tables[newTableID])
435✔
236

435✔
237
        table := &s.tables[newTableID]
435✔
238
        for i := range s.components {
2,826✔
239
                id := ID{id: uint8(i)}
2,391✔
240
                comps := &s.components[i]
2,391✔
241
                if archetype.mask.Get(id) {
3,984✔
242
                        comps.columns = append(comps.columns, table.GetColumn(id))
1,593✔
243
                } else {
2,391✔
244
                        comps.columns = append(comps.columns, nil)
798✔
245
                }
798✔
246
        }
247
        return table
435✔
248
}
249

250
func (s *storage) getExchangeMask(mask *Mask, add []ID, rem []ID) {
806✔
251
        for _, comp := range rem {
2,004✔
252
                if !mask.Get(comp) {
1,198✔
253
                        panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", s.registry.Types[comp.id]))
×
254
                }
255
                mask.Set(comp, false)
1,198✔
256
        }
257
        for _, comp := range add {
1,986✔
258
                if mask.Get(comp) {
1,180✔
259
                        panic(fmt.Sprintf("entity already has component of type %v, can't add", s.registry.Types[comp.id]))
×
260
                }
261
                mask.Set(comp, true)
1,180✔
262
        }
263
}
264

265
// Removes empty archetypes that have a target relation to the given entity.
266
func (s *storage) cleanupArchetypes(target Entity) {
2✔
267
        newRelations := []RelationID{}
2✔
268
        for _, arch := range s.relationArchetypes {
4✔
269
                archetype := &s.archetypes[arch]
2✔
270
                for _, t := range archetype.tables {
6✔
271
                        table := &s.tables[t]
4✔
272

4✔
273
                        foundTarget := false
4✔
274
                        for _, rel := range table.relationIDs {
8✔
275
                                if rel.target.id == target.id {
6✔
276
                                        newRelations = append(newRelations, RelationID{component: rel.component, target: Entity{}})
2✔
277
                                        foundTarget = true
2✔
278
                                }
2✔
279
                        }
280
                        if !foundTarget {
6✔
281
                                continue
2✔
282
                        }
283

284
                        allRelations := s.getExchangeTargetsUnchecked(table, newRelations)
2✔
285
                        newTable, ok := archetype.GetTable(s, allRelations)
2✔
286
                        if !ok {
4✔
287
                                newTable = s.createTable(archetype, newRelations)
2✔
288
                        }
2✔
289
                        s.moveEntities(table, newTable, uint32(table.Len()))
2✔
290
                        archetype.FreeTable(table.id)
2✔
291

2✔
292
                        newRelations = newRelations[:0]
2✔
293
                }
294
                archetype.RemoveTarget(target)
2✔
295
        }
296
}
297

298
// moveEntities moves all entities from src to dst.
299
func (s *storage) moveEntities(src, dst *table, count uint32) {
11✔
300
        oldLen := dst.Len()
11✔
301
        dst.AddAll(src, count)
11✔
302

11✔
303
        newLen := dst.Len()
11✔
304
        newTable := dst.id
11✔
305
        for i := oldLen; i < newLen; i++ {
259✔
306
                entity := dst.GetEntity(uintptr(i))
248✔
307
                s.entities[entity.id] = entityIndex{table: newTable, row: uint32(i)}
248✔
308
        }
248✔
309
        src.Reset()
11✔
310
}
311

312
func (s *storage) getExchangeTargetsUnchecked(oldTable *table, relations []RelationID) []RelationID {
2✔
313
        targets := make([]Entity, len(oldTable.columns))
2✔
314
        for i := range oldTable.columns {
6✔
315
                targets[i] = oldTable.columns[i].target
4✔
316
        }
4✔
317
        for _, rel := range relations {
4✔
318
                index := oldTable.components[rel.component.id]
2✔
319
                if rel.target == targets[index] {
2✔
320
                        continue
×
321
                }
322
                targets[index] = rel.target
2✔
323
        }
324

325
        result := make([]RelationID, 0, len(oldTable.relationIDs))
2✔
326
        for i, e := range targets {
6✔
327
                if !oldTable.columns[i].isRelation {
6✔
328
                        continue
2✔
329
                }
330
                id := oldTable.ids[i]
2✔
331
                result = append(result, RelationID{component: id, target: e})
2✔
332
        }
333

334
        return result
2✔
335
}
336

337
func (s *storage) getExchangeTargets(oldTable *table, relations []RelationID) ([]RelationID, bool) {
52✔
338
        changed := false
52✔
339
        targets := make([]Entity, len(oldTable.columns))
52✔
340
        for i := range oldTable.columns {
197✔
341
                targets[i] = oldTable.columns[i].target
145✔
342
        }
145✔
343
        for _, rel := range relations {
105✔
344
                if !rel.target.IsZero() && !s.entityPool.Alive(rel.target) {
53✔
345
                        panic("can't make a dead entity a relation target")
×
346
                }
347
                index := oldTable.components[rel.component.id]
53✔
348
                if !oldTable.columns[index].isRelation {
53✔
349
                        panic(fmt.Sprintf("component %d is not a relation component", rel.component.id))
×
350
                }
351
                if rel.target == targets[index] {
53✔
352
                        continue
×
353
                }
354
                targets[index] = rel.target
53✔
355
                changed = true
53✔
356
        }
357
        if !changed {
52✔
358
                return nil, false
×
359
        }
×
360

361
        result := make([]RelationID, 0, len(oldTable.relationIDs))
52✔
362
        for i, e := range targets {
197✔
363
                if !oldTable.columns[i].isRelation {
236✔
364
                        continue
91✔
365
                }
366
                id := oldTable.ids[i]
54✔
367
                result = append(result, RelationID{component: id, target: e})
54✔
368
        }
369

370
        return result, true
52✔
371
}
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