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

mlange-42 / arche / 4854284421

01 May 2023 07:40PM CUT coverage: 100.0%. Remained the same
4854284421

Pull #248

github

GitHub
Merge c98fd7372 into 45b5016aa
Pull Request #248: Nodes manage archetypes

193 of 193 new or added lines in 4 files covered. (100.0%)

3257 of 3257 relevant lines covered (100.0%)

1969.67 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        pagedSlice[archetype]
20
        archetypeMap      map[Entity]*archetype
21
        freeIndices       []int32
22
        TransitionAdd     idMap[*archetypeNode] // Mapping from component ID to add to the resulting archetype
23
        TransitionRemove  idMap[*archetypeNode] // Mapping from component ID to remove to the resulting archetype
24
        relation          int8
25
        zeroValue         []byte         // Used as source for setting storage to zero
26
        zeroPointer       unsafe.Pointer // Points to zeroValue for fast access
27
        capacityIncrement uint32         // Capacity increment
28
}
29

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

46
func (a *archetypeNode) Matches(f Filter) bool {
1,650✔
47
        return f.Matches(a.mask, nil)
1,650✔
48
}
1,650✔
49

50
func (a *archetypeNode) Archetypes() archetypes {
1,706✔
51
        if a.HasRelation() {
1,777✔
52
                return &a.archetypes
71✔
53
        }
71✔
54
        if a.archetype == nil {
1,781✔
55
                return nil
146✔
56
        }
146✔
57
        return batchArchetype{Archetype: a.archetype, StartIndex: 0}
1,489✔
58
}
59

60
func (a *archetypeNode) GetArchetype(id Entity) *archetype {
7,570✔
61
        if a.relation >= 0 {
12,629✔
62
                return a.archetypeMap[id]
5,059✔
63
        }
5,059✔
64
        return a.archetype
2,511✔
65
}
66

67
func (a *archetypeNode) SetArchetype(arch *archetype) {
1,175✔
68
        a.archetype = arch
1,175✔
69
}
1,175✔
70

71
func (a *archetypeNode) CreateArchetype(target Entity, components ...componentType) *archetype {
49✔
72
        var arch *archetype
49✔
73
        var archIndex int32
49✔
74
        lenFree := len(a.freeIndices)
49✔
75
        if lenFree > 0 {
51✔
76
                archIndex = a.freeIndices[lenFree-1]
2✔
77
                arch = a.archetypes.Get(archIndex)
2✔
78
                a.freeIndices = a.freeIndices[:lenFree-1]
2✔
79
                arch.Activate(target, archIndex)
2✔
80
        } else {
49✔
81
                a.archetypes.Add(archetype{})
47✔
82
                archIndex := a.archetypes.Len() - 1
47✔
83
                arch = a.archetypes.Get(archIndex)
47✔
84
                arch.Init(a, archIndex, true, target, a.relation, components...)
47✔
85
        }
47✔
86
        a.archetypeMap[target] = arch
49✔
87
        return arch
49✔
88
}
89

90
func (a *archetypeNode) DeleteArchetype(arch *archetype) {
10✔
91
        delete(a.archetypeMap, arch.Relation)
10✔
92
        idx := arch.index
10✔
93
        a.freeIndices = append(a.freeIndices, idx)
10✔
94
        a.archetypes.Get(idx).Deactivate()
10✔
95
}
10✔
96

97
func (a *archetypeNode) HasRelation() bool {
6,602✔
98
        return a.relation >= 0
6,602✔
99
}
6,602✔
100

101
// Helper for accessing data from an archetype
102
type archetypeAccess struct {
103
        Mask              Mask           // Archetype's mask
104
        basePointer       unsafe.Pointer // Pointer to the first component column layout.
105
        entityPointer     unsafe.Pointer // Pointer to the entity storage
106
        Relation          Entity
107
        RelationComponent int8
108
}
109

110
// Matches checks if the archetype matches the given mask.
111
func (a *archetype) Matches(f Filter) bool {
1,811✔
112
        return f.Matches(a.Mask, &a.Relation)
1,811✔
113
}
1,811✔
114

115
// GetEntity returns the entity at the given index
116
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
1,726✔
117
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
1,726✔
118
}
1,726✔
119

120
// Get returns the component with the given ID at the given index
121
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
16,880✔
122
        return a.getLayout(id).Get(index)
16,880✔
123
}
16,880✔
124

125
// GetEntity returns the entity at the given index
126
func (a *archetypeAccess) GetRelation() Entity {
21✔
127
        return a.Relation
21✔
128
}
21✔
129

130
// HasComponent returns whether the archetype contains the given component ID.
131
func (a *archetypeAccess) HasComponent(id ID) bool {
8,175✔
132
        return a.getLayout(id).pointer != nil
8,175✔
133
}
8,175✔
134

135
// HasRelation returns whether the archetype has a relation component.
136
func (a *archetypeAccess) HasRelation() bool {
12✔
137
        return a.RelationComponent >= 0
12✔
138
}
12✔
139

140
// GetLayout returns the column layout for a component.
141
func (a *archetypeAccess) getLayout(id ID) *layout {
31,614✔
142
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
31,614✔
143
}
31,614✔
144

145
// layout specification of a component column.
146
type layout struct {
147
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
148
        itemSize uintptr        // Component/step size
149
}
150

151
// Get returns a pointer to the item at the given index.
152
func (l *layout) Get(index uintptr) unsafe.Pointer {
16,878✔
153
        if l.pointer == nil {
16,879✔
154
                return nil
1✔
155
        }
1✔
156
        return unsafe.Add(l.pointer, l.itemSize*index)
16,877✔
157
}
158

159
// archetype represents an ECS archetype
160
type archetype struct {
161
        archetypeAccess                 // Access helper, passed to queries.
162
        graphNode       *archetypeNode  // Node in the archetype graph.
163
        layouts         []layout        // Column layouts by ID.
164
        indices         idMap[uint32]   // Mapping from IDs to buffer indices.
165
        buffers         []reflect.Value // Reflection arrays containing component data.
166
        entityBuffer    reflect.Value   // Reflection array containing entity data.
167
        index           int32           // Index of the archetype in the world.
168
        len             uint32          // Current number of entities
169
        cap             uint32          // Current capacity
170
}
171

172
// Init initializes an archetype
173
func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
1,231✔
174
        var mask Mask
1,231✔
175
        if len(components) > 0 && node.Ids == nil {
2,348✔
176
                node.Ids = make([]ID, len(components))
1,117✔
177

1,117✔
178
                var maxSize uintptr = 0
1,117✔
179
                for i, c := range components {
6,387✔
180
                        node.Ids[i] = c.ID
5,270✔
181
                        size, align := c.Type.Size(), uintptr(c.Type.Align())
5,270✔
182
                        size = (size + (align - 1)) / align * align
5,270✔
183
                        if size > maxSize {
6,386✔
184
                                maxSize = size
1,116✔
185
                        }
1,116✔
186
                }
187

188
                if maxSize > 0 {
2,231✔
189
                        node.zeroValue = make([]byte, maxSize)
1,114✔
190
                        node.zeroPointer = unsafe.Pointer(&node.zeroValue[0])
1,114✔
191
                }
1,114✔
192
        }
193

194
        a.buffers = make([]reflect.Value, len(components))
1,231✔
195
        a.layouts = make([]layout, MaskTotalBits)
1,231✔
196
        a.indices = newIDMap[uint32]()
1,231✔
197
        a.index = index
1,231✔
198

1,231✔
199
        cap := 1
1,231✔
200
        if forStorage {
2,382✔
201
                cap = int(node.capacityIncrement)
1,151✔
202
        }
1,151✔
203

204
        prev := -1
1,231✔
205
        for i, c := range components {
6,550✔
206
                if int(c.ID) <= prev {
5,320✔
207
                        panic("component arguments must be sorted by ID")
1✔
208
                }
209
                prev = int(c.ID)
5,318✔
210
                mask.Set(c.ID, true)
5,318✔
211

5,318✔
212
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,318✔
213
                size = (size + (align - 1)) / align * align
5,318✔
214

5,318✔
215
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,318✔
216
                a.layouts[c.ID] = layout{
5,318✔
217
                        a.buffers[i].Addr().UnsafePointer(),
5,318✔
218
                        size,
5,318✔
219
                }
5,318✔
220
                a.indices.Set(c.ID, uint32(i))
5,318✔
221
        }
222
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,230✔
223

1,230✔
224
        a.archetypeAccess = archetypeAccess{
1,230✔
225
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,230✔
226
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,230✔
227
                Mask:              mask,
1,230✔
228
                Relation:          relation,
1,230✔
229
                RelationComponent: relationComp,
1,230✔
230
        }
1,230✔
231

1,230✔
232
        a.graphNode = node
1,230✔
233

1,230✔
234
        a.len = 0
1,230✔
235
        a.cap = uint32(cap)
1,230✔
236
}
237

238
// Add adds an entity with optionally zeroed components to the archetype
239
func (a *archetype) Alloc(entity Entity) uintptr {
9,742✔
240
        idx := uintptr(a.len)
9,742✔
241
        a.extend(1)
9,742✔
242
        a.addEntity(idx, &entity)
9,742✔
243
        a.len++
9,742✔
244
        return idx
9,742✔
245
}
9,742✔
246

247
// Add adds storage to the archetype
248
func (a *archetype) AllocN(count uint32) {
30✔
249
        a.extend(count)
30✔
250
        a.len += count
30✔
251
}
30✔
252

253
// Add adds an entity with components to the archetype
254
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
255
        if len(components) != len(a.graphNode.Ids) {
10✔
256
                panic("Invalid number of components")
1✔
257
        }
258
        idx := uintptr(a.len)
8✔
259

8✔
260
        a.extend(1)
8✔
261
        a.addEntity(idx, &entity)
8✔
262
        for _, c := range components {
24✔
263
                lay := a.getLayout(c.ID)
16✔
264
                size := lay.itemSize
16✔
265
                if size == 0 {
18✔
266
                        continue
2✔
267
                }
268
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
269
                dst := a.Get(uintptr(idx), c.ID)
14✔
270
                a.copy(src, dst, size)
14✔
271
        }
272
        a.len++
8✔
273
        return idx
8✔
274
}
275

276
// Remove removes an entity and its components from the archetype.
277
//
278
// Performs a swap-remove and reports whether a swap was necessary
279
// (i.e. not the last entity that was removed).
280
func (a *archetype) Remove(index uintptr) bool {
4,936✔
281
        swapped := a.removeEntity(index)
4,936✔
282

4,936✔
283
        old := uintptr(a.len - 1)
4,936✔
284

4,936✔
285
        if index != old {
5,118✔
286
                for _, id := range a.graphNode.Ids {
411✔
287
                        lay := a.getLayout(id)
229✔
288
                        size := lay.itemSize
229✔
289
                        if size == 0 {
238✔
290
                                continue
9✔
291
                        }
292
                        src := unsafe.Add(lay.pointer, old*size)
220✔
293
                        dst := unsafe.Add(lay.pointer, index*size)
220✔
294
                        a.copy(src, dst, size)
220✔
295
                }
296
        }
297
        a.ZeroAll(old)
4,936✔
298
        a.len--
4,936✔
299

4,936✔
300
        return swapped
4,936✔
301
}
302

303
// ZeroAll resets a block of storage in all buffers.
304
func (a *archetype) ZeroAll(index uintptr) {
4,936✔
305
        for _, id := range a.graphNode.Ids {
7,827✔
306
                a.Zero(index, id)
2,891✔
307
        }
2,891✔
308
}
309

310
// ZeroAll resets a block of storage in one buffer.
311
func (a *archetype) Zero(index uintptr, id ID) {
2,891✔
312
        lay := a.getLayout(id)
2,891✔
313
        size := lay.itemSize
2,891✔
314
        if size == 0 {
5,422✔
315
                return
2,531✔
316
        }
2,531✔
317
        dst := unsafe.Add(lay.pointer, index*size)
360✔
318
        a.copy(a.graphNode.zeroPointer, dst, size)
360✔
319
}
320

321
// SetEntity overwrites an entity
322
func (a *archetype) SetEntity(index uintptr, entity Entity) {
1,062✔
323
        a.addEntity(index, &entity)
1,062✔
324
}
1,062✔
325

326
// Set overwrites a component with the data behind the given pointer
327
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
828✔
328
        lay := a.getLayout(id)
828✔
329
        dst := a.Get(index, id)
828✔
330
        size := lay.itemSize
828✔
331
        if size == 0 {
857✔
332
                return dst
29✔
333
        }
29✔
334
        rValue := reflect.ValueOf(comp)
799✔
335

799✔
336
        src := rValue.UnsafePointer()
799✔
337
        a.copy(src, dst, size)
799✔
338
        return dst
799✔
339
}
340

341
// SetPointer overwrites a component with the data behind the given pointer
342
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
2,550✔
343
        lay := a.getLayout(id)
2,550✔
344
        dst := a.Get(index, id)
2,550✔
345
        size := lay.itemSize
2,550✔
346
        if size == 0 {
5,075✔
347
                return dst
2,525✔
348
        }
2,525✔
349

350
        a.copy(comp, dst, size)
25✔
351
        return dst
25✔
352
}
353

354
// Reset removes all entities and components.
355
//
356
// Does NOT free the reserved memory.
357
func (a *archetype) Reset() {
32✔
358
        if a.len == 0 {
47✔
359
                return
15✔
360
        }
15✔
361
        a.len = 0
17✔
362
        for _, buf := range a.buffers {
38✔
363
                buf.SetZero()
21✔
364
        }
21✔
365
}
366

367
func (a *archetype) Deactivate() {
10✔
368
        a.Reset()
10✔
369
        a.index = -1
10✔
370
}
10✔
371

372
func (a *archetype) Activate(target Entity, index int32) {
2✔
373
        a.index = index
2✔
374
        a.Relation = target
2✔
375
}
2✔
376

377
func (a *archetype) IsActive() bool {
1,847✔
378
        return a.index >= 0
1,847✔
379
}
1,847✔
380

381
// Components returns the component IDs for this archetype
382
func (a *archetype) Components() []ID {
2,191✔
383
        return a.graphNode.Ids
2,191✔
384
}
2,191✔
385

386
// Len reports the number of entities in the archetype
387
func (a *archetype) Len() uint32 {
6,239✔
388
        return a.len
6,239✔
389
}
6,239✔
390

391
// Cap reports the current capacity of the archetype
392
func (a *archetype) Cap() uint32 {
23✔
393
        return a.cap
23✔
394
}
23✔
395

396
// Stats generates statistics for an archetype
397
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
6✔
398
        ids := a.Components()
6✔
399
        aCompCount := len(ids)
6✔
400
        aTypes := make([]reflect.Type, aCompCount)
6✔
401
        for j, id := range ids {
10✔
402
                aTypes[j], _ = reg.ComponentType(id)
4✔
403
        }
4✔
404

405
        cap := int(a.Cap())
6✔
406
        memPerEntity := 0
6✔
407
        for _, id := range a.graphNode.Ids {
10✔
408
                lay := a.getLayout(id)
4✔
409
                memPerEntity += int(lay.itemSize)
4✔
410
        }
4✔
411
        memory := cap * (int(entitySize) + memPerEntity)
6✔
412

6✔
413
        return stats.ArchetypeStats{
6✔
414
                IsActive:        a.IsActive(),
6✔
415
                Size:            int(a.Len()),
6✔
416
                Capacity:        cap,
6✔
417
                Components:      aCompCount,
6✔
418
                ComponentIDs:    ids,
6✔
419
                ComponentTypes:  aTypes,
6✔
420
                Memory:          memory,
6✔
421
                MemoryPerEntity: memPerEntity,
6✔
422
        }
6✔
423
}
424

425
// UpdateStats updates statistics for an archetype
426
func (a *archetype) UpdateStats(stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
7✔
427
        if stats.Dirty {
8✔
428
                ids := a.Components()
1✔
429
                aCompCount := len(ids)
1✔
430
                aTypes := make([]reflect.Type, aCompCount)
1✔
431
                for j, id := range ids {
2✔
432
                        aTypes[j], _ = reg.ComponentType(id)
1✔
433
                }
1✔
434

435
                memPerEntity := 0
1✔
436
                for _, id := range a.graphNode.Ids {
2✔
437
                        lay := a.getLayout(id)
1✔
438
                        memPerEntity += int(lay.itemSize)
1✔
439
                }
1✔
440

441
                stats.IsActive = a.IsActive()
1✔
442
                stats.Components = aCompCount
1✔
443
                stats.ComponentIDs = ids
1✔
444
                stats.ComponentTypes = aTypes
1✔
445
                stats.MemoryPerEntity = memPerEntity
1✔
446
                stats.Dirty = false
1✔
447
        }
448

449
        cap := int(a.Cap())
7✔
450
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
7✔
451

7✔
452
        stats.Size = int(a.Len())
7✔
453
        stats.Capacity = cap
7✔
454
        stats.Memory = memory
7✔
455

456
}
457

458
// copy from one pointer to another.
459
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
12,412✔
460
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
12,412✔
461
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
12,412✔
462
        copy(dstSlice, srcSlice)
12,412✔
463
}
12,412✔
464

465
// extend the memory buffers if necessary for adding an entity.
466
func (a *archetype) extend(by uint32) {
9,783✔
467
        required := a.len + by
9,783✔
468
        if a.cap >= required {
19,529✔
469
                return
9,746✔
470
        }
9,746✔
471
        a.cap = capacityU32(required, a.graphNode.capacityIncrement)
37✔
472

37✔
473
        old := a.entityBuffer
37✔
474
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
37✔
475
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
37✔
476
        reflect.Copy(a.entityBuffer, old)
37✔
477

37✔
478
        for _, id := range a.graphNode.Ids {
77✔
479
                lay := a.getLayout(id)
40✔
480
                if lay.itemSize == 0 {
41✔
481
                        continue
1✔
482
                }
483
                index, _ := a.indices.Get(id)
39✔
484
                old := a.buffers[index]
39✔
485
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
39✔
486
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
39✔
487
                reflect.Copy(a.buffers[index], old)
39✔
488
        }
489
}
490

491
// Adds an entity at the given index. Does not extend the entity buffer.
492
func (a *archetype) addEntity(index uintptr, entity *Entity) {
10,812✔
493
        dst := unsafe.Add(a.entityPointer, entitySize*index)
10,812✔
494
        src := unsafe.Pointer(entity)
10,812✔
495
        a.copy(src, dst, entitySize)
10,812✔
496
}
10,812✔
497

498
// removeEntity removes an entity from tne archetype.
499
// Components need to be removed separately.
500
func (a *archetype) removeEntity(index uintptr) bool {
4,936✔
501
        old := uintptr(a.len - 1)
4,936✔
502

4,936✔
503
        if index == old {
9,690✔
504
                return false
4,754✔
505
        }
4,754✔
506

507
        src := unsafe.Add(a.entityPointer, old*entitySize)
182✔
508
        dst := unsafe.Add(a.entityPointer, index*entitySize)
182✔
509
        a.copy(src, dst, entitySize)
182✔
510

182✔
511
        return true
182✔
512
}
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