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

mlange-42 / ark / 13768706324

10 Mar 2025 03:38PM CUT coverage: 99.437%. Remained the same
13768706324

Pull #179

github

web-flow
Merge 8094be2da into 093db9782
Pull Request #179: Fix false-positive debug checks

6538 of 6575 relevant lines covered (99.44%)

27299.41 hits per line

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

99.53
/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
        id             archetypeID
18
        node           nodeID
19
        mask           bitMask
20
        components     []ID                     // components IDs of the archetype in arbitrary order
21
        itemSizes      []uint32                 // item size per component ID
22
        componentsMap  []int16                  // mapping from component IDs to column indices; -1 indicates none
23
        isRelation     []bool                   // whether columns are relations components, indexed by column index
24
        relationTables []map[entityID]*tableIDs // lookup for relation targets of tables, indexed by column index
25
        tables         []tableID                // all active tables
26
        freeTables     []tableID                // all inactive/free tables
27
        numRelations   uint8                    // number of relation components
28
}
29

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

34
func newArchetype(id archetypeID, node nodeID, mask *bitMask, components []ID, tables []tableID, reg *componentRegistry) archetype {
622✔
35
        componentsMap := make([]int16, maskTotalBits)
622✔
36
        for i := range maskTotalBits {
159,854✔
37
                componentsMap[i] = -1
159,232✔
38
        }
159,232✔
39
        sizes := make([]uint32, len(components))
622✔
40
        for i, id := range components {
2,085✔
41
                componentsMap[id.id] = int16(i)
1,463✔
42
                tp := reg.Types[id.id]
1,463✔
43
                sizes[i] = uint32(sizeOf(tp))
1,463✔
44
        }
1,463✔
45

46
        numRelations := uint8(0)
622✔
47
        isRelation := make([]bool, len(components))
622✔
48
        relationTables := make([]map[entityID]*tableIDs, len(components))
622✔
49
        for i, id := range components {
2,085✔
50
                if reg.IsRelation[id.id] {
1,518✔
51
                        isRelation[i] = true
55✔
52
                        relationTables[i] = map[entityID]*tableIDs{}
55✔
53
                        numRelations++
55✔
54
                }
55✔
55
        }
56
        return archetype{
622✔
57
                id:             id,
622✔
58
                node:           node,
622✔
59
                mask:           *mask,
622✔
60
                components:     components,
622✔
61
                itemSizes:      sizes,
622✔
62
                componentsMap:  componentsMap,
622✔
63
                isRelation:     isRelation,
622✔
64
                tables:         tables,
622✔
65
                numRelations:   numRelations,
622✔
66
                relationTables: relationTables,
622✔
67
        }
622✔
68
}
69

70
func (a *archetype) HasRelations() bool {
500,755✔
71
        return a.numRelations > 0
500,755✔
72
}
500,755✔
73

74
func (a *archetype) GetTable(storage *storage, relations []RelationID) (*table, bool) {
498,040✔
75
        if len(a.tables) == 0 {
498,452✔
76
                return nil, false
412✔
77
        }
412✔
78
        if !a.HasRelations() {
994,588✔
79
                return &storage.tables[a.tables[0]], true
496,960✔
80
        }
496,960✔
81
        if len(relations) != int(a.numRelations) {
669✔
82
                panic("relations must be fully specified")
1✔
83
        }
84
        index := a.componentsMap[relations[0].component.id]
667✔
85
        tables, ok := a.relationTables[index][relations[0].target.id]
667✔
86
        if !ok {
746✔
87
                return nil, false
79✔
88
        }
79✔
89
        for _, t := range tables.tables {
1,195✔
90
                table := &storage.tables[t]
607✔
91
                if table.MatchesExact(relations) {
1,193✔
92
                        return table, true
586✔
93
                }
586✔
94
        }
95
        return nil, false
2✔
96
}
97

98
func (a *archetype) GetTables(relations []RelationID) []tableID {
118✔
99
        if !a.HasRelations() || len(relations) == 0 {
163✔
100
                return a.tables
45✔
101
        }
45✔
102
        index := a.componentsMap[relations[0].component.id]
73✔
103
        if tables, ok := a.relationTables[index][relations[0].target.id]; ok {
146✔
104
                return tables.tables
73✔
105
        }
73✔
106
        return nil
×
107
}
108

109
func (a *archetype) GetFreeTable() (tableID, bool) {
493✔
110
        if len(a.freeTables) == 0 {
982✔
111
                return 0, false
489✔
112
        }
489✔
113
        last := len(a.freeTables) - 1
4✔
114
        table := a.freeTables[last]
4✔
115

4✔
116
        a.freeTables = a.freeTables[:last]
4✔
117

4✔
118
        return table, true
4✔
119
}
120

121
func (a *archetype) FreeTable(table tableID) {
15✔
122
        // TODO: can we speed this up for large numbers of relation targets?
15✔
123
        index := slices.Index(a.tables, table)
15✔
124
        last := len(a.tables) - 1
15✔
125

15✔
126
        if index != last {
21✔
127
                a.tables[index], a.tables[last] = a.tables[last], a.tables[index]
6✔
128
        }
6✔
129
        a.tables = a.tables[:last]
15✔
130

15✔
131
        a.freeTables = append(a.freeTables, table)
15✔
132
}
133

134
func (a *archetype) AddTable(table *table) {
493✔
135
        a.tables = append(a.tables, table.id)
493✔
136
        if !a.HasRelations() {
854✔
137
                return
361✔
138
        }
361✔
139

140
        for i := range table.ids {
619✔
141
                column := &table.columns[i]
487✔
142
                if !column.isRelation {
829✔
143
                        continue
342✔
144
                }
145
                target := column.target
145✔
146
                relations := a.relationTables[i]
145✔
147

145✔
148
                if tables, ok := relations[target.id]; ok {
152✔
149
                        tables.tables = append(tables.tables, table.id)
7✔
150
                } else {
145✔
151
                        relations[target.id] = &tableIDs{tables: []tableID{table.id}}
138✔
152
                }
138✔
153
        }
154
}
155

156
func (a *archetype) RemoveTarget(entity Entity) {
19✔
157
        for i := range a.relationTables {
55✔
158
                if !a.isRelation[i] {
47✔
159
                        continue
11✔
160
                }
161
                delete(a.relationTables[i], entity.id)
25✔
162
        }
163
}
164

165
func (a *archetype) Reset(storage *storage) {
4✔
166
        if !a.HasRelations() {
7✔
167
                storage.tables[a.tables[0]].Reset()
3✔
168
                return
3✔
169
        }
3✔
170

171
        for _, tab := range a.tables {
2✔
172
                table := &storage.tables[tab]
1✔
173
                table.Reset()
1✔
174
        }
1✔
175

176
        for i := len(a.tables) - 1; i >= 0; i-- {
2✔
177
                storage.cache.removeTable(storage, &storage.tables[a.tables[i]])
1✔
178
                a.FreeTable(a.tables[i])
1✔
179
        }
1✔
180

181
        for _, m := range a.relationTables {
3✔
182
                for key := range m {
3✔
183
                        delete(m, key)
1✔
184
                }
1✔
185
        }
186
}
187

188
// Stats generates statistics for an archetype.
189
func (a *archetype) Stats(storage *storage) stats.Archetype {
4✔
190
        ids := a.components
4✔
191
        aCompCount := len(ids)
4✔
192
        aTypes := make([]reflect.Type, aCompCount)
4✔
193
        for j, id := range ids {
12✔
194
                aTypes[j], _ = storage.registry.ComponentType(id.id)
8✔
195
        }
8✔
196

197
        memPerEntity := int(entitySize)
4✔
198
        intIDs := make([]uint8, len(ids))
4✔
199
        for j, id := range ids {
12✔
200
                intIDs[j] = id.id
8✔
201
                memPerEntity += int(a.itemSizes[j])
8✔
202
        }
8✔
203

204
        cap := 0
4✔
205
        count := 0
4✔
206
        memory := 0
4✔
207
        memoryUsed := 0
4✔
208
        tableStats := make([]stats.Table, len(a.tables))
4✔
209
        for i, id := range a.tables {
7✔
210
                table := &storage.tables[id]
3✔
211
                tableStats[i] = table.Stats(memPerEntity, &storage.registry)
3✔
212
                stats := &tableStats[i]
3✔
213
                cap += stats.Capacity
3✔
214
                count += stats.Size
3✔
215
                memory += stats.Memory
3✔
216
                memoryUsed += stats.MemoryUsed
3✔
217
        }
3✔
218
        for _, id := range a.freeTables {
6✔
219
                table := &storage.tables[id]
2✔
220
                cap += int(table.cap)
2✔
221
                memory += memPerEntity * int(table.cap)
2✔
222
        }
2✔
223

224
        return stats.Archetype{
4✔
225
                FreeTables:      len(a.freeTables),
4✔
226
                NumRelations:    int(a.numRelations),
4✔
227
                Components:      aCompCount,
4✔
228
                ComponentIDs:    intIDs,
4✔
229
                ComponentTypes:  aTypes,
4✔
230
                Memory:          memory,
4✔
231
                MemoryUsed:      memoryUsed,
4✔
232
                MemoryPerEntity: memPerEntity,
4✔
233
                Size:            count,
4✔
234
                Capacity:        cap,
4✔
235
                Tables:          tableStats,
4✔
236
        }
4✔
237
}
238

239
// UpdateStats updates statistics for an archetype.
240
func (a *archetype) UpdateStats(stats *stats.Archetype, storage *storage) {
11✔
241
        tables := a.tables
11✔
242

11✔
243
        cap := 0
11✔
244
        count := 0
11✔
245
        memory := 0
11✔
246
        memoryUsed := 0
11✔
247

11✔
248
        cntOld := int32(len(stats.Tables))
11✔
249
        cntNew := int32(len(tables))
11✔
250
        if cntNew < cntOld {
12✔
251
                stats.Tables = stats.Tables[:cntNew]
1✔
252
                cntOld = cntNew
1✔
253
        }
1✔
254
        var i int32
11✔
255
        for i = 0; i < cntOld; i++ {
22✔
256
                tableStats := &stats.Tables[i]
11✔
257
                table := &storage.tables[tables[i]]
11✔
258
                table.UpdateStats(stats.MemoryPerEntity, tableStats, &storage.registry)
11✔
259
                cap += tableStats.Capacity
11✔
260
                count += tableStats.Size
11✔
261
                memory += tableStats.Memory
11✔
262
                memoryUsed += tableStats.MemoryUsed
11✔
263
        }
11✔
264
        for i = cntOld; i < cntNew; i++ {
14✔
265
                table := &storage.tables[tables[i]]
3✔
266
                tableStats := table.Stats(stats.MemoryPerEntity, &storage.registry)
3✔
267
                stats.Tables = append(stats.Tables, tableStats)
3✔
268
                cap += tableStats.Capacity
3✔
269
                count += tableStats.Size
3✔
270
                memory += tableStats.Memory
3✔
271
                memoryUsed += tableStats.MemoryUsed
3✔
272
        }
3✔
273
        for _, id := range a.freeTables {
14✔
274
                table := &storage.tables[id]
3✔
275
                cap += int(table.cap)
3✔
276
                memory += stats.MemoryPerEntity * int(table.cap)
3✔
277
        }
3✔
278

279
        stats.FreeTables = len(a.freeTables)
11✔
280
        stats.Capacity = cap
11✔
281
        stats.Size = count
11✔
282
        stats.Memory = memory
11✔
283
        stats.MemoryUsed = memoryUsed
11✔
284
}
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