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

mlange-42 / arche / 7502900423

12 Jan 2024 01:35PM CUT coverage: 100.0%. Remained the same
7502900423

push

github

web-flow
Component ID getters in world and query copy ID slice (#325)

Both return a copy of the archetype's component IDs slice, for safety.
This means that the result can be manipulated safely,
but also that calling the method may incur some significant cost.

4814 of 4814 relevant lines covered (100.0%)

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

43
// GetLayout returns the column layout for a component.
44
func (a *archetypeAccess) getLayout(id ID) *layout {
90,686✔
45
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id)))
90,686✔
46
}
90,686✔
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,637✔
56
        if l.pointer == nil {
26,638✔
57
                return nil
1✔
58
        }
1✔
59
        return unsafe.Add(l.pointer, l.itemSize*index)
26,636✔
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,459✔
81
        if !node.IsActive {
2,760✔
82
                node.IsActive = true
1,301✔
83
        }
1,301✔
84

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

1,459✔
90
        cap := 1
1,459✔
91
        if forStorage {
2,791✔
92
                cap = int(node.capacityIncrement)
1,332✔
93
        }
1,332✔
94

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

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

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

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

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

123
// Add adds an entity with optionally zeroed components to the archetype
124
func (a *archetype) Alloc(entity Entity) uint32 {
35,442✔
125
        idx := a.len
35,442✔
126
        a.extend(1)
35,442✔
127
        a.addEntity(idx, &entity)
35,442✔
128
        a.len++
35,442✔
129
        return idx
35,442✔
130
}
35,442✔
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,564✔
166
        swapped := a.removeEntity(index)
30,564✔
167

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

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

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

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

195
// ZeroAll resets a block of storage in one buffer.
196
func (a *archetype) Zero(index uint32, id ID) {
28,711✔
197
        lay := a.getLayout(id)
28,711✔
198
        size := lay.itemSize
28,711✔
199
        if size == 0 {
31,857✔
200
                return
3,146✔
201
        }
3,146✔
202
        dst := unsafe.Add(lay.pointer, index*size)
25,565✔
203
        a.copy(a.node.zeroPointer, dst, size)
25,565✔
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,808✔
277
        return a.len
5,261,808✔
278
}
5,261,808✔
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,210✔
323
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
2,875,210✔
324
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
2,875,210✔
325
        copy(dstSlice, srcSlice)
2,875,210✔
326
}
2,875,210✔
327

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

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

52✔
341
        for _, id := range a.node.Ids {
106✔
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,288✔
356
        dst := unsafe.Add(a.entityPointer, entitySize*index)
2,796,288✔
357
        src := unsafe.Pointer(entity)
2,796,288✔
358
        a.copy(src, dst, entitySize)
2,796,288✔
359
}
2,796,288✔
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,564✔
364
        old := a.len - 1
30,564✔
365

30,564✔
366
        if index == old {
35,891✔
367
                return false
5,327✔
368
        }
5,327✔
369

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

25,237✔
374
        return true
25,237✔
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