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

mlange-42 / arche / 4903779843

06 May 2023 09:50PM CUT coverage: 100.0%. Remained the same
4903779843

push

github

GitHub
Optimize archetype iteration (#272)

38 of 38 new or added lines in 3 files covered. (100.0%)

3719 of 3719 relevant lines covered (100.0%)

85808.07 hits per line

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

100.0
/ecs/archetype.go
1
package ecs
2

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

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

11
// layoutSize is the size of an archetype column layout in bytes.
12
var layoutSize = unsafe.Sizeof(layout{})
13

14
// Helper for accessing data from an archetype
15
type archetypeAccess struct {
16
        Mask              Mask           // Archetype's mask
17
        basePointer       unsafe.Pointer // Pointer to the first component column layout.
18
        entityPointer     unsafe.Pointer // Pointer to the entity storage
19
        RelationTarget    Entity
20
        RelationComponent int8
21
}
22

23
// Matches checks if the archetype matches the given mask.
24
func (a *archetype) Matches(f Filter) bool {
51✔
25
        return f.Matches(a.Mask, &a.RelationTarget)
51✔
26
}
51✔
27

28
// GetEntity returns the entity at the given index
29
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
2,534,308✔
30
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
2,534,308✔
31
}
2,534,308✔
32

33
// Get returns the component with the given ID at the given index
34
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
23,507✔
35
        return a.getLayout(id).Get(index)
23,507✔
36
}
23,507✔
37

38
// HasComponent returns whether the archetype contains the given component ID.
39
func (a *archetypeAccess) HasComponent(id ID) bool {
1,773✔
40
        return a.getLayout(id).pointer != nil
1,773✔
41
}
1,773✔
42

43
// HasRelation returns whether the archetype has a relation component.
44
func (a *archetypeAccess) HasRelation() bool {
2,461✔
45
        return a.RelationComponent >= 0
2,461✔
46
}
2,461✔
47

48
// GetLayout returns the column layout for a component.
49
func (a *archetypeAccess) getLayout(id ID) *layout {
86,239✔
50
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
86,239✔
51
}
86,239✔
52

53
// layout specification of a component column.
54
type layout struct {
55
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
56
        itemSize uintptr        // Component/step size
57
}
58

59
// Get returns a pointer to the item at the given index.
60
func (l *layout) Get(index uintptr) unsafe.Pointer {
23,505✔
61
        if l.pointer == nil {
23,506✔
62
                return nil
1✔
63
        }
1✔
64
        return unsafe.Add(l.pointer, l.itemSize*index)
23,504✔
65
}
66

67
// archetype represents an ECS archetype
68
type archetype struct {
69
        archetypeAccess                 // Access helper, passed to queries.
70
        node            *archNode       // Node in the archetype graph.
71
        layouts         []layout        // Column layouts by ID.
72
        indices         idMap[uint32]   // Mapping from IDs to buffer indices.
73
        buffers         []reflect.Value // Reflection arrays containing component data.
74
        entityBuffer    reflect.Value   // Reflection array containing entity data.
75
        index           int32           // Index of the archetype in the world.
76
        len             uint32          // Current number of entities
77
        cap             uint32          // Current capacity
78
}
79

80
// Init initializes an archetype
81
func (a *archetype) Init(node *archNode, index int32, forStorage bool, relation Entity) {
1,386✔
82
        if !node.IsActive {
2,623✔
83
                node.IsActive = true
1,237✔
84
        }
1,237✔
85

86
        a.buffers = make([]reflect.Value, len(node.Ids))
1,386✔
87
        a.layouts = make([]layout, MaskTotalBits)
1,386✔
88
        a.indices = newIDMap[uint32]()
1,386✔
89
        a.index = index
1,386✔
90

1,386✔
91
        cap := 1
1,386✔
92
        if forStorage {
2,674✔
93
                cap = int(node.capacityIncrement)
1,288✔
94
        }
1,288✔
95

96
        for i, id := range node.Ids {
6,862✔
97
                tp := node.Types[i]
5,476✔
98
                size, align := tp.Size(), uintptr(tp.Align())
5,476✔
99
                size = (size + (align - 1)) / align * align
5,476✔
100

5,476✔
101
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
5,476✔
102
                a.layouts[id] = layout{
5,476✔
103
                        a.buffers[i].Addr().UnsafePointer(),
5,476✔
104
                        size,
5,476✔
105
                }
5,476✔
106
                a.indices.Set(id, uint32(i))
5,476✔
107
        }
5,476✔
108
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,386✔
109

1,386✔
110
        a.archetypeAccess = archetypeAccess{
1,386✔
111
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,386✔
112
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,386✔
113
                Mask:              node.Mask,
1,386✔
114
                RelationTarget:    relation,
1,386✔
115
                RelationComponent: node.Relation,
1,386✔
116
        }
1,386✔
117

1,386✔
118
        a.node = node
1,386✔
119

1,386✔
120
        a.len = 0
1,386✔
121
        a.cap = uint32(cap)
1,386✔
122
}
123

124
// Add adds an entity with optionally zeroed components to the archetype
125
func (a *archetype) Alloc(entity Entity) uintptr {
35,397✔
126
        idx := uintptr(a.len)
35,397✔
127
        a.extend(1)
35,397✔
128
        a.addEntity(idx, &entity)
35,397✔
129
        a.len++
35,397✔
130
        return idx
35,397✔
131
}
35,397✔
132

133
// AllocN allocates storage for the given number of entities.
134
func (a *archetype) AllocN(count uint32) {
27,581✔
135
        a.extend(count)
27,581✔
136
        a.len += count
27,581✔
137
}
27,581✔
138

139
// Add adds an entity with components to the archetype.
140
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
141
        if len(components) != len(a.node.Ids) {
10✔
142
                panic("Invalid number of components")
1✔
143
        }
144
        idx := uintptr(a.len)
8✔
145

8✔
146
        a.extend(1)
8✔
147
        a.addEntity(idx, &entity)
8✔
148
        for _, c := range components {
24✔
149
                lay := a.getLayout(c.ID)
16✔
150
                size := lay.itemSize
16✔
151
                if size == 0 {
18✔
152
                        continue
2✔
153
                }
154
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
155
                dst := a.Get(uintptr(idx), c.ID)
14✔
156
                a.copy(src, dst, size)
14✔
157
        }
158
        a.len++
8✔
159
        return idx
8✔
160
}
161

162
// Remove removes an entity and its components from the archetype.
163
//
164
// Performs a swap-remove and reports whether a swap was necessary
165
// (i.e. not the last entity that was removed).
166
func (a *archetype) Remove(index uintptr) bool {
30,553✔
167
        swapped := a.removeEntity(index)
30,553✔
168

30,553✔
169
        old := uintptr(a.len - 1)
30,553✔
170

30,553✔
171
        if index != old {
55,781✔
172
                for _, id := range a.node.Ids {
50,604✔
173
                        lay := a.getLayout(id)
25,376✔
174
                        size := lay.itemSize
25,376✔
175
                        if size == 0 {
25,685✔
176
                                continue
309✔
177
                        }
178
                        src := unsafe.Add(lay.pointer, old*size)
25,067✔
179
                        dst := unsafe.Add(lay.pointer, index*size)
25,067✔
180
                        a.copy(src, dst, size)
25,067✔
181
                }
182
        }
183
        a.ZeroAll(old)
30,553✔
184
        a.len--
30,553✔
185

30,553✔
186
        return swapped
30,553✔
187
}
188

189
// ZeroAll resets a block of storage in all buffers.
190
func (a *archetype) ZeroAll(index uintptr) {
30,553✔
191
        for _, id := range a.node.Ids {
59,258✔
192
                a.Zero(index, id)
28,705✔
193
        }
28,705✔
194
}
195

196
// ZeroAll resets a block of storage in one buffer.
197
func (a *archetype) Zero(index uintptr, id ID) {
28,705✔
198
        lay := a.getLayout(id)
28,705✔
199
        size := lay.itemSize
28,705✔
200
        if size == 0 {
31,847✔
201
                return
3,142✔
202
        }
3,142✔
203
        dst := unsafe.Add(lay.pointer, index*size)
25,563✔
204
        a.copy(a.node.zeroPointer, dst, size)
25,563✔
205
}
206

207
// SetEntity overwrites an entity
208
func (a *archetype) SetEntity(index uintptr, entity Entity) {
2,758,393✔
209
        a.addEntity(index, &entity)
2,758,393✔
210
}
2,758,393✔
211

212
// Set overwrites a component with the data behind the given pointer
213
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
837✔
214
        lay := a.getLayout(id)
837✔
215
        dst := a.Get(index, id)
837✔
216
        size := lay.itemSize
837✔
217
        if size == 0 {
867✔
218
                return dst
30✔
219
        }
30✔
220
        rValue := reflect.ValueOf(comp)
807✔
221

807✔
222
        src := rValue.UnsafePointer()
807✔
223
        a.copy(src, dst, size)
807✔
224
        return dst
807✔
225
}
226

227
// SetPointer overwrites a component with the data behind the given pointer
228
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
5,859✔
229
        lay := a.getLayout(id)
5,859✔
230
        dst := a.Get(index, id)
5,859✔
231
        size := lay.itemSize
5,859✔
232
        if size == 0 {
10,293✔
233
                return dst
4,434✔
234
        }
4,434✔
235

236
        a.copy(comp, dst, size)
1,425✔
237
        return dst
1,425✔
238
}
239

240
// Reset removes all entities and components.
241
//
242
// Does NOT free the reserved memory.
243
func (a *archetype) Reset() {
52,503✔
244
        if a.len == 0 {
77,548✔
245
                return
25,045✔
246
        }
25,045✔
247
        a.len = 0
27,458✔
248
        for _, buf := range a.buffers {
54,929✔
249
                buf.SetZero()
27,471✔
250
        }
27,471✔
251
}
252

253
// Deactivate the archetype for later re-use.
254
func (a *archetype) Deactivate() {
27,413✔
255
        a.Reset()
27,413✔
256
        a.index = -1
27,413✔
257
}
27,413✔
258

259
// Activate reactivates a de-activated archetype.
260
func (a *archetype) Activate(target Entity, index int32) {
27,402✔
261
        a.index = index
27,402✔
262
        a.RelationTarget = target
27,402✔
263
}
27,402✔
264

265
// IsActive returns whether the archetype is active.
266
// Otherwise, it is eligible for re-use.
267
func (a *archetype) IsActive() bool {
5,102,607✔
268
        return a.index >= 0
5,102,607✔
269
}
5,102,607✔
270

271
// Components returns the component IDs for this archetype
272
func (a *archetype) Components() []ID {
2,955✔
273
        return a.node.Ids
2,955✔
274
}
2,955✔
275

276
// Len reports the number of entities in the archetype
277
func (a *archetype) Len() uint32 {
5,261,951✔
278
        return a.len
5,261,951✔
279
}
5,261,951✔
280

281
// Cap reports the current capacity of the archetype
282
func (a *archetype) Cap() uint32 {
5,100,047✔
283
        return a.cap
5,100,047✔
284
}
5,100,047✔
285

286
// Stats generates statistics for an archetype
287
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
113✔
288
        ids := a.Components()
113✔
289
        aCompCount := len(ids)
113✔
290
        aTypes := make([]reflect.Type, aCompCount)
113✔
291
        for j, id := range ids {
225✔
292
                aTypes[j], _ = reg.ComponentType(id)
112✔
293
        }
112✔
294

295
        cap := int(a.Cap())
113✔
296
        memPerEntity := 0
113✔
297
        for _, id := range a.node.Ids {
225✔
298
                lay := a.getLayout(id)
112✔
299
                memPerEntity += int(lay.itemSize)
112✔
300
        }
112✔
301
        memory := cap * (int(entitySize) + memPerEntity)
113✔
302

113✔
303
        return stats.ArchetypeStats{
113✔
304
                IsActive: a.IsActive(),
113✔
305
                Size:     int(a.Len()),
113✔
306
                Capacity: cap,
113✔
307
                Memory:   memory,
113✔
308
        }
113✔
309
}
310

311
// UpdateStats updates statistics for an archetype
312
func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
5,099,924✔
313
        cap := int(a.Cap())
5,099,924✔
314
        memory := cap * (int(entitySize) + node.MemoryPerEntity)
5,099,924✔
315

5,099,924✔
316
        stats.IsActive = a.IsActive()
5,099,924✔
317
        stats.Size = int(a.Len())
5,099,924✔
318
        stats.Capacity = cap
5,099,924✔
319
        stats.Memory = memory
5,099,924✔
320
}
5,099,924✔
321

322
// copy from one pointer to another.
323
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
2,871,902✔
324
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
2,871,902✔
325
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
2,871,902✔
326
        copy(dstSlice, srcSlice)
2,871,902✔
327
}
2,871,902✔
328

329
// extend the memory buffers if necessary for adding an entity.
330
func (a *archetype) extend(by uint32) {
62,989✔
331
        required := a.len + by
62,989✔
332
        if a.cap >= required {
125,931✔
333
                return
62,942✔
334
        }
62,942✔
335
        a.cap = capacityU32(required, a.node.capacityIncrement)
47✔
336

47✔
337
        old := a.entityBuffer
47✔
338
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
47✔
339
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
47✔
340
        reflect.Copy(a.entityBuffer, old)
47✔
341

47✔
342
        for _, id := range a.node.Ids {
101✔
343
                lay := a.getLayout(id)
54✔
344
                if lay.itemSize == 0 {
60✔
345
                        continue
6✔
346
                }
347
                index, _ := a.indices.Get(id)
48✔
348
                old := a.buffers[index]
48✔
349
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
48✔
350
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
48✔
351
                reflect.Copy(a.buffers[index], old)
48✔
352
        }
353
}
354

355
// Adds an entity at the given index. Does not extend the entity buffer.
356
func (a *archetype) addEntity(index uintptr, entity *Entity) {
2,793,798✔
357
        dst := unsafe.Add(a.entityPointer, entitySize*index)
2,793,798✔
358
        src := unsafe.Pointer(entity)
2,793,798✔
359
        a.copy(src, dst, entitySize)
2,793,798✔
360
}
2,793,798✔
361

362
// removeEntity removes an entity from tne archetype.
363
// Components need to be removed separately.
364
func (a *archetype) removeEntity(index uintptr) bool {
30,553✔
365
        old := uintptr(a.len - 1)
30,553✔
366

30,553✔
367
        if index == old {
35,878✔
368
                return false
5,325✔
369
        }
5,325✔
370

371
        src := unsafe.Add(a.entityPointer, old*entitySize)
25,228✔
372
        dst := unsafe.Add(a.entityPointer, index*entitySize)
25,228✔
373
        a.copy(src, dst, entitySize)
25,228✔
374

25,228✔
375
        return true
25,228✔
376
}
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