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

mlange-42 / arche / 4725747591

17 Apr 2023 08:58PM CUT coverage: 100.0%. Remained the same
4725747591

push

github

GitHub
Add parallel simulations example (#223)

2729 of 2729 relevant lines covered (100.0%)

4272.62 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
        TransitionAdd    idMap[*archetypeNode] // Mapping from component ID to add to the resulting archetype
19
        TransitionRemove idMap[*archetypeNode] // Mapping from component ID to remove to the resulting archetype
20
}
21

22
// Creates a new archetypeNode
23
func newArchetypeNode(mask Mask) archetypeNode {
1,204✔
24
        return archetypeNode{
1,204✔
25
                mask:             mask,
1,204✔
26
                TransitionAdd:    newIDMap[*archetypeNode](),
1,204✔
27
                TransitionRemove: newIDMap[*archetypeNode](),
1,204✔
28
        }
1,204✔
29
}
1,204✔
30

31
// Helper for accessing data from an archetype
32
type archetypeAccess struct {
33
        basePointer   unsafe.Pointer // Pointer to the first component column layout.
34
        entityPointer unsafe.Pointer // Pointer to the entity storage
35
        Mask          Mask           // Archetype's mask
36
}
37

38
// GetEntity returns the entity at the given index
39
func (a *archetypeAccess) GetEntity(index uintptr) Entity {
32,296✔
40
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
32,296✔
41
}
32,296✔
42

43
// Get returns the component with the given ID at the given index
44
func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
142,044✔
45
        return a.getLayout(id).Get(index)
142,044✔
46
}
142,044✔
47

48
// HasComponent returns whether the archetype contains the given component ID
49
func (a *archetypeAccess) HasComponent(id ID) bool {
48,458✔
50
        return a.getLayout(id).pointer != nil
48,458✔
51
}
48,458✔
52

53
// GetLayout returns the column layout for a component.
54
func (a *archetypeAccess) getLayout(id ID) *layout {
232,265✔
55
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
232,265✔
56
}
232,265✔
57

58
// layout specification of a component column.
59
type layout struct {
60
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
61
        itemSize uintptr        // Component/step size
62
}
63

64
// Get returns a pointer to the item at the given index.
65
func (l *layout) Get(index uintptr) unsafe.Pointer {
142,042✔
66
        if l.pointer == nil {
142,043✔
67
                return nil
1✔
68
        }
1✔
69
        return unsafe.Add(l.pointer, l.itemSize*index)
142,041✔
70
}
71

72
// archetype represents an ECS archetype
73
type archetype struct {
74
        archetypeAccess                   // Access helper, passed to queries.
75
        graphNode         *archetypeNode  // Node in the archetype graph.
76
        Ids               []ID            // List of component IDs.
77
        layouts           []layout        // Column layouts by ID.
78
        indices           idMap[uint32]   // Mapping from IDs to buffer indices.
79
        buffers           []reflect.Value // Reflection arrays containing component data.
80
        entityBuffer      reflect.Value   // Reflection array containing entity data.
81
        len               uint32          // Current number of entities
82
        cap               uint32          // Current capacity
83
        capacityIncrement uint32          // Capacity increment
84
        zeroValue         []byte          // Used as source for setting storage to zero
85
        zeroPointer       unsafe.Pointer  // Points to zeroValue for fast access
86
}
87

88
// Init initializes an archetype
89
func (a *archetype) Init(node *archetypeNode, capacityIncrement int, forStorage bool, components ...componentType) {
1,188✔
90
        var mask Mask
1,188✔
91
        if len(components) > 0 {
2,300✔
92
                a.Ids = make([]ID, len(components))
1,112✔
93
        }
1,112✔
94

95
        a.buffers = make([]reflect.Value, len(components))
1,188✔
96
        a.layouts = make([]layout, MaskTotalBits)
1,188✔
97
        a.indices = newIDMap[uint32]()
1,188✔
98

1,188✔
99
        cap := 1
1,188✔
100
        if forStorage {
2,296✔
101
                cap = capacityIncrement
1,108✔
102
        }
1,108✔
103

104
        prev := -1
1,188✔
105
        var maxSize uintptr = 0
1,188✔
106
        for i, c := range components {
6,453✔
107
                if int(c.ID) <= prev {
5,266✔
108
                        panic("component arguments must be sorted by ID")
1✔
109
                }
110
                prev = int(c.ID)
5,264✔
111
                mask.Set(c.ID, true)
5,264✔
112

5,264✔
113
                size, align := c.Type.Size(), uintptr(c.Type.Align())
5,264✔
114
                size = (size + (align - 1)) / align * align
5,264✔
115
                if size > maxSize {
6,376✔
116
                        maxSize = size
1,112✔
117
                }
1,112✔
118

119
                a.Ids[i] = c.ID
5,264✔
120
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
5,264✔
121
                a.layouts[c.ID] = layout{
5,264✔
122
                        a.buffers[i].Addr().UnsafePointer(),
5,264✔
123
                        size,
5,264✔
124
                }
5,264✔
125
                a.indices.Set(c.ID, uint32(i))
5,264✔
126
        }
127
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,187✔
128

1,187✔
129
        a.archetypeAccess = archetypeAccess{
1,187✔
130
                basePointer:   unsafe.Pointer(&a.layouts[0]),
1,187✔
131
                entityPointer: a.entityBuffer.Addr().UnsafePointer(),
1,187✔
132
                Mask:          mask,
1,187✔
133
        }
1,187✔
134

1,187✔
135
        a.graphNode = node
1,187✔
136

1,187✔
137
        a.capacityIncrement = uint32(capacityIncrement)
1,187✔
138
        a.len = 0
1,187✔
139
        a.cap = uint32(cap)
1,187✔
140

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

147
// Add adds an entity with optionally zeroed components to the archetype
148
func (a *archetype) Alloc(entity Entity) uintptr {
4,650✔
149
        idx := uintptr(a.len)
4,650✔
150
        a.extend(1)
4,650✔
151
        a.addEntity(idx, &entity)
4,650✔
152
        a.len++
4,650✔
153
        return idx
4,650✔
154
}
4,650✔
155

156
// Add adds storage to the archetype
157
func (a *archetype) AllocN(count uint32) {
28✔
158
        a.extend(count)
28✔
159
        a.len += count
28✔
160
}
28✔
161

162
// Add adds an entity with components to the archetype
163
func (a *archetype) Add(entity Entity, components ...Component) uintptr {
9✔
164
        if len(components) != len(a.Ids) {
10✔
165
                panic("Invalid number of components")
1✔
166
        }
167
        idx := uintptr(a.len)
8✔
168

8✔
169
        a.extend(1)
8✔
170
        a.addEntity(idx, &entity)
8✔
171
        for _, c := range components {
24✔
172
                lay := a.getLayout(c.ID)
16✔
173
                size := lay.itemSize
16✔
174
                if size == 0 {
18✔
175
                        continue
2✔
176
                }
177
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
178
                dst := a.Get(uintptr(idx), c.ID)
14✔
179
                a.copy(src, dst, size)
14✔
180
        }
181
        a.len++
8✔
182
        return idx
8✔
183
}
184

185
// Remove removes an entity and its components from the archetype.
186
//
187
// Performs a swap-remove and reports whether a swap was necessary
188
// (i.e. not the last entity that was removed).
189
func (a *archetype) Remove(index uintptr) bool {
2,402✔
190
        swapped := a.removeEntity(index)
2,402✔
191

2,402✔
192
        old := uintptr(a.len - 1)
2,402✔
193

2,402✔
194
        if index != old {
2,570✔
195
                for _, id := range a.Ids {
377✔
196
                        lay := a.getLayout(id)
209✔
197
                        size := lay.itemSize
209✔
198
                        if size == 0 {
210✔
199
                                continue
1✔
200
                        }
201
                        src := unsafe.Add(lay.pointer, old*size)
208✔
202
                        dst := unsafe.Add(lay.pointer, index*size)
208✔
203
                        a.copy(src, dst, size)
208✔
204
                }
205
        }
206
        a.ZeroAll(old)
2,402✔
207
        a.len--
2,402✔
208

2,402✔
209
        return swapped
2,402✔
210
}
211

212
// ZeroAll resets a block of storage in all buffers.
213
func (a *archetype) ZeroAll(index uintptr) {
2,402✔
214
        for _, id := range a.Ids {
2,746✔
215
                a.Zero(index, id)
344✔
216
        }
344✔
217
}
218

219
// ZeroAll resets a block of storage in one buffer.
220
func (a *archetype) Zero(index uintptr, id ID) {
344✔
221
        lay := a.getLayout(id)
344✔
222
        size := lay.itemSize
344✔
223
        if size == 0 {
347✔
224
                return
3✔
225
        }
3✔
226
        dst := unsafe.Add(lay.pointer, index*size)
341✔
227
        a.copy(a.zeroPointer, dst, size)
341✔
228
}
229

230
// SetEntity overwrites an entity
231
func (a *archetype) SetEntity(index uintptr, entity Entity) {
71,342✔
232
        a.addEntity(index, &entity)
71,342✔
233
}
71,342✔
234

235
// Set overwrites a component with the data behind the given pointer
236
func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
41,122✔
237
        lay := a.getLayout(id)
41,122✔
238
        dst := a.Get(index, id)
41,122✔
239
        size := lay.itemSize
41,122✔
240
        if size == 0 {
41,123✔
241
                return dst
1✔
242
        }
1✔
243
        rValue := reflect.ValueOf(comp)
41,121✔
244

41,121✔
245
        src := rValue.UnsafePointer()
41,121✔
246
        a.copy(src, dst, size)
41,121✔
247
        return dst
41,121✔
248
}
249

250
// SetPointer overwrites a component with the data behind the given pointer
251
func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
10✔
252
        lay := a.getLayout(id)
10✔
253
        dst := a.Get(index, id)
10✔
254
        size := lay.itemSize
10✔
255
        if size == 0 {
11✔
256
                return dst
1✔
257
        }
1✔
258

259
        a.copy(comp, dst, size)
9✔
260
        return dst
9✔
261
}
262

263
// Reset removes all entities and components.
264
//
265
// Does NOT free the reserved memory.
266
func (a *archetype) Reset() {
15✔
267
        a.len = 0
15✔
268
        for _, buf := range a.buffers {
31✔
269
                buf.SetZero()
16✔
270
        }
16✔
271
}
272

273
// Components returns the component IDs for this archetype
274
func (a *archetype) Components() []ID {
2,187✔
275
        return a.Ids
2,187✔
276
}
2,187✔
277

278
// Len reports the number of entities in the archetype
279
func (a *archetype) Len() uint32 {
1,710✔
280
        return a.len
1,710✔
281
}
1,710✔
282

283
// Cap reports the current capacity of the archetype
284
func (a *archetype) Cap() uint32 {
18✔
285
        return a.cap
18✔
286
}
18✔
287

288
// Stats generates statistics for an archetype
289
func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
5✔
290
        ids := a.Components()
5✔
291
        aCompCount := len(ids)
5✔
292
        aTypes := make([]reflect.Type, aCompCount)
5✔
293
        for j, id := range ids {
9✔
294
                aTypes[j], _ = reg.ComponentType(id)
4✔
295
        }
4✔
296

297
        cap := int(a.Cap())
5✔
298
        memPerEntity := 0
5✔
299
        for _, id := range a.Ids {
9✔
300
                lay := a.getLayout(id)
4✔
301
                memPerEntity += int(lay.itemSize)
4✔
302
        }
4✔
303
        memory := cap * (int(entitySize) + memPerEntity)
5✔
304

5✔
305
        return stats.ArchetypeStats{
5✔
306
                Size:            int(a.Len()),
5✔
307
                Capacity:        cap,
5✔
308
                Components:      aCompCount,
5✔
309
                ComponentIDs:    ids,
5✔
310
                ComponentTypes:  aTypes,
5✔
311
                Memory:          memory,
5✔
312
                MemoryPerEntity: memPerEntity,
5✔
313
        }
5✔
314
}
315

316
// UpdateStats updates statistics for an archetype
317
func (a *archetype) UpdateStats(stats *stats.ArchetypeStats) {
3✔
318
        cap := int(a.Cap())
3✔
319
        memory := cap * (int(entitySize) + stats.MemoryPerEntity)
3✔
320

3✔
321
        stats.Size = int(a.Len())
3✔
322
        stats.Capacity = cap
3✔
323
        stats.Memory = memory
3✔
324
}
3✔
325

326
// copy from one pointer to another.
327
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
117,861✔
328
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
117,861✔
329
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
117,861✔
330
        copy(dstSlice, srcSlice)
117,861✔
331
}
117,861✔
332

333
// extend the memory buffers if necessary for adding an entity.
334
func (a *archetype) extend(by uint32) {
4,689✔
335
        required := a.len + by
4,689✔
336
        if a.cap >= required {
9,334✔
337
                return
4,645✔
338
        }
4,645✔
339
        a.cap = capacityU32(required, a.capacityIncrement)
44✔
340

44✔
341
        old := a.entityBuffer
44✔
342
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
44✔
343
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
44✔
344
        reflect.Copy(a.entityBuffer, old)
44✔
345

44✔
346
        for _, id := range a.Ids {
102✔
347
                lay := a.getLayout(id)
58✔
348
                if lay.itemSize == 0 {
59✔
349
                        continue
1✔
350
                }
351
                index, _ := a.indices.Get(id)
57✔
352
                old := a.buffers[index]
57✔
353
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
57✔
354
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
57✔
355
                reflect.Copy(a.buffers[index], old)
57✔
356
        }
357
}
358

359
// Adds an entity at the given index. Does not extend the entity buffer.
360
func (a *archetype) addEntity(index uintptr, entity *Entity) {
76,000✔
361
        dst := unsafe.Add(a.entityPointer, entitySize*index)
76,000✔
362
        src := unsafe.Pointer(entity)
76,000✔
363
        a.copy(src, dst, entitySize)
76,000✔
364
}
76,000✔
365

366
// removeEntity removes an entity from tne archetype.
367
// Components need to be removed separately.
368
func (a *archetype) removeEntity(index uintptr) bool {
2,402✔
369
        old := uintptr(a.len - 1)
2,402✔
370

2,402✔
371
        if index == old {
4,636✔
372
                return false
2,234✔
373
        }
2,234✔
374

375
        src := unsafe.Add(a.entityPointer, old*entitySize)
168✔
376
        dst := unsafe.Add(a.entityPointer, index*entitySize)
168✔
377
        a.copy(src, dst, entitySize)
168✔
378

168✔
379
        return true
168✔
380
}
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