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

mlange-42 / ark / 13898493716

17 Mar 2025 11:37AM CUT coverage: 99.782%. Remained the same
13898493716

push

github

web-flow
Update how to cite (#209)

8244 of 8262 relevant lines covered (99.78%)

20288.75 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 ID
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
type tableIDs struct {
31
        tables []tableID
32
}
33

34
func newArchetype(id archetypeID, node nodeID, mask *bitMask, components []ID, tables []tableID, reg *componentRegistry) archetype {
734✔
35
        componentsMap := make([]int16, maskTotalBits)
734✔
36
        for i := range maskTotalBits {
188,638✔
37
                componentsMap[i] = -1
187,904✔
38
        }
187,904✔
39

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

1,941✔
46
                itemSize := sizeOf(tp)
1,941✔
47
                sizes[i] = uint32(itemSize)
1,941✔
48
                if itemSize > maxSize {
2,410✔
49
                        maxSize = itemSize
469✔
50
                }
469✔
51
        }
52
        var zeroValue []byte
734✔
53
        if maxSize > 0 {
1,468✔
54
                zeroValue = make([]byte, maxSize)
734✔
55
        }
734✔
56

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

82
func (a *archetype) HasRelations() bool {
502,668✔
83
        return a.numRelations > 0
502,668✔
84
}
502,668✔
85

86
func (a *archetype) GetTable(storage *storage, relations []RelationID) (*table, bool) {
499,669✔
87
        if len(a.tables) == 0 {
500,149✔
88
                return nil, false
480✔
89
        }
480✔
90
        if !a.HasRelations() {
997,688✔
91
                return &storage.tables[a.tables[0]], true
498,499✔
92
        }
498,499✔
93
        if len(relations) != int(a.numRelations) {
691✔
94
                panic("relations must be fully specified")
1✔
95
        }
96
        index := a.componentsMap[relations[0].component.id]
689✔
97
        tables, ok := a.relationTables[index][relations[0].target.id]
689✔
98
        if !ok {
780✔
99
                return nil, false
91✔
100
        }
91✔
101
        for _, t := range tables.tables {
1,234✔
102
                table := &storage.tables[t]
636✔
103
                if table.MatchesExact(relations) {
1,231✔
104
                        return table, true
595✔
105
                }
595✔
106
        }
107
        return nil, false
3✔
108
}
109

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

121
func (a *archetype) GetFreeTable() (tableID, bool) {
574✔
122
        if len(a.freeTables) == 0 {
1,144✔
123
                return 0, false
570✔
124
        }
570✔
125
        last := len(a.freeTables) - 1
4✔
126
        table := a.freeTables[last]
4✔
127

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

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

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

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

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

146
func (a *archetype) AddTable(table *table) {
574✔
147
        a.tables = append(a.tables, table.id)
574✔
148
        if !a.HasRelations() {
995✔
149
                return
421✔
150
        }
421✔
151

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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