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

mlange-42 / arche / 4846735678

30 Apr 2023 10:35PM CUT coverage: 100.0%. Remained the same
4846735678

push

github

GitHub
Remove archetypes with relation on reset (#247)

18 of 18 new or added lines in 3 files covered. (100.0%)

3149 of 3149 relevant lines covered (100.0%)

2542.58 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,214✔
30
        var arch map[Entity]*archetype
1,214✔
31
        if relation >= 0 {
1,226✔
32
                arch = map[Entity]*archetype{}
12✔
33
        }
12✔
34
        return archetypeNode{
1,214✔
35
                mask:              mask,
1,214✔
36
                archetypes:        arch,
1,214✔
37
                TransitionAdd:     newIDMap[*archetypeNode](),
1,214✔
38
                TransitionRemove:  newIDMap[*archetypeNode](),
1,214✔
39
                relation:          relation,
1,214✔
40
                capacityIncrement: uint32(capacityIncrement),
1,214✔
41
        }
1,214✔
42
}
43

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

51
func (a *archetypeNode) SetArchetype(id Entity, arch *archetype) {
1,219✔
52
        if a.relation >= 0 {
1,265✔
53
                a.archetypes[id] = arch
46✔
54
        } else {
1,219✔
55
                a.archetype = arch
1,173✔
56
        }
1,173✔
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,131✔
70
        return f.Matches(a.Mask, &a.Relation)
3,131✔
71
}
3,131✔
72

73
// GetEntity returns the entity at the given index
74
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
1,689✔
75
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
1,689✔
76
}
1,689✔
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,880✔
80
        return a.getLayout(id).Get(index)
106,880✔
81
}
106,880✔
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
// HasRelation returns whether the archetype has a relation component.
94
func (a *archetypeAccess) HasRelation() bool {
12✔
95
        return a.RelationComponent >= 0
12✔
96
}
12✔
97

98
// GetLayout returns the column layout for a component.
99
func (a *archetypeAccess) getLayout(id ID) *layout {
121,614✔
100
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
121,614✔
101
}
121,614✔
102

103
// layout specification of a component column.
104
type layout struct {
105
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
106
        itemSize uintptr        // Component/step size
107
}
108

109
// Get returns a pointer to the item at the given index.
110
func (l *layout) Get(index uintptr) unsafe.Pointer {
106,878✔
111
        if l.pointer == nil {
106,879✔
112
                return nil
1✔
113
        }
1✔
114
        return unsafe.Add(l.pointer, l.itemSize*index)
106,877✔
115
}
116

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

130
// Init initializes an archetype
131
func (a *archetype) Init(node *archetypeNode, index int32, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
1,228✔
132
        var mask Mask
1,228✔
133
        if len(components) > 0 && len(node.Ids) == 0 {
2,343✔
134
                node.Ids = make([]ID, len(components))
1,115✔
135

1,115✔
136
                var maxSize uintptr = 0
1,115✔
137
                for i, c := range components {
6,382✔
138
                        node.Ids[i] = c.ID
5,267✔
139
                        size, align := c.Type.Size(), uintptr(c.Type.Align())
5,267✔
140
                        size = (size + (align - 1)) / align * align
5,267✔
141
                        if size > maxSize {
6,381✔
142
                                maxSize = size
1,114✔
143
                        }
1,114✔
144
                }
145

146
                if maxSize > 0 {
2,227✔
147
                        node.zeroValue = make([]byte, maxSize)
1,112✔
148
                        node.zeroPointer = unsafe.Pointer(&node.zeroValue[0])
1,112✔
149
                }
1,112✔
150
        }
151

152
        a.buffers = make([]reflect.Value, len(components))
1,228✔
153
        a.layouts = make([]layout, MaskTotalBits)
1,228✔
154
        a.indices = newIDMap[uint32]()
1,228✔
155
        a.index = index
1,228✔
156

1,228✔
157
        cap := 1
1,228✔
158
        if forStorage {
2,377✔
159
                cap = int(node.capacityIncrement)
1,149✔
160
        }
1,149✔
161

162
        prev := -1
1,228✔
163
        for i, c := range components {
6,544✔
164
                if int(c.ID) <= prev {
5,317✔
165
                        panic("component arguments must be sorted by ID")
1✔
166
                }
167
                prev = int(c.ID)
5,315✔
168
                mask.Set(c.ID, true)
5,315✔
169

5,315✔
170
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,315✔
171
                size = (size + (align - 1)) / align * align
5,315✔
172

5,315✔
173
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,315✔
174
                a.layouts[c.ID] = layout{
5,315✔
175
                        a.buffers[i].Addr().UnsafePointer(),
5,315✔
176
                        size,
5,315✔
177
                }
5,315✔
178
                a.indices.Set(c.ID, uint32(i))
5,315✔
179
        }
180
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,227✔
181

1,227✔
182
        a.archetypeAccess = archetypeAccess{
1,227✔
183
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,227✔
184
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,227✔
185
                Mask:              mask,
1,227✔
186
                Relation:          relation,
1,227✔
187
                RelationComponent: relationComp,
1,227✔
188
        }
1,227✔
189

1,227✔
190
        a.graphNode = node
1,227✔
191

1,227✔
192
        a.len = 0
1,227✔
193
        a.cap = uint32(cap)
1,227✔
194
}
195

196
// Add adds an entity with optionally zeroed components to the archetype
197
func (a *archetype) Alloc(entity Entity) uintptr {
9,738✔
198
        idx := uintptr(a.len)
9,738✔
199
        a.extend(1)
9,738✔
200
        a.addEntity(idx, &entity)
9,738✔
201
        a.len++
9,738✔
202
        return idx
9,738✔
203
}
9,738✔
204

205
// Add adds storage to the archetype
206
func (a *archetype) AllocN(count uint32) {
27✔
207
        a.extend(count)
27✔
208
        a.len += count
27✔
209
}
27✔
210

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

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

234
// Remove removes an entity and its components from the archetype.
235
//
236
// Performs a swap-remove and reports whether a swap was necessary
237
// (i.e. not the last entity that was removed).
238
func (a *archetype) Remove(index uintptr) bool {
4,936✔
239
        swapped := a.removeEntity(index)
4,936✔
240

4,936✔
241
        old := uintptr(a.len - 1)
4,936✔
242

4,936✔
243
        if index != old {
5,114✔
244
                for _, id := range a.graphNode.Ids {
403✔
245
                        lay := a.getLayout(id)
225✔
246
                        size := lay.itemSize
225✔
247
                        if size == 0 {
234✔
248
                                continue
9✔
249
                        }
250
                        src := unsafe.Add(lay.pointer, old*size)
216✔
251
                        dst := unsafe.Add(lay.pointer, index*size)
216✔
252
                        a.copy(src, dst, size)
216✔
253
                }
254
        }
255
        a.ZeroAll(old)
4,936✔
256
        a.len--
4,936✔
257

4,936✔
258
        return swapped
4,936✔
259
}
260

261
// ZeroAll resets a block of storage in all buffers.
262
func (a *archetype) ZeroAll(index uintptr) {
4,936✔
263
        for _, id := range a.graphNode.Ids {
7,827✔
264
                a.Zero(index, id)
2,891✔
265
        }
2,891✔
266
}
267

268
// ZeroAll resets a block of storage in one buffer.
269
func (a *archetype) Zero(index uintptr, id ID) {
2,891✔
270
        lay := a.getLayout(id)
2,891✔
271
        size := lay.itemSize
2,891✔
272
        if size == 0 {
5,422✔
273
                return
2,531✔
274
        }
2,531✔
275
        dst := unsafe.Add(lay.pointer, index*size)
360✔
276
        a.copy(a.graphNode.zeroPointer, dst, size)
360✔
277
}
278

279
// SetEntity overwrites an entity
280
func (a *archetype) SetEntity(index uintptr, entity Entity) {
1,032✔
281
        a.addEntity(index, &entity)
1,032✔
282
}
1,032✔
283

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

799✔
294
        src := rValue.UnsafePointer()
799✔
295
        a.copy(src, dst, size)
799✔
296
        return dst
799✔
297
}
298

299
// SetPointer overwrites a component with the data behind the given pointer
300
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
2,550✔
301
        lay := a.getLayout(id)
2,550✔
302
        dst := a.Get(index, id)
2,550✔
303
        size := lay.itemSize
2,550✔
304
        if size == 0 {
5,075✔
305
                return dst
2,525✔
306
        }
2,525✔
307

308
        a.copy(comp, dst, size)
25✔
309
        return dst
25✔
310
}
311

312
// Reset removes all entities and components.
313
//
314
// Does NOT free the reserved memory.
315
func (a *archetype) Reset() {
25✔
316
        a.len = 0
25✔
317
        for _, buf := range a.buffers {
57✔
318
                buf.SetZero()
32✔
319
        }
32✔
320
}
321

322
func (a *archetype) Deactivate() {
7✔
323
        a.Reset()
7✔
324
        a.index = -1
7✔
325
        a.graphNode = nil
7✔
326
        a.layouts = nil
7✔
327
        a.indices = newIDMap[uint32]()
7✔
328
        a.buffers = nil
7✔
329
        a.entityBuffer.SetZero()
7✔
330
}
7✔
331

332
func (a *archetype) IsActive() bool {
3,159✔
333
        return a.index >= 0
3,159✔
334
}
3,159✔
335

336
// Components returns the component IDs for this archetype
337
func (a *archetype) Components() []ID {
2,193✔
338
        return a.graphNode.Ids
2,193✔
339
}
2,193✔
340

341
// Len reports the number of entities in the archetype
342
func (a *archetype) Len() uint32 {
7,446✔
343
        return a.len
7,446✔
344
}
7,446✔
345

346
// Cap reports the current capacity of the archetype
347
func (a *archetype) Cap() uint32 {
25✔
348
        return a.cap
25✔
349
}
25✔
350

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

360
        cap := int(a.Cap())
8✔
361
        memPerEntity := 0
8✔
362
        for _, id := range a.graphNode.Ids {
16✔
363
                lay := a.getLayout(id)
8✔
364
                memPerEntity += int(lay.itemSize)
8✔
365
        }
8✔
366
        memory := cap * (int(entitySize) + memPerEntity)
8✔
367

8✔
368
        return stats.ArchetypeStats{
8✔
369
                IsActive:        a.IsActive(),
8✔
370
                Size:            int(a.Len()),
8✔
371
                Capacity:        cap,
8✔
372
                Components:      aCompCount,
8✔
373
                ComponentIDs:    ids,
8✔
374
                ComponentTypes:  aTypes,
8✔
375
                Memory:          memory,
8✔
376
                MemoryPerEntity: memPerEntity,
8✔
377
        }
8✔
378
}
379

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

390
                memPerEntity := 0
1✔
391
                for _, id := range a.graphNode.Ids {
2✔
392
                        lay := a.getLayout(id)
1✔
393
                        memPerEntity += int(lay.itemSize)
1✔
394
                }
1✔
395

396
                stats.IsActive = a.IsActive()
1✔
397
                stats.Components = aCompCount
1✔
398
                stats.ComponentIDs = ids
1✔
399
                stats.ComponentTypes = aTypes
1✔
400
                stats.MemoryPerEntity = memPerEntity
1✔
401
                stats.Dirty = false
1✔
402
        }
403

404
        cap := int(a.Cap())
7✔
405
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
7✔
406

7✔
407
        stats.Size = int(a.Len())
7✔
408
        stats.Capacity = cap
7✔
409
        stats.Memory = memory
7✔
410

411
}
412

413
// copy from one pointer to another.
414
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
12,370✔
415
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
12,370✔
416
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
12,370✔
417
        copy(dstSlice, srcSlice)
12,370✔
418
}
12,370✔
419

420
// extend the memory buffers if necessary for adding an entity.
421
func (a *archetype) extend(by uint32) {
9,776✔
422
        required := a.len + by
9,776✔
423
        if a.cap >= required {
19,516✔
424
                return
9,740✔
425
        }
9,740✔
426
        a.cap = capacityU32(required, a.graphNode.capacityIncrement)
36✔
427

36✔
428
        old := a.entityBuffer
36✔
429
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
36✔
430
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
36✔
431
        reflect.Copy(a.entityBuffer, old)
36✔
432

36✔
433
        for _, id := range a.graphNode.Ids {
76✔
434
                lay := a.getLayout(id)
40✔
435
                if lay.itemSize == 0 {
41✔
436
                        continue
1✔
437
                }
438
                index, _ := a.indices.Get(id)
39✔
439
                old := a.buffers[index]
39✔
440
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
39✔
441
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
39✔
442
                reflect.Copy(a.buffers[index], old)
39✔
443
        }
444
}
445

446
// Adds an entity at the given index. Does not extend the entity buffer.
447
func (a *archetype) addEntity(index uintptr, entity *Entity) {
10,778✔
448
        dst := unsafe.Add(a.entityPointer, entitySize*index)
10,778✔
449
        src := unsafe.Pointer(entity)
10,778✔
450
        a.copy(src, dst, entitySize)
10,778✔
451
}
10,778✔
452

453
// removeEntity removes an entity from tne archetype.
454
// Components need to be removed separately.
455
func (a *archetype) removeEntity(index uintptr) bool {
4,936✔
456
        old := uintptr(a.len - 1)
4,936✔
457

4,936✔
458
        if index == old {
9,694✔
459
                return false
4,758✔
460
        }
4,758✔
461

462
        src := unsafe.Add(a.entityPointer, old*entitySize)
178✔
463
        dst := unsafe.Add(a.entityPointer, index*entitySize)
178✔
464
        a.copy(src, dst, entitySize)
178✔
465

178✔
466
        return true
178✔
467
}
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