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

mlange-42 / arche / 13261627439

11 Feb 2025 11:14AM CUT coverage: 100.0%. Remained the same
13261627439

Pull #488

github

web-flow
Merge a604d0914 into a78d11404
Pull Request #488: Switch docs to Hextra theme

6549 of 6549 relevant lines covered (100.0%)

115072.44 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
const layoutChunkSize uint8 = 16
12

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

16
// Helper for accessing data from an archetype
17
type archetypeAccess struct {
18
        basePointer          unsafe.Pointer // Pointer to the first component column layout.
19
        entityPointer        unsafe.Pointer // Pointer to the entity storage
20
        Mask                 Mask           // Archetype's mask
21
        RelationTarget       Entity         // Target entity of the archetype (if it has a relation component)
22
        RelationComponent    ID             // Relation component of the archetype
23
        HasRelationComponent bool           // Whether the archetype has a relation
24
}
25

26
// GetEntity returns the entity at the given index
27
func (a *archetypeAccess) GetEntity(index uint32) Entity {
7,595,356✔
28
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
7,595,356✔
29
}
7,595,356✔
30

31
// Get returns the component with the given ID at the given index
32
func (a *archetypeAccess) Get(index uint32, id ID) unsafe.Pointer {
3,064,638✔
33
        return a.getLayout(id).Get(index)
3,064,638✔
34
}
3,064,638✔
35

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

41
// HasRelation returns whether the archetype has a relation component.
42
func (a *archetypeAccess) HasRelation() bool {
28,924✔
43
        return a.HasRelationComponent
28,924✔
44
}
28,924✔
45

46
// GetLayout returns the column layout for a component.
47
func (a *archetypeAccess) getLayout(id ID) *layout {
6,188,729✔
48
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id.id)))
6,188,729✔
49
}
6,188,729✔
50

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

57
// Get returns a pointer to the item at the given index.
58
func (l *layout) Get(index uint32) unsafe.Pointer {
3,064,670✔
59
        if l.pointer == nil {
3,064,671✔
60
                return nil
1✔
61
        }
1✔
62
        return unsafe.Add(l.pointer, l.itemSize*index)
3,064,669✔
63
}
64

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

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

82
// Init initializes an archetype
83
func (a *archetype) Init(node *archNode, data *archetypeData, index int32, forStorage bool, layouts uint8, relation Entity) {
1,526✔
84
        if !node.IsActive {
2,889✔
85
                node.IsActive = true
1,363✔
86
        }
1,363✔
87

88
        a.archetypeData = data
1,526✔
89
        a.buffers = make([]reflect.Value, len(node.Ids))
1,526✔
90
        a.indices = newIDMap[uint32]()
1,526✔
91
        a.index = index
1,526✔
92
        a.layouts = make([]layout, layouts)
1,526✔
93

1,526✔
94
        cap := 1
1,526✔
95
        if forStorage {
2,906✔
96
                cap = int(node.initialCapacity)
1,380✔
97
        }
1,380✔
98

99
        for i, id := range node.Ids {
7,154✔
100
                tp := node.Types[i]
5,628✔
101
                size, align := tp.Size(), uintptr(tp.Align())
5,628✔
102
                size = (size + (align - 1)) / align * align
5,628✔
103

5,628✔
104
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
5,628✔
105
                a.layouts[id.id] = layout{
5,628✔
106
                        a.buffers[i].Addr().UnsafePointer(),
5,628✔
107
                        uint32(size),
5,628✔
108
                }
5,628✔
109
                a.indices.Set(id.id, uint32(i))
5,628✔
110
        }
5,628✔
111
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,526✔
112

1,526✔
113
        a.archetypeAccess = archetypeAccess{
1,526✔
114
                basePointer:          unsafe.Pointer(&a.layouts[0]),
1,526✔
115
                entityPointer:        a.entityBuffer.Addr().UnsafePointer(),
1,526✔
116
                Mask:                 node.Mask,
1,526✔
117
                RelationTarget:       relation,
1,526✔
118
                RelationComponent:    node.Relation,
1,526✔
119
                HasRelationComponent: node.HasRelation,
1,526✔
120
        }
1,526✔
121

1,526✔
122
        a.node = node
1,526✔
123

1,526✔
124
        a.len = 0
1,526✔
125
        a.cap = uint32(cap)
1,526✔
126
}
127

128
// Add adds an entity with optionally zeroed components to the archetype
129
func (a *archetype) Alloc(entity Entity) uint32 {
1,568,836✔
130
        idx := a.len
1,568,836✔
131
        a.extend(1)
1,568,836✔
132
        a.addEntity(idx, &entity)
1,568,836✔
133
        a.len++
1,568,836✔
134
        return idx
1,568,836✔
135
}
1,568,836✔
136

137
// AllocN allocates storage for the given number of entities.
138
func (a *archetype) AllocN(count uint32) {
27,641✔
139
        a.extend(count)
27,641✔
140
        a.len += count
27,641✔
141
}
27,641✔
142

143
// Remove removes an entity and its components from the archetype.
144
//
145
// Performs a swap-remove and reports whether a swap was necessary
146
// (i.e. not the last entity that was removed).
147
func (a *archetype) Remove(index uint32) bool {
1,561,781✔
148
        swapped := a.removeEntity(index)
1,561,781✔
149

1,561,781✔
150
        old := a.len - 1
1,561,781✔
151

1,561,781✔
152
        if index != old {
2,600,174✔
153
                for _, id := range a.node.Ids {
2,077,032✔
154
                        lay := a.getLayout(id)
1,038,639✔
155
                        size := lay.itemSize
1,038,639✔
156
                        if size == 0 {
1,038,949✔
157
                                continue
310✔
158
                        }
159
                        src := unsafe.Add(lay.pointer, old*size)
1,038,329✔
160
                        dst := unsafe.Add(lay.pointer, index*size)
1,038,329✔
161
                        a.copy(src, dst, size)
1,038,329✔
162
                }
163
        }
164

165
        // Zero the free memory to allow the garbage collector
166
        // to take into account pointers in the removed component.
167
        a.ZeroAll(old)
1,561,781✔
168
        a.len--
1,561,781✔
169

1,561,781✔
170
        return swapped
1,561,781✔
171
}
172

173
// ZeroAll resets a block of storage in all buffers.
174
func (a *archetype) ZeroAll(index uint32) {
1,561,781✔
175
        for _, id := range a.node.Ids {
2,605,835✔
176
                a.Zero(index, id)
1,044,054✔
177
        }
1,044,054✔
178
}
179

180
// Zero resets a block of storage in one buffer.
181
func (a *archetype) Zero(index uint32, id ID) {
1,044,054✔
182
        lay := a.getLayout(id)
1,044,054✔
183
        size := lay.itemSize
1,044,054✔
184
        if size == 0 {
1,047,207✔
185
                return
3,153✔
186
        }
3,153✔
187
        dst := unsafe.Add(lay.pointer, index*size)
1,040,901✔
188
        a.copy(a.node.zeroPointer, dst, size)
1,040,901✔
189
}
190

191
// Zero resets a block of storage in one buffer.
192
func (a *archetype) ZeroRange(start, len uint32, id ID) {
45✔
193
        lay := a.getLayout(id)
45✔
194
        size := lay.itemSize
45✔
195
        if size == 0 {
63✔
196
                return
18✔
197
        }
18✔
198
        var i uint32
27✔
199
        for i = 0; i < len; i++ {
187✔
200
                dst := unsafe.Add(lay.pointer, (i+start)*size)
160✔
201
                a.copy(a.node.zeroPointer, dst, size)
160✔
202
        }
160✔
203
}
204

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

210
// Set overwrites a component with the data behind the given pointer.
211
//
212
// Deprecated: Method is slow and should not be used.
213
func (a *archetype) Set(index uint32, id ID, comp interface{}) unsafe.Pointer {
517,019✔
214
        lay := a.getLayout(id)
517,019✔
215
        dst := a.Get(index, id)
517,019✔
216
        size := lay.itemSize
517,019✔
217
        if size == 0 {
517,092✔
218
                return dst
73✔
219
        }
73✔
220
        rValue := reflect.ValueOf(comp).Elem()
516,946✔
221

516,946✔
222
        valueType := rValue.Type()
516,946✔
223
        valuePtr := reflect.NewAt(valueType, dst)
516,946✔
224
        valuePtr.Elem().Set(rValue)
516,946✔
225

516,946✔
226
        return dst
516,946✔
227
}
228

229
// SetPointer overwrites a component with the data behind the given pointer
230
func (a *archetype) SetPointer(index uint32, id ID, comp unsafe.Pointer) unsafe.Pointer {
6,171✔
231
        lay := a.getLayout(id)
6,171✔
232
        dst := a.Get(index, id)
6,171✔
233
        size := lay.itemSize
6,171✔
234
        if size == 0 {
10,710✔
235
                return dst
4,539✔
236
        }
4,539✔
237

238
        a.copy(comp, dst, size)
1,632✔
239
        return dst
1,632✔
240
}
241

242
// CopyFrom copies an entire component column from another archetype into this one,
243
// starting at startIndex in this one.
244
func (a *archetype) CopyFrom(other *archetype, id ID, startIndex uint32) {
50✔
245
        if !a.Mask.Get(id) {
71✔
246
                return
21✔
247
        }
21✔
248
        lay := a.getLayout(id)
29✔
249
        size := lay.itemSize
29✔
250
        if size == 0 {
41✔
251
                return
12✔
252
        }
12✔
253

254
        otherLay := other.getLayout(id)
17✔
255

17✔
256
        dst := lay.Get(uint32(startIndex))
17✔
257
        src := otherLay.Get(0)
17✔
258

17✔
259
        a.copy(src, dst, size*other.len)
17✔
260
}
261

262
// CopyFrom copies all entities from another archetype into this one,
263
// starting at startIndex in this one.
264
func (a *archetype) CopyEntitiesFrom(other *archetype, startIndex uint32) {
29✔
265
        dst := unsafe.Add(a.entityPointer, startIndex*entitySize)
29✔
266
        src := other.entityPointer
29✔
267

29✔
268
        a.copy(src, dst, entitySize*other.len)
29✔
269
}
29✔
270

271
// Reset removes all entities and components.
272
//
273
// Does NOT free the reserved memory.
274
func (a *archetype) Reset() {
52,555✔
275
        if a.len == 0 {
77,613✔
276
                return
25,058✔
277
        }
25,058✔
278
        if a.len <= 64 { // A coarse estimate where manually zeroing is faster
27,530✔
279
                for _, id := range a.node.Ids {
78✔
280
                        a.ZeroRange(0, a.len, id)
45✔
281
                }
45✔
282
        } else {
27,464✔
283
                for _, buf := range a.buffers {
54,951✔
284
                        buf.SetZero()
27,487✔
285
                }
27,487✔
286
        }
287
        a.len = 0
27,497✔
288
}
289

290
// Deactivate the archetype for later re-use.
291
func (a *archetype) Deactivate() {
27,427✔
292
        a.Reset()
27,427✔
293
        a.index = -1
27,427✔
294
}
27,427✔
295

296
// Activate reactivates a de-activated archetype.
297
func (a *archetype) Activate(target Entity, index int32) {
27,404✔
298
        a.index = index
27,404✔
299
        a.RelationTarget = target
27,404✔
300
}
27,404✔
301

302
func (a *archetype) ExtendLayouts(count uint8) {
8✔
303
        if len(a.layouts) >= int(count) {
10✔
304
                return
2✔
305
        }
2✔
306
        temp := a.layouts
6✔
307
        a.layouts = make([]layout, count)
6✔
308
        copy(a.layouts, temp)
6✔
309
        a.archetypeAccess.basePointer = unsafe.Pointer(&a.layouts[0])
6✔
310
}
311

312
// IsActive returns whether the archetype is active.
313
// Otherwise, it is eligible for re-use.
314
func (a *archetype) IsActive() bool {
5,102,574✔
315
        return a.index >= 0
5,102,574✔
316
}
5,102,574✔
317

318
// Components returns the component IDs for this archetype
319
func (a *archetype) Components() []ID {
519,078✔
320
        return a.node.Ids
519,078✔
321
}
519,078✔
322

323
// Len reports the number of entities in the archetype
324
func (a *archetype) Len() uint32 {
6,797,737✔
325
        return a.len
6,797,737✔
326
}
6,797,737✔
327

328
// Cap reports the current capacity of the archetype
329
func (a *archetype) Cap() uint32 {
5,100,047✔
330
        return a.cap
5,100,047✔
331
}
5,100,047✔
332

333
// Stats generates statistics for an archetype
334
func (a *archetype) Stats(reg *componentRegistry) stats.Archetype {
113✔
335
        ids := a.Components()
113✔
336
        aCompCount := len(ids)
113✔
337
        aTypes := make([]reflect.Type, aCompCount)
113✔
338
        for j, id := range ids {
225✔
339
                aTypes[j], _ = reg.ComponentType(id.id)
112✔
340
        }
112✔
341

342
        cap := int(a.Cap())
113✔
343
        memPerEntity := 0
113✔
344
        for _, id := range a.node.Ids {
225✔
345
                lay := a.getLayout(id)
112✔
346
                memPerEntity += int(lay.itemSize)
112✔
347
        }
112✔
348
        memory := cap * (int(entitySize) + memPerEntity)
113✔
349

113✔
350
        return stats.Archetype{
113✔
351
                IsActive: a.IsActive(),
113✔
352
                Size:     int(a.Len()),
113✔
353
                Capacity: cap,
113✔
354
                Memory:   memory,
113✔
355
        }
113✔
356
}
357

358
// UpdateStats updates statistics for an archetype
359
func (a *archetype) UpdateStats(node *stats.Node, stats *stats.Archetype, reg *componentRegistry) {
5,099,924✔
360
        cap := int(a.Cap())
5,099,924✔
361
        memory := cap * (int(entitySize) + node.MemoryPerEntity)
5,099,924✔
362

5,099,924✔
363
        stats.IsActive = a.IsActive()
5,099,924✔
364
        stats.Size = int(a.Len())
5,099,924✔
365
        stats.Capacity = cap
5,099,924✔
366
        stats.Memory = memory
5,099,924✔
367
}
5,099,924✔
368

369
// copy from one pointer to another.
370
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uint32) {
7,448,179✔
371
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
7,448,179✔
372
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
7,448,179✔
373
        copy(dstSlice, srcSlice)
7,448,179✔
374
}
7,448,179✔
375

376
// extend the memory buffers if necessary for adding an entity.
377
func (a *archetype) extend(by uint32) {
1,596,480✔
378
        required := a.len + by
1,596,480✔
379
        if a.cap >= required {
3,192,899✔
380
                return
1,596,419✔
381
        }
1,596,419✔
382
        for a.cap < required {
144✔
383
                a.cap *= 2
83✔
384
        }
83✔
385
        a.cap = max(a.cap, a.node.initialCapacity)
61✔
386

61✔
387
        old := a.entityBuffer
61✔
388
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
61✔
389
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
61✔
390
        reflect.Copy(a.entityBuffer, old)
61✔
391

61✔
392
        for _, id := range a.node.Ids {
121✔
393
                lay := a.getLayout(id)
60✔
394
                if lay.itemSize == 0 {
68✔
395
                        continue
8✔
396
                }
397
                index, _ := a.indices.Get(id.id)
52✔
398
                old := a.buffers[index]
52✔
399
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
52✔
400
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
52✔
401
                reflect.Copy(a.buffers[index], old)
52✔
402
        }
403
}
404

405
// Adds an entity at the given index. Does not extend the entity buffer.
406
func (a *archetype) addEntity(index uint32, entity *Entity) {
4,328,718✔
407
        dst := unsafe.Add(a.entityPointer, entitySize*index)
4,328,718✔
408
        src := unsafe.Pointer(entity)
4,328,718✔
409
        a.copy(src, dst, entitySize)
4,328,718✔
410
}
4,328,718✔
411

412
// removeEntity removes an entity from tne archetype.
413
// Components need to be removed separately.
414
func (a *archetype) removeEntity(index uint32) bool {
1,561,781✔
415
        old := a.len - 1
1,561,781✔
416

1,561,781✔
417
        if index == old {
2,085,169✔
418
                return false
523,388✔
419
        }
523,388✔
420

421
        src := unsafe.Add(a.entityPointer, old*entitySize)
1,038,393✔
422
        dst := unsafe.Add(a.entityPointer, index*entitySize)
1,038,393✔
423
        a.copy(src, dst, entitySize)
1,038,393✔
424

1,038,393✔
425
        return true
1,038,393✔
426
}
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