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

mlange-42 / ark / 13605379170

01 Mar 2025 01:41PM CUT coverage: 97.11%. Remained the same
13605379170

push

github

web-flow
Update README for ark-serde feature (#84)

3831 of 3945 relevant lines covered (97.11%)

46743.07 hits per line

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

94.51
/ecs/storage.go
1
package ecs
2

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

8
type storage struct {
9
        registry   componentRegistry
10
        entities   entities
11
        isTarget   []bool
12
        entityPool entityPool
13

14
        archetypes         []archetype
15
        relationArchetypes []archetypeID
16
        tables             []table
17
        components         []componentStorage
18
        initialCapacity    uint32
19
}
20

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

25
func newStorage(capacity uint32) storage {
119✔
26
        reg := newComponentRegistry()
119✔
27
        entities := make([]entityIndex, reservedEntities, capacity+reservedEntities)
119✔
28
        isTarget := make([]bool, reservedEntities, capacity+reservedEntities)
119✔
29
        // Reserved zero and wildcard entities
119✔
30
        for i := range reservedEntities {
357✔
31
                entities[i] = entityIndex{table: maxTableID, row: 0}
238✔
32
        }
238✔
33
        componentsMap := make([]int16, MaskTotalBits)
119✔
34
        for i := range MaskTotalBits {
30,583✔
35
                componentsMap[i] = -1
30,464✔
36
        }
30,464✔
37

38
        tables := make([]table, 0, 128)
119✔
39
        tables = append(tables, newTable(0, 0, capacity, &reg, []ID{}, componentsMap, []bool{}, []Entity{}, []RelationID{}))
119✔
40
        archetypes := make([]archetype, 0, 128)
119✔
41
        archetypes = append(archetypes, newArchetype(0, &Mask{}, []ID{}, []tableID{0}, &reg))
119✔
42
        return storage{
119✔
43
                registry:        reg,
119✔
44
                entities:        entities,
119✔
45
                isTarget:        isTarget,
119✔
46
                entityPool:      newEntityPool(capacity, reservedEntities),
119✔
47
                archetypes:      archetypes,
119✔
48
                tables:          tables,
119✔
49
                initialCapacity: capacity,
119✔
50
                components:      make([]componentStorage, 0, MaskTotalBits),
119✔
51
        }
119✔
52
}
53

54
func (s *storage) findOrCreateTable(oldTable *table, mask *Mask, relations []RelationID) *table {
495,892✔
55
        // TODO: use archetype graph
495,892✔
56
        var arch *archetype
495,892✔
57
        for i := range s.archetypes {
1,487,599✔
58
                if s.archetypes[i].mask.Equals(mask) {
1,487,457✔
59
                        arch = &s.archetypes[i]
495,750✔
60
                        break
495,750✔
61
                }
62
        }
63
        if arch == nil {
496,034✔
64
                arch = s.createArchetype(mask)
142✔
65
        }
142✔
66
        allRelation := appendNew(oldTable.relationIDs, relations...)
495,892✔
67
        table, ok := arch.GetTable(s, allRelation)
495,892✔
68
        if !ok {
496,058✔
69
                table = s.createTable(arch, allRelation)
166✔
70
        }
166✔
71
        return table
495,892✔
72
}
73

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

81
// RemoveEntity removes the given entity from the world.
82
func (s *storage) RemoveEntity(entity Entity) {
494,381✔
83
        if !s.entityPool.Alive(entity) {
494,381✔
84
                panic("can't remove a dead entity")
×
85
        }
86
        index := &s.entities[entity.id]
494,381✔
87
        table := &s.tables[index.table]
494,381✔
88

494,381✔
89
        swapped := table.Remove(index.row)
494,381✔
90

494,381✔
91
        s.entityPool.Recycle(entity)
494,381✔
92

494,381✔
93
        if swapped {
987,564✔
94
                swapEntity := table.GetEntity(uintptr(index.row))
493,183✔
95
                s.entities[swapEntity.id].row = index.row
493,183✔
96
        }
493,183✔
97
        index.table = maxTableID
494,381✔
98

494,381✔
99
        if s.isTarget[entity.id] {
494,382✔
100
                s.cleanupArchetypes(entity)
1✔
101
                s.isTarget[entity.id] = false
1✔
102
        }
1✔
103
}
104

105
func (s *storage) get(entity Entity, component ID) unsafe.Pointer {
5✔
106
        if !s.entityPool.Alive(entity) {
6✔
107
                panic("can't get component of a dead entity")
1✔
108
        }
109
        return s.getUnchecked(entity, component)
4✔
110
}
111

112
func (s *storage) getUnchecked(entity Entity, component ID) unsafe.Pointer {
5✔
113
        s.checkHasComponent(entity, component)
5✔
114
        index := s.entities[entity.id]
5✔
115
        return s.tables[index.table].Get(component, uintptr(index.row))
5✔
116
}
5✔
117

118
func (s *storage) has(entity Entity, component ID) bool {
17✔
119
        if !s.entityPool.Alive(entity) {
18✔
120
                panic("can't get component of a dead entity")
1✔
121
        }
122
        return s.hasUnchecked(entity, component)
16✔
123
}
124

125
func (s *storage) hasUnchecked(entity Entity, component ID) bool {
18✔
126
        s.checkHasComponent(entity, component)
18✔
127
        index := s.entities[entity.id]
18✔
128
        return s.tables[index.table].Has(component)
18✔
129
}
18✔
130

131
func (s *storage) getRelation(entity Entity, comp ID) Entity {
129✔
132
        if !s.entityPool.Alive(entity) {
129✔
133
                panic("can't get relation for a dead entity")
×
134
        }
135
        return s.getRelationUnchecked(entity, comp)
129✔
136
}
137

138
func (s *storage) getRelationUnchecked(entity Entity, comp ID) Entity {
133✔
139
        s.checkHasComponent(entity, comp)
133✔
140
        return s.tables[s.entities[entity.id].table].GetRelation(comp)
133✔
141
}
133✔
142

143
func (s *storage) registerTargets(relations []RelationID) {
495,935✔
144
        for _, rel := range relations {
496,409✔
145
                s.isTarget[rel.target.id] = true
474✔
146
        }
474✔
147
}
148

149
func (s *storage) createEntity(table tableID) (Entity, uint32) {
495,560✔
150
        entity := s.entityPool.Get()
495,560✔
151

495,560✔
152
        idx := s.tables[table].Add(entity)
495,560✔
153
        len := len(s.entities)
495,560✔
154
        if int(entity.id) == len {
499,815✔
155
                s.entities = append(s.entities, entityIndex{table: table, row: idx})
4,255✔
156
                s.isTarget = append(s.isTarget, false)
4,255✔
157
        } else {
495,560✔
158
                s.entities[entity.id] = entityIndex{table: table, row: idx}
491,305✔
159
        }
491,305✔
160
        return entity, idx
495,560✔
161
}
162

163
func (s *storage) createEntities(table *table, count int) {
16✔
164
        startIdx := table.Len()
16✔
165
        table.Alloc(uint32(count))
16✔
166

16✔
167
        len := len(s.entities)
16✔
168
        for i := range count {
400✔
169
                index := uint32(startIdx + i)
384✔
170
                entity := s.entityPool.Get()
384✔
171
                table.SetEntity(index, entity)
384✔
172

384✔
173
                if int(entity.id) == len {
768✔
174
                        s.entities = append(s.entities, entityIndex{table: table.id, row: index})
384✔
175
                        s.isTarget = append(s.isTarget, false)
384✔
176
                        len++
384✔
177
                } else {
384✔
178
                        s.entities[entity.id] = entityIndex{table: table.id, row: index}
×
179
                }
×
180
        }
181
}
182

183
func (s *storage) createArchetype(mask *Mask) *archetype {
142✔
184
        comps := mask.toTypes(&s.registry.registry)
142✔
185
        index := len(s.archetypes)
142✔
186
        s.archetypes = append(s.archetypes, newArchetype(archetypeID(index), mask, comps, nil, &s.registry))
142✔
187
        archetype := &s.archetypes[index]
142✔
188
        if archetype.HasRelations() {
169✔
189
                s.relationArchetypes = append(s.relationArchetypes, archetype.id)
27✔
190
        }
27✔
191
        return archetype
142✔
192
}
193

194
func (s *storage) createTable(archetype *archetype, relations []RelationID) *table {
179✔
195
        targets := make([]Entity, len(archetype.components))
179✔
196
        numRelations := uint8(0)
179✔
197
        for _, rel := range relations {
251✔
198
                idx := archetype.componentsMap[rel.component.id]
72✔
199
                targets[idx] = rel.target
72✔
200
                numRelations++
72✔
201
        }
72✔
202
        if numRelations != archetype.numRelations {
179✔
203
                panic("relations must be fully specified")
×
204
        }
205

206
        var newTableID tableID
179✔
207
        if id, ok := archetype.GetFreeTable(); ok {
180✔
208
                newTableID = id
1✔
209
                s.tables[newTableID].recycle(targets, relations)
1✔
210
        } else {
179✔
211
                newTableID = tableID(len(s.tables))
178✔
212
                s.tables = append(s.tables, newTable(
178✔
213
                        newTableID, archetype.id, s.initialCapacity, &s.registry,
178✔
214
                        archetype.components, archetype.componentsMap,
178✔
215
                        archetype.isRelation, targets, relations))
178✔
216
        }
178✔
217
        archetype.AddTable(&s.tables[newTableID])
179✔
218

179✔
219
        table := &s.tables[newTableID]
179✔
220
        for i := range s.components {
985✔
221
                id := ID{id: uint8(i)}
806✔
222
                comps := &s.components[i]
806✔
223
                if archetype.mask.Get(id) {
1,426✔
224
                        comps.columns = append(comps.columns, table.GetColumn(id))
620✔
225
                } else {
806✔
226
                        comps.columns = append(comps.columns, nil)
186✔
227
                }
186✔
228
        }
229
        return table
179✔
230
}
231

232
func (s *storage) getExchangeMask(mask *Mask, add []ID, rem []ID) {
646✔
233
        for _, comp := range rem {
1,636✔
234
                if !mask.Get(comp) {
990✔
235
                        panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", s.registry.Types[comp.id]))
×
236
                }
237
                mask.Set(comp, false)
990✔
238
        }
239
        for _, comp := range add {
1,394✔
240
                if mask.Get(comp) {
748✔
241
                        panic(fmt.Sprintf("entity already has component of type %v, can't add", s.registry.Types[comp.id]))
×
242
                }
243
                mask.Set(comp, true)
748✔
244
        }
245
}
246

247
// Removes empty archetypes that have a target relation to the given entity.
248
func (s *storage) cleanupArchetypes(target Entity) {
1✔
249
        newRelations := []RelationID{}
1✔
250
        for _, arch := range s.relationArchetypes {
2✔
251
                archetype := &s.archetypes[arch]
1✔
252
                for _, t := range archetype.tables {
3✔
253
                        table := &s.tables[t]
2✔
254

2✔
255
                        foundTarget := false
2✔
256
                        for _, rel := range table.relationIDs {
4✔
257
                                if rel.target.id == target.id {
3✔
258
                                        newRelations = append(newRelations, RelationID{component: rel.component, target: Entity{}})
1✔
259
                                        foundTarget = true
1✔
260
                                }
1✔
261
                        }
262
                        if !foundTarget {
3✔
263
                                continue
1✔
264
                        }
265

266
                        allRelations := s.getExchangeTargetsUnchecked(table, newRelations)
1✔
267
                        newTable, ok := archetype.GetTable(s, allRelations)
1✔
268
                        if !ok {
2✔
269
                                newTable = s.createTable(archetype, newRelations)
1✔
270
                        }
1✔
271
                        s.moveEntities(table, newTable)
1✔
272
                        archetype.FreeTable(table.id)
1✔
273

1✔
274
                        newRelations = newRelations[:0]
1✔
275
                }
276
                archetype.RemoveTarget(target)
1✔
277
        }
278
}
279

280
// moveEntities moves all entities from src to dst.
281
func (s *storage) moveEntities(src, dst *table) {
1✔
282
        oldLen := dst.Len()
1✔
283
        dst.AddAll(src)
1✔
284

1✔
285
        newLen := dst.Len()
1✔
286
        newTable := dst.id
1✔
287
        for i := oldLen; i < newLen; i++ {
33✔
288
                entity := dst.GetEntity(uintptr(i))
32✔
289
                s.entities[entity.id] = entityIndex{table: newTable, row: uint32(i)}
32✔
290
        }
32✔
291
        src.Reset()
1✔
292
}
293

294
func (s *storage) getExchangeTargetsUnchecked(oldTable *table, relations []RelationID) []RelationID {
1✔
295
        targets := make([]Entity, len(oldTable.columns))
1✔
296
        for i := range oldTable.columns {
3✔
297
                targets[i] = oldTable.columns[i].target
2✔
298
        }
2✔
299
        for _, rel := range relations {
2✔
300
                index := oldTable.components[rel.component.id]
1✔
301
                if rel.target == targets[index] {
1✔
302
                        continue
×
303
                }
304
                targets[index] = rel.target
1✔
305
        }
306

307
        result := make([]RelationID, 0, len(oldTable.relationIDs))
1✔
308
        for i, e := range targets {
3✔
309
                if !oldTable.columns[i].isRelation {
3✔
310
                        continue
1✔
311
                }
312
                id := oldTable.ids[i]
1✔
313
                result = append(result, RelationID{component: id, target: e})
1✔
314
        }
315

316
        return result
1✔
317
}
318

319
func (s *storage) getExchangeTargets(oldTable *table, relations []RelationID) ([]RelationID, bool) {
43✔
320
        changed := false
43✔
321
        targets := make([]Entity, len(oldTable.columns))
43✔
322
        for i := range oldTable.columns {
149✔
323
                targets[i] = oldTable.columns[i].target
106✔
324
        }
106✔
325
        for _, rel := range relations {
87✔
326
                if !rel.target.IsZero() && !s.entityPool.Alive(rel.target) {
44✔
327
                        panic("can't make a dead entity a relation target")
×
328
                }
329
                index := oldTable.components[rel.component.id]
44✔
330
                if !oldTable.columns[index].isRelation {
44✔
331
                        panic(fmt.Sprintf("component %d is not a relation component", rel.component.id))
×
332
                }
333
                if rel.target == targets[index] {
44✔
334
                        continue
×
335
                }
336
                targets[index] = rel.target
44✔
337
                changed = true
44✔
338
        }
339
        if !changed {
43✔
340
                return nil, false
×
341
        }
×
342

343
        result := make([]RelationID, 0, len(oldTable.relationIDs))
43✔
344
        for i, e := range targets {
149✔
345
                if !oldTable.columns[i].isRelation {
167✔
346
                        continue
61✔
347
                }
348
                id := oldTable.ids[i]
45✔
349
                result = append(result, RelationID{component: id, target: e})
45✔
350
        }
351

352
        return result, true
43✔
353
}
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