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

mlange-42 / arche / 12458527168

22 Dec 2024 11:22PM CUT coverage: 99.768% (-0.2%) from 100.0%
12458527168

Pull #445

github

web-flow
Merge 0d6fa849d into d7b0dda90
Pull Request #445: Add callback methods, fix premature notifications issue

251 of 265 new or added lines in 5 files covered. (94.72%)

1 existing line in 1 file now uncovered.

6440 of 6455 relevant lines covered (99.77%)

115913.46 hits per line

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

99.54
/ecs/world_internal.go
1
package ecs
2

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

8
        "github.com/mlange-42/arche/ecs/event"
9
)
10

11
// fromConfig creates a new [World] from a [Config].
12
func fromConfig(conf Config) World {
138✔
13
        if conf.CapacityIncrement < 1 {
139✔
14
                panic("invalid CapacityIncrement in config, must be > 0")
1✔
15
        }
16
        if conf.RelationCapacityIncrement < 1 {
269✔
17
                conf.RelationCapacityIncrement = conf.CapacityIncrement
132✔
18
        }
132✔
19
        entities := make([]entityIndex, 1, conf.CapacityIncrement)
137✔
20
        entities[0] = entityIndex{arch: nil, index: 0}
137✔
21
        targetEntities := bitSet{}
137✔
22
        targetEntities.ExtendTo(1)
137✔
23

137✔
24
        w := World{
137✔
25
                config:         conf,
137✔
26
                entities:       entities,
137✔
27
                targetEntities: targetEntities,
137✔
28
                entityPool:     newEntityPool(uint32(conf.CapacityIncrement)),
137✔
29
                registry:       newComponentRegistry(),
137✔
30
                archetypes:     pagedSlice[archetype]{},
137✔
31
                archetypeData:  pagedSlice[archetypeData]{},
137✔
32
                nodes:          pagedSlice[archNode]{},
137✔
33
                relationNodes:  []*archNode{},
137✔
34
                locks:          lockMask{},
137✔
35
                listener:       nil,
137✔
36
                resources:      newResources(),
137✔
37
                filterCache:    newCache(),
137✔
38
        }
137✔
39
        node := w.createArchetypeNode(Mask{}, ID{}, false)
137✔
40
        w.createArchetype(node, Entity{}, false)
137✔
41
        return w
137✔
42
}
43

44
// Creates a new entity with a relation and a target entity.
45
func (w *World) newEntityTarget(targetID ID, target Entity, comps ...ID) Entity {
14✔
46
        w.checkLocked()
14✔
47

14✔
48
        if !target.IsZero() && !w.entityPool.Alive(target) {
15✔
49
                panic("can't make a dead entity a relation target")
1✔
50
        }
51

52
        arch := w.archetypes.Get(0)
13✔
53

13✔
54
        if len(comps) > 0 {
26✔
55
                arch = w.findOrCreateArchetype(arch, comps, nil, target)
13✔
56
        }
13✔
57
        w.checkRelation(arch, targetID)
13✔
58

13✔
59
        entity := w.createEntity(arch)
13✔
60

13✔
61
        if !target.IsZero() {
26✔
62
                w.targetEntities.Set(target.id, true)
13✔
63
        }
13✔
64

65
        if w.listener != nil {
14✔
66
                bits := subscription(true, false, len(comps) > 0, false, true, true)
1✔
67
                trigger := w.listener.Subscriptions() & bits
1✔
68
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, &targetID) {
2✔
69
                        w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: comps, NewRelation: &targetID, EventTypes: bits})
1✔
70
                }
1✔
71
        }
72
        return entity
13✔
73
}
74

75
// Creates a new entity with a relation and a target entity.
76
func (w *World) newEntityTargetWith(targetID ID, target Entity, comps ...Component) Entity {
3✔
77
        w.checkLocked()
3✔
78

3✔
79
        if !target.IsZero() && !w.entityPool.Alive(target) {
4✔
80
                panic("can't make a dead entity a relation target")
1✔
81
        }
82

83
        ids := make([]ID, len(comps))
2✔
84
        for i, c := range comps {
6✔
85
                ids[i] = c.ID
4✔
86
        }
4✔
87

88
        arch := w.archetypes.Get(0)
2✔
89
        arch = w.findOrCreateArchetype(arch, ids, nil, target)
2✔
90
        w.checkRelation(arch, targetID)
2✔
91

2✔
92
        entity := w.createEntity(arch)
2✔
93

2✔
94
        if !target.IsZero() {
4✔
95
                w.targetEntities.Set(target.id, true)
2✔
96
        }
2✔
97

98
        for _, c := range comps {
6✔
99
                w.copyTo(entity, c.ID, c.Comp)
4✔
100
        }
4✔
101

102
        if w.listener != nil {
3✔
103
                bits := subscription(true, false, len(comps) > 0, false, true, true)
1✔
104
                trigger := w.listener.Subscriptions() & bits
1✔
105
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, &targetID) {
2✔
106
                        w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: ids, NewRelation: &targetID, EventTypes: bits})
1✔
107
                }
1✔
108
        }
109
        return entity
2✔
110
}
111

112
// Creates new entities without returning a query over them.
113
// Used via [World.Batch].
114
func (w *World) newEntities(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) (*archetype, uint32) {
27,545✔
115
        arch, startIdx := w.newEntitiesNoNotify(count, targetID, hasTarget, target, comps...)
27,545✔
116

27,545✔
117
        if w.listener != nil {
27,558✔
118
                var newRel *ID
13✔
119
                if arch.HasRelationComponent {
25✔
120
                        newRel = &arch.RelationComponent
12✔
121
                }
12✔
122
                bits := subscription(true, false, len(comps) > 0, false, newRel != nil, newRel != nil)
13✔
123
                trigger := w.listener.Subscriptions() & bits
13✔
124
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, newRel) {
26✔
125
                        cnt := uint32(count)
13✔
126
                        var i uint32
13✔
127
                        for i = 0; i < cnt; i++ {
768✔
128
                                idx := startIdx + i
755✔
129
                                entity := arch.GetEntity(idx)
755✔
130
                                w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: comps, NewRelation: newRel, EventTypes: bits})
755✔
131
                        }
755✔
132
                }
133
        }
134

135
        return arch, startIdx
27,545✔
136
}
137

138
// Creates new entities and returns a query over them.
139
// Used via [World.Batch].
140
func (w *World) newEntitiesQuery(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) Query {
39✔
141
        arch, startIdx := w.newEntitiesNoNotify(count, targetID, hasTarget, target, comps...)
39✔
142
        lock := w.lock()
39✔
143

39✔
144
        batches := batchArchetypes{
39✔
145
                Added:   arch.Components(),
39✔
146
                Removed: nil,
39✔
147
        }
39✔
148
        batches.Add(arch, nil, startIdx, arch.Len())
39✔
149
        return newBatchQuery(w, lock, &batches)
39✔
150
}
39✔
151

152
// Creates new entities with component values without returning a query over them.
153
// Used via [World.Batch].
154
func (w *World) newEntitiesWith(count int, targetID ID, hasTarget bool, target Entity, comps ...Component) (*archetype, uint32) {
7✔
155
        ids := make([]ID, len(comps))
7✔
156
        for i, c := range comps {
20✔
157
                ids[i] = c.ID
13✔
158
        }
13✔
159

160
        arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, hasTarget, target, ids, comps...)
7✔
161

7✔
162
        if w.listener != nil {
11✔
163
                var newRel *ID
4✔
164
                if arch.HasRelationComponent {
7✔
165
                        newRel = &arch.RelationComponent
3✔
166
                }
3✔
167
                bits := subscription(true, false, len(comps) > 0, false, newRel != nil, newRel != nil)
4✔
168
                trigger := w.listener.Subscriptions() & bits
4✔
169
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, newRel) {
8✔
170
                        var i uint32
4✔
171
                        cnt := uint32(count)
4✔
172
                        for i = 0; i < cnt; i++ {
129✔
173
                                idx := startIdx + i
125✔
174
                                entity := arch.GetEntity(idx)
125✔
175
                                w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: ids, NewRelation: newRel, EventTypes: bits})
125✔
176
                        }
125✔
177
                }
178
        }
179

180
        return arch, startIdx
6✔
181
}
182

183
// Creates new entities with component values and returns a query over them.
184
// Used via [World.Batch].
185
func (w *World) newEntitiesWithQuery(count int, targetID ID, hasTarget bool, target Entity, comps ...Component) Query {
8✔
186
        ids := make([]ID, len(comps))
8✔
187
        for i, c := range comps {
21✔
188
                ids[i] = c.ID
13✔
189
        }
13✔
190

191
        arch, startIdx := w.newEntitiesWithNoNotify(count, targetID, hasTarget, target, ids, comps...)
8✔
192
        lock := w.lock()
8✔
193
        batches := batchArchetypes{
8✔
194
                Added:   arch.Components(),
8✔
195
                Removed: nil,
8✔
196
        }
8✔
197
        batches.Add(arch, nil, startIdx, arch.Len())
8✔
198
        return newBatchQuery(w, lock, &batches)
8✔
199
}
200

201
// Internal method to create new entities.
202
func (w *World) newEntitiesNoNotify(count int, targetID ID, hasTarget bool, target Entity, comps ...ID) (*archetype, uint32) {
27,586✔
203
        w.checkLocked()
27,586✔
204

27,586✔
205
        if count < 1 {
27,587✔
206
                panic("can only create a positive number of entities")
1✔
207
        }
208

209
        if !target.IsZero() && !w.entityPool.Alive(target) {
27,586✔
210
                panic("can't make a dead entity a relation target")
1✔
211
        }
212

213
        arch := w.archetypes.Get(0)
27,584✔
214
        if len(comps) > 0 {
55,167✔
215
                arch = w.findOrCreateArchetype(arch, comps, nil, target)
27,583✔
216
        }
27,583✔
217
        if hasTarget {
55,108✔
218
                w.checkRelation(arch, targetID)
27,524✔
219
                if !target.IsZero() {
55,048✔
220
                        w.targetEntities.Set(target.id, true)
27,524✔
221
                }
27,524✔
222
        }
223

224
        startIdx := arch.Len()
27,584✔
225
        w.createEntities(arch, uint32(count))
27,584✔
226

27,584✔
227
        return arch, startIdx
27,584✔
228
}
229

230
// Internal method to create new entities with component values.
231
func (w *World) newEntitiesWithNoNotify(count int, targetID ID, hasTarget bool, target Entity, ids []ID, comps ...Component) (*archetype, uint32) {
15✔
232
        w.checkLocked()
15✔
233

15✔
234
        if count < 1 {
16✔
235
                panic("can only create a positive number of entities")
1✔
236
        }
237

238
        if !target.IsZero() && !w.entityPool.Alive(target) {
15✔
239
                panic("can't make a dead entity a relation target")
1✔
240
        }
241

242
        if len(comps) == 0 {
14✔
243
                return w.newEntitiesNoNotify(count, targetID, hasTarget, target)
1✔
244
        }
1✔
245

246
        cnt := uint32(count)
12✔
247

12✔
248
        arch := w.archetypes.Get(0)
12✔
249
        if len(comps) > 0 {
24✔
250
                arch = w.findOrCreateArchetype(arch, ids, nil, target)
12✔
251
        }
12✔
252
        if hasTarget {
17✔
253
                w.checkRelation(arch, targetID)
5✔
254
                if !target.IsZero() {
10✔
255
                        w.targetEntities.Set(target.id, true)
5✔
256
                }
5✔
257
        }
258

259
        startIdx := arch.Len()
12✔
260
        w.createEntities(arch, uint32(count))
12✔
261

12✔
262
        var i uint32
12✔
263
        for i = 0; i < cnt; i++ {
397✔
264
                idx := startIdx + i
385✔
265
                entity := arch.GetEntity(idx)
385✔
266
                for _, c := range comps {
1,135✔
267
                        w.copyTo(entity, c.ID, c.Comp)
750✔
268
                }
750✔
269
        }
270

271
        return arch, startIdx
12✔
272
}
273

274
// createEntity creates an Entity and adds it to the given archetype.
275
func (w *World) createEntity(arch *archetype) Entity {
1,037,417✔
276
        entity := w.entityPool.Get()
1,037,417✔
277
        idx := arch.Alloc(entity)
1,037,417✔
278
        len := len(w.entities)
1,037,417✔
279
        if int(entity.id) == len {
1,048,607✔
280
                if len == cap(w.entities) {
11,271✔
281
                        old := w.entities
81✔
282
                        w.entities = make([]entityIndex, len, len+w.config.CapacityIncrement)
81✔
283
                        copy(w.entities, old)
81✔
284

81✔
285
                }
81✔
286
                w.entities = append(w.entities, entityIndex{arch: arch, index: idx})
11,190✔
287
                w.targetEntities.ExtendTo(len + w.config.CapacityIncrement)
11,190✔
288
        } else {
1,026,227✔
289
                w.entities[entity.id] = entityIndex{arch: arch, index: idx}
1,026,227✔
290
                w.targetEntities.Set(entity.id, false)
1,026,227✔
291
        }
1,026,227✔
292
        return entity
1,037,417✔
293
}
294

295
// createEntity creates multiple Entities and adds them to the given archetype.
296
func (w *World) createEntities(arch *archetype, count uint32) {
27,596✔
297
        startIdx := arch.Len()
27,596✔
298
        arch.AllocN(count)
27,596✔
299

27,596✔
300
        len := len(w.entities)
27,596✔
301
        required := len + int(count) - w.entityPool.Available()
27,596✔
302
        capacity := capacity(required, w.config.CapacityIncrement)
27,596✔
303
        if required > cap(w.entities) {
27,617✔
304
                old := w.entities
21✔
305
                w.entities = make([]entityIndex, required, capacity)
21✔
306
                copy(w.entities, old)
21✔
307
        } else if required > len {
30,170✔
308
                w.entities = w.entities[:required]
2,574✔
309
        }
2,574✔
310
        w.targetEntities.ExtendTo(capacity)
27,596✔
311

27,596✔
312
        var i uint32
27,596✔
313
        for i = 0; i < count; i++ {
2,785,567✔
314
                idx := startIdx + i
2,757,971✔
315
                entity := w.entityPool.Get()
2,757,971✔
316
                arch.SetEntity(idx, entity)
2,757,971✔
317
                w.entities[entity.id] = entityIndex{arch: arch, index: idx}
2,757,971✔
318
                w.targetEntities.Set(entity.id, false)
2,757,971✔
319
        }
2,757,971✔
320
}
321

322
// RemoveEntities removes and recycles all entities matching a filter.
323
//
324
// Returns the number of removed entities.
325
//
326
// Panics when called on a locked world.
327
// Do not use during [Query] iteration!
328
func (w *World) removeEntities(filter Filter) int {
25,014✔
329
        w.checkLocked()
25,014✔
330

25,014✔
331
        lock := w.lock()
25,014✔
332

25,014✔
333
        var bits event.Subscription
25,014✔
334
        var listen bool
25,014✔
335

25,014✔
336
        var count uint32
25,014✔
337

25,014✔
338
        arches := w.getArchetypes(filter)
25,014✔
339
        numArches := int32(len(arches))
25,014✔
340
        var i int32
25,014✔
341
        for i = 0; i < numArches; i++ {
50,041✔
342
                arch := arches[i]
25,027✔
343
                ln := arch.Len()
25,027✔
344
                if ln == 0 {
25,033✔
345
                        continue
6✔
346
                }
347

348
                count += ln
25,021✔
349

25,021✔
350
                var oldRel *ID
25,021✔
351
                var oldIds []ID
25,021✔
352
                if w.listener != nil {
25,029✔
353
                        if arch.HasRelationComponent {
12✔
354
                                oldRel = &arch.RelationComponent
4✔
355
                        }
4✔
356
                        if len(arch.node.Ids) > 0 {
14✔
357
                                oldIds = arch.node.Ids
6✔
358
                        }
6✔
359
                        bits = subscription(false, true, false, len(oldIds) > 0, oldRel != nil, oldRel != nil)
8✔
360
                        trigger := w.listener.Subscriptions() & bits
8✔
361
                        listen = trigger != 0 && subscribes(trigger, nil, &arch.Mask, w.listener.Components(), oldRel, nil)
8✔
362
                }
363

364
                var j uint32
25,021✔
365
                for j = 0; j < ln; j++ {
2,525,501✔
366
                        entity := arch.GetEntity(j)
2,500,480✔
367
                        if listen {
2,500,716✔
368
                                w.listener.Notify(w, EntityEvent{Entity: entity, Removed: arch.Mask, RemovedIDs: oldIds, OldRelation: oldRel, OldTarget: arch.RelationTarget, EventTypes: bits})
236✔
369
                        }
236✔
370
                        index := &w.entities[entity.id]
2,500,480✔
371
                        index.arch = nil
2,500,480✔
372

2,500,480✔
373
                        if w.targetEntities.Get(entity.id) {
2,500,487✔
374
                                w.cleanupArchetypes(entity)
7✔
375
                                w.targetEntities.Set(entity.id, false)
7✔
376
                        }
7✔
377

378
                        w.entityPool.Recycle(entity)
2,500,480✔
379
                }
380
                arch.Reset()
25,021✔
381
                w.cleanupArchetype(arch)
25,021✔
382
        }
383
        w.unlock(lock)
25,014✔
384

25,014✔
385
        return int(count)
25,014✔
386
}
387

388
// assign with relation target.
389
func (w *World) assign(entity Entity, relation ID, hasRelation bool, target Entity, comps ...Component) {
493,249✔
390
        len := len(comps)
493,249✔
391
        if len == 0 {
493,250✔
392
                panic("no components given to assign")
1✔
393
        }
394
        ids := make([]ID, len)
493,248✔
395
        for i, c := range comps {
986,500✔
396
                ids[i] = c.ID
493,252✔
397
        }
493,252✔
398
        arch, oldMask, oldTarget, oldRel := w.exchangeNoNotify(entity, ids, nil, relation, hasRelation, target)
493,248✔
399
        for _, c := range comps {
986,498✔
400
                w.copyTo(entity, c.ID, c.Comp)
493,250✔
401
        }
493,250✔
402
        if w.listener != nil {
493,247✔
403
                w.notifyExchange(arch, oldMask, entity, ids, nil, oldTarget, oldRel)
1✔
404
        }
1✔
405
}
406

407
// exchange with relation target.
408
// Panics if adding a component already present or removing a component not present.
409
// Also panics if the same component ID is in the add or remove list twice.
410
func (w *World) exchange(entity Entity, add []ID, rem []ID, relation ID, hasRelation bool, target Entity, fn func(Entity)) {
2,800✔
411
        if w.listener != nil {
3,405✔
412
                arch, oldMask, oldTarget, oldRel := w.exchangeNoNotify(entity, add, rem, relation, hasRelation, target)
605✔
413
                if fn != nil {
605✔
NEW
414
                        fn(entity)
×
NEW
415
                }
×
416
                w.notifyExchange(arch, oldMask, entity, add, rem, oldTarget, oldRel)
604✔
417
                return
604✔
418
        }
419
        w.exchangeNoNotify(entity, add, rem, relation, hasRelation, target)
2,195✔
420
        if fn != nil {
2,195✔
NEW
421
                fn(entity)
×
NEW
422
        }
×
423
}
424

425
// perform exchange operation without notifying listeners.
426
func (w *World) exchangeNoNotify(entity Entity, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) (*archetype, *Mask, Entity, *ID) {
496,048✔
427
        w.checkLocked()
496,048✔
428

496,048✔
429
        if !w.entityPool.Alive(entity) {
496,050✔
430
                panic("can't exchange components on a dead entity")
2✔
431
        }
432

433
        if len(add) == 0 && len(rem) == 0 {
496,048✔
434
                if hasRelation {
5✔
435
                        panic("exchange operation has no effect, but a relation is specified. Use World.Relation instead")
1✔
436
                }
437
                return nil, nil, Entity{}, nil
3✔
438
        }
439
        index := &w.entities[entity.id]
496,040✔
440
        oldArch := index.arch
496,040✔
441

496,040✔
442
        oldMask := oldArch.Mask
496,040✔
443
        mask := w.getExchangeMask(oldMask, add, rem)
496,040✔
444

496,040✔
445
        if hasRelation {
496,047✔
446
                if !mask.Get(relation) {
8✔
447
                        tp, _ := w.registry.ComponentType(relation.id)
1✔
448
                        panic(fmt.Sprintf("can't add relation: resulting entity has no component %s", tp.Name()))
1✔
449
                }
450
                if !w.registry.IsRelation.Get(relation) {
7✔
451
                        tp, _ := w.registry.ComponentType(relation.id)
1✔
452
                        panic(fmt.Sprintf("can't add relation: %s is not a relation component", tp.Name()))
1✔
453
                }
454
        } else {
496,029✔
455
                target = oldArch.RelationTarget
496,029✔
456
                if !oldArch.RelationTarget.IsZero() && oldArch.Mask.ContainsAny(&w.registry.IsRelation) {
496,430✔
457
                        for _, id := range rem {
602✔
458
                                // Removing a relation
201✔
459
                                if w.registry.IsRelation.Get(id) {
202✔
460
                                        target = Entity{}
1✔
461
                                        break
1✔
462
                                }
463
                        }
464
                }
465
        }
466

467
        oldIDs := oldArch.Components()
496,034✔
468

496,034✔
469
        arch := w.findOrCreateArchetype(oldArch, add, rem, target)
496,034✔
470
        newIndex := arch.Alloc(entity)
496,034✔
471

496,034✔
472
        for _, id := range oldIDs {
497,066✔
473
                if mask.Get(id) {
1,650✔
474
                        comp := oldArch.Get(index.index, id)
618✔
475
                        arch.SetPointer(newIndex, id, comp)
618✔
476
                }
618✔
477
        }
478

479
        swapped := oldArch.Remove(index.index)
496,031✔
480

496,031✔
481
        if swapped {
496,347✔
482
                swapEntity := oldArch.GetEntity(index.index)
316✔
483
                w.entities[swapEntity.id].index = index.index
316✔
484
        }
316✔
485
        w.entities[entity.id] = entityIndex{arch: arch, index: newIndex}
496,031✔
486

496,031✔
487
        var oldRel *ID
496,031✔
488
        if oldArch.HasRelationComponent {
496,637✔
489
                oldRel = &oldArch.RelationComponent
606✔
490
        }
606✔
491
        oldTarget := oldArch.RelationTarget
496,031✔
492

496,031✔
493
        if !target.IsZero() {
496,435✔
494
                w.targetEntities.Set(target.id, true)
404✔
495
        }
404✔
496

497
        w.cleanupArchetype(oldArch)
496,031✔
498

496,031✔
499
        return arch, &oldMask, oldTarget, oldRel
496,031✔
500
}
501

502
// notify listeners for an exchange.
503
func (w *World) notifyExchange(arch *archetype, oldMask *Mask, entity Entity, add []ID, rem []ID, oldTarget Entity, oldRel *ID) {
605✔
504
        var newRel *ID
605✔
505
        if arch.HasRelationComponent {
1,007✔
506
                newRel = &arch.RelationComponent
402✔
507
        }
402✔
508
        relChanged := false
605✔
509
        if oldRel != nil || newRel != nil {
1,209✔
510
                relChanged = (oldRel == nil) != (newRel == nil) || *oldRel != *newRel
604✔
511
        }
604✔
512
        targChanged := oldTarget != arch.RelationTarget
605✔
513

605✔
514
        bits := subscription(false, false, len(add) > 0, len(rem) > 0, relChanged, relChanged || targChanged)
605✔
515
        trigger := w.listener.Subscriptions() & bits
605✔
516
        if trigger != 0 {
1,210✔
517
                changed := oldMask.Xor(&arch.Mask)
605✔
518
                added := arch.Mask.And(&changed)
605✔
519
                removed := oldMask.And(&changed)
605✔
520
                if subscribes(trigger, &added, &removed, w.listener.Components(), oldRel, newRel) {
1,210✔
521
                        w.listener.Notify(w,
605✔
522
                                EntityEvent{Entity: entity, Added: added, Removed: removed,
605✔
523
                                        AddedIDs: add, RemovedIDs: rem, OldRelation: oldRel, NewRelation: newRel,
605✔
524
                                        OldTarget: oldTarget, EventTypes: bits},
605✔
525
                        )
605✔
526
                }
605✔
527
        }
528
}
529

530
// Modify a mask by adding and removing IDs.
531
// Panics if adding a component already present or removing a component not present.
532
// Also panics if the same component ID is in the add or remove list twice.
533
func (w *World) getExchangeMask(mask Mask, add []ID, rem []ID) Mask {
496,070✔
534
        for _, comp := range rem {
496,510✔
535
                if !mask.Get(comp) {
442✔
536
                        panic(fmt.Sprintf("entity does not have a component of type %v, can't remove", w.registry.Types[comp.id]))
2✔
537
                }
538
                mask.Set(comp, false)
438✔
539
        }
540
        for _, comp := range add {
996,840✔
541
                if mask.Get(comp) {
500,774✔
542
                        panic(fmt.Sprintf("entity already has component of type %v, can't add", w.registry.Types[comp.id]))
2✔
543
                }
544
                mask.Set(comp, true)
500,770✔
545
        }
546
        return mask
496,066✔
547
}
548

549
// ExchangeBatch exchanges components for many entities, matching a filter.
550
//
551
// If the callback argument is given, it is called with a [Query] over the affected entities,
552
// one Query for each affected archetype.
553
//
554
// Panics:
555
//   - when called with components that can't be added or removed because they are already present/not present, respectively.
556
//   - when called on a locked world. Do not use during [Query] iteration!
557
//
558
// See also [World.Exchange].
559
func (w *World) exchangeBatch(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) int {
17✔
560
        batches := batchArchetypes{
17✔
561
                Added:   add,
17✔
562
                Removed: rem,
17✔
563
        }
17✔
564

17✔
565
        count := w.exchangeBatchNoNotify(filter, add, rem, relation, hasRelation, target, &batches)
17✔
566

17✔
567
        if w.listener != nil {
25✔
568
                w.notifyQuery(&batches)
8✔
569
        }
8✔
570
        return count
14✔
571
}
572

573
func (w *World) exchangeBatchQuery(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) Query {
9✔
574
        batches := batchArchetypes{
9✔
575
                Added:   add,
9✔
576
                Removed: rem,
9✔
577
        }
9✔
578

9✔
579
        w.exchangeBatchNoNotify(filter, add, rem, relation, hasRelation, target, &batches)
9✔
580

9✔
581
        lock := w.lock()
9✔
582
        return newBatchQuery(w, lock, &batches)
9✔
583
}
9✔
584

585
func (w *World) exchangeBatchNoNotify(filter Filter, add []ID, rem []ID, relation ID, hasRelation bool, target Entity, batches *batchArchetypes) int {
26✔
586
        w.checkLocked()
26✔
587

26✔
588
        if len(add) == 0 && len(rem) == 0 {
29✔
589
                if hasRelation {
5✔
590
                        panic("exchange operation has no effect, but a relation is specified. Use Batch.SetRelation instead")
2✔
591
                }
592
                return 0
1✔
593
        }
594

595
        arches := w.getArchetypes(filter)
23✔
596
        lengths := make([]uint32, len(arches))
23✔
597
        var totalEntities uint32 = 0
23✔
598
        for i, arch := range arches {
77✔
599
                lengths[i] = arch.Len()
54✔
600
                totalEntities += arch.Len()
54✔
601
        }
54✔
602

603
        for i, arch := range arches {
77✔
604
                archLen := lengths[i]
54✔
605

54✔
606
                if archLen == 0 {
78✔
607
                        continue
24✔
608
                }
609

610
                newArch, start := w.exchangeArch(arch, archLen, add, rem, relation, hasRelation, target)
30✔
611
                batches.Add(newArch, arch, start, newArch.Len())
30✔
612
        }
613

614
        return int(totalEntities)
21✔
615
}
616

617
func (w *World) exchangeArch(oldArch *archetype, oldArchLen uint32, add []ID, rem []ID, relation ID, hasRelation bool, target Entity) (*archetype, uint32) {
30✔
618
        mask := w.getExchangeMask(oldArch.Mask, add, rem)
30✔
619
        oldIDs := oldArch.Components()
30✔
620

30✔
621
        if hasRelation {
34✔
622
                if !mask.Get(relation) {
5✔
623
                        tp, _ := w.registry.ComponentType(relation.id)
1✔
624
                        panic(fmt.Sprintf("can't add relation: resulting entity has no component %s", tp.Name()))
1✔
625
                }
626
                if !w.registry.IsRelation.Get(relation) {
4✔
627
                        tp, _ := w.registry.ComponentType(relation.id)
1✔
628
                        panic(fmt.Sprintf("can't add relation: %s is not a relation component", tp.Name()))
1✔
629
                }
630
        } else {
26✔
631
                target = oldArch.RelationTarget
26✔
632
                if !target.IsZero() && oldArch.Mask.ContainsAny(&w.registry.IsRelation) {
40✔
633
                        for _, id := range rem {
25✔
634
                                // Removing a relation
11✔
635
                                if w.registry.IsRelation.Get(id) {
15✔
636
                                        target = Entity{}
4✔
637
                                        break
4✔
638
                                }
639
                        }
640
                }
641
        }
642

643
        arch := w.findOrCreateArchetype(oldArch, add, rem, target)
28✔
644

28✔
645
        startIdx := arch.Len()
28✔
646
        count := oldArchLen
28✔
647
        arch.AllocN(uint32(count))
28✔
648

28✔
649
        var i uint32
28✔
650
        for i = 0; i < count; i++ {
2,184✔
651
                idx := startIdx + i
2,156✔
652
                entity := oldArch.GetEntity(i)
2,156✔
653
                index := &w.entities[entity.id]
2,156✔
654
                arch.SetEntity(idx, entity)
2,156✔
655
                index.arch = arch
2,156✔
656
                index.index = idx
2,156✔
657

2,156✔
658
                for _, id := range oldIDs {
5,599✔
659
                        if mask.Get(id) {
5,343✔
660
                                comp := oldArch.Get(i, id)
1,900✔
661
                                arch.SetPointer(idx, id, comp)
1,900✔
662
                        }
1,900✔
663
                }
664
        }
665

666
        if !target.IsZero() {
40✔
667
                w.targetEntities.Set(target.id, true)
12✔
668
        }
12✔
669

670
        // Theoretically, it could be oldArchLen < oldArch.Len(),
671
        // which means we can't reset the archetype.
672
        // However, this should not be possible as processing an entity twice
673
        // would mean an illegal component addition/removal.
674
        oldArch.Reset()
28✔
675
        w.cleanupArchetype(oldArch)
28✔
676

28✔
677
        return arch, startIdx
28✔
678
}
679

680
// getRelation returns the target entity for an entity relation.
681
//
682
// Panics:
683
//   - when called for a removed (and potentially recycled) entity.
684
//   - when called for a missing component.
685
//   - when called for a component that is not a relation.
686
//
687
// See [Relation] for details and examples.
688
func (w *World) getRelation(entity Entity, comp ID) Entity {
616✔
689
        if !w.entityPool.Alive(entity) {
617✔
690
                panic("can't get relation of a dead entity")
1✔
691
        }
692

693
        index := &w.entities[entity.id]
615✔
694
        w.checkRelation(index.arch, comp)
615✔
695

615✔
696
        return index.arch.RelationTarget
615✔
697
}
698

699
// getRelationUnchecked returns the target entity for an entity relation.
700
//
701
// getRelationUnchecked is an optimized version of [World.getRelation].
702
// Does not check if the entity is alive or that the component ID is applicable.
703
func (w *World) getRelationUnchecked(entity Entity, comp ID) Entity {
2✔
704
        _ = comp
2✔
705
        index := &w.entities[entity.id]
2✔
706
        return index.arch.RelationTarget
2✔
707
}
2✔
708

709
// setRelation sets the target entity for an entity relation.
710
//
711
// Panics:
712
//   - when called for a removed (and potentially recycled) entity.
713
//   - when called for a removed (and potentially recycled) target.
714
//   - when called for a missing component.
715
//   - when called for a component that is not a relation.
716
//   - when called on a locked world. Do not use during [Query] iteration!
717
//
718
// See [Relation] for details and examples.
719
func (w *World) setRelation(entity Entity, comp ID, target Entity) {
2,543✔
720
        w.checkLocked()
2,543✔
721

2,543✔
722
        if !w.entityPool.Alive(entity) {
2,544✔
723
                panic("can't set relation for a dead entity")
1✔
724
        }
725
        if !target.IsZero() && !w.entityPool.Alive(target) {
2,543✔
726
                panic("can't make a dead entity a relation target")
1✔
727
        }
728

729
        index := &w.entities[entity.id]
2,541✔
730
        w.checkRelation(index.arch, comp)
2,541✔
731

2,541✔
732
        oldArch := index.arch
2,541✔
733

2,541✔
734
        if oldArch.RelationTarget == target {
2,543✔
735
                return
2✔
736
        }
2✔
737

738
        arch, ok := oldArch.node.GetArchetype(target)
2,536✔
739
        if !ok {
2,577✔
740
                arch = w.createArchetype(oldArch.node, target, true)
41✔
741
        }
41✔
742

743
        newIndex := arch.Alloc(entity)
2,536✔
744
        for _, id := range oldArch.node.Ids {
5,089✔
745
                comp := oldArch.Get(index.index, id)
2,553✔
746
                arch.SetPointer(newIndex, id, comp)
2,553✔
747
        }
2,553✔
748

749
        swapped := oldArch.Remove(index.index)
2,536✔
750

2,536✔
751
        if swapped {
2,543✔
752
                swapEntity := oldArch.GetEntity(index.index)
7✔
753
                w.entities[swapEntity.id].index = index.index
7✔
754
        }
7✔
755
        w.entities[entity.id] = entityIndex{arch: arch, index: newIndex}
2,536✔
756

2,536✔
757
        if !target.IsZero() {
5,067✔
758
                w.targetEntities.Set(target.id, true)
2,531✔
759
        }
2,531✔
760

761
        oldTarget := oldArch.RelationTarget
2,536✔
762
        w.cleanupArchetype(oldArch)
2,536✔
763

2,536✔
764
        if w.listener != nil {
2,551✔
765
                trigger := w.listener.Subscriptions() & event.TargetChanged
15✔
766
                if trigger != 0 && subscribes(trigger, nil, nil, w.listener.Components(), &comp, &comp) {
30✔
767
                        w.listener.Notify(w, EntityEvent{Entity: entity, OldRelation: &comp, NewRelation: &comp, OldTarget: oldTarget, EventTypes: event.TargetChanged})
15✔
768
                }
15✔
769
        }
770
}
771

772
// set relation target in batches.
773
func (w *World) setRelationBatch(filter Filter, comp ID, target Entity) int {
5✔
774
        batches := batchArchetypes{}
5✔
775
        count := w.setRelationBatchNoNotify(filter, comp, target, &batches)
5✔
776
        if w.listener != nil && w.listener.Subscriptions().Contains(event.TargetChanged) {
7✔
777
                w.notifyQuery(&batches)
2✔
778
        }
2✔
779
        return count
4✔
780
}
781

782
func (w *World) setRelationBatchQuery(filter Filter, comp ID, target Entity) Query {
6✔
783
        batches := batchArchetypes{}
6✔
784
        w.setRelationBatchNoNotify(filter, comp, target, &batches)
6✔
785
        lock := w.lock()
6✔
786
        return newBatchQuery(w, lock, &batches)
6✔
787
}
6✔
788

789
func (w *World) setRelationBatchNoNotify(filter Filter, comp ID, target Entity, batches *batchArchetypes) int {
11✔
790
        w.checkLocked()
11✔
791

11✔
792
        if !target.IsZero() && !w.entityPool.Alive(target) {
12✔
793
                panic("can't make a dead entity a relation target")
1✔
794
        }
795

796
        arches := w.getArchetypes(filter)
10✔
797
        lengths := make([]uint32, len(arches))
10✔
798
        var totalEntities uint32 = 0
10✔
799
        for i, arch := range arches {
26✔
800
                lengths[i] = arch.Len()
16✔
801
                totalEntities += arch.Len()
16✔
802
        }
16✔
803

804
        for i, arch := range arches {
26✔
805
                archLen := lengths[i]
16✔
806

16✔
807
                if archLen == 0 {
21✔
808
                        continue
5✔
809
                }
810

811
                if arch.RelationTarget == target {
13✔
812
                        continue
2✔
813
                }
814

815
                newArch, start, end := w.setRelationArch(arch, archLen, comp, target)
9✔
816
                batches.Add(newArch, arch, start, end)
9✔
817
        }
818
        return int(totalEntities)
10✔
819
}
820

821
func (w *World) setRelationArch(oldArch *archetype, oldArchLen uint32, comp ID, target Entity) (*archetype, uint32, uint32) {
9✔
822
        w.checkRelation(oldArch, comp)
9✔
823

9✔
824
        // Before, entities with unchanged target were included in the query,
9✔
825
        // and events were emitted for them. Seems better to skip them completely,
9✔
826
        // which is done in World.setRelationBatchNoNotify.
9✔
827
        //if oldArch.RelationTarget == target {
9✔
828
        //        return oldArch, 0, oldArchLen
9✔
829
        //}
9✔
830

9✔
831
        oldIDs := oldArch.Components()
9✔
832

9✔
833
        arch, ok := oldArch.node.GetArchetype(target)
9✔
834
        if !ok {
14✔
835
                arch = w.createArchetype(oldArch.node, target, true)
5✔
836
        }
5✔
837

838
        startIdx := arch.Len()
9✔
839
        count := oldArchLen
9✔
840
        arch.AllocN(count)
9✔
841

9✔
842
        var i uint32
9✔
843
        for i = 0; i < count; i++ {
1,609✔
844
                idx := startIdx + i
1,600✔
845
                entity := oldArch.GetEntity(i)
1,600✔
846
                index := &w.entities[entity.id]
1,600✔
847
                arch.SetEntity(idx, entity)
1,600✔
848
                index.arch = arch
1,600✔
849
                index.index = idx
1,600✔
850

1,600✔
851
                for _, id := range oldIDs {
4,600✔
852
                        comp := oldArch.Get(i, id)
3,000✔
853
                        arch.SetPointer(idx, id, comp)
3,000✔
854
                }
3,000✔
855
        }
856

857
        if !target.IsZero() {
17✔
858
                w.targetEntities.Set(target.id, true)
8✔
859
        }
8✔
860

861
        // Theoretically, it could be oldArchLen < oldArch.Len(),
862
        // which means we can't reset the archetype.
863
        // However, this should not be possible as processing an entity twice
864
        // would mean an illegal component addition/removal.
865
        oldArch.Reset()
9✔
866
        w.cleanupArchetype(oldArch)
9✔
867

9✔
868
        return arch, uint32(startIdx), arch.Len()
9✔
869
}
870

871
func (w *World) checkRelation(arch *archetype, comp ID) {
30,709✔
872
        if arch.node.Relation.id != comp.id {
30,715✔
873
                w.relationError(arch, comp)
6✔
874
        }
6✔
875
}
876

877
func (w *World) relationError(arch *archetype, comp ID) {
6✔
878
        if !arch.HasComponent(comp) {
10✔
879
                panic(fmt.Sprintf("entity does not have relation component %v", w.registry.Types[comp.id]))
4✔
880
        }
881
        panic(fmt.Sprintf("not a relation component: %v", w.registry.Types[comp.id]))
2✔
882
}
883

884
// lock the world and get the lock bit for later unlocking.
885
func (w *World) lock() uint8 {
27,563✔
886
        return w.locks.Lock()
27,563✔
887
}
27,563✔
888

889
// unlock unlocks the given lock bit.
890
func (w *World) unlock(l uint8) {
27,302✔
891
        w.locks.Unlock(l)
27,302✔
892
}
27,302✔
893

894
// checkLocked checks if the world is locked, and panics if so.
895
func (w *World) checkLocked() {
2,620,129✔
896
        if w.IsLocked() {
2,620,133✔
897
                panic("attempt to modify a locked world")
4✔
898
        }
899
}
900

901
// Copies a component to an entity.
902
//
903
// Deprecated: Method is slow and should not be used.
904
func (w *World) copyTo(entity Entity, id ID, comp interface{}) unsafe.Pointer {
494,161✔
905
        if !w.Has(entity, id) {
494,162✔
906
                panic("can't copy component into entity that has no such component type")
1✔
907
        }
908
        index := &w.entities[entity.id]
494,160✔
909
        arch := index.arch
494,160✔
910

494,160✔
911
        return arch.Set(index.index, id, comp)
494,160✔
912
}
913

914
// Tries to find an archetype by traversing the archetype graph,
915
// searching by mask and extending the graph if necessary.
916
// A new archetype is created for the final graph node if not already present.
917
func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, target Entity) *archetype {
1,065,602✔
918
        curr := start.node
1,065,602✔
919
        mask := start.Mask
1,065,602✔
920
        relation := start.RelationComponent
1,065,602✔
921
        hasRelation := start.HasRelationComponent
1,065,602✔
922
        for _, id := range rem {
1,066,044✔
923
                // Not required, as removing happens only via exchange,
442✔
924
                // which calls getExchangeMask, which does the same check.
442✔
925
                //if !mask.Get(id) {
442✔
926
                //        panic(fmt.Sprintf("entity does not have a component of type %v, or it was removed twice", w.registry.Types[id.id]))
442✔
927
                //}
442✔
928
                mask.Set(id, false)
442✔
929
                if w.registry.IsRelation.Get(id) {
650✔
930
                        relation = ID{}
208✔
931
                        hasRelation = false
208✔
932
                }
208✔
933
                if next, ok := curr.neighbors.Get(id.id); ok {
868✔
934
                        curr = next
426✔
935
                } else {
442✔
936
                        next, _ := w.findOrCreateArchetypeSlow(mask, relation, hasRelation)
16✔
937
                        next.neighbors.Set(id.id, curr)
16✔
938
                        curr.neighbors.Set(id.id, next)
16✔
939
                        curr = next
16✔
940
                }
16✔
941
        }
942
        for _, id := range add {
2,136,100✔
943
                if mask.Get(id) {
1,070,499✔
944
                        panic(fmt.Sprintf("entity already has component of type %v, or it was added twice", w.registry.Types[id.id]))
1✔
945
                }
946
                if start.Mask.Get(id) {
1,070,498✔
947
                        panic(fmt.Sprintf("component of type %v added and removed in the same exchange operation", w.registry.Types[id.id]))
1✔
948
                }
949
                mask.Set(id, true)
1,070,496✔
950
                if w.registry.IsRelation.Get(id) {
1,100,597✔
951
                        if hasRelation {
30,104✔
952
                                panic("entity already has a relation component")
3✔
953
                        }
954
                        relation = id
30,098✔
955
                        hasRelation = true
30,098✔
956
                }
957
                if next, ok := curr.neighbors.Get(id.id); ok {
2,139,747✔
958
                        curr = next
1,069,254✔
959
                } else {
1,070,493✔
960
                        next, _ := w.findOrCreateArchetypeSlow(mask, relation, hasRelation)
1,239✔
961
                        next.neighbors.Set(id.id, curr)
1,239✔
962
                        curr.neighbors.Set(id.id, next)
1,239✔
963
                        curr = next
1,239✔
964
                }
1,239✔
965
        }
966
        arch, ok := curr.GetArchetype(target)
1,065,597✔
967
        if !ok {
1,094,322✔
968
                arch = w.createArchetype(curr, target, true)
28,725✔
969
        }
28,725✔
970
        return arch
1,065,597✔
971
}
972

973
// Tries to find an archetype for a mask, when it can't be reached through the archetype graph.
974
// Creates an archetype graph node.
975
func (w *World) findOrCreateArchetypeSlow(mask Mask, relation ID, hasRelation bool) (*archNode, bool) {
1,255✔
976
        if arch, ok := w.findArchetypeSlow(mask); ok {
1,264✔
977
                return arch, false
9✔
978
        }
9✔
979
        return w.createArchetypeNode(mask, relation, hasRelation), true
1,246✔
980
}
981

982
// Searches for an archetype by a mask.
983
func (w *World) findArchetypeSlow(mask Mask) (*archNode, bool) {
1,259✔
984
        length := w.nodes.Len()
1,259✔
985
        var i int32
1,259✔
986
        for i = 0; i < length; i++ {
525,529✔
987
                nd := w.nodes.Get(i)
524,270✔
988
                if nd.Mask == mask {
524,283✔
989
                        return nd, true
13✔
990
                }
13✔
991
        }
992
        return nil, false
1,246✔
993
}
994

995
// Creates a node in the archetype graph.
996
func (w *World) createArchetypeNode(mask Mask, relation ID, hasRelation bool) *archNode {
1,383✔
997
        capInc := w.config.CapacityIncrement
1,383✔
998
        if hasRelation {
1,436✔
999
                capInc = w.config.RelationCapacityIncrement
53✔
1000
        }
53✔
1001

1002
        types := mask.toTypes(&w.registry)
1,383✔
1003

1,383✔
1004
        w.nodeData.Add(nodeData{})
1,383✔
1005
        w.nodes.Add(newArchNode(mask, w.nodeData.Get(w.nodeData.Len()-1), relation, hasRelation, capInc, types))
1,383✔
1006
        nd := w.nodes.Get(w.nodes.Len() - 1)
1,383✔
1007
        w.relationNodes = append(w.relationNodes, nd)
1,383✔
1008
        w.nodePointers = append(w.nodePointers, nd)
1,383✔
1009

1,383✔
1010
        return nd
1,383✔
1011
}
1012

1013
// Creates an archetype for the given archetype graph node.
1014
// Initializes the archetype with a capacity according to CapacityIncrement if forStorage is true,
1015
// and with a capacity of 1 otherwise.
1016
func (w *World) createArchetype(node *archNode, target Entity, forStorage bool) *archetype {
28,908✔
1017
        var arch *archetype
28,908✔
1018
        layouts := capacityNonZero(w.registry.Count(), int(layoutChunkSize))
28,908✔
1019

28,908✔
1020
        if node.HasRelation {
56,523✔
1021
                arch = node.CreateArchetype(uint8(layouts), target)
27,615✔
1022
        } else {
28,908✔
1023
                w.archetypes.Add(archetype{})
1,293✔
1024
                w.archetypeData.Add(archetypeData{})
1,293✔
1025
                archIndex := w.archetypes.Len() - 1
1,293✔
1026
                arch = w.archetypes.Get(archIndex)
1,293✔
1027
                arch.Init(node, w.archetypeData.Get(archIndex), archIndex, forStorage, uint8(layouts), Entity{})
1,293✔
1028
                node.SetArchetype(arch)
1,293✔
1029
        }
1,293✔
1030
        w.filterCache.addArchetype(arch)
28,908✔
1031
        return arch
28,908✔
1032
}
1033

1034
// Returns all archetypes that match the given filter.
1035
func (w *World) getArchetypes(filter Filter) []*archetype {
25,072✔
1036
        if cached, ok := filter.(*CachedFilter); ok {
25,074✔
1037
                return w.filterCache.get(cached).Archetypes.pointers
2✔
1038
        }
2✔
1039

1040
        arches := []*archetype{}
25,070✔
1041
        nodes := w.nodePointers
25,070✔
1042

25,070✔
1043
        for _, nd := range nodes {
100,302✔
1044
                if !nd.IsActive || !nd.Matches(filter) {
125,371✔
1045
                        continue
50,139✔
1046
                }
1047

1048
                if rf, ok := filter.(*RelationFilter); ok {
50,108✔
1049
                        target := rf.Target
25,015✔
1050
                        if arch, ok := nd.archetypeMap[target]; ok {
50,027✔
1051
                                arches = append(arches, arch)
25,012✔
1052
                        }
25,012✔
1053
                        continue
25,015✔
1054
                }
1055

1056
                nodeArches := nd.Archetypes()
78✔
1057
                ln2 := int32(nodeArches.Len())
78✔
1058
                var j int32
78✔
1059
                for j = 0; j < ln2; j++ {
193✔
1060
                        a := nodeArches.Get(j)
115✔
1061
                        if a.IsActive() {
220✔
1062
                                arches = append(arches, a)
105✔
1063
                        }
105✔
1064
                }
1065
        }
1066

1067
        return arches
25,070✔
1068
}
1069

1070
// Removes the archetype if it is empty, and has a relation to a dead target.
1071
func (w *World) cleanupArchetype(arch *archetype) {
1,555,049✔
1072
        if arch.Len() > 0 || !arch.node.HasRelation {
3,082,515✔
1073
                return
1,527,466✔
1074
        }
1,527,466✔
1075
        target := arch.RelationTarget
27,583✔
1076
        if target.IsZero() || w.Alive(target) {
55,157✔
1077
                return
27,574✔
1078
        }
27,574✔
1079

1080
        w.removeArchetype(arch)
9✔
1081
}
1082

1083
// Removes empty archetypes that have a target relation to the given entity.
1084
func (w *World) cleanupArchetypes(target Entity) {
25,017✔
1085
        for _, node := range w.relationNodes {
100,081✔
1086
                if arch, ok := node.archetypeMap[target]; ok && arch.Len() == 0 {
100,075✔
1087
                        w.removeArchetype(arch)
25,011✔
1088
                }
25,011✔
1089
        }
1090
}
1091

1092
// Removes/de-activates a relation archetype.
1093
func (w *World) removeArchetype(arch *archetype) {
25,020✔
1094
        arch.node.RemoveArchetype(arch)
25,020✔
1095
        w.Cache().removeArchetype(arch)
25,020✔
1096
}
25,020✔
1097

1098
// Extend the number of access layouts in archetypes.
1099
func (w *World) extendArchetypeLayouts(count uint8) {
2✔
1100
        len := w.nodes.Len()
2✔
1101
        var i int32
2✔
1102
        for i = 0; i < len; i++ {
7✔
1103
                w.nodes.Get(i).ExtendArchetypeLayouts(count)
5✔
1104
        }
5✔
1105
}
1106

1107
// componentID returns the ID for a component type, and registers it if not already registered.
1108
func (w *World) componentID(tp reflect.Type) ID {
270✔
1109
        id, newID := w.registry.ComponentID(tp)
270✔
1110
        if newID {
534✔
1111
                if w.IsLocked() {
265✔
1112
                        w.registry.unregisterLastComponent()
1✔
1113
                        panic("attempt to register a new component in a locked world")
1✔
1114
                }
1115
                if id > 0 && id%layoutChunkSize == 0 {
265✔
1116
                        w.extendArchetypeLayouts(id + layoutChunkSize)
2✔
1117
                }
2✔
1118
        }
1119
        return ID{id: id}
269✔
1120
}
1121

1122
// resourceID returns the ID for a resource type, and registers it if not already registered.
1123
func (w *World) resourceID(tp reflect.Type) ResID {
17✔
1124
        id, _ := w.resources.registry.ComponentID(tp)
17✔
1125
        return ResID{id: id}
17✔
1126
}
17✔
1127

1128
// closeQuery closes a query and unlocks the world.
1129
func (w *World) closeQuery(query *Query) {
2,177✔
1130
        query.nodeIndex = -2
2,177✔
1131
        query.archIndex = -2
2,177✔
1132
        w.unlock(query.lockBit)
2,177✔
1133

2,177✔
1134
        if w.listener != nil {
2,211✔
1135
                if arch, ok := query.nodeArchetypes.(*batchArchetypes); ok {
56✔
1136
                        w.notifyQuery(arch)
22✔
1137
                }
22✔
1138
        }
1139
}
1140

1141
// notifies the listener for all entities on a batch query.
1142
func (w *World) notifyQuery(batchArch *batchArchetypes) {
32✔
1143
        count := batchArch.Len()
32✔
1144
        var i int32
32✔
1145
        for i = 0; i < count; i++ {
67✔
1146
                arch := batchArch.Get(i)
35✔
1147

35✔
1148
                var newRel *ID
35✔
1149
                if arch.HasRelationComponent {
56✔
1150
                        newRel = &arch.RelationComponent
21✔
1151
                }
21✔
1152

1153
                event := EntityEvent{
35✔
1154
                        Entity: Entity{}, Added: arch.Mask, Removed: Mask{}, AddedIDs: batchArch.Added, RemovedIDs: batchArch.Removed,
35✔
1155
                        OldRelation: nil, NewRelation: newRel,
35✔
1156
                        OldTarget: Entity{}, EventTypes: 0,
35✔
1157
                }
35✔
1158

35✔
1159
                oldArch := batchArch.OldArchetype[i]
35✔
1160
                relChanged := newRel != nil
35✔
1161
                targChanged := !arch.RelationTarget.IsZero()
35✔
1162

35✔
1163
                if oldArch != nil {
56✔
1164
                        var oldRel *ID
21✔
1165
                        if oldArch.HasRelationComponent {
38✔
1166
                                oldRel = &oldArch.RelationComponent
17✔
1167
                        }
17✔
1168
                        relChanged = false
21✔
1169
                        if oldRel != nil || newRel != nil {
38✔
1170
                                relChanged = (oldRel == nil) != (newRel == nil) || *oldRel != *newRel
17✔
1171
                        }
17✔
1172
                        targChanged = oldArch.RelationTarget != arch.RelationTarget
21✔
1173
                        changed := event.Added.Xor(&oldArch.node.Mask)
21✔
1174
                        event.Added = changed.And(&event.Added)
21✔
1175
                        event.Removed = changed.And(&oldArch.node.Mask)
21✔
1176
                        event.OldTarget = oldArch.RelationTarget
21✔
1177
                        event.OldRelation = oldRel
21✔
1178
                }
1179

1180
                bits := subscription(oldArch == nil, false, len(batchArch.Added) > 0, len(batchArch.Removed) > 0, relChanged, relChanged || targChanged)
35✔
1181
                event.EventTypes = bits
35✔
1182

35✔
1183
                trigger := w.listener.Subscriptions() & bits
35✔
1184
                if trigger != 0 && subscribes(trigger, &event.Added, &event.Removed, w.listener.Components(), event.OldRelation, event.NewRelation) {
70✔
1185
                        start, end := batchArch.StartIndex[i], batchArch.EndIndex[i]
35✔
1186
                        var e uint32
35✔
1187
                        for e = start; e < end; e++ {
3,582✔
1188
                                entity := arch.GetEntity(e)
3,547✔
1189
                                event.Entity = entity
3,547✔
1190
                                w.listener.Notify(w, event)
3,547✔
1191
                        }
3,547✔
1192
                }
1193
        }
1194
}
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