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

mlange-42 / arche / 4859967421

02 May 2023 10:03AM CUT coverage: 100.0%. Remained the same
4859967421

push

github

GitHub
Document entity relations (#256)

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

3407 of 3407 relevant lines covered (100.0%)

1878.66 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 single archetype for nodes without entity
19
        archetypes        pagedSlice[archetype] // Storage for archetypes in nodes with entity relation
20
        archetypeMap      map[Entity]*archetype // Mapping from relation targets to archetypes
21
        freeIndices       []int32               // Indices of free/inactive archetypes
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                  // The node's relation component ID. Negative value stands for no relation
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,223✔
32
        var arch map[Entity]*archetype
1,223✔
33
        if relation >= 0 {
1,238✔
34
                arch = map[Entity]*archetype{}
15✔
35
        }
15✔
36
        return archetypeNode{
1,223✔
37
                mask:              mask,
1,223✔
38
                archetypeMap:      arch,
1,223✔
39
                TransitionAdd:     newIDMap[*archetypeNode](),
1,223✔
40
                TransitionRemove:  newIDMap[*archetypeNode](),
1,223✔
41
                relation:          relation,
1,223✔
42
                capacityIncrement: uint32(capacityIncrement),
1,223✔
43
        }
1,223✔
44
}
45

46
// Matches the archetype node against a filter.
47
// Ignores the relation target.
48
func (a *archetypeNode) Matches(f Filter) bool {
1,667✔
49
        return f.Matches(a.mask, nil)
1,667✔
50
}
1,667✔
51

52
// Archetypes of the node.
53
// Returns a single wrapped archetype if there are no relations.
54
// Returns nil if the node has no archetype(s).
55
func (a *archetypeNode) Archetypes() archetypes {
1,715✔
56
        if a.HasRelation() {
1,792✔
57
                return &a.archetypes
77✔
58
        }
77✔
59
        if a.archetype == nil {
1,784✔
60
                return nil
146✔
61
        }
146✔
62
        return batchArchetype{Archetype: a.archetype, StartIndex: 0}
1,492✔
63
}
64

65
// GetArchetype returns the archetype for the given relation target.
66
//
67
// The target is ignored if the node has no relation component.
68
func (a *archetypeNode) GetArchetype(target Entity) *archetype {
7,593✔
69
        if a.relation >= 0 {
12,671✔
70
                return a.archetypeMap[target]
5,078✔
71
        }
5,078✔
72
        return a.archetype
2,515✔
73
}
74

75
// SetArchetype sets the archetype for a node without a relation.
76
//
77
// Do not use on nodes without a relation component!
78
func (a *archetypeNode) SetArchetype(arch *archetype) {
1,178✔
79
        a.archetype = arch
1,178✔
80
}
1,178✔
81

82
// CreateArchetype creates a new archetype in nodes with relation component.
83
func (a *archetypeNode) CreateArchetype(target Entity, components ...componentType) *archetype {
54✔
84
        var arch *archetype
54✔
85
        var archIndex int32
54✔
86
        lenFree := len(a.freeIndices)
54✔
87
        if lenFree > 0 {
56✔
88
                archIndex = a.freeIndices[lenFree-1]
2✔
89
                arch = a.archetypes.Get(archIndex)
2✔
90
                a.freeIndices = a.freeIndices[:lenFree-1]
2✔
91
                arch.Activate(target, archIndex)
2✔
92
        } else {
54✔
93
                a.archetypes.Add(archetype{})
52✔
94
                archIndex := a.archetypes.Len() - 1
52✔
95
                arch = a.archetypes.Get(archIndex)
52✔
96
                arch.Init(a, archIndex, true, target, a.relation, components...)
52✔
97
        }
52✔
98
        a.archetypeMap[target] = arch
54✔
99
        return arch
54✔
100
}
101

102
// RemoveArchetype de-activates an archetype.
103
// The archetype will be re-used by CreateArchetype.
104
func (a *archetypeNode) RemoveArchetype(arch *archetype) {
11✔
105
        delete(a.archetypeMap, arch.Relation)
11✔
106
        idx := arch.index
11✔
107
        a.freeIndices = append(a.freeIndices, idx)
11✔
108
        a.archetypes.Get(idx).Deactivate()
11✔
109
}
11✔
110

111
// HasRelation returns whether the node has a relation component.
112
func (a *archetypeNode) HasRelation() bool {
6,629✔
113
        return a.relation >= 0
6,629✔
114
}
6,629✔
115

116
// Helper for accessing data from an archetype
117
type archetypeAccess struct {
118
        Mask              Mask           // Archetype's mask
119
        basePointer       unsafe.Pointer // Pointer to the first component column layout.
120
        entityPointer     unsafe.Pointer // Pointer to the entity storage
121
        Relation          Entity
122
        RelationComponent int8
123
}
124

125
// Matches checks if the archetype matches the given mask.
126
func (a *archetype) Matches(f Filter) bool {
506✔
127
        return f.Matches(a.Mask, &a.Relation)
506✔
128
}
506✔
129

130
// GetEntity returns the entity at the given index
131
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
1,726✔
132
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
1,726✔
133
}
1,726✔
134

135
// Get returns the component with the given ID at the given index
136
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
16,902✔
137
        return a.getLayout(id).Get(index)
16,902✔
138
}
16,902✔
139

140
// GetEntity returns the entity at the given index
141
func (a *archetypeAccess) GetRelation() Entity {
22✔
142
        return a.Relation
22✔
143
}
22✔
144

145
// HasComponent returns whether the archetype contains the given component ID.
146
func (a *archetypeAccess) HasComponent(id ID) bool {
8,179✔
147
        return a.getLayout(id).pointer != nil
8,179✔
148
}
8,179✔
149

150
// HasRelation returns whether the archetype has a relation component.
151
func (a *archetypeAccess) HasRelation() bool {
12✔
152
        return a.RelationComponent >= 0
12✔
153
}
12✔
154

155
// GetLayout returns the column layout for a component.
156
func (a *archetypeAccess) getLayout(id ID) *layout {
31,663✔
157
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
31,663✔
158
}
31,663✔
159

160
// layout specification of a component column.
161
type layout struct {
162
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
163
        itemSize uintptr        // Component/step size
164
}
165

166
// Get returns a pointer to the item at the given index.
167
func (l *layout) Get(index uintptr) unsafe.Pointer {
16,900✔
168
        if l.pointer == nil {
16,901✔
169
                return nil
1✔
170
        }
1✔
171
        return unsafe.Add(l.pointer, l.itemSize*index)
16,899✔
172
}
173

174
// archetype represents an ECS archetype
175
type archetype struct {
176
        archetypeAccess                 // Access helper, passed to queries.
177
        graphNode       *archetypeNode  // Node in the archetype graph.
178
        layouts         []layout        // Column layouts by ID.
179
        indices         idMap[uint32]   // Mapping from IDs to buffer indices.
180
        buffers         []reflect.Value // Reflection arrays containing component data.
181
        entityBuffer    reflect.Value   // Reflection array containing entity data.
182
        index           int32           // Index of the archetype in the world.
183
        len             uint32          // Current number of entities
184
        cap             uint32          // Current capacity
185
}
186

187
// Init initializes an archetype
188
func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
1,239✔
189
        var mask Mask
1,239✔
190
        if len(components) > 0 && node.Ids == nil {
2,359✔
191
                node.Ids = make([]ID, len(components))
1,120✔
192

1,120✔
193
                var maxSize uintptr = 0
1,120✔
194
                for i, c := range components {
6,393✔
195
                        node.Ids[i] = c.ID
5,273✔
196
                        size, align := c.Type.Size(), uintptr(c.Type.Align())
5,273✔
197
                        size = (size + (align - 1)) / align * align
5,273✔
198
                        if size > maxSize {
6,390✔
199
                                maxSize = size
1,117✔
200
                        }
1,117✔
201
                }
202

203
                if maxSize > 0 {
2,235✔
204
                        node.zeroValue = make([]byte, maxSize)
1,115✔
205
                        node.zeroPointer = unsafe.Pointer(&node.zeroValue[0])
1,115✔
206
                }
1,115✔
207
        }
208

209
        a.buffers = make([]reflect.Value, len(components))
1,239✔
210
        a.layouts = make([]layout, MaskTotalBits)
1,239✔
211
        a.indices = newIDMap[uint32]()
1,239✔
212
        a.index = index
1,239✔
213

1,239✔
214
        cap := 1
1,239✔
215
        if forStorage {
2,396✔
216
                cap = int(node.capacityIncrement)
1,157✔
217
        }
1,157✔
218

219
        prev := -1
1,239✔
220
        for i, c := range components {
6,564✔
221
                if int(c.ID) <= prev {
5,326✔
222
                        panic("component arguments must be sorted by ID")
1✔
223
                }
224
                prev = int(c.ID)
5,324✔
225
                mask.Set(c.ID, true)
5,324✔
226

5,324✔
227
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,324✔
228
                size = (size + (align - 1)) / align * align
5,324✔
229

5,324✔
230
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,324✔
231
                a.layouts[c.ID] = layout{
5,324✔
232
                        a.buffers[i].Addr().UnsafePointer(),
5,324✔
233
                        size,
5,324✔
234
                }
5,324✔
235
                a.indices.Set(c.ID, uint32(i))
5,324✔
236
        }
237
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,238✔
238

1,238✔
239
        a.archetypeAccess = archetypeAccess{
1,238✔
240
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,238✔
241
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,238✔
242
                Mask:              mask,
1,238✔
243
                Relation:          relation,
1,238✔
244
                RelationComponent: relationComp,
1,238✔
245
        }
1,238✔
246

1,238✔
247
        a.graphNode = node
1,238✔
248

1,238✔
249
        a.len = 0
1,238✔
250
        a.cap = uint32(cap)
1,238✔
251
}
252

253
// Add adds an entity with optionally zeroed components to the archetype
254
func (a *archetype) Alloc(entity Entity) uintptr {
9,766✔
255
        idx := uintptr(a.len)
9,766✔
256
        a.extend(1)
9,766✔
257
        a.addEntity(idx, &entity)
9,766✔
258
        a.len++
9,766✔
259
        return idx
9,766✔
260
}
9,766✔
261

262
// Add adds storage to the archetype
263
func (a *archetype) AllocN(count uint32) {
30✔
264
        a.extend(count)
30✔
265
        a.len += count
30✔
266
}
30✔
267

268
// Add adds an entity with components to the archetype
269
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
270
        if len(components) != len(a.graphNode.Ids) {
10✔
271
                panic("Invalid number of components")
1✔
272
        }
273
        idx := uintptr(a.len)
8✔
274

8✔
275
        a.extend(1)
8✔
276
        a.addEntity(idx, &entity)
8✔
277
        for _, c := range components {
24✔
278
                lay := a.getLayout(c.ID)
16✔
279
                size := lay.itemSize
16✔
280
                if size == 0 {
18✔
281
                        continue
2✔
282
                }
283
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
284
                dst := a.Get(uintptr(idx), c.ID)
14✔
285
                a.copy(src, dst, size)
14✔
286
        }
287
        a.len++
8✔
288
        return idx
8✔
289
}
290

291
// Remove removes an entity and its components from the archetype.
292
//
293
// Performs a swap-remove and reports whether a swap was necessary
294
// (i.e. not the last entity that was removed).
295
func (a *archetype) Remove(index uintptr) bool {
4,947✔
296
        swapped := a.removeEntity(index)
4,947✔
297

4,947✔
298
        old := uintptr(a.len - 1)
4,947✔
299

4,947✔
300
        if index != old {
5,128✔
301
                for _, id := range a.graphNode.Ids {
409✔
302
                        lay := a.getLayout(id)
228✔
303
                        size := lay.itemSize
228✔
304
                        if size == 0 {
237✔
305
                                continue
9✔
306
                        }
307
                        src := unsafe.Add(lay.pointer, old*size)
219✔
308
                        dst := unsafe.Add(lay.pointer, index*size)
219✔
309
                        a.copy(src, dst, size)
219✔
310
                }
311
        }
312
        a.ZeroAll(old)
4,947✔
313
        a.len--
4,947✔
314

4,947✔
315
        return swapped
4,947✔
316
}
317

318
// ZeroAll resets a block of storage in all buffers.
319
func (a *archetype) ZeroAll(index uintptr) {
4,947✔
320
        for _, id := range a.graphNode.Ids {
7,849✔
321
                a.Zero(index, id)
2,902✔
322
        }
2,902✔
323
}
324

325
// ZeroAll resets a block of storage in one buffer.
326
func (a *archetype) Zero(index uintptr, id ID) {
2,902✔
327
        lay := a.getLayout(id)
2,902✔
328
        size := lay.itemSize
2,902✔
329
        if size == 0 {
5,443✔
330
                return
2,541✔
331
        }
2,541✔
332
        dst := unsafe.Add(lay.pointer, index*size)
361✔
333
        a.copy(a.graphNode.zeroPointer, dst, size)
361✔
334
}
335

336
// SetEntity overwrites an entity
337
func (a *archetype) SetEntity(index uintptr, entity Entity) {
1,062✔
338
        a.addEntity(index, &entity)
1,062✔
339
}
1,062✔
340

341
// Set overwrites a component with the data behind the given pointer
342
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
832✔
343
        lay := a.getLayout(id)
832✔
344
        dst := a.Get(index, id)
832✔
345
        size := lay.itemSize
832✔
346
        if size == 0 {
861✔
347
                return dst
29✔
348
        }
29✔
349
        rValue := reflect.ValueOf(comp)
803✔
350

803✔
351
        src := rValue.UnsafePointer()
803✔
352
        a.copy(src, dst, size)
803✔
353
        return dst
803✔
354
}
355

356
// SetPointer overwrites a component with the data behind the given pointer
357
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
2,559✔
358
        lay := a.getLayout(id)
2,559✔
359
        dst := a.Get(index, id)
2,559✔
360
        size := lay.itemSize
2,559✔
361
        if size == 0 {
5,093✔
362
                return dst
2,534✔
363
        }
2,534✔
364

365
        a.copy(comp, dst, size)
25✔
366
        return dst
25✔
367
}
368

369
// Reset removes all entities and components.
370
//
371
// Does NOT free the reserved memory.
372
func (a *archetype) Reset() {
33✔
373
        if a.len == 0 {
49✔
374
                return
16✔
375
        }
16✔
376
        a.len = 0
17✔
377
        for _, buf := range a.buffers {
38✔
378
                buf.SetZero()
21✔
379
        }
21✔
380
}
381

382
// Deactivate the archetype for later re-use.
383
func (a *archetype) Deactivate() {
11✔
384
        a.Reset()
11✔
385
        a.index = -1
11✔
386
}
11✔
387

388
// Activate reactivates a de-activated archetype.
389
func (a *archetype) Activate(target Entity, index int32) {
2✔
390
        a.index = index
2✔
391
        a.Relation = target
2✔
392
}
2✔
393

394
// IsActive returns whether the archetype is active.
395
// Otherwise, it is eligible for re-use.
396
func (a *archetype) IsActive() bool {
536✔
397
        return a.index >= 0
536✔
398
}
536✔
399

400
// Components returns the component IDs for this archetype
401
func (a *archetype) Components() []ID {
2,191✔
402
        return a.graphNode.Ids
2,191✔
403
}
2,191✔
404

405
// Len reports the number of entities in the archetype
406
func (a *archetype) Len() uint32 {
5,660✔
407
        return a.len
5,660✔
408
}
5,660✔
409

410
// Cap reports the current capacity of the archetype
411
func (a *archetype) Cap() uint32 {
23✔
412
        return a.cap
23✔
413
}
23✔
414

415
// Stats generates statistics for an archetype
416
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
6✔
417
        ids := a.Components()
6✔
418
        aCompCount := len(ids)
6✔
419
        aTypes := make([]reflect.Type, aCompCount)
6✔
420
        for j, id := range ids {
10✔
421
                aTypes[j], _ = reg.ComponentType(id)
4✔
422
        }
4✔
423

424
        cap := int(a.Cap())
6✔
425
        memPerEntity := 0
6✔
426
        for _, id := range a.graphNode.Ids {
10✔
427
                lay := a.getLayout(id)
4✔
428
                memPerEntity += int(lay.itemSize)
4✔
429
        }
4✔
430
        memory := cap * (int(entitySize) + memPerEntity)
6✔
431

6✔
432
        return stats.ArchetypeStats{
6✔
433
                IsActive:        a.IsActive(),
6✔
434
                Size:            int(a.Len()),
6✔
435
                Capacity:        cap,
6✔
436
                Components:      aCompCount,
6✔
437
                ComponentIDs:    ids,
6✔
438
                ComponentTypes:  aTypes,
6✔
439
                Memory:          memory,
6✔
440
                MemoryPerEntity: memPerEntity,
6✔
441
        }
6✔
442
}
443

444
// UpdateStats updates statistics for an archetype
445
func (a *archetype) UpdateStats(stats *stats.ArchetypeStats, reg *componentRegistry[ID]) {
7✔
446
        if stats.Dirty {
8✔
447
                ids := a.Components()
1✔
448
                aCompCount := len(ids)
1✔
449
                aTypes := make([]reflect.Type, aCompCount)
1✔
450
                for j, id := range ids {
2✔
451
                        aTypes[j], _ = reg.ComponentType(id)
1✔
452
                }
1✔
453

454
                memPerEntity := 0
1✔
455
                for _, id := range a.graphNode.Ids {
2✔
456
                        lay := a.getLayout(id)
1✔
457
                        memPerEntity += int(lay.itemSize)
1✔
458
                }
1✔
459

460
                stats.IsActive = a.IsActive()
1✔
461
                stats.Components = aCompCount
1✔
462
                stats.ComponentIDs = ids
1✔
463
                stats.ComponentTypes = aTypes
1✔
464
                stats.MemoryPerEntity = memPerEntity
1✔
465
                stats.Dirty = false
1✔
466
        }
467

468
        cap := int(a.Cap())
7✔
469
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
7✔
470

7✔
471
        stats.Size = int(a.Len())
7✔
472
        stats.Capacity = cap
7✔
473
        stats.Memory = memory
7✔
474

475
}
476

477
// copy from one pointer to another.
478
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
12,439✔
479
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
12,439✔
480
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
12,439✔
481
        copy(dstSlice, srcSlice)
12,439✔
482
}
12,439✔
483

484
// extend the memory buffers if necessary for adding an entity.
485
func (a *archetype) extend(by uint32) {
9,807✔
486
        required := a.len + by
9,807✔
487
        if a.cap >= required {
19,577✔
488
                return
9,770✔
489
        }
9,770✔
490
        a.cap = capacityU32(required, a.graphNode.capacityIncrement)
37✔
491

37✔
492
        old := a.entityBuffer
37✔
493
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
37✔
494
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
37✔
495
        reflect.Copy(a.entityBuffer, old)
37✔
496

37✔
497
        for _, id := range a.graphNode.Ids {
77✔
498
                lay := a.getLayout(id)
40✔
499
                if lay.itemSize == 0 {
41✔
500
                        continue
1✔
501
                }
502
                index, _ := a.indices.Get(id)
39✔
503
                old := a.buffers[index]
39✔
504
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
39✔
505
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
39✔
506
                reflect.Copy(a.buffers[index], old)
39✔
507
        }
508
}
509

510
// Adds an entity at the given index. Does not extend the entity buffer.
511
func (a *archetype) addEntity(index uintptr, entity *Entity) {
10,836✔
512
        dst := unsafe.Add(a.entityPointer, entitySize*index)
10,836✔
513
        src := unsafe.Pointer(entity)
10,836✔
514
        a.copy(src, dst, entitySize)
10,836✔
515
}
10,836✔
516

517
// removeEntity removes an entity from tne archetype.
518
// Components need to be removed separately.
519
func (a *archetype) removeEntity(index uintptr) bool {
4,947✔
520
        old := uintptr(a.len - 1)
4,947✔
521

4,947✔
522
        if index == old {
9,713✔
523
                return false
4,766✔
524
        }
4,766✔
525

526
        src := unsafe.Add(a.entityPointer, old*entitySize)
181✔
527
        dst := unsafe.Add(a.entityPointer, index*entitySize)
181✔
528
        a.copy(src, dst, entitySize)
181✔
529

181✔
530
        return true
181✔
531
}
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