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

mlange-42 / ark / 13618434117

02 Mar 2025 07:05PM CUT coverage: 97.49% (+0.07%) from 97.422%
13618434117

Pull #91

github

web-flow
Merge 5fd0bd29b into a609eb208
Pull Request #91: Batch operations

511 of 524 new or added lines in 5 files covered. (97.52%)

21 existing lines in 2 files now uncovered.

4389 of 4502 relevant lines covered (97.49%)

42144.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.5
/ecs/world_internal.go
1
package ecs
2

3
import (
4
        "reflect"
5
        "unsafe"
6
)
7

8
func (w *World) newEntityWith(ids []ID, comps []unsafe.Pointer, relations []RelationID) Entity {
505,717✔
9
        w.checkLocked()
505,717✔
10

505,717✔
11
        mask := NewMask(ids...)
505,717✔
12
        newTable := w.storage.findOrCreateTable(&w.storage.tables[0], &mask, relations)
505,717✔
13
        entity, idx := w.storage.createEntity(newTable.id)
505,717✔
14

505,717✔
15
        if comps != nil {
1,011,360✔
16
                if len(ids) != len(comps) {
505,643✔
17
                        panic("lengths of IDs and components to add do not match")
×
18
                }
19
                for i, id := range ids {
1,014,240✔
20
                        newTable.Set(id, idx, comps[i])
508,597✔
21
                }
508,597✔
22
        }
23
        w.storage.registerTargets(relations)
505,717✔
24
        return entity
505,717✔
25
}
26

27
func (w *World) newEntitiesWith(count int, ids []ID, comps []unsafe.Pointer, relations []RelationID) {
8✔
28
        w.checkLocked()
8✔
29

8✔
30
        mask := NewMask(ids...)
8✔
31
        newTable := w.storage.findOrCreateTable(&w.storage.tables[0], &mask, relations)
8✔
32

8✔
33
        startIdx := newTable.Len()
8✔
34
        w.storage.createEntities(newTable, count)
8✔
35

8✔
36
        if comps != nil {
16✔
37
                if len(ids) != len(comps) {
8✔
38
                        panic("lengths of IDs and components to add do not match")
×
39
                }
40
                for i := range count {
200✔
41
                        for j, id := range ids {
1,056✔
42
                                newTable.Set(id, uint32(startIdx+i), comps[j])
864✔
43
                        }
864✔
44
                }
45
        }
46
        w.storage.registerTargets(relations)
8✔
47
}
48

49
func (w *World) newEntities(count int, ids []ID, relations []RelationID) (tableID, int) {
40✔
50
        w.checkLocked()
40✔
51

40✔
52
        mask := NewMask(ids...)
40✔
53
        newTable := w.storage.findOrCreateTable(&w.storage.tables[0], &mask, relations)
40✔
54

40✔
55
        startIdx := newTable.Len()
40✔
56
        w.storage.createEntities(newTable, count)
40✔
57
        w.storage.registerTargets(relations)
40✔
58

40✔
59
        return newTable.id, startIdx
40✔
60
}
40✔
61

62
func (w *World) exchange(entity Entity, add []ID, rem []ID, addComps []unsafe.Pointer, relations []RelationID) {
646✔
63
        w.checkLocked()
646✔
64

646✔
65
        if !w.Alive(entity) {
646✔
66
                panic("can't exchange components on a dead entity")
×
67
        }
68
        if len(add) == 0 && len(rem) == 0 {
646✔
69
                return
×
70
        }
×
71

72
        index := &w.storage.entities[entity.id]
646✔
73
        oldTable := &w.storage.tables[index.table]
646✔
74
        oldArchetype := &w.storage.archetypes[oldTable.archetype]
646✔
75

646✔
76
        mask := oldArchetype.mask
646✔
77
        w.storage.getExchangeMask(&mask, add, rem)
646✔
78

646✔
79
        oldIDs := oldArchetype.components
646✔
80

646✔
81
        newTable := w.storage.findOrCreateTable(oldTable, &mask, relations)
646✔
82
        newIndex := newTable.Add(entity)
646✔
83

646✔
84
        for _, id := range oldIDs {
2,348✔
85
                if mask.Get(id) {
2,414✔
86
                        comp := oldTable.Get(id, uintptr(index.row))
712✔
87
                        newTable.Set(id, newIndex, comp)
712✔
88
                }
712✔
89
        }
90
        if addComps != nil {
973✔
91
                if len(add) != len(addComps) {
327✔
92
                        panic("lengths of IDs and components to add do not match")
×
93
                }
94
                for i, id := range add {
1,046✔
95
                        newTable.Set(id, newIndex, addComps[i])
719✔
96
                }
719✔
97
        }
98

99
        swapped := oldTable.Remove(index.row)
646✔
100

646✔
101
        if swapped {
744✔
102
                swapEntity := oldTable.GetEntity(uintptr(index.row))
98✔
103
                w.storage.entities[swapEntity.id].row = index.row
98✔
104
        }
98✔
105
        w.storage.entities[entity.id] = entityIndex{table: newTable.id, row: newIndex}
646✔
106

646✔
107
        w.storage.registerTargets(relations)
646✔
108
}
109

110
func (w *World) exchangeBatch(batch *Batch, add []ID, rem []ID,
111
        addComps []unsafe.Pointer, relations []RelationID, fn func(table tableID, start, len int)) int {
32✔
112
        w.checkLocked()
32✔
113

32✔
114
        if len(add) == 0 && len(rem) == 0 {
32✔
NEW
115
                if len(relations) > 0 {
×
NEW
116
                        panic("exchange operation has no effect, but relations were specified. Use SetRelationBatch instead")
×
117
                }
NEW
118
                return 0
×
119
        }
120

121
        tables := w.getTables(batch)
32✔
122
        lengths := make([]uint32, len(tables))
32✔
123
        var totalEntities uint32 = 0
32✔
124
        for i, table := range tables {
96✔
125
                lengths[i] = uint32(table.Len())
64✔
126
                totalEntities += uint32(table.Len())
64✔
127
        }
64✔
128

129
        for i, table := range tables {
96✔
130
                tableLen := lengths[i]
64✔
131

64✔
132
                if tableLen == 0 {
64✔
NEW
133
                        continue
×
134
                }
135
                t, start, len := w.exchangeTable(table, int(tableLen), add, rem, addComps, relations)
64✔
136
                if fn != nil {
96✔
137
                        fn(t, start, len)
32✔
138
                }
32✔
139
        }
140

141
        return int(totalEntities)
32✔
142
}
143

144
func (w *World) exchangeTable(oldTable *table, oldLen int, add []ID, rem []ID, addComps []unsafe.Pointer, relations []RelationID) (tableID, int, int) {
64✔
145
        w.checkLocked()
64✔
146

64✔
147
        oldArchetype := &w.storage.archetypes[oldTable.archetype]
64✔
148

64✔
149
        mask := oldArchetype.mask
64✔
150
        w.storage.getExchangeMask(&mask, add, rem)
64✔
151

64✔
152
        oldIDs := oldArchetype.components
64✔
153

64✔
154
        newTable := w.storage.findOrCreateTable(oldTable, &mask, relations)
64✔
155
        startIdx := uintptr(newTable.Len())
64✔
156
        count := uintptr(oldLen)
64✔
157

64✔
158
        var i uintptr
64✔
159
        for i = 0; i < count; i++ {
832✔
160
                idx := startIdx + i
768✔
161
                entity := oldTable.GetEntity(i)
768✔
162
                index := &w.storage.entities[entity.id]
768✔
163
                index.table = newTable.id
768✔
164
                index.row = uint32(idx)
768✔
165
        }
768✔
166

167
        newTable.AddAllEntities(oldTable, true)
64✔
168
        for _, id := range oldIDs {
304✔
169
                if mask.Get(id) {
336✔
170
                        oldCol := oldTable.GetColumn(id)
96✔
171
                        newCol := newTable.GetColumn(id)
96✔
172
                        newCol.SetLast(oldCol)
96✔
173
                }
96✔
174
        }
175
        if addComps != nil {
80✔
176
                if len(add) != len(addComps) {
16✔
NEW
177
                        panic("lengths of IDs and components to add do not match")
×
178
                }
179
                for i := range count {
208✔
180
                        for j, id := range add {
1,056✔
181
                                newTable.Set(id, uint32(startIdx+i), addComps[j])
864✔
182
                        }
864✔
183
                }
184
        }
185

186
        oldTable.Reset()
64✔
187
        w.storage.registerTargets(relations)
64✔
188

64✔
189
        return newTable.id, int(startIdx), int(count)
64✔
190
}
191

192
func (w *World) getTables(batch *Batch) []*table {
32✔
193
        tables := []*table{}
32✔
194

32✔
195
        for i := range w.storage.archetypes {
160✔
196
                archetype := &w.storage.archetypes[i]
128✔
197
                if !batch.filter.matches(&archetype.mask) {
192✔
198
                        continue
64✔
199
                }
200

201
                if !archetype.HasRelations() {
128✔
202
                        table := &w.storage.tables[archetype.tables[0]]
64✔
203
                        tables = append(tables, table)
64✔
204
                        continue
64✔
205
                }
206

NEW
UNCOV
207
                tableIDs := archetype.GetTables(batch.relations)
×
NEW
UNCOV
208
                for _, tab := range tableIDs {
×
NEW
UNCOV
209
                        table := &w.storage.tables[tab]
×
NEW
UNCOV
210
                        if !table.Matches(batch.relations) {
×
NEW
UNCOV
211
                                continue
×
212
                        }
NEW
UNCOV
213
                        tables = append(tables, table)
×
214
                }
215
        }
216
        return tables
32✔
217
}
218

219
// setRelations sets the target entities for an entity relations.
220
func (w *World) setRelations(entity Entity, relations []RelationID) {
43✔
221
        w.checkLocked()
43✔
222

43✔
223
        if !w.storage.entityPool.Alive(entity) {
43✔
UNCOV
224
                panic("can't set relation for a dead entity")
×
225
        }
226

227
        index := &w.storage.entities[entity.id]
43✔
228
        oldTable := &w.storage.tables[index.table]
43✔
229

43✔
230
        newRelations, changed := w.storage.getExchangeTargets(oldTable, relations)
43✔
231
        if !changed {
43✔
232
                return
×
233
        }
×
234

235
        oldArch := &w.storage.archetypes[oldTable.archetype]
43✔
236
        newTable, ok := oldArch.GetTable(&w.storage, newRelations)
43✔
237
        if !ok {
55✔
238
                newTable = w.storage.createTable(oldArch, newRelations)
12✔
239
        }
12✔
240
        newIndex := newTable.Add(entity)
43✔
241

43✔
242
        for _, id := range oldArch.components {
149✔
243
                comp := oldTable.Get(id, uintptr(index.row))
106✔
244
                newTable.Set(id, newIndex, comp)
106✔
245
        }
106✔
246

247
        swapped := oldTable.Remove(index.row)
43✔
248

43✔
249
        if swapped {
59✔
250
                swapEntity := oldTable.GetEntity(uintptr(index.row))
16✔
251
                w.storage.entities[swapEntity.id].row = index.row
16✔
252
        }
16✔
253
        w.storage.entities[entity.id] = entityIndex{table: newTable.id, row: newIndex}
43✔
254

43✔
255
        w.storage.registerTargets(relations)
43✔
256
}
257

258
func (w *World) componentID(tp reflect.Type) ID {
2,123✔
259
        id, newID := w.storage.registry.ComponentID(tp)
2,123✔
260
        if newID {
2,713✔
261
                if w.IsLocked() {
590✔
262
                        w.storage.registry.unregisterLastComponent()
×
263
                        panic("attempt to register a new component in a locked world")
×
264
                }
265
                w.storage.AddComponent(id)
590✔
266
        }
267
        return ID{id: id}
2,123✔
268
}
269

270
func (w *World) resourceID(tp reflect.Type) ResID {
9✔
271
        id, _ := w.resources.registry.ComponentID(tp)
9✔
272
        return ResID{id: id}
9✔
273
}
9✔
274

275
// lock the world and get the lock bit for later unlocking.
276
func (w *World) lock() uint8 {
1,201✔
277
        return w.locks.Lock()
1,201✔
278
}
1,201✔
279

280
// unlock unlocks the given lock bit.
281
func (w *World) unlock(l uint8) {
1,201✔
282
        w.locks.Unlock(l)
1,201✔
283
}
1,201✔
284

285
// checkLocked checks if the world is locked, and panics if so.
286
func (w *World) checkLocked() {
1,011,214✔
287
        if w.IsLocked() {
1,011,214✔
288
                panic("attempt to modify a locked world")
×
289
        }
290
}
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