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

mlange-42 / arche / 4835562198

28 Apr 2023 11:21PM CUT coverage: 100.0%. Remained the same
4835562198

Pull #231

github

GitHub
Merge e036f4802 into c9fbc3a05
Pull Request #231: Entity relations

124 of 124 new or added lines in 9 files covered. (100.0%)

2814 of 2814 relevant lines covered (100.0%)

4441.52 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
        archetype        *archetype // The archetype
18
        archetypes       map[Entity]*archetype
19
        TransitionAdd    idMap[*archetypeNode] // Mapping from component ID to add to the resulting archetype
20
        TransitionRemove idMap[*archetypeNode] // Mapping from component ID to remove to the resulting archetype
21
        relation         int8
22
}
23

24
// Creates a new archetypeNode
25
func newArchetypeNode(mask Mask, relation int8) archetypeNode {
1,214✔
26
        var arch map[Entity]*archetype
1,214✔
27
        if relation >= 0 {
1,218✔
28
                arch = map[Entity]*archetype{}
4✔
29
        }
4✔
30
        return archetypeNode{
1,214✔
31
                mask:             mask,
1,214✔
32
                archetypes:       arch,
1,214✔
33
                TransitionAdd:    newIDMap[*archetypeNode](),
1,214✔
34
                TransitionRemove: newIDMap[*archetypeNode](),
1,214✔
35
                relation:         relation,
1,214✔
36
        }
1,214✔
37
}
38

39
func (a *archetypeNode) GetArchetype(id Entity) *archetype {
7,536✔
40
        if a.relation >= 0 {
12,556✔
41
                return a.archetypes[id]
5,020✔
42
        }
5,020✔
43
        return a.archetype
2,516✔
44
}
45

46
func (a *archetypeNode) SetArchetype(id Entity, arch *archetype) {
1,216✔
47
        if a.relation >= 0 {
1,247✔
48
                a.archetypes[id] = arch
31✔
49
        } else {
1,216✔
50
                a.archetype = arch
1,185✔
51
        }
1,185✔
52
}
53

54
// Helper for accessing data from an archetype
55
type archetypeAccess struct {
56
        Mask              Mask           // Archetype's mask
57
        basePointer       unsafe.Pointer // Pointer to the first component column layout.
58
        entityPointer     unsafe.Pointer // Pointer to the entity storage
59
        Relation          Entity
60
        RelationComponent int8
61
}
62

63
// Matches checks if the archetype matches the given mask.
64
func (a *archetype) Matches(f Filter) bool {
3,136✔
65
        return f.Matches(a.Mask, &a.Relation)
3,136✔
66
}
3,136✔
67

68
// GetEntity returns the entity at the given index
69
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
32,320✔
70
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
32,320✔
71
}
32,320✔
72

73
// Get returns the component with the given ID at the given index
74
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
149,659✔
75
        return a.getLayout(id).Get(index)
149,659✔
76
}
149,659✔
77

78
// HasComponent returns whether the archetype contains the given component ID
79
func (a *archetypeAccess) HasComponent(id ID) bool {
48,488✔
80
        return a.getLayout(id).pointer != nil
48,488✔
81
}
48,488✔
82

83
// GetLayout returns the column layout for a component.
84
func (a *archetypeAccess) getLayout(id ID) *layout {
244,968✔
85
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
244,968✔
86
}
244,968✔
87

88
// layout specification of a component column.
89
type layout struct {
90
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
91
        itemSize uintptr        // Component/step size
92
}
93

94
// Get returns a pointer to the item at the given index.
95
func (l *layout) Get(index uintptr) unsafe.Pointer {
149,657✔
96
        if l.pointer == nil {
149,658✔
97
                return nil
1✔
98
        }
1✔
99
        return unsafe.Add(l.pointer, l.itemSize*index)
149,656✔
100
}
101

102
// archetype represents an ECS archetype
103
type archetype struct {
104
        archetypeAccess                   // Access helper, passed to queries.
105
        graphNode         *archetypeNode  // Node in the archetype graph.
106
        Ids               []ID            // List of component IDs.
107
        layouts           []layout        // Column layouts by ID.
108
        indices           idMap[uint32]   // Mapping from IDs to buffer indices.
109
        buffers           []reflect.Value // Reflection arrays containing component data.
110
        entityBuffer      reflect.Value   // Reflection array containing entity data.
111
        len               uint32          // Current number of entities
112
        cap               uint32          // Current capacity
113
        capacityIncrement uint32          // Capacity increment
114
        zeroValue         []byte          // Used as source for setting storage to zero
115
        zeroPointer       unsafe.Pointer  // Points to zeroValue for fast access
116
}
117

118
// Init initializes an archetype
119
func (a *archetype) Init(node *archetypeNode, capacityIncrement int, forStorage bool, relation Entity, relationComp int8, components ...componentType) {
1,225✔
120
        var mask Mask
1,225✔
121
        if len(components) > 0 {
2,371✔
122
                a.Ids = make([]ID, len(components))
1,146✔
123
        }
1,146✔
124

125
        a.buffers = make([]reflect.Value, len(components))
1,225✔
126
        a.layouts = make([]layout, MaskTotalBits)
1,225✔
127
        a.indices = newIDMap[uint32]()
1,225✔
128

1,225✔
129
        cap := 1
1,225✔
130
        if forStorage {
2,367✔
131
                cap = capacityIncrement
1,142✔
132
        }
1,142✔
133

134
        prev := -1
1,225✔
135
        var maxSize uintptr = 0
1,225✔
136
        for i, c := range components {
6,526✔
137
                if int(c.ID) <= prev {
5,302✔
138
                        panic("component arguments must be sorted by ID")
1✔
139
                }
140
                prev = int(c.ID)
5,300✔
141
                mask.Set(c.ID, true)
5,300✔
142

5,300✔
143
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,300✔
144
                size = (size + (align - 1)) / align * align
5,300✔
145
                if size > maxSize {
6,446✔
146
                        maxSize = size
1,146✔
147
                }
1,146✔
148

149
                a.Ids[i] = c.ID
5,300✔
150
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,300✔
151
                a.layouts[c.ID] = layout{
5,300✔
152
                        a.buffers[i].Addr().UnsafePointer(),
5,300✔
153
                        size,
5,300✔
154
                }
5,300✔
155
                a.indices.Set(c.ID, uint32(i))
5,300✔
156
        }
157
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,224✔
158

1,224✔
159
        a.archetypeAccess = archetypeAccess{
1,224✔
160
                basePointer:       unsafe.Pointer(&a.layouts[0]),
1,224✔
161
                entityPointer:     a.entityBuffer.Addr().UnsafePointer(),
1,224✔
162
                Mask:              mask,
1,224✔
163
                Relation:          relation,
1,224✔
164
                RelationComponent: relationComp,
1,224✔
165
        }
1,224✔
166

1,224✔
167
        a.graphNode = node
1,224✔
168

1,224✔
169
        a.capacityIncrement = uint32(capacityIncrement)
1,224✔
170
        a.len = 0
1,224✔
171
        a.cap = uint32(cap)
1,224✔
172

1,224✔
173
        if maxSize > 0 {
2,368✔
174
                a.zeroValue = make([]byte, maxSize)
1,144✔
175
                a.zeroPointer = unsafe.Pointer(&a.zeroValue[0])
1,144✔
176
        }
1,144✔
177
}
178

179
// Add adds an entity with optionally zeroed components to the archetype
180
func (a *archetype) Alloc(entity Entity) uintptr {
9,700✔
181
        idx := uintptr(a.len)
9,700✔
182
        a.extend(1)
9,700✔
183
        a.addEntity(idx, &entity)
9,700✔
184
        a.len++
9,700✔
185
        return idx
9,700✔
186
}
9,700✔
187

188
// Add adds storage to the archetype
189
func (a *archetype) AllocN(count uint32) {
28✔
190
        a.extend(count)
28✔
191
        a.len += count
28✔
192
}
28✔
193

194
// Add adds an entity with components to the archetype
195
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
196
        if len(components) != len(a.Ids) {
10✔
197
                panic("Invalid number of components")
1✔
198
        }
199
        idx := uintptr(a.len)
8✔
200

8✔
201
        a.extend(1)
8✔
202
        a.addEntity(idx, &entity)
8✔
203
        for _, c := range components {
24✔
204
                lay := a.getLayout(c.ID)
16✔
205
                size := lay.itemSize
16✔
206
                if size == 0 {
18✔
207
                        continue
2✔
208
                }
209
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
210
                dst := a.Get(uintptr(idx), c.ID)
14✔
211
                a.copy(src, dst, size)
14✔
212
        }
213
        a.len++
8✔
214
        return idx
8✔
215
}
216

217
// Remove removes an entity and its components from the archetype.
218
//
219
// Performs a swap-remove and reports whether a swap was necessary
220
// (i.e. not the last entity that was removed).
221
func (a *archetype) Remove(index uintptr) bool {
4,914✔
222
        swapped := a.removeEntity(index)
4,914✔
223

4,914✔
224
        old := uintptr(a.len - 1)
4,914✔
225

4,914✔
226
        if index != old {
5,081✔
227
                for _, id := range a.Ids {
377✔
228
                        lay := a.getLayout(id)
210✔
229
                        size := lay.itemSize
210✔
230
                        if size == 0 {
211✔
231
                                continue
1✔
232
                        }
233
                        src := unsafe.Add(lay.pointer, old*size)
209✔
234
                        dst := unsafe.Add(lay.pointer, index*size)
209✔
235
                        a.copy(src, dst, size)
209✔
236
                }
237
        }
238
        a.ZeroAll(old)
4,914✔
239
        a.len--
4,914✔
240

4,914✔
241
        return swapped
4,914✔
242
}
243

244
// ZeroAll resets a block of storage in all buffers.
245
func (a *archetype) ZeroAll(index uintptr) {
4,914✔
246
        for _, id := range a.Ids {
7,774✔
247
                a.Zero(index, id)
2,860✔
248
        }
2,860✔
249
}
250

251
// ZeroAll resets a block of storage in one buffer.
252
func (a *archetype) Zero(index uintptr, id ID) {
2,860✔
253
        lay := a.getLayout(id)
2,860✔
254
        size := lay.itemSize
2,860✔
255
        if size == 0 {
2,863✔
256
                return
3✔
257
        }
3✔
258
        dst := unsafe.Add(lay.pointer, index*size)
2,857✔
259
        a.copy(a.zeroPointer, dst, size)
2,857✔
260
}
261

262
// SetEntity overwrites an entity
263
func (a *archetype) SetEntity(index uintptr, entity Entity) {
71,342✔
264
        a.addEntity(index, &entity)
71,342✔
265
}
71,342✔
266

267
// Set overwrites a component with the data behind the given pointer
268
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
41,150✔
269
        lay := a.getLayout(id)
41,150✔
270
        dst := a.Get(index, id)
41,150✔
271
        size := lay.itemSize
41,150✔
272
        if size == 0 {
41,151✔
273
                return dst
1✔
274
        }
1✔
275
        rValue := reflect.ValueOf(comp)
41,149✔
276

41,149✔
277
        src := rValue.UnsafePointer()
41,149✔
278
        a.copy(src, dst, size)
41,149✔
279
        return dst
41,149✔
280
}
281

282
// SetPointer overwrites a component with the data behind the given pointer
283
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
2,523✔
284
        lay := a.getLayout(id)
2,523✔
285
        dst := a.Get(index, id)
2,523✔
286
        size := lay.itemSize
2,523✔
287
        if size == 0 {
2,524✔
288
                return dst
1✔
289
        }
1✔
290

291
        a.copy(comp, dst, size)
2,522✔
292
        return dst
2,522✔
293
}
294

295
// Reset removes all entities and components.
296
//
297
// Does NOT free the reserved memory.
298
func (a *archetype) Reset() {
15✔
299
        a.len = 0
15✔
300
        for _, buf := range a.buffers {
31✔
301
                buf.SetZero()
16✔
302
        }
16✔
303
}
304

305
// Components returns the component IDs for this archetype
306
func (a *archetype) Components() []ID {
2,189✔
307
        return a.Ids
2,189✔
308
}
2,189✔
309

310
// Len reports the number of entities in the archetype
311
func (a *archetype) Len() uint32 {
2,497✔
312
        return a.len
2,497✔
313
}
2,497✔
314

315
// Cap reports the current capacity of the archetype
316
func (a *archetype) Cap() uint32 {
18✔
317
        return a.cap
18✔
318
}
18✔
319

320
// Stats generates statistics for an archetype
321
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
5✔
322
        ids := a.Components()
5✔
323
        aCompCount := len(ids)
5✔
324
        aTypes := make([]reflect.Type, aCompCount)
5✔
325
        for j, id := range ids {
9✔
326
                aTypes[j], _ = reg.ComponentType(id)
4✔
327
        }
4✔
328

329
        cap := int(a.Cap())
5✔
330
        memPerEntity := 0
5✔
331
        for _, id := range a.Ids {
9✔
332
                lay := a.getLayout(id)
4✔
333
                memPerEntity += int(lay.itemSize)
4✔
334
        }
4✔
335
        memory := cap * (int(entitySize) + memPerEntity)
5✔
336

5✔
337
        return stats.ArchetypeStats{
5✔
338
                Size:            int(a.Len()),
5✔
339
                Capacity:        cap,
5✔
340
                Components:      aCompCount,
5✔
341
                ComponentIDs:    ids,
5✔
342
                ComponentTypes:  aTypes,
5✔
343
                Memory:          memory,
5✔
344
                MemoryPerEntity: memPerEntity,
5✔
345
        }
5✔
346
}
347

348
// UpdateStats updates statistics for an archetype
349
func (a *archetype) UpdateStats(stats *stats.ArchetypeStats) {
3✔
350
        cap := int(a.Cap())
3✔
351
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
3✔
352

3✔
353
        stats.Size = int(a.Len())
3✔
354
        stats.Capacity = cap
3✔
355
        stats.Memory = memory
3✔
356
}
3✔
357

358
// copy from one pointer to another.
359
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
127,968✔
360
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
127,968✔
361
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
127,968✔
362
        copy(dstSlice, srcSlice)
127,968✔
363
}
127,968✔
364

365
// extend the memory buffers if necessary for adding an entity.
366
func (a *archetype) extend(by uint32) {
9,739✔
367
        required := a.len + by
9,739✔
368
        if a.cap >= required {
19,434✔
369
                return
9,695✔
370
        }
9,695✔
371
        a.cap = capacityU32(required, a.capacityIncrement)
44✔
372

44✔
373
        old := a.entityBuffer
44✔
374
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
44✔
375
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
44✔
376
        reflect.Copy(a.entityBuffer, old)
44✔
377

44✔
378
        for _, id := range a.Ids {
102✔
379
                lay := a.getLayout(id)
58✔
380
                if lay.itemSize == 0 {
59✔
381
                        continue
1✔
382
                }
383
                index, _ := a.indices.Get(id)
57✔
384
                old := a.buffers[index]
57✔
385
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
57✔
386
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
57✔
387
                reflect.Copy(a.buffers[index], old)
57✔
388
        }
389
}
390

391
// Adds an entity at the given index. Does not extend the entity buffer.
392
func (a *archetype) addEntity(index uintptr, entity *Entity) {
81,050✔
393
        dst := unsafe.Add(a.entityPointer, entitySize*index)
81,050✔
394
        src := unsafe.Pointer(entity)
81,050✔
395
        a.copy(src, dst, entitySize)
81,050✔
396
}
81,050✔
397

398
// removeEntity removes an entity from tne archetype.
399
// Components need to be removed separately.
400
func (a *archetype) removeEntity(index uintptr) bool {
4,914✔
401
        old := uintptr(a.len - 1)
4,914✔
402

4,914✔
403
        if index == old {
9,661✔
404
                return false
4,747✔
405
        }
4,747✔
406

407
        src := unsafe.Add(a.entityPointer, old*entitySize)
167✔
408
        dst := unsafe.Add(a.entityPointer, index*entitySize)
167✔
409
        a.copy(src, dst, entitySize)
167✔
410

167✔
411
        return true
167✔
412
}
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