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

mlange-42 / arche / 7775301742

04 Feb 2024 04:17PM CUT coverage: 100.0%. Remained the same
7775301742

push

github

web-flow
Fix Zenodo DOI in README.md (#360)

6159 of 6159 relevant lines covered (100.0%)

52100.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
const layoutChunkSize uint8 = 16
12

13
// layoutSize is the size of an archetype column layout in bytes.
14
var layoutSize uint32 = uint32(unsafe.Sizeof(layout{}))
15

16
// Helper for accessing data from an archetype
17
type archetypeAccess struct {
18
        Mask                 Mask           // Archetype's mask
19
        basePointer          unsafe.Pointer // Pointer to the first component column layout.
20
        entityPointer        unsafe.Pointer // Pointer to the entity storage
21
        RelationTarget       Entity         // Target entity of the archetype (if it has a relation component)
22
        RelationComponent    ID             // Relation component of the archetype
23
        HasRelationComponent bool           // Whether the archetype has a relation
24
}
25

26
// GetEntity returns the entity at the given index
27
func (a *archetypeAccess) GetEntity(index uint32) Entity {
2,537,104✔
28
        return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
2,537,104✔
29
}
2,537,104✔
30

31
// Get returns the component with the given ID at the given index
32
func (a *archetypeAccess) Get(index uint32, id ID) unsafe.Pointer {
28,426✔
33
        return a.getLayout(id).Get(index)
28,426✔
34
}
28,426✔
35

36
// HasComponent returns whether the archetype contains the given component ID.
37
func (a *archetypeAccess) HasComponent(id ID) bool {
1,863✔
38
        return a.getLayout(id).pointer != nil
1,863✔
39
}
1,863✔
40

41
// HasRelation returns whether the archetype has a relation component.
42
func (a *archetypeAccess) HasRelation() bool {
28,890✔
43
        return a.HasRelationComponent
28,890✔
44
}
28,890✔
45

46
// GetLayout returns the column layout for a component.
47
func (a *archetypeAccess) getLayout(id ID) *layout {
93,798✔
48
        return (*layout)(unsafe.Add(a.basePointer, layoutSize*uint32(id.id)))
93,798✔
49
}
93,798✔
50

51
// layout specification of a component column.
52
type layout struct {
53
        pointer  unsafe.Pointer // Pointer to the first element in the component column.
54
        itemSize uint32         // Component/step size
55
}
56

57
// Get returns a pointer to the item at the given index.
58
func (l *layout) Get(index uint32) unsafe.Pointer {
28,424✔
59
        if l.pointer == nil {
28,425✔
60
                return nil
1✔
61
        }
1✔
62
        return unsafe.Add(l.pointer, l.itemSize*index)
28,423✔
63
}
64

65
// archetype represents an ECS archetype
66
type archetype struct {
67
        archetypeAccess // Access helper, passed to queries.
68
        *archetypeData
69
        node *archNode // Node in the archetype graph.
70
        len  uint32    // Current number of entities
71
        cap  uint32    // Current capacity
72
}
73

74
type archetypeData struct {
75
        layouts      []layout        // Column layouts by ID.
76
        indices      idMap[uint32]   // Mapping from IDs to buffer indices.
77
        buffers      []reflect.Value // Reflection arrays containing component data.
78
        entityBuffer reflect.Value   // Reflection array containing entity data.
79
        index        int32           // Index of the archetype in the world.
80
}
81

82
// Init initializes an archetype
83
func (a *archetype) Init(node *archNode, data *archetypeData, index int32, forStorage bool, layouts uint8, relation Entity) {
1,492✔
84
        if !node.IsActive {
2,823✔
85
                node.IsActive = true
1,331✔
86
        }
1,331✔
87

88
        a.archetypeData = data
1,492✔
89
        a.buffers = make([]reflect.Value, len(node.Ids))
1,492✔
90
        a.indices = newIDMap[uint32]()
1,492✔
91
        a.index = index
1,492✔
92
        a.layouts = make([]layout, layouts)
1,492✔
93

1,492✔
94
        cap := 1
1,492✔
95
        if forStorage {
2,849✔
96
                cap = int(node.capacityIncrement)
1,357✔
97
        }
1,357✔
98

99
        for i, id := range node.Ids {
7,075✔
100
                tp := node.Types[i]
5,583✔
101
                size, align := tp.Size(), uintptr(tp.Align())
5,583✔
102
                size = (size + (align - 1)) / align * align
5,583✔
103

5,583✔
104
                a.buffers[i] = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
5,583✔
105
                a.layouts[id.id] = layout{
5,583✔
106
                        a.buffers[i].Addr().UnsafePointer(),
5,583✔
107
                        uint32(size),
5,583✔
108
                }
5,583✔
109
                a.indices.Set(id.id, uint32(i))
5,583✔
110
        }
5,583✔
111
        a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
1,492✔
112

1,492✔
113
        a.archetypeAccess = archetypeAccess{
1,492✔
114
                basePointer:          unsafe.Pointer(&a.layouts[0]),
1,492✔
115
                entityPointer:        a.entityBuffer.Addr().UnsafePointer(),
1,492✔
116
                Mask:                 node.Mask,
1,492✔
117
                RelationTarget:       relation,
1,492✔
118
                RelationComponent:    node.Relation,
1,492✔
119
                HasRelationComponent: node.HasRelation,
1,492✔
120
        }
1,492✔
121

1,492✔
122
        a.node = node
1,492✔
123

1,492✔
124
        a.len = 0
1,492✔
125
        a.cap = uint32(cap)
1,492✔
126
}
127

128
// Add adds an entity with optionally zeroed components to the archetype
129
func (a *archetype) Alloc(entity Entity) uint32 {
35,461✔
130
        idx := a.len
35,461✔
131
        a.extend(1)
35,461✔
132
        a.addEntity(idx, &entity)
35,461✔
133
        a.len++
35,461✔
134
        return idx
35,461✔
135
}
35,461✔
136

137
// AllocN allocates storage for the given number of entities.
138
func (a *archetype) AllocN(count uint32) {
27,627✔
139
        a.extend(count)
27,627✔
140
        a.len += count
27,627✔
141
}
27,627✔
142

143
// Add adds an entity with components to the archetype.
144
func (a *archetype) Add(entity Entity, components ...Component) uint32 {
9✔
145
        if len(components) != len(a.node.Ids) {
10✔
146
                panic("Invalid number of components")
1✔
147
        }
148
        idx := a.len
8✔
149

8✔
150
        a.extend(1)
8✔
151
        a.addEntity(idx, &entity)
8✔
152
        for _, c := range components {
24✔
153
                lay := a.getLayout(c.ID)
16✔
154
                size := lay.itemSize
16✔
155
                if size == 0 {
18✔
156
                        continue
2✔
157
                }
158
                src := reflect.ValueOf(c.Comp).UnsafePointer()
14✔
159
                dst := a.Get(idx, c.ID)
14✔
160
                a.copy(src, dst, size)
14✔
161
        }
162
        a.len++
8✔
163
        return idx
8✔
164
}
165

166
// Remove removes an entity and its components from the archetype.
167
//
168
// Performs a swap-remove and reports whether a swap was necessary
169
// (i.e. not the last entity that was removed).
170
func (a *archetype) Remove(index uint32) bool {
30,568✔
171
        swapped := a.removeEntity(index)
30,568✔
172

30,568✔
173
        old := a.len - 1
30,568✔
174

30,568✔
175
        if index != old {
55,796✔
176
                for _, id := range a.node.Ids {
50,701✔
177
                        lay := a.getLayout(id)
25,473✔
178
                        size := lay.itemSize
25,473✔
179
                        if size == 0 {
25,782✔
180
                                continue
309✔
181
                        }
182
                        src := unsafe.Add(lay.pointer, old*size)
25,164✔
183
                        dst := unsafe.Add(lay.pointer, index*size)
25,164✔
184
                        a.copy(src, dst, size)
25,164✔
185
                }
186
        }
187
        a.ZeroAll(old)
30,568✔
188
        a.len--
30,568✔
189

30,568✔
190
        return swapped
30,568✔
191
}
192

193
// ZeroAll resets a block of storage in all buffers.
194
func (a *archetype) ZeroAll(index uint32) {
30,568✔
195
        for _, id := range a.node.Ids {
59,489✔
196
                a.Zero(index, id)
28,921✔
197
        }
28,921✔
198
}
199

200
// ZeroAll resets a block of storage in one buffer.
201
func (a *archetype) Zero(index uint32, id ID) {
28,921✔
202
        lay := a.getLayout(id)
28,921✔
203
        size := lay.itemSize
28,921✔
204
        if size == 0 {
32,072✔
205
                return
3,151✔
206
        }
3,151✔
207
        dst := unsafe.Add(lay.pointer, index*size)
25,770✔
208
        a.copy(a.node.zeroPointer, dst, size)
25,770✔
209
}
210

211
// SetEntity overwrites an entity
212
func (a *archetype) SetEntity(index uint32, entity Entity) {
2,761,548✔
213
        a.addEntity(index, &entity)
2,761,548✔
214
}
2,761,548✔
215

216
// Set overwrites a component with the data behind the given pointer
217
func (a *archetype) Set(index uint32, id ID, comp interface{}) unsafe.Pointer {
921✔
218
        lay := a.getLayout(id)
921✔
219
        dst := a.Get(index, id)
921✔
220
        size := lay.itemSize
921✔
221
        if size == 0 {
992✔
222
                return dst
71✔
223
        }
71✔
224
        rValue := reflect.ValueOf(comp)
850✔
225

850✔
226
        src := rValue.UnsafePointer()
850✔
227
        a.copy(src, dst, size)
850✔
228
        return dst
850✔
229
}
230

231
// SetPointer overwrites a component with the data behind the given pointer
232
func (a *archetype) SetPointer(index uint32, id ID, comp unsafe.Pointer) unsafe.Pointer {
8,009✔
233
        lay := a.getLayout(id)
8,009✔
234
        dst := a.Get(index, id)
8,009✔
235
        size := lay.itemSize
8,009✔
236
        if size == 0 {
13,368✔
237
                return dst
5,359✔
238
        }
5,359✔
239

240
        a.copy(comp, dst, size)
2,650✔
241
        return dst
2,650✔
242
}
243

244
// Reset removes all entities and components.
245
//
246
// Does NOT free the reserved memory.
247
func (a *archetype) Reset() {
52,549✔
248
        if a.len == 0 {
77,606✔
249
                return
25,057✔
250
        }
25,057✔
251
        a.len = 0
27,492✔
252
        for _, buf := range a.buffers {
55,012✔
253
                buf.SetZero()
27,520✔
254
        }
27,520✔
255
}
256

257
// Deactivate the archetype for later re-use.
258
func (a *archetype) Deactivate() {
27,427✔
259
        a.Reset()
27,427✔
260
        a.index = -1
27,427✔
261
}
27,427✔
262

263
// Activate reactivates a de-activated archetype.
264
func (a *archetype) Activate(target Entity, index int32) {
27,404✔
265
        a.index = index
27,404✔
266
        a.RelationTarget = target
27,404✔
267
}
27,404✔
268

269
func (a *archetype) ExtendLayouts(count uint8) {
6✔
270
        if len(a.layouts) >= int(count) {
8✔
271
                return
2✔
272
        }
2✔
273
        temp := a.layouts
4✔
274
        a.layouts = make([]layout, count)
4✔
275
        copy(a.layouts, temp)
4✔
276
        a.archetypeAccess.basePointer = unsafe.Pointer(&a.layouts[0])
4✔
277
}
278

279
// IsActive returns whether the archetype is active.
280
// Otherwise, it is eligible for re-use.
281
func (a *archetype) IsActive() bool {
5,102,563✔
282
        return a.index >= 0
5,102,563✔
283
}
5,102,563✔
284

285
// Components returns the component IDs for this archetype
286
func (a *archetype) Components() []ID {
2,987✔
287
        return a.node.Ids
2,987✔
288
}
2,987✔
289

290
// Len reports the number of entities in the archetype
291
func (a *archetype) Len() uint32 {
5,261,952✔
292
        return a.len
5,261,952✔
293
}
5,261,952✔
294

295
// Cap reports the current capacity of the archetype
296
func (a *archetype) Cap() uint32 {
5,100,047✔
297
        return a.cap
5,100,047✔
298
}
5,100,047✔
299

300
// Stats generates statistics for an archetype
301
func (a *archetype) Stats(reg *componentRegistry) stats.ArchetypeStats {
113✔
302
        ids := a.Components()
113✔
303
        aCompCount := len(ids)
113✔
304
        aTypes := make([]reflect.Type, aCompCount)
113✔
305
        for j, id := range ids {
225✔
306
                aTypes[j], _ = reg.ComponentType(id.id)
112✔
307
        }
112✔
308

309
        cap := int(a.Cap())
113✔
310
        memPerEntity := 0
113✔
311
        for _, id := range a.node.Ids {
225✔
312
                lay := a.getLayout(id)
112✔
313
                memPerEntity += int(lay.itemSize)
112✔
314
        }
112✔
315
        memory := cap * (int(entitySize) + memPerEntity)
113✔
316

113✔
317
        return stats.ArchetypeStats{
113✔
318
                IsActive: a.IsActive(),
113✔
319
                Size:     int(a.Len()),
113✔
320
                Capacity: cap,
113✔
321
                Memory:   memory,
113✔
322
        }
113✔
323
}
324

325
// UpdateStats updates statistics for an archetype
326
func (a *archetype) UpdateStats(node *stats.NodeStats, stats *stats.ArchetypeStats, reg *componentRegistry) {
5,099,924✔
327
        cap := int(a.Cap())
5,099,924✔
328
        memory := cap * (int(entitySize) + node.MemoryPerEntity)
5,099,924✔
329

5,099,924✔
330
        stats.IsActive = a.IsActive()
5,099,924✔
331
        stats.Size = int(a.Len())
5,099,924✔
332
        stats.Capacity = cap
5,099,924✔
333
        stats.Memory = memory
5,099,924✔
334
}
5,099,924✔
335

336
// copy from one pointer to another.
337
func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uint32) {
2,876,693✔
338
        dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
2,876,693✔
339
        srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
2,876,693✔
340
        copy(dstSlice, srcSlice)
2,876,693✔
341
}
2,876,693✔
342

343
// extend the memory buffers if necessary for adding an entity.
344
func (a *archetype) extend(by uint32) {
63,099✔
345
        required := a.len + by
63,099✔
346
        if a.cap >= required {
126,144✔
347
                return
63,045✔
348
        }
63,045✔
349
        a.cap = capacityU32(required, a.node.capacityIncrement)
54✔
350

54✔
351
        old := a.entityBuffer
54✔
352
        a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
54✔
353
        a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
54✔
354
        reflect.Copy(a.entityBuffer, old)
54✔
355

54✔
356
        for _, id := range a.node.Ids {
111✔
357
                lay := a.getLayout(id)
57✔
358
                if lay.itemSize == 0 {
64✔
359
                        continue
7✔
360
                }
361
                index, _ := a.indices.Get(id.id)
50✔
362
                old := a.buffers[index]
50✔
363
                a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
50✔
364
                lay.pointer = a.buffers[index].Addr().UnsafePointer()
50✔
365
                reflect.Copy(a.buffers[index], old)
50✔
366
        }
367
}
368

369
// Adds an entity at the given index. Does not extend the entity buffer.
370
func (a *archetype) addEntity(index uint32, entity *Entity) {
2,797,017✔
371
        dst := unsafe.Add(a.entityPointer, entitySize*index)
2,797,017✔
372
        src := unsafe.Pointer(entity)
2,797,017✔
373
        a.copy(src, dst, entitySize)
2,797,017✔
374
}
2,797,017✔
375

376
// removeEntity removes an entity from tne archetype.
377
// Components need to be removed separately.
378
func (a *archetype) removeEntity(index uint32) bool {
30,568✔
379
        old := a.len - 1
30,568✔
380

30,568✔
381
        if index == old {
35,908✔
382
                return false
5,340✔
383
        }
5,340✔
384

385
        src := unsafe.Add(a.entityPointer, old*entitySize)
25,228✔
386
        dst := unsafe.Add(a.entityPointer, index*entitySize)
25,228✔
387
        a.copy(src, dst, entitySize)
25,228✔
388

25,228✔
389
        return true
25,228✔
390
}
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