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

mlange-42 / arche / 4846030190

30 Apr 2023 07:29PM CUT coverage: 100.0%. Remained the same
4846030190

push

github

GitHub
Lock registered filters, partial recompile (#241)

136 of 136 new or added lines in 2 files covered. (100.0%)

3095 of 3095 relevant lines covered (100.0%)

2585.35 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
// archetypeNode is a node in the archetype graph.
15
type archetypeNode struct {
16
        mask              Mask       // Mask of the archetype
17
        Ids               []ID       // List of component IDs.
18
        archetype         *archetype // The archetype
19
        archetypes        map[Entity]*archetype
20
        TransitionAdd     idMap[*archetypeNode] // Mapping from component ID to add to the resulting archetype
21
        TransitionRemove  idMap[*archetypeNode] // Mapping from component ID to remove to the resulting archetype
22
        relation          int8
23
        zeroValue         []byte         // Used as source for setting storage to zero
24
        zeroPointer       unsafe.Pointer // Points to zeroValue for fast access
25
        capacityIncrement uint32         // Capacity increment
26
}
27

28
// Creates a new archetypeNode
29
func newArchetypeNode(mask Mask, relation int8, capacityIncrement int) archetypeNode {
1,212✔
30
        var arch map[Entity]*archetype
1,212✔
31
        if relation >= 0 {
1,223✔
32
                arch = map[Entity]*archetype{}
11✔
33
        }
11✔
34
        return archetypeNode{
1,212✔
35
                mask:              mask,
1,212✔
36
                archetypes:        arch,
1,212✔
37
                TransitionAdd:     newIDMap[*archetypeNode](),
1,212✔
38
                TransitionRemove:  newIDMap[*archetypeNode](),
1,212✔
39
                relation:          relation,
1,212✔
40
                capacityIncrement: uint32(capacityIncrement),
1,212✔
41
        }
1,212✔
42
}
43

44
func (a *archetypeNode) GetArchetype(id Entity) *archetype {
7,563✔
45
        if a.relation >= 0 {
12,615✔
46
                return a.archetypes[id]
5,052✔
47
        }
5,052✔
48
        return a.archetype
2,511✔
49
}
50

51
func (a *archetypeNode) SetArchetype(id Entity, arch *archetype) {
1,215✔
52
        if a.relation >= 0 {
1,258✔
53
                a.archetypes[id] = arch
43✔
54
        } else {
1,215✔
55
                a.archetype = arch
1,172✔
56
        }
1,172✔
57
}
58

59
// Helper for accessing data from an archetype
60
type archetypeAccess struct {
61
        Mask              Mask           // Archetype's mask
62
        basePointer       unsafe.Pointer // Pointer to the first component column layout.
63
        entityPointer     unsafe.Pointer // Pointer to the entity storage
64
        Relation          Entity
65
        RelationComponent int8
66
}
67

68
// Matches checks if the archetype matches the given mask.
69
func (a *archetype) Matches(f Filter) bool {
3,130✔
70
        return f.Matches(a.Mask, &a.Relation)
3,130✔
71
}
3,130✔
72

73
// GetEntity returns the entity at the given index
74
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
1,688✔
75
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
1,688✔
76
}
1,688✔
77

78
// Get returns the component with the given ID at the given index
79
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
106,872✔
80
        return a.getLayout(id).Get(index)
106,872✔
81
}
106,872✔
82

83
// GetEntity returns the entity at the given index
84
func (a *archetypeAccess) GetRelation() Entity {
21✔
85
        return a.Relation
21✔
86
}
21✔
87

88
// HasComponent returns whether the archetype contains the given component ID
89
func (a *archetypeAccess) HasComponent(id ID) bool {
8,175✔
90
        return a.getLayout(id).pointer != nil
8,175✔
91
}
8,175✔
92

93
// GetLayout returns the column layout for a component.
94
func (a *archetypeAccess) getLayout(id ID) *layout {
121,595✔
95
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
121,595✔
96
}
121,595✔
97

98
// layout specification of a component column.
99
type layout struct {
100
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
101
        itemSize uintptr        // Component/step size
102
}
103

104
// Get returns a pointer to the item at the given index.
105
func (l *layout) Get(index uintptr) unsafe.Pointer {
106,870✔
106
        if l.pointer == nil {
106,871✔
107
                return nil
1✔
108
        }
1✔
109
        return unsafe.Add(l.pointer, l.itemSize*index)
106,869✔
110
}
111

112
// archetype represents an ECS archetype
113
type archetype struct {
114
        archetypeAccess                 // Access helper, passed to queries.
115
        graphNode       *archetypeNode  // Node in the archetype graph.
116
        layouts         []layout        // Column layouts by ID.
117
        indices         idMap[uint32]   // Mapping from IDs to buffer indices.
118
        buffers         []reflect.Value // Reflection arrays containing component data.
119
        entityBuffer    reflect.Value   // Reflection array containing entity data.
120
        index           int32           // Index of the archetype in the world.
121
        len             uint32          // Current number of entities
122
        cap             uint32          // Current capacity
123
}
124

125
// Init initializes an archetype
126
func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
1,224✔
127
        var mask Mask
1,224✔
128
        if len(components) > 0 && len(node.Ids) == 0 {
2,338✔
129
                node.Ids = make([]ID, len(components))
1,114✔
130

1,114✔
131
                var maxSize uintptr = 0
1,114✔
132
                for i, c := range components {
6,379✔
133
                        node.Ids[i] = c.ID
5,265✔
134
                        size, align := c.Type.Size(), uintptr(c.Type.Align())
5,265✔
135
                        size = (size + (align - 1)) / align * align
5,265✔
136
                        if size > maxSize {
6,378✔
137
                                maxSize = size
1,113✔
138
                        }
1,113✔
139
                }
140

141
                if maxSize > 0 {
2,225✔
142
                        node.zeroValue = make([]byte, maxSize)
1,111✔
143
                        node.zeroPointer = unsafe.Pointer(&node.zeroValue[0])
1,111✔
144
                }
1,111✔
145
        }
146

147
        a.buffers = make([]reflect.Value, len(components))
1,224✔
148
        a.layouts = make([]layout, MaskTotalBits)
1,224✔
149
        a.indices = newIDMap[uint32]()
1,224✔
150
        a.index = index
1,224✔
151

1,224✔
152
        cap := 1
1,224✔
153
        if forStorage {
2,370✔
154
                cap = int(node.capacityIncrement)
1,146✔
155
        }
1,146✔
156

157
        prev := -1
1,224✔
158
        for i, c := range components {
6,534✔
159
                if int(c.ID) <= prev {
5,311✔
160
                        panic("component arguments must be sorted by ID")
1✔
161
                }
162
                prev = int(c.ID)
5,309✔
163
                mask.Set(c.ID, true)
5,309✔
164

5,309✔
165
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,309✔
166
                size = (size + (align - 1)) / align * align
5,309✔
167

5,309✔
168
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,309✔
169
                a.layouts[c.ID] = layout{
5,309✔
170
                        a.buffers[i].Addr().UnsafePointer(),
5,309✔
171
                        size,
5,309✔
172
                }
5,309✔
173
                a.indices.Set(c.ID, uint32(i))
5,309✔
174
        }
175
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,223✔
176

1,223✔
177
        a.archetypeAccess = archetypeAccess{
1,223✔
178
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,223✔
179
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,223✔
180
                Mask:              mask,
1,223✔
181
                Relation:          relation,
1,223✔
182
                RelationComponent: relationComp,
1,223✔
183
        }
1,223✔
184

1,223✔
185
        a.graphNode = node
1,223✔
186

1,223✔
187
        a.len = 0
1,223✔
188
        a.cap = uint32(cap)
1,223✔
189
}
190

191
// Add adds an entity with optionally zeroed components to the archetype
192
func (a *archetype) Alloc(entity Entity) uintptr {
9,733✔
193
        idx := uintptr(a.len)
9,733✔
194
        a.extend(1)
9,733✔
195
        a.addEntity(idx, &entity)
9,733✔
196
        a.len++
9,733✔
197
        return idx
9,733✔
198
}
9,733✔
199

200
// Add adds storage to the archetype
201
func (a *archetype) AllocN(count uint32) {
27✔
202
        a.extend(count)
27✔
203
        a.len += count
27✔
204
}
27✔
205

206
// Add adds an entity with components to the archetype
207
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
208
        if len(components) != len(a.graphNode.Ids) {
10✔
209
                panic("Invalid number of components")
1✔
210
        }
211
        idx := uintptr(a.len)
8✔
212

8✔
213
        a.extend(1)
8✔
214
        a.addEntity(idx, &entity)
8✔
215
        for _, c := range components {
24✔
216
                lay := a.getLayout(c.ID)
16✔
217
                size := lay.itemSize
16✔
218
                if size == 0 {
18✔
219
                        continue
2✔
220
                }
221
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
222
                dst := a.Get(uintptr(idx), c.ID)
14✔
223
                a.copy(src, dst, size)
14✔
224
        }
225
        a.len++
8✔
226
        return idx
8✔
227
}
228

229
// Remove removes an entity and its components from the archetype.
230
//
231
// Performs a swap-remove and reports whether a swap was necessary
232
// (i.e. not the last entity that was removed).
233
func (a *archetype) Remove(index uintptr) bool {
4,932✔
234
        swapped := a.removeEntity(index)
4,932✔
235

4,932✔
236
        old := uintptr(a.len - 1)
4,932✔
237

4,932✔
238
        if index != old {
5,109✔
239
                for _, id := range a.graphNode.Ids {
401✔
240
                        lay := a.getLayout(id)
224✔
241
                        size := lay.itemSize
224✔
242
                        if size == 0 {
232✔
243
                                continue
8✔
244
                        }
245
                        src := unsafe.Add(lay.pointer, old*size)
216✔
246
                        dst := unsafe.Add(lay.pointer, index*size)
216✔
247
                        a.copy(src, dst, size)
216✔
248
                }
249
        }
250
        a.ZeroAll(old)
4,932✔
251
        a.len--
4,932✔
252

4,932✔
253
        return swapped
4,932✔
254
}
255

256
// ZeroAll resets a block of storage in all buffers.
257
func (a *archetype) ZeroAll(index uintptr) {
4,932✔
258
        for _, id := range a.graphNode.Ids {
7,817✔
259
                a.Zero(index, id)
2,885✔
260
        }
2,885✔
261
}
262

263
// ZeroAll resets a block of storage in one buffer.
264
func (a *archetype) Zero(index uintptr, id ID) {
2,885✔
265
        lay := a.getLayout(id)
2,885✔
266
        size := lay.itemSize
2,885✔
267
        if size == 0 {
5,413✔
268
                return
2,528✔
269
        }
2,528✔
270
        dst := unsafe.Add(lay.pointer, index*size)
357✔
271
        a.copy(a.graphNode.zeroPointer, dst, size)
357✔
272
}
273

274
// SetEntity overwrites an entity
275
func (a *archetype) SetEntity(index uintptr, entity Entity) {
1,032✔
276
        a.addEntity(index, &entity)
1,032✔
277
}
1,032✔
278

279
// Set overwrites a component with the data behind the given pointer
280
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
828✔
281
        lay := a.getLayout(id)
828✔
282
        dst := a.Get(index, id)
828✔
283
        size := lay.itemSize
828✔
284
        if size == 0 {
857✔
285
                return dst
29✔
286
        }
29✔
287
        rValue := reflect.ValueOf(comp)
799✔
288

799✔
289
        src := rValue.UnsafePointer()
799✔
290
        a.copy(src, dst, size)
799✔
291
        return dst
799✔
292
}
293

294
// SetPointer overwrites a component with the data behind the given pointer
295
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
2,546✔
296
        lay := a.getLayout(id)
2,546✔
297
        dst := a.Get(index, id)
2,546✔
298
        size := lay.itemSize
2,546✔
299
        if size == 0 {
5,069✔
300
                return dst
2,523✔
301
        }
2,523✔
302

303
        a.copy(comp, dst, size)
23✔
304
        return dst
23✔
305
}
306

307
// Reset removes all entities and components.
308
//
309
// Does NOT free the reserved memory.
310
func (a *archetype) Reset() {
23✔
311
        a.len = 0
23✔
312
        for _, buf := range a.buffers {
50✔
313
                buf.SetZero()
27✔
314
        }
27✔
315
}
316

317
func (a *archetype) Deactivate() {
5✔
318
        a.Reset()
5✔
319
        a.index = -1
5✔
320
        a.graphNode = nil
5✔
321
        a.layouts = nil
5✔
322
        a.indices = newIDMap[uint32]()
5✔
323
        a.buffers = nil
5✔
324
        a.entityBuffer.SetZero()
5✔
325
}
5✔
326

327
func (a *archetype) IsActive() bool {
3,143✔
328
        return a.index >= 0
3,143✔
329
}
3,143✔
330

331
// Components returns the component IDs for this archetype
332
func (a *archetype) Components() []ID {
2,193✔
333
        return a.graphNode.Ids
2,193✔
334
}
2,193✔
335

336
// Len reports the number of entities in the archetype
337
func (a *archetype) Len() uint32 {
7,441✔
338
        return a.len
7,441✔
339
}
7,441✔
340

341
// Cap reports the current capacity of the archetype
342
func (a *archetype) Cap() uint32 {
25✔
343
        return a.cap
25✔
344
}
25✔
345

346
// Stats generates statistics for an archetype
347
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
8✔
348
        ids := a.Components()
8✔
349
        aCompCount := len(ids)
8✔
350
        aTypes := make([]reflect.Type, aCompCount)
8✔
351
        for j, id := range ids {
16✔
352
                aTypes[j], _ = reg.ComponentType(id)
8✔
353
        }
8✔
354

355
        cap := int(a.Cap())
8✔
356
        memPerEntity := 0
8✔
357
        for _, id := range a.graphNode.Ids {
16✔
358
                lay := a.getLayout(id)
8✔
359
                memPerEntity += int(lay.itemSize)
8✔
360
        }
8✔
361
        memory := cap * (int(entitySize) + memPerEntity)
8✔
362

8✔
363
        return stats.ArchetypeStats{
8✔
364
                IsActive:        a.IsActive(),
8✔
365
                Size:            int(a.Len()),
8✔
366
                Capacity:        cap,
8✔
367
                Components:      aCompCount,
8✔
368
                ComponentIDs:    ids,
8✔
369
                ComponentTypes:  aTypes,
8✔
370
                Memory:          memory,
8✔
371
                MemoryPerEntity: memPerEntity,
8✔
372
        }
8✔
373
}
374

375
// UpdateStats updates statistics for an archetype
376
func (a *archetype) UpdateStats(stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
7✔
377
        if stats.Dirty {
8✔
378
                ids := a.Components()
1✔
379
                aCompCount := len(ids)
1✔
380
                aTypes := make([]reflect.Type, aCompCount)
1✔
381
                for j, id := range ids {
2✔
382
                        aTypes[j], _ = reg.ComponentType(id)
1✔
383
                }
1✔
384

385
                memPerEntity := 0
1✔
386
                for _, id := range a.graphNode.Ids {
2✔
387
                        lay := a.getLayout(id)
1✔
388
                        memPerEntity += int(lay.itemSize)
1✔
389
                }
1✔
390

391
                stats.IsActive = a.IsActive()
1✔
392
                stats.Components = aCompCount
1✔
393
                stats.ComponentIDs = ids
1✔
394
                stats.ComponentTypes = aTypes
1✔
395
                stats.MemoryPerEntity = memPerEntity
1✔
396
                stats.Dirty = false
1✔
397
        }
398

399
        cap := int(a.Cap())
7✔
400
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
7✔
401

7✔
402
        stats.Size = int(a.Len())
7✔
403
        stats.Capacity = cap
7✔
404
        stats.Memory = memory
7✔
405

406
}
407

408
// copy from one pointer to another.
409
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
12,359✔
410
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
12,359✔
411
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
12,359✔
412
        copy(dstSlice, srcSlice)
12,359✔
413
}
12,359✔
414

415
// extend the memory buffers if necessary for adding an entity.
416
func (a *archetype) extend(by uint32) {
9,771✔
417
        required := a.len + by
9,771✔
418
        if a.cap >= required {
19,507✔
419
                return
9,736✔
420
        }
9,736✔
421
        a.cap = capacityU32(required, a.graphNode.capacityIncrement)
35✔
422

35✔
423
        old := a.entityBuffer
35✔
424
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
35✔
425
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
35✔
426
        reflect.Copy(a.entityBuffer, old)
35✔
427

35✔
428
        for _, id := range a.graphNode.Ids {
75✔
429
                lay := a.getLayout(id)
40✔
430
                if lay.itemSize == 0 {
41✔
431
                        continue
1✔
432
                }
433
                index, _ := a.indices.Get(id)
39✔
434
                old := a.buffers[index]
39✔
435
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
39✔
436
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
39✔
437
                reflect.Copy(a.buffers[index], old)
39✔
438
        }
439
}
440

441
// Adds an entity at the given index. Does not extend the entity buffer.
442
func (a *archetype) addEntity(index uintptr, entity *Entity) {
10,773✔
443
        dst := unsafe.Add(a.entityPointer, entitySize*index)
10,773✔
444
        src := unsafe.Pointer(entity)
10,773✔
445
        a.copy(src, dst, entitySize)
10,773✔
446
}
10,773✔
447

448
// removeEntity removes an entity from tne archetype.
449
// Components need to be removed separately.
450
func (a *archetype) removeEntity(index uintptr) bool {
4,932✔
451
        old := uintptr(a.len - 1)
4,932✔
452

4,932✔
453
        if index == old {
9,687✔
454
                return false
4,755✔
455
        }
4,755✔
456

457
        src := unsafe.Add(a.entityPointer, old*entitySize)
177✔
458
        dst := unsafe.Add(a.entityPointer, index*entitySize)
177✔
459
        a.copy(src, dst, entitySize)
177✔
460

177✔
461
        return true
177✔
462
}
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