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

mlange-42 / arche / 4931060884

09 May 2023 10:27PM CUT coverage: 100.0%. Remained the same
4931060884

push

github

GitHub
Use `uint32` instead of `uintptr` for indices and query iteration counters (#283)

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

3757 of 3757 relevant lines covered (100.0%)

84802.02 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 uint32 = uint32(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
// GetEntity returns the entity at the given index
24
func (a *archetypeAccess) GetEntity(index uint32) Entity {
2,534,320✔
25
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
2,534,320✔
26
}
2,534,320✔
27

28
// Get returns the component with the given ID at the given index
29
func (a *archetypeAccess) Get(index uint32, id ID) unsafe.Pointer {
23,507✔
30
        return a.getLayout(id).Get(index)
23,507✔
31
}
23,507✔
32

33
// HasComponent returns whether the archetype contains the given component ID.
34
func (a *archetypeAccess) HasComponent(id ID) bool {
1,773✔
35
        return a.getLayout(id).pointer != nil
1,773✔
36
}
1,773✔
37

38
// HasRelation returns whether the archetype has a relation component.
39
func (a *archetypeAccess) HasRelation() bool {
2,461✔
40
        return a.RelationComponent >= 0
2,461✔
41
}
2,461✔
42

43
// GetLayout returns the column layout for a component.
44
func (a *archetypeAccess) getLayout(id ID) *layout {
86,253✔
45
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id)))
86,253✔
46
}
86,253✔
47

48
// layout specification of a component column.
49
type layout struct {
50
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
51
        itemSize uint32         // Component/step size
52
}
53

54
// Get returns a pointer to the item at the given index.
55
func (l *layout) Get(index uint32) unsafe.Pointer {
23,505✔
56
        if l.pointer == nil {
23,506✔
57
                return nil
1✔
58
        }
1✔
59
        return unsafe.Add(l.pointer, l.itemSize*index)
23,504✔
60
}
61

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

75
// Init initializes an archetype
76
func (a *archetype) Init(node *archNode, index int32, forStorage bool, relation Entity) {
1,392✔
77
        if !node.IsActive {
2,635✔
78
                node.IsActive = true
1,243✔
79
        }
1,243✔
80

81
        a.buffers = make([]reflect.Value, len(node.Ids))
1,392✔
82
        a.layouts = make([]layout, MaskTotalBits)
1,392✔
83
        a.indices = newIDMap[uint32]()
1,392✔
84
        a.index = index
1,392✔
85

1,392✔
86
        cap := 1
1,392✔
87
        if forStorage {
2,684✔
88
                cap = int(node.capacityIncrement)
1,292✔
89
        }
1,292✔
90

91
        for i, id := range node.Ids {
6,872✔
92
                tp := node.Types[i]
5,480✔
93
                size, align := tp.Size(), uintptr(tp.Align())
5,480✔
94
                size = (size + (align - 1)) / align * align
5,480✔
95

5,480✔
96
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
5,480✔
97
                a.layouts[id] = layout{
5,480✔
98
                        a.buffers[i].Addr().UnsafePointer(),
5,480✔
99
                        uint32(size),
5,480✔
100
                }
5,480✔
101
                a.indices.Set(id, uint32(i))
5,480✔
102
        }
5,480✔
103
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,392✔
104

1,392✔
105
        a.archetypeAccess = archetypeAccess{
1,392✔
106
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,392✔
107
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,392✔
108
                Mask:              node.Mask,
1,392✔
109
                RelationTarget:    relation,
1,392✔
110
                RelationComponent: node.Relation,
1,392✔
111
        }
1,392✔
112

1,392✔
113
        a.node = node
1,392✔
114

1,392✔
115
        a.len = 0
1,392✔
116
        a.cap = uint32(cap)
1,392✔
117
}
118

119
// Add adds an entity with optionally zeroed components to the archetype
120
func (a *archetype) Alloc(entity Entity) uint32 {
35,402✔
121
        idx := a.len
35,402✔
122
        a.extend(1)
35,402✔
123
        a.addEntity(idx, &entity)
35,402✔
124
        a.len++
35,402✔
125
        return idx
35,402✔
126
}
35,402✔
127

128
// AllocN allocates storage for the given number of entities.
129
func (a *archetype) AllocN(count uint32) {
27,583✔
130
        a.extend(count)
27,583✔
131
        a.len += count
27,583✔
132
}
27,583✔
133

134
// Add adds an entity with components to the archetype.
135
func (a *archetype) Add(entity Entity, components ...Component) uint32 {
9✔
136
        if len(components) != len(a.node.Ids) {
10✔
137
                panic("Invalid number of components")
1✔
138
        }
139
        idx := a.len
8✔
140

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

157
// Remove removes an entity and its components from the archetype.
158
//
159
// Performs a swap-remove and reports whether a swap was necessary
160
// (i.e. not the last entity that was removed).
161
func (a *archetype) Remove(index uint32) bool {
30,555✔
162
        swapped := a.removeEntity(index)
30,555✔
163

30,555✔
164
        old := a.len - 1
30,555✔
165

30,555✔
166
        if index != old {
55,795✔
167
                for _, id := range a.node.Ids {
50,628✔
168
                        lay := a.getLayout(id)
25,388✔
169
                        size := lay.itemSize
25,388✔
170
                        if size == 0 {
25,697✔
171
                                continue
309✔
172
                        }
173
                        src := unsafe.Add(lay.pointer, old*size)
25,079✔
174
                        dst := unsafe.Add(lay.pointer, index*size)
25,079✔
175
                        a.copy(src, dst, size)
25,079✔
176
                }
177
        }
178
        a.ZeroAll(old)
30,555✔
179
        a.len--
30,555✔
180

30,555✔
181
        return swapped
30,555✔
182
}
183

184
// ZeroAll resets a block of storage in all buffers.
185
func (a *archetype) ZeroAll(index uint32) {
30,555✔
186
        for _, id := range a.node.Ids {
59,262✔
187
                a.Zero(index, id)
28,707✔
188
        }
28,707✔
189
}
190

191
// ZeroAll resets a block of storage in one buffer.
192
func (a *archetype) Zero(index uint32, id ID) {
28,707✔
193
        lay := a.getLayout(id)
28,707✔
194
        size := lay.itemSize
28,707✔
195
        if size == 0 {
31,850✔
196
                return
3,143✔
197
        }
3,143✔
198
        dst := unsafe.Add(lay.pointer, index*size)
25,564✔
199
        a.copy(a.node.zeroPointer, dst, size)
25,564✔
200
}
201

202
// SetEntity overwrites an entity
203
func (a *archetype) SetEntity(index uint32, entity Entity) {
2,758,428✔
204
        a.addEntity(index, &entity)
2,758,428✔
205
}
2,758,428✔
206

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

807✔
217
        src := rValue.UnsafePointer()
807✔
218
        a.copy(src, dst, size)
807✔
219
        return dst
807✔
220
}
221

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

231
        a.copy(comp, dst, size)
1,425✔
232
        return dst
1,425✔
233
}
234

235
// Reset removes all entities and components.
236
//
237
// Does NOT free the reserved memory.
238
func (a *archetype) Reset() {
52,504✔
239
        if a.len == 0 {
77,550✔
240
                return
25,046✔
241
        }
25,046✔
242
        a.len = 0
27,458✔
243
        for _, buf := range a.buffers {
54,929✔
244
                buf.SetZero()
27,471✔
245
        }
27,471✔
246
}
247

248
// Deactivate the archetype for later re-use.
249
func (a *archetype) Deactivate() {
27,414✔
250
        a.Reset()
27,414✔
251
        a.index = -1
27,414✔
252
}
27,414✔
253

254
// Activate reactivates a de-activated archetype.
255
func (a *archetype) Activate(target Entity, index int32) {
27,402✔
256
        a.index = index
27,402✔
257
        a.RelationTarget = target
27,402✔
258
}
27,402✔
259

260
// IsActive returns whether the archetype is active.
261
// Otherwise, it is eligible for re-use.
262
func (a *archetype) IsActive() bool {
5,102,556✔
263
        return a.index >= 0
5,102,556✔
264
}
5,102,556✔
265

266
// Components returns the component IDs for this archetype
267
func (a *archetype) Components() []ID {
2,956✔
268
        return a.node.Ids
2,956✔
269
}
2,956✔
270

271
// Len reports the number of entities in the archetype
272
func (a *archetype) Len() uint32 {
5,261,547✔
273
        return a.len
5,261,547✔
274
}
5,261,547✔
275

276
// Cap reports the current capacity of the archetype
277
func (a *archetype) Cap() uint32 {
5,100,047✔
278
        return a.cap
5,100,047✔
279
}
5,100,047✔
280

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

290
        cap := int(a.Cap())
113✔
291
        memPerEntity := 0
113✔
292
        for _, id := range a.node.Ids {
225✔
293
                lay := a.getLayout(id)
112✔
294
                memPerEntity += int(lay.itemSize)
112✔
295
        }
112✔
296
        memory := cap * (int(entitySize) + memPerEntity)
113✔
297

113✔
298
        return stats.ArchetypeStats{
113✔
299
                IsActive: a.IsActive(),
113✔
300
                Size:     int(a.Len()),
113✔
301
                Capacity: cap,
113✔
302
                Memory:   memory,
113✔
303
        }
113✔
304
}
305

306
// UpdateStats updates statistics for an archetype
307
func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
5,099,924✔
308
        cap := int(a.Cap())
5,099,924✔
309
        memory := cap * (int(entitySize) + node.MemoryPerEntity)
5,099,924✔
310

5,099,924✔
311
        stats.IsActive = a.IsActive()
5,099,924✔
312
        stats.Size = int(a.Len())
5,099,924✔
313
        stats.Capacity = cap
5,099,924✔
314
        stats.Memory = memory
5,099,924✔
315
}
5,099,924✔
316

317
// copy from one pointer to another.
318
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uint32) {
2,871,967✔
319
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
2,871,967✔
320
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
2,871,967✔
321
        copy(dstSlice, srcSlice)
2,871,967✔
322
}
2,871,967✔
323

324
// extend the memory buffers if necessary for adding an entity.
325
func (a *archetype) extend(by uint32) {
62,996✔
326
        required := a.len + by
62,996✔
327
        if a.cap >= required {
125,944✔
328
                return
62,948✔
329
        }
62,948✔
330
        a.cap = capacityU32(required, a.node.capacityIncrement)
48✔
331

48✔
332
        old := a.entityBuffer
48✔
333
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
48✔
334
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
48✔
335
        reflect.Copy(a.entityBuffer, old)
48✔
336

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

350
// Adds an entity at the given index. Does not extend the entity buffer.
351
func (a *archetype) addEntity(index uint32, entity *Entity) {
2,793,838✔
352
        dst := unsafe.Add(a.entityPointer, entitySize*index)
2,793,838✔
353
        src := unsafe.Pointer(entity)
2,793,838✔
354
        a.copy(src, dst, entitySize)
2,793,838✔
355
}
2,793,838✔
356

357
// removeEntity removes an entity from tne archetype.
358
// Components need to be removed separately.
359
func (a *archetype) removeEntity(index uint32) bool {
30,555✔
360
        old := a.len - 1
30,555✔
361

30,555✔
362
        if index == old {
35,870✔
363
                return false
5,315✔
364
        }
5,315✔
365

366
        src := unsafe.Add(a.entityPointer, old*entitySize)
25,240✔
367
        dst := unsafe.Add(a.entityPointer, index*entitySize)
25,240✔
368
        a.copy(src, dst, entitySize)
25,240✔
369

25,240✔
370
        return true
25,240✔
371
}
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