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

mlange-42 / ark / 15069999656

16 May 2025 01:48PM CUT coverage: 99.797%. First build
15069999656

Pull #249

github

web-flow
Merge 611a431d4 into 9f5dff38a
Pull Request #249: Use faster pointer copying for trivial component types

61 of 63 new or added lines in 6 files covered. (96.83%)

8349 of 8366 relevant lines covered (99.8%)

18499.32 hits per line

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

99.55
/ecs/archetype.go
1
package ecs
2

3
import (
4
        "math"
5
        "reflect"
6
        "slices"
7

8
        "github.com/mlange-42/ark/ecs/stats"
9
)
10

11
type archetypeID uint32
12

13
// maxArchetypeID is used as unassigned archetype ID.
14
const maxArchetypeID = math.MaxUint32
15

16
type archetype struct {
17
        components     []ID                     // components IDs of the archetype in arbitrary order
18
        itemSizes      []uint32                 // item size per component index
19
        componentsMap  []int16                  // mapping from component IDs to column indices; -1 indicates none
20
        isRelation     []bool                   // whether columns are relations components, indexed by column index
21
        relationTables []map[entityID]*tableIDs // lookup for relation targets of tables, indexed by column index
22
        tables         []tableID                // all active tables
23
        freeTables     []tableID                // all inactive/free tables
24
        zeroValue      []byte                   // zero value with the size of the largest item type, for fast zeroing
25
        mask           *bitMask
26
        id             archetypeID
27
        node           nodeID
28
        numRelations   uint8 // number of relation components
29
}
30

31
type tableIDs struct {
32
        tables []tableID
33
}
34

35
func newArchetype(id archetypeID, node nodeID, mask *bitMask, components []ID, tables []tableID, reg *componentRegistry) archetype {
742✔
36
        componentsMap := make([]int16, maskTotalBits)
742✔
37
        for i := range maskTotalBits {
190,694✔
38
                componentsMap[i] = -1
189,952✔
39
        }
189,952✔
40

41
        sizes := make([]uint32, len(components))
742✔
42
        var maxSize uintptr = entitySize
742✔
43
        for i, id := range components {
2,692✔
44
                componentsMap[id.id] = int16(i)
1,950✔
45
                tp := reg.Types[id.id]
1,950✔
46

1,950✔
47
                itemSize := sizeOf(tp)
1,950✔
48
                sizes[i] = uint32(itemSize)
1,950✔
49
                if itemSize > maxSize {
2,424✔
50
                        maxSize = itemSize
474✔
51
                }
474✔
52
        }
53
        var zeroValue []byte
742✔
54
        if maxSize > 0 {
1,484✔
55
                zeroValue = make([]byte, maxSize)
742✔
56
        }
742✔
57

58
        numRelations := uint8(0)
742✔
59
        isRelation := make([]bool, len(components))
742✔
60
        relationTables := make([]map[entityID]*tableIDs, len(components))
742✔
61
        for i, id := range components {
2,692✔
62
                if reg.IsRelation[id.id] {
2,015✔
63
                        isRelation[i] = true
65✔
64
                        relationTables[i] = map[entityID]*tableIDs{}
65✔
65
                        numRelations++
65✔
66
                }
65✔
67
        }
68
        return archetype{
742✔
69
                id:             id,
742✔
70
                node:           node,
742✔
71
                mask:           mask,
742✔
72
                components:     components,
742✔
73
                itemSizes:      sizes,
742✔
74
                componentsMap:  componentsMap,
742✔
75
                isRelation:     isRelation,
742✔
76
                tables:         tables,
742✔
77
                numRelations:   numRelations,
742✔
78
                relationTables: relationTables,
742✔
79
                zeroValue:      zeroValue,
742✔
80
        }
742✔
81
}
82

83
func (a *archetype) HasRelations() bool {
511,909✔
84
        return a.numRelations > 0
511,909✔
85
}
511,909✔
86

87
func (a *archetype) GetTable(storage *storage, relations []RelationID) (*table, bool) {
504,891✔
88
        if len(a.tables) == 0 {
505,376✔
89
                return nil, false
485✔
90
        }
485✔
91
        if !a.HasRelations() {
1,006,122✔
92
                return &storage.tables[a.tables[0]], true
501,716✔
93
        }
501,716✔
94
        if uint8(len(relations)) < a.numRelations {
2,691✔
95
                panic("relation targets must be fully specified")
1✔
96
        }
97
        index := a.componentsMap[relations[0].component.id]
2,689✔
98
        tables, ok := a.relationTables[index][relations[0].target.id]
2,689✔
99
        if !ok {
4,778✔
100
                return nil, false
2,089✔
101
        }
2,089✔
102
        for _, t := range tables.tables {
1,238✔
103
                table := &storage.tables[t]
638✔
104
                if table.MatchesExact(relations) {
1,235✔
105
                        return table, true
597✔
106
                }
597✔
107
        }
108
        return nil, false
3✔
109
}
110

111
func (a *archetype) GetTables(relations []RelationID) []tableID {
136✔
112
        if !a.HasRelations() || len(relations) == 0 {
185✔
113
                return a.tables
49✔
114
        }
49✔
115
        index := a.componentsMap[relations[0].component.id]
87✔
116
        if tables, ok := a.relationTables[index][relations[0].target.id]; ok {
174✔
117
                return tables.tables
87✔
118
        }
87✔
119
        return nil
×
120
}
121

122
func (a *archetype) GetFreeTable() (tableID, bool) {
2,577✔
123
        if len(a.freeTables) == 0 {
5,150✔
124
                return 0, false
2,573✔
125
        }
2,573✔
126
        last := len(a.freeTables) - 1
4✔
127
        table := a.freeTables[last]
4✔
128

4✔
129
        a.freeTables = a.freeTables[:last]
4✔
130

4✔
131
        return table, true
4✔
132
}
133

134
func (a *archetype) FreeTable(table tableID) {
15✔
135
        // TODO: can we speed this up for large numbers of relation targets?
15✔
136
        index := slices.Index(a.tables, table)
15✔
137
        last := len(a.tables) - 1
15✔
138

15✔
139
        if index != last {
21✔
140
                a.tables[index], a.tables[last] = a.tables[last], a.tables[index]
6✔
141
        }
6✔
142
        a.tables = a.tables[:last]
15✔
143

15✔
144
        a.freeTables = append(a.freeTables, table)
15✔
145
}
146

147
func (a *archetype) AddTable(table *table) {
2,577✔
148
        a.tables = append(a.tables, table.id)
2,577✔
149
        if !a.HasRelations() {
3,001✔
150
                return
424✔
151
        }
424✔
152

153
        for i := range table.ids {
7,853✔
154
                column := &table.columns[i]
5,700✔
155
                if !column.isRelation {
9,233✔
156
                        continue
3,533✔
157
                }
158
                target := column.target
2,167✔
159
                relations := a.relationTables[i]
2,167✔
160

2,167✔
161
                if tables, ok := relations[target.id]; ok {
2,175✔
162
                        tables.tables = append(tables.tables, table.id)
8✔
163
                } else {
2,167✔
164
                        relations[target.id] = &tableIDs{tables: []tableID{table.id}}
2,159✔
165
                }
2,159✔
166
        }
167
}
168

169
func (a *archetype) RemoveTarget(entity Entity) {
19✔
170
        for i := range a.relationTables {
55✔
171
                if !a.isRelation[i] {
47✔
172
                        continue
11✔
173
                }
174
                delete(a.relationTables[i], entity.id)
25✔
175
        }
176
}
177

178
func (a *archetype) Reset(storage *storage) {
4✔
179
        if !a.HasRelations() {
7✔
180
                storage.tables[a.tables[0]].Reset()
3✔
181
                return
3✔
182
        }
3✔
183

184
        for _, tab := range a.tables {
2✔
185
                table := &storage.tables[tab]
1✔
186
                table.Reset()
1✔
187
        }
1✔
188

189
        for i := len(a.tables) - 1; i >= 0; i-- {
2✔
190
                storage.cache.removeTable(storage, &storage.tables[a.tables[i]])
1✔
191
                a.FreeTable(a.tables[i])
1✔
192
        }
1✔
193

194
        for _, m := range a.relationTables {
3✔
195
                for key := range m {
3✔
196
                        delete(m, key)
1✔
197
                }
1✔
198
        }
199
}
200

201
// Stats generates statistics for an archetype.
202
func (a *archetype) Stats(storage *storage) stats.Archetype {
4✔
203
        ids := a.components
4✔
204
        aTypes := make([]reflect.Type, len(ids))
4✔
205
        for j, id := range ids {
12✔
206
                aTypes[j], _ = storage.registry.ComponentType(id.id)
8✔
207
        }
8✔
208

209
        memPerEntity := int(entitySize)
4✔
210
        intIDs := make([]uint8, len(ids))
4✔
211
        for j, id := range ids {
12✔
212
                intIDs[j] = id.id
8✔
213
                memPerEntity += int(a.itemSizes[j])
8✔
214
        }
8✔
215

216
        cap := 0
4✔
217
        count := 0
4✔
218
        memory := 0
4✔
219
        memoryUsed := 0
4✔
220
        tableStats := make([]stats.Table, len(a.tables))
4✔
221
        for i, id := range a.tables {
7✔
222
                table := &storage.tables[id]
3✔
223
                tableStats[i] = table.Stats(memPerEntity, &storage.registry)
3✔
224
                stats := &tableStats[i]
3✔
225
                cap += stats.Capacity
3✔
226
                count += stats.Size
3✔
227
                memory += stats.Memory
3✔
228
                memoryUsed += stats.MemoryUsed
3✔
229
        }
3✔
230
        for _, id := range a.freeTables {
6✔
231
                table := &storage.tables[id]
2✔
232
                cap += int(table.cap)
2✔
233
                memory += memPerEntity * int(table.cap)
2✔
234
        }
2✔
235

236
        return stats.Archetype{
4✔
237
                FreeTables:      len(a.freeTables),
4✔
238
                NumRelations:    int(a.numRelations),
4✔
239
                ComponentIDs:    intIDs,
4✔
240
                ComponentTypes:  aTypes,
4✔
241
                Memory:          memory,
4✔
242
                MemoryUsed:      memoryUsed,
4✔
243
                MemoryPerEntity: memPerEntity,
4✔
244
                Size:            count,
4✔
245
                Capacity:        cap,
4✔
246
                Tables:          tableStats,
4✔
247
        }
4✔
248
}
249

250
// UpdateStats updates statistics for an archetype.
251
func (a *archetype) UpdateStats(stats *stats.Archetype, storage *storage) {
11✔
252
        tables := a.tables
11✔
253

11✔
254
        cap := 0
11✔
255
        count := 0
11✔
256
        memory := 0
11✔
257
        memoryUsed := 0
11✔
258

11✔
259
        cntOld := int32(len(stats.Tables))
11✔
260
        cntNew := int32(len(tables))
11✔
261
        if cntNew < cntOld {
12✔
262
                stats.Tables = stats.Tables[:cntNew]
1✔
263
                cntOld = cntNew
1✔
264
        }
1✔
265
        var i int32
11✔
266
        for i := range cntOld {
22✔
267
                tableStats := &stats.Tables[i]
11✔
268
                table := &storage.tables[tables[i]]
11✔
269
                table.UpdateStats(stats.MemoryPerEntity, tableStats, &storage.registry)
11✔
270
                cap += tableStats.Capacity
11✔
271
                count += tableStats.Size
11✔
272
                memory += tableStats.Memory
11✔
273
                memoryUsed += tableStats.MemoryUsed
11✔
274
        }
11✔
275
        for i = cntOld; i < cntNew; i++ {
14✔
276
                table := &storage.tables[tables[i]]
3✔
277
                tableStats := table.Stats(stats.MemoryPerEntity, &storage.registry)
3✔
278
                stats.Tables = append(stats.Tables, tableStats)
3✔
279
                cap += tableStats.Capacity
3✔
280
                count += tableStats.Size
3✔
281
                memory += tableStats.Memory
3✔
282
                memoryUsed += tableStats.MemoryUsed
3✔
283
        }
3✔
284
        for _, id := range a.freeTables {
14✔
285
                table := &storage.tables[id]
3✔
286
                cap += int(table.cap)
3✔
287
                memory += stats.MemoryPerEntity * int(table.cap)
3✔
288
        }
3✔
289

290
        stats.FreeTables = len(a.freeTables)
11✔
291
        stats.Capacity = cap
11✔
292
        stats.Size = count
11✔
293
        stats.Memory = memory
11✔
294
        stats.MemoryUsed = memoryUsed
11✔
295
}
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