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

mlange-42 / arche / 6783344875

07 Nov 2023 10:42AM CUT coverage: 100.0%. Remained the same
6783344875

push

github

web-flow
upgrade to Go 1.21 (#308)

3837 of 3837 relevant lines covered (100.0%)

83117.77 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,536,132✔
25
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
2,536,132✔
26
}
2,536,132✔
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 {
26,511✔
30
        return a.getLayout(id).Get(index)
26,511✔
31
}
26,511✔
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 {
28,845✔
40
        return a.RelationComponent >= 0
28,845✔
41
}
28,845✔
42

43
// GetLayout returns the column layout for a component.
44
func (a *archetypeAccess) getLayout(id ID) *layout {
90,552✔
45
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id)))
90,552✔
46
}
90,552✔
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 {
26,509✔
56
        if l.pointer == nil {
26,510✔
57
                return nil
1✔
58
        }
1✔
59
        return unsafe.Add(l.pointer, l.itemSize*index)
26,508✔
60
}
61

62
// archetype represents an ECS archetype
63
type archetype struct {
64
        archetypeAccess // Access helper, passed to queries.
65
        *archetypeData
66
        node *archNode // Node in the archetype graph.
67
        len  uint32    // Current number of entities
68
        cap  uint32    // Current capacity
69
}
70

71
type archetypeData struct {
72
        layouts      [MaskTotalBits]layout // Column layouts by ID.
73
        indices      idMap[uint32]         // Mapping from IDs to buffer indices.
74
        buffers      []reflect.Value       // Reflection arrays containing component data.
75
        entityBuffer reflect.Value         // Reflection array containing entity data.
76
        index        int32                 // Index of the archetype in the world.
77
}
78

79
// Init initializes an archetype
80
func (a *archetype) Init(node *archNode, data *archetypeData, index int32, forStorage bool, relation Entity) {
1,445✔
81
        if !node.IsActive {
2,732✔
82
                node.IsActive = true
1,287✔
83
        }
1,287✔
84

85
        a.archetypeData = data
1,445✔
86
        a.buffers = make([]reflect.Value, len(node.Ids))
1,445✔
87
        a.indices = newIDMap[uint32]()
1,445✔
88
        a.index = index
1,445✔
89

1,445✔
90
        cap := 1
1,445✔
91
        if forStorage {
2,772✔
92
                cap = int(node.capacityIncrement)
1,327✔
93
        }
1,327✔
94

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

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

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

1,445✔
117
        a.node = node
1,445✔
118

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

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

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

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

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

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

30,560✔
168
        old := a.len - 1
30,560✔
169

30,560✔
170
        if index != old {
55,791✔
171
                for _, id := range a.node.Ids {
50,609✔
172
                        lay := a.getLayout(id)
25,378✔
173
                        size := lay.itemSize
25,378✔
174
                        if size == 0 {
25,687✔
175
                                continue
309✔
176
                        }
177
                        src := unsafe.Add(lay.pointer, old*size)
25,069✔
178
                        dst := unsafe.Add(lay.pointer, index*size)
25,069✔
179
                        a.copy(src, dst, size)
25,069✔
180
                }
181
        }
182
        a.ZeroAll(old)
30,560✔
183
        a.len--
30,560✔
184

30,560✔
185
        return swapped
30,560✔
186
}
187

188
// ZeroAll resets a block of storage in all buffers.
189
func (a *archetype) ZeroAll(index uint32) {
30,560✔
190
        for _, id := range a.node.Ids {
59,270✔
191
                a.Zero(index, id)
28,710✔
192
        }
28,710✔
193
}
194

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

206
// SetEntity overwrites an entity
207
func (a *archetype) SetEntity(index uint32, entity Entity) {
2,760,838✔
208
        a.addEntity(index, &entity)
2,760,838✔
209
}
2,760,838✔
210

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

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

226
// SetPointer overwrites a component with the data behind the given pointer
227
func (a *archetype) SetPointer(index uint32, id ID, comp unsafe.Pointer) unsafe.Pointer {
7,161✔
228
        lay := a.getLayout(id)
7,161✔
229
        dst := a.Get(index, id)
7,161✔
230
        size := lay.itemSize
7,161✔
231
        if size == 0 {
12,097✔
232
                return dst
4,936✔
233
        }
4,936✔
234

235
        a.copy(comp, dst, size)
2,225✔
236
        return dst
2,225✔
237
}
238

239
// Reset removes all entities and components.
240
//
241
// Does NOT free the reserved memory.
242
func (a *archetype) Reset() {
52,541✔
243
        if a.len == 0 {
77,598✔
244
                return
25,057✔
245
        }
25,057✔
246
        a.len = 0
27,484✔
247
        for _, buf := range a.buffers {
54,989✔
248
                buf.SetZero()
27,505✔
249
        }
27,505✔
250
}
251

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

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

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

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

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

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

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

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

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

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

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

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

328
// extend the memory buffers if necessary for adding an entity.
329
func (a *archetype) extend(by uint32) {
63,046✔
330
        required := a.len + by
63,046✔
331
        if a.cap >= required {
126,043✔
332
                return
62,997✔
333
        }
62,997✔
334
        a.cap = capacityU32(required, a.node.capacityIncrement)
49✔
335

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

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

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

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

30,560✔
366
        if index == old {
35,889✔
367
                return false
5,329✔
368
        }
5,329✔
369

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

25,231✔
374
        return true
25,231✔
375
}
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