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

mlange-42 / arche / 4839081671

29 Apr 2023 01:41PM CUT coverage: 99.823% (-0.2%) from 100.0%
4839081671

Pull #231

github

GitHub
Merge 5046c4f83 into c9fbc3a05
Pull Request #231: Entity relations

130 of 130 new or added lines in 8 files covered. (100.0%)

2815 of 2820 relevant lines covered (99.82%)

4413.3 hits per line

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

99.14
/ecs/world.go
1
package ecs
2

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

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

11
// ComponentID returns the [ID] for a component type via generics.
12
// Registers the type if it is not already registered.
13
//
14
// The number of unique component types per [World] is limited to [MaskTotalBits].
15
func ComponentID[T any](w *World) ID {
129✔
16
        tp := reflect.TypeOf((*T)(nil)).Elem()
129✔
17
        return w.componentID(tp)
129✔
18
}
129✔
19

20
// TypeID returns the [ID] for a component type.
21
// Registers the type if it is not already registered.
22
//
23
// The number of unique component types per [World] is limited to [MaskTotalBits].
24
func TypeID(w *World, tp reflect.Type) ID {
3✔
25
        return w.componentID(tp)
3✔
26
}
3✔
27

28
// ResourceID returns the [ResID] for a resource type via generics.
29
// Registers the type if it is not already registered.
30
//
31
// The number of resources per [World] is limited to [MaskTotalBits].
32
func ResourceID[T any](w *World) ResID {
12✔
33
        tp := reflect.TypeOf((*T)(nil)).Elem()
12✔
34
        return w.resourceID(tp)
12✔
35
}
12✔
36

37
// GetResource returns a pointer to the given resource type in world.
38
//
39
// Returns nil if there is no such resource.
40
//
41
// Uses reflection. For more efficient access, see [World.Resources],
42
// and [github.com/mlange-42/arche/generic.Resource.Get] for a generic variant.
43
// These methods are more than 20 times faster than the GetResource function.
44
func GetResource[T any](w *World) *T {
3✔
45
        return w.resources.Get(ResourceID[T](w)).(*T)
3✔
46
}
3✔
47

48
// AddResource adds a resource to the world.
49
// Returns the ID for the added resource.
50
//
51
// Panics if there is already such a resource.
52
//
53
// Uses reflection. For more efficient access, see [World.AddResource],
54
// and [github.com/mlange-42/arche/generic.Resource.Add] for a generic variant.
55
//
56
// The number of resources per [World] is limited to [MaskTotalBits].
57
func AddResource[T any](w *World, res *T) ResID {
4✔
58
        id := ResourceID[T](w)
4✔
59
        w.resources.Add(id, res)
4✔
60
        return id
4✔
61
}
4✔
62

63
// World is the central type holding [Entity] and component data, as well as resources.
64
type World struct {
65
        config      Config                    // World configuration.
66
        listener    func(e *EntityEvent)      // Component change listener.
67
        resources   Resources                 // World resources.
68
        entities    []entityIndex             // Mapping from entities to archetype and index.
69
        entityPool  entityPool                // Pool for entities.
70
        archetypes  pagedSlice[archetype]     // The archetypes.
71
        graph       pagedSlice[archetypeNode] // The archetype graph.
72
        locks       lockMask                  // World locks.
73
        registry    componentRegistry[ID]     // Component registry.
74
        filterCache Cache                     // Cache for registered filters.
75
        stats       stats.WorldStats          // Cached world statistics
76
}
77

78
// NewWorld creates a new [World] from an optional [Config].
79
//
80
// Uses the default [Config] if called without an argument.
81
// Accepts zero or one arguments.
82
func NewWorld(config ...Config) World {
81✔
83
        if len(config) > 1 {
82✔
84
                panic("can't use more than one Config")
1✔
85
        }
86
        if len(config) == 1 {
90✔
87
                return fromConfig(config[0])
10✔
88
        }
10✔
89
        return fromConfig(NewConfig())
70✔
90
}
91

92
// fromConfig creates a new [World] from a [Config].
93
func fromConfig(conf Config) World {
80✔
94
        if conf.CapacityIncrement < 1 {
81✔
95
                panic("invalid CapacityIncrement in config, must be > 0")
1✔
96
        }
97
        entities := make([]entityIndex, 1, conf.CapacityIncrement)
79✔
98
        entities[0] = entityIndex{arch: nil, index: 0}
79✔
99
        w := World{
79✔
100
                config:      conf,
79✔
101
                entities:    entities,
79✔
102
                entityPool:  newEntityPool(uint32(conf.CapacityIncrement)),
79✔
103
                registry:    newComponentRegistry(),
79✔
104
                archetypes:  newPagedSlice[archetype](),
79✔
105
                graph:       newPagedSlice[archetypeNode](),
79✔
106
                locks:       lockMask{},
79✔
107
                listener:    nil,
79✔
108
                resources:   newResources(),
79✔
109
                filterCache: newCache(),
79✔
110
        }
79✔
111
        node := w.createArchetypeNode(Mask{}, -1)
79✔
112
        w.createArchetype(node, Entity{}, 0, false)
79✔
113
        return w
79✔
114
}
115

116
// NewEntity returns a new or recycled [Entity].
117
// The given component types are added to the entity.
118
//
119
// Panics when called on a locked world.
120
// Do not use during [Query] iteration!
121
//
122
// Note that calling a method with varargs in Go causes a slice allocation.
123
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
124
//
125
//        // fast
126
//        world.NewEntity(idA, idB, idC)
127
//        // even faster
128
//        world.NewEntity(ids...)
129
//
130
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
131
func (w *World) NewEntity(comps ...ID) Entity {
4,939✔
132
        w.checkLocked()
4,939✔
133

4,939✔
134
        arch := w.archetypes.Get(0)
4,939✔
135
        if len(comps) > 0 {
7,688✔
136
                arch = w.findOrCreateArchetype(arch, comps, nil, Entity{}, 0)
2,749✔
137
        }
2,749✔
138

139
        entity := w.createEntity(arch)
4,937✔
140

4,937✔
141
        if w.listener != nil {
4,951✔
142
                w.listener(&EntityEvent{entity, Mask{}, arch.Mask, comps, nil, arch.Ids, 1})
14✔
143
        }
14✔
144
        return entity
4,937✔
145
}
146

147
// NewEntityWith returns a new or recycled [Entity].
148
// The given component values are assigned to the entity.
149
//
150
// The components in the Comp field of [Component] must be pointers.
151
// The passed pointers are no valid references to the assigned memory!
152
//
153
// Panics when called on a locked world.
154
// Do not use during [Query] iteration!
155
//
156
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
157
func (w *World) NewEntityWith(comps ...Component) Entity {
67✔
158
        w.checkLocked()
67✔
159

67✔
160
        if len(comps) == 0 {
68✔
161
                return w.NewEntity()
1✔
162
        }
1✔
163

164
        ids := make([]ID, len(comps))
66✔
165
        for i, c := range comps {
206✔
166
                ids[i] = c.ID
140✔
167
        }
140✔
168

169
        arch := w.archetypes.Get(0)
66✔
170
        arch = w.findOrCreateArchetype(arch, ids, nil, Entity{}, 0)
66✔
171

66✔
172
        entity := w.createEntity(arch)
66✔
173

66✔
174
        for _, c := range comps {
206✔
175
                w.copyTo(entity, c.ID, c.Comp)
140✔
176
        }
140✔
177

178
        if w.listener != nil {
67✔
179
                w.listener(&EntityEvent{entity, Mask{}, arch.Mask, ids, nil, arch.Ids, 1})
1✔
180
        }
1✔
181
        return entity
66✔
182
}
183

184
// Creates new entities without returning a query over them.
185
// Used via [World.Batch].
186
func (w *World) newEntities(count int, comps ...ID) (*archetype, uint32) {
10✔
187
        arch, startIdx := w.newEntitiesNoNotify(count, comps...)
10✔
188

10✔
189
        if w.listener != nil {
12✔
190
                cnt := uint32(count)
2✔
191
                var i uint32
2✔
192
                for i = 0; i < cnt; i++ {
202✔
193
                        idx := startIdx + i
200✔
194
                        entity := arch.GetEntity(uintptr(idx))
200✔
195
                        w.listener(&EntityEvent{entity, Mask{}, arch.Mask, comps, nil, arch.Ids, 1})
200✔
196
                }
200✔
197
        }
198

199
        return arch, startIdx
10✔
200
}
201

202
// Creates new entities and returns a query over them.
203
// Used via [World.Batch].
204
func (w *World) newEntitiesQuery(count int, comps ...ID) Query {
8✔
205
        arch, startIdx := w.newEntitiesNoNotify(count, comps...)
8✔
206
        lock := w.lock()
8✔
207
        return newArchQuery(w, lock, arch, startIdx)
8✔
208
}
8✔
209

210
// Creates new entities with component values without returning a query over them.
211
// Used via [World.Batch].
212
func (w *World) newEntitiesWith(count int, comps ...Component) (*archetype, uint32) {
3✔
213
        ids := make([]ID, len(comps))
3✔
214
        for i, c := range comps {
9✔
215
                ids[i] = c.ID
6✔
216
        }
6✔
217

218
        arch, startIdx := w.newEntitiesWithNoNotify(count, ids, comps...)
3✔
219

3✔
220
        if w.listener != nil {
5✔
221
                var i uint32
2✔
222
                cnt := uint32(count)
2✔
223
                for i = 0; i < cnt; i++ {
202✔
224
                        idx := startIdx + i
200✔
225
                        entity := arch.GetEntity(uintptr(idx))
200✔
226
                        w.listener(&EntityEvent{entity, Mask{}, arch.Mask, ids, nil, arch.Ids, 1})
200✔
227
                }
200✔
228
        }
229

230
        return arch, startIdx
3✔
231
}
232

233
// Creates new entities with component values and returns a query over them.
234
// Used via [World.Batch].
235
func (w *World) newEntitiesWithQuery(count int, comps ...Component) Query {
6✔
236
        ids := make([]ID, len(comps))
6✔
237
        for i, c := range comps {
16✔
238
                ids[i] = c.ID
10✔
239
        }
10✔
240

241
        arch, startIdx := w.newEntitiesWithNoNotify(count, ids, comps...)
6✔
242
        lock := w.lock()
6✔
243
        return newArchQuery(w, lock, arch, startIdx)
6✔
244
}
245

246
// RemoveEntity removes and recycles an [Entity].
247
//
248
// Panics when called for a removed (and potentially recycled) entity.
249
//
250
// Panics when called on a locked world or for an already removed entity.
251
// Do not use during [Query] iteration!
252
func (w *World) RemoveEntity(entity Entity) {
220✔
253
        w.checkLocked()
220✔
254

220✔
255
        if !w.entityPool.Alive(entity) {
223✔
256
                panic("can't remove a dead entity")
3✔
257
        }
258

259
        index := &w.entities[entity.id]
216✔
260
        oldArch := index.arch
216✔
261

216✔
262
        if w.listener != nil {
318✔
263
                lock := w.lock()
102✔
264
                w.listener(&EntityEvent{entity, oldArch.Mask, Mask{}, nil, oldArch.Ids, nil, -1})
102✔
265
                w.unlock(lock)
102✔
266
        }
102✔
267

268
        swapped := oldArch.Remove(index.index)
216✔
269

216✔
270
        w.entityPool.Recycle(entity)
216✔
271

216✔
272
        if swapped {
367✔
273
                swapEntity := oldArch.GetEntity(index.index)
151✔
274
                w.entities[swapEntity.id].index = index.index
151✔
275
        }
151✔
276

277
        index.arch = nil
216✔
278
}
279

280
// removeEntities removes and recycles all entities matching a filter.
281
//
282
// Returns the number of removed entities.
283
//
284
// Panics when called on a locked world.
285
// Do not use during [Query] iteration!
286
func (w *World) removeEntities(filter Filter) int {
3✔
287
        w.checkLocked()
3✔
288

3✔
289
        lock := w.lock()
3✔
290
        numArches := w.archetypes.Len()
3✔
291
        var count uintptr
3✔
292
        var i int32
3✔
293
        for i = 0; i < numArches; i++ {
11✔
294
                arch := w.archetypes.Get(i)
8✔
295

8✔
296
                if !arch.Matches(filter) {
13✔
297
                        continue
5✔
298
                }
299

300
                len := uintptr(arch.Len())
3✔
301
                count += len
3✔
302

3✔
303
                var j uintptr
3✔
304
                for j = 0; j < len; j++ {
10,203✔
305
                        entity := arch.GetEntity(j)
10,200✔
306
                        if w.listener != nil {
10,400✔
307
                                w.listener(&EntityEvent{entity, arch.Mask, Mask{}, nil, arch.Ids, nil, -1})
200✔
308
                        }
200✔
309
                        w.entities[entity.id].arch = nil
10,200✔
310
                        w.entityPool.Recycle(entity)
10,200✔
311
                }
312

313
                arch.Reset()
3✔
314
        }
315
        w.unlock(lock)
3✔
316

3✔
317
        return int(count)
3✔
318
}
319

320
// Alive reports whether an entity is still alive.
321
func (w *World) Alive(entity Entity) bool {
3✔
322
        return w.entityPool.Alive(entity)
3✔
323
}
3✔
324

325
// Get returns a pointer to the given component of an [Entity].
326
// Returns nil if the entity has no such component.
327
//
328
// Panics when called for a removed (and potentially recycled) entity.
329
//
330
// See [World.GetUnchecked] for an optimized version for static entities.
331
// See also [github.com/mlange-42/arche/generic.Map.Get] for a generic variant.
332
func (w *World) Get(entity Entity, comp ID) unsafe.Pointer {
326✔
333
        if !w.entityPool.Alive(entity) {
329✔
334
                panic("can't get component of a dead entity")
3✔
335
        }
336
        index := &w.entities[entity.id]
323✔
337
        return index.arch.Get(index.index, comp)
323✔
338
}
339

340
// GetUnchecked returns a pointer to the given component of an [Entity].
341
// Returns nil if the entity has no such component.
342
//
343
// GetUnchecked is an optimized version of [World.Get],
344
// for cases where entities are static or checked with [World.Alive] in user code.
345
// It can also be used after getting another component of the same entity with [World.Get].
346
//
347
// Panics when called for a removed entity, but not for a recycled entity.
348
//
349
// See also [github.com/mlange-42/arche/generic.Map.Get] for a generic variant.
350
func (w *World) GetUnchecked(entity Entity, comp ID) unsafe.Pointer {
2✔
351
        index := &w.entities[entity.id]
2✔
352
        return index.arch.Get(index.index, comp)
2✔
353
}
2✔
354

355
// Has returns whether an [Entity] has a given component.
356
//
357
// Panics when called for a removed (and potentially recycled) entity.
358
//
359
// See [World.HasUnchecked] for an optimized version for static entities.
360
// See also [github.com/mlange-42/arche/generic.Map.Has] for a generic variant.
361
func (w *World) Has(entity Entity, comp ID) bool {
41,172✔
362
        if !w.entityPool.Alive(entity) {
41,173✔
363
                panic("can't check for component of a dead entity")
1✔
364
        }
365
        return w.entities[entity.id].arch.HasComponent(comp)
41,171✔
366
}
367

368
// HasUnchecked returns whether an [Entity] has a given component.
369
//
370
// HasUnchecked is an optimized version of [World.Has],
371
// for cases where entities are static or checked with [World.Alive] in user code.
372
//
373
// Panics when called for a removed entity, but not for a recycled entity.
374
//
375
// See also [github.com/mlange-42/arche/generic.Map.Has] for a generic variant.
376
func (w *World) HasUnchecked(entity Entity, comp ID) bool {
2✔
377
        return w.entities[entity.id].arch.HasComponent(comp)
2✔
378
}
2✔
379

380
// Add adds components to an [Entity].
381
//
382
// Panics:
383
//   - when called for a removed (and potentially recycled) entity.
384
//   - when called with components that can't be added because they are already present.
385
//   - when called on a locked world. Do not use during [Query] iteration!
386
//
387
// Note that calling a method with varargs in Go causes a slice allocation.
388
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
389
//
390
//        // fast
391
//        world.Add(entity, idA, idB, idC)
392
//        // even faster
393
//        world.Add(entity, ids...)
394
//
395
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
396
func (w *World) Add(entity Entity, comps ...ID) {
2,170✔
397
        w.Exchange(entity, comps, nil)
2,170✔
398
}
2,170✔
399

400
// Assign assigns multiple components to an [Entity], using pointers for the content.
401
//
402
// The components in the Comp field of [Component] must be pointers.
403
// The passed pointers are no valid references to the assigned memory!
404
//
405
// Panics:
406
//   - when called for a removed (and potentially recycled) entity.
407
//   - when called with components that can't be added because they are already present.
408
//   - when called on a locked world. Do not use during [Query] iteration!
409
//
410
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
411
func (w *World) Assign(entity Entity, comps ...Component) {
6✔
412
        len := len(comps)
6✔
413
        if len == 0 {
7✔
414
                panic("no components given to assign")
1✔
415
        }
416
        if len == 1 {
8✔
417
                c := comps[0]
3✔
418
                w.Exchange(entity, []ID{c.ID}, nil)
3✔
419
                w.copyTo(entity, c.ID, c.Comp)
3✔
420
                return
3✔
421
        }
3✔
422
        ids := make([]ID, len)
2✔
423
        for i, c := range comps {
7✔
424
                ids[i] = c.ID
5✔
425
        }
5✔
426
        w.Exchange(entity, ids, nil)
2✔
427
        for _, c := range comps {
7✔
428
                w.copyTo(entity, c.ID, c.Comp)
5✔
429
        }
5✔
430
}
431

432
// Set overwrites a component for an [Entity], using a given pointer for the content.
433
//
434
// The passed component must be a pointer.
435
// Returns a pointer to the assigned memory.
436
// The passed in pointer is not a valid reference to that memory!
437
//
438
// Panics:
439
//   - when called for a removed (and potentially recycled) entity.
440
//   - if the entity does not have a component of that type.
441
//   - when called on a locked world. Do not use during [Query] iteration!
442
//
443
// See also [github.com/mlange-42/arche/generic.Map.Set] for a generic variant.
444
func (w *World) Set(entity Entity, id ID, comp interface{}) unsafe.Pointer {
2✔
445
        return w.copyTo(entity, id, comp)
2✔
446
}
2✔
447

448
// Remove removes components from an entity.
449
//
450
// Panics:
451
//   - when called for a removed (and potentially recycled) entity.
452
//   - when called with components that can't be removed because they are not present.
453
//   - when called on a locked world. Do not use during [Query] iteration!
454
//
455
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
456
func (w *World) Remove(entity Entity, comps ...ID) {
9✔
457
        w.Exchange(entity, nil, comps)
9✔
458
}
9✔
459

460
// Exchange adds and removes components in one pass.
461
//
462
// Panics:
463
//   - when called for a removed (and potentially recycled) entity.
464
//   - when called with components that can't be added or removed because they are already present/not present, respectively.
465
//   - when called on a locked world. Do not use during [Query] iteration!
466
//
467
// See also the generic variants under [github.com/mlange-42/arche/generic.Exchange].
468
func (w *World) Exchange(entity Entity, add []ID, rem []ID) {
2,194✔
469
        w.checkLocked()
2,194✔
470

2,194✔
471
        if !w.entityPool.Alive(entity) {
2,196✔
472
                panic("can't exchange components on a dead entity")
2✔
473
        }
474

475
        if len(add) == 0 && len(rem) == 0 {
2,193✔
476
                return
3✔
477
        }
3✔
478
        index := &w.entities[entity.id]
2,187✔
479
        oldArch := index.arch
2,187✔
480
        mask := oldArch.Mask
2,187✔
481
        oldMask := mask
2,187✔
482
        for _, comp := range add {
9,486✔
483
                if oldArch.HasComponent(comp) {
7,301✔
484
                        panic("entity already has this component, can't add")
2✔
485
                }
486
                mask.Set(comp, true)
7,297✔
487
        }
488
        for _, comp := range rem {
2,197✔
489
                if !oldArch.HasComponent(comp) {
13✔
490
                        panic("entity does not have this component, can't remove")
1✔
491
                }
492
                mask.Set(comp, false)
11✔
493
        }
494

495
        oldIDs := oldArch.Components()
2,184✔
496

2,184✔
497
        arch := w.findOrCreateArchetype(oldArch, add, rem, Entity{}, 0)
2,184✔
498
        newIndex := arch.Alloc(entity)
2,184✔
499

2,184✔
500
        for _, id := range oldIDs {
2,206✔
501
                if mask.Get(id) {
33✔
502
                        comp := oldArch.Get(index.index, id)
11✔
503
                        arch.SetPointer(newIndex, id, comp)
11✔
504
                }
11✔
505
        }
506

507
        swapped := oldArch.Remove(index.index)
2,183✔
508

2,183✔
509
        if swapped {
2,199✔
510
                swapEntity := oldArch.GetEntity(index.index)
16✔
511
                w.entities[swapEntity.id].index = index.index
16✔
512
        }
16✔
513
        w.entities[entity.id] = entityIndex{arch: arch, index: newIndex}
2,183✔
514

2,183✔
515
        if w.listener != nil {
2,185✔
516
                w.listener(&EntityEvent{entity, oldMask, arch.Mask, add, rem, arch.Ids, 0})
2✔
517
        }
2✔
518
}
519

520
// GetRelation returns the target entity for an entity relation.
521
func (w *World) GetRelation(entity Entity, comp ID) Entity {
3✔
522
        if !w.entityPool.Alive(entity) {
3✔
523
                panic("can't exchange components on a dead entity")
×
524
        }
525

526
        index := &w.entities[entity.id]
3✔
527
        if index.arch.graphNode.relation != int8(comp) {
3✔
528
                if !index.arch.HasComponent(comp) {
×
529
                        panic(fmt.Sprintf("entity does not have relation component %T", w.registry.Types[comp]))
×
530
                }
531
                panic(fmt.Sprintf("not a relation component: %T", w.registry.Types[comp]))
×
532
        }
533

534
        return index.arch.Relation
3✔
535
}
536

537
// SetRelation sets the target entity for an entity relation.
538
func (w *World) SetRelation(entity Entity, comp ID, target Entity) {
2,513✔
539
        w.checkLocked()
2,513✔
540

2,513✔
541
        if !w.entityPool.Alive(entity) {
2,514✔
542
                panic("can't exchange components on a dead entity")
1✔
543
        }
544

545
        index := &w.entities[entity.id]
2,512✔
546
        oldArch := index.arch
2,512✔
547

2,512✔
548
        if oldArch.graphNode.relation != int8(comp) {
2,513✔
549
                if !oldArch.HasComponent(comp) {
2✔
550
                        panic(fmt.Sprintf("entity does not have relation component %T", w.registry.Types[comp]))
1✔
551
                }
552
                panic(fmt.Sprintf("not a relation component: %T", w.registry.Types[comp]))
×
553
        }
554

555
        if index.arch.Relation == target {
2,512✔
556
                return
1✔
557
        }
1✔
558

559
        arch := oldArch.graphNode.GetArchetype(target)
2,510✔
560
        if arch == nil {
2,538✔
561
                arch = w.createArchetype(oldArch.graphNode, target, int8(oldArch.graphNode.relation), true)
28✔
562
        }
28✔
563

564
        newIndex := arch.Alloc(entity)
2,510✔
565
        for _, id := range oldArch.Ids {
5,022✔
566
                comp := oldArch.Get(index.index, id)
2,512✔
567
                arch.SetPointer(newIndex, id, comp)
2,512✔
568
        }
2,512✔
569

570
        swapped := oldArch.Remove(index.index)
2,510✔
571

2,510✔
572
        if swapped {
2,511✔
573
                swapEntity := oldArch.GetEntity(index.index)
1✔
574
                w.entities[swapEntity.id].index = index.index
1✔
575
        }
1✔
576
        w.entities[entity.id] = entityIndex{arch: arch, index: newIndex}
2,510✔
577
}
578

579
// Reset removes all entities and resources from the world.
580
//
581
// Does NOT free reserved memory, remove archetypes, clear the registry etc.
582
//
583
// Can be used to run systematic simulations without the need to re-allocate memory for each run.
584
// Accelerates re-populating the world by a factor of 2-3.
585
func (w *World) Reset() {
5✔
586
        w.checkLocked()
5✔
587

5✔
588
        w.entities = w.entities[:1]
5✔
589
        w.entityPool.Reset()
5✔
590
        w.locks.Reset()
5✔
591
        w.resources.reset()
5✔
592

5✔
593
        len := w.archetypes.Len()
5✔
594
        var i int32
5✔
595
        for i = 0; i < len; i++ {
16✔
596
                w.archetypes.Get(i).Reset()
11✔
597
        }
11✔
598
}
599

600
// Query creates a [Query] iterator.
601
//
602
// The [ecs] core package provides only the filter [All] for querying the given components.
603
// Further, it can be chained with [Mask.Without] (see the examples) to exclude components.
604
//
605
// Locks the world to prevent changes to component compositions.
606
// The lock is released automatically when the query finishes iteration, or when [Query.Close] is called.
607
// The number of simultaneous locks (and thus open queries) at a given time is limited to [MaskTotalBits].
608
//
609
// For type-safe generics queries, see package [github.com/mlange-42/arche/generic].
610
// For advanced filtering, see package [github.com/mlange-42/arche/filter].
611
func (w *World) Query(filter Filter) Query {
311✔
612
        l := w.lock()
311✔
613
        if cached, ok := filter.(*CachedFilter); ok {
320✔
614
                archetypes := &w.filterCache.get(cached).Archetypes
9✔
615
                return newQuery(w, cached, l, archetypes, true)
9✔
616
        }
9✔
617

618
        return newQuery(w, filter, l, &w.archetypes, false)
301✔
619
}
620

621
// Resources of the world.
622
//
623
// Resources are component-like data that is not associated to an entity, but unique to the world.
624
func (w *World) Resources() *Resources {
17✔
625
        return &w.resources
17✔
626
}
17✔
627

628
// Batch creates a [Batch] processing helper.
629
//
630
// It provides the functionality to create and remove large numbers of entities in batches,
631
// in a more efficient way.
632
func (w *World) Batch() *Batch {
14✔
633
        return &Batch{w}
14✔
634
}
14✔
635

636
// Cache returns the [Cache] of the world, for registering filters.
637
//
638
// See [Cache] for details on filter caching.
639
func (w *World) Cache() *Cache {
6✔
640
        if w.filterCache.getArchetypes == nil {
10✔
641
                w.filterCache.getArchetypes = w.getArchetypes
4✔
642
        }
4✔
643
        return &w.filterCache
6✔
644
}
645

646
// IsLocked returns whether the world is locked by any queries.
647
func (w *World) IsLocked() bool {
11,080✔
648
        return w.locks.IsLocked()
11,080✔
649
}
11,080✔
650

651
// Mask returns the archetype [Mask] for the given [Entity].
652
//
653
// Can be used for fast checks of the entity composition, e.g. using a [Filter].
654
func (w *World) Mask(entity Entity) Mask {
10✔
655
        if !w.entityPool.Alive(entity) {
12✔
656
                panic("can't exchange components on a dead entity")
2✔
657
        }
658
        return w.entities[entity.id].arch.Mask
8✔
659
}
660

661
// ComponentType returns the reflect.Type for a given component ID, as well as whether the ID is in use.
662
func (w *World) ComponentType(id ID) (reflect.Type, bool) {
3✔
663
        return w.registry.ComponentType(id)
3✔
664
}
3✔
665

666
// SetListener sets a listener callback func(e EntityEvent) for the world.
667
// The listener is immediately called on every [ecs.Entity] change.
668
// Replaces the current listener. Call with nil to remove a listener.
669
//
670
// For details, see [EntityEvent].
671
func (w *World) SetListener(listener func(e *EntityEvent)) {
9✔
672
        w.listener = listener
9✔
673
}
9✔
674

675
// Stats reports statistics for inspecting the World.
676
func (w *World) Stats() *stats.WorldStats {
3✔
677
        w.stats.Entities = stats.EntityStats{
3✔
678
                Used:     w.entityPool.Len(),
3✔
679
                Total:    w.entityPool.Cap(),
3✔
680
                Recycled: w.entityPool.Available(),
3✔
681
                Capacity: w.entityPool.TotalCap(),
3✔
682
        }
3✔
683

3✔
684
        compCount := len(w.registry.Components)
3✔
685
        types := append([]reflect.Type{}, w.registry.Types[:compCount]...)
3✔
686

3✔
687
        memory := cap(w.entities)*int(entityIndexSize) + w.entityPool.TotalCap()*int(entitySize)
3✔
688

3✔
689
        cntOld := int32(len(w.stats.Archetypes))
3✔
690
        cntNew := int32(w.archetypes.Len())
3✔
691
        var i int32
3✔
692
        for i = 0; i < cntOld; i++ {
6✔
693
                arch := &w.stats.Archetypes[i]
3✔
694
                w.archetypes.Get(i).UpdateStats(arch)
3✔
695
                memory += arch.Memory
3✔
696
        }
3✔
697
        for i = cntOld; i < cntNew; i++ {
8✔
698
                w.stats.Archetypes = append(w.stats.Archetypes, w.archetypes.Get(i).Stats(&w.registry))
5✔
699
                memory += w.stats.Archetypes[i].Memory
5✔
700
        }
5✔
701

702
        w.stats.ComponentCount = compCount
3✔
703
        w.stats.ComponentTypes = types
3✔
704
        w.stats.Locked = w.IsLocked()
3✔
705
        w.stats.Memory = memory
3✔
706

3✔
707
        return &w.stats
3✔
708
}
709

710
// lock the world and get the lock bit for later unlocking.
711
func (w *World) lock() uint8 {
428✔
712
        return w.locks.Lock()
428✔
713
}
428✔
714

715
// unlock unlocks the given lock bit.
716
func (w *World) unlock(l uint8) {
295✔
717
        w.locks.Unlock(l)
295✔
718
}
295✔
719

720
// checkLocked checks if the world is locked, and panics if so.
721
func (w *World) checkLocked() {
9,969✔
722
        if w.IsLocked() {
9,973✔
723
                panic("attempt to modify a locked world")
4✔
724
        }
725
}
726

727
// Internal method to create new entities.
728
func (w *World) newEntitiesNoNotify(count int, comps ...ID) (*archetype, uint32) {
19✔
729
        w.checkLocked()
19✔
730

19✔
731
        if count < 1 {
20✔
732
                panic("can only create a positive number of entities")
1✔
733
        }
734

735
        arch := w.archetypes.Get(0)
18✔
736
        if len(comps) > 0 {
34✔
737
                arch = w.findOrCreateArchetype(arch, comps, nil, Entity{}, 0)
16✔
738
        }
16✔
739
        startIdx := arch.Len()
18✔
740
        w.createEntities(arch, uint32(count))
18✔
741

18✔
742
        return arch, startIdx
18✔
743
}
744

745
// Internal method to create new entities with component values.
746
func (w *World) newEntitiesWithNoNotify(count int, ids []ID, comps ...Component) (*archetype, uint32) {
9✔
747
        w.checkLocked()
9✔
748

9✔
749
        if count < 1 {
10✔
750
                panic("can only create a positive number of entities")
1✔
751
        }
752
        if len(comps) == 0 {
9✔
753
                return w.newEntitiesNoNotify(count)
1✔
754
        }
1✔
755

756
        cnt := uint32(count)
7✔
757

7✔
758
        arch := w.archetypes.Get(0)
7✔
759
        if len(comps) > 0 {
14✔
760
                arch = w.findOrCreateArchetype(arch, ids, nil, Entity{}, 0)
7✔
761
        }
7✔
762
        startIdx := arch.Len()
7✔
763
        w.createEntities(arch, uint32(count))
7✔
764

7✔
765
        var i uint32
7✔
766
        for i = 0; i < cnt; i++ {
20,507✔
767
                idx := startIdx + i
20,500✔
768
                entity := arch.GetEntity(uintptr(idx))
20,500✔
769
                for _, c := range comps {
61,500✔
770
                        w.copyTo(entity, c.ID, c.Comp)
41,000✔
771
                }
41,000✔
772
        }
773

774
        return arch, startIdx
7✔
775
}
776

777
// createEntity creates an Entity and adds it to the given archetype.
778
func (w *World) createEntity(arch *archetype) Entity {
5,003✔
779
        entity := w.entityPool.Get()
5,003✔
780
        idx := arch.Alloc(entity)
5,003✔
781
        len := len(w.entities)
5,003✔
782
        if int(entity.id) == len {
9,995✔
783
                if len == cap(w.entities) {
5,027✔
784
                        old := w.entities
35✔
785
                        w.entities = make([]entityIndex, len, len+w.config.CapacityIncrement)
35✔
786
                        copy(w.entities, old)
35✔
787
                }
35✔
788
                w.entities = append(w.entities, entityIndex{arch: arch, index: idx})
4,992✔
789
        } else {
11✔
790
                w.entities[entity.id] = entityIndex{arch: arch, index: idx}
11✔
791
        }
11✔
792
        return entity
5,003✔
793
}
794

795
// createEntity creates multiple Entities and adds them to the given archetype.
796
func (w *World) createEntities(arch *archetype, count uint32) {
25✔
797
        startIdx := arch.Len()
25✔
798
        arch.AllocN(uint32(count))
25✔
799

25✔
800
        len := len(w.entities)
25✔
801
        required := len + int(count) - w.entityPool.Available()
25✔
802
        if required > cap(w.entities) {
42✔
803
                cap := capacity(required, w.config.CapacityIncrement)
17✔
804
                old := w.entities
17✔
805
                w.entities = make([]entityIndex, required, cap)
17✔
806
                copy(w.entities, old)
17✔
807
        } else if required > len {
32✔
808
                w.entities = w.entities[:required]
7✔
809
        }
7✔
810

811
        var i uint32
25✔
812
        for i = 0; i < count; i++ {
71,366✔
813
                idx := startIdx + i
71,341✔
814
                entity := w.entityPool.Get()
71,341✔
815
                arch.SetEntity(uintptr(idx), entity)
71,341✔
816
                w.entities[entity.id] = entityIndex{arch: arch, index: uintptr(idx)}
71,341✔
817
        }
71,341✔
818
}
819

820
// Copies a component to an entity
821
func (w *World) copyTo(entity Entity, id ID, comp interface{}) unsafe.Pointer {
41,149✔
822
        if !w.Has(entity, id) {
41,150✔
823
                panic("can't copy component into entity that has no such component type")
1✔
824
        }
825
        index := &w.entities[entity.id]
41,148✔
826
        arch := index.arch
41,148✔
827

41,148✔
828
        return arch.Set(index.index, id, comp)
41,148✔
829
}
830

831
// Tries to find an archetype by traversing the archetype graph,
832
// searching by mask and extending the graph if necessary.
833
// A new archetype is created for the final graph node if not already present.
834
func (w *World) findOrCreateArchetype(start *archetype, add []ID, rem []ID, target Entity, targetComponent int8) *archetype {
5,028✔
835
        curr := start.graphNode
5,028✔
836
        mask := start.Mask
5,028✔
837
        relation := start.graphNode.relation
5,028✔
838
        for _, id := range rem {
5,046✔
839
                mask.Set(id, false)
18✔
840
                if w.registry.IsRelation.Get(id) {
19✔
841
                        relation = -1
1✔
842
                }
1✔
843
                if next, ok := curr.TransitionRemove.Get(id); ok {
26✔
844
                        curr = next
8✔
845
                } else {
18✔
846
                        next, _ := w.findOrCreateArchetypeSlow(mask, relation)
10✔
847
                        next.TransitionAdd.Set(id, curr)
10✔
848
                        curr.TransitionRemove.Set(id, next)
10✔
849
                        curr = next
10✔
850
                }
10✔
851
        }
852
        for _, id := range add {
15,285✔
853
                mask.Set(id, true)
10,257✔
854
                if w.registry.IsRelation.Get(id) {
12,770✔
855
                        if relation >= 0 {
2,515✔
856
                                panic("entity already has a relation component")
2✔
857
                        }
858
                        relation = int8(id)
2,511✔
859
                }
860
                if next, ok := curr.TransitionAdd.Get(id); ok {
19,378✔
861
                        curr = next
9,123✔
862
                } else {
10,255✔
863
                        next, _ := w.findOrCreateArchetypeSlow(mask, relation)
1,132✔
864
                        next.TransitionRemove.Set(id, curr)
1,132✔
865
                        curr.TransitionAdd.Set(id, next)
1,132✔
866
                        curr = next
1,132✔
867
                }
1,132✔
868
        }
869
        arch := curr.GetArchetype(target)
5,026✔
870
        if arch == nil {
6,135✔
871
                arch = w.createArchetype(curr, target, targetComponent, true)
1,109✔
872
        }
1,109✔
873
        return arch
5,026✔
874
}
875

876
// Tries to find an archetype for a mask, when it can't be reached through the archetype graph.
877
// Creates an archetype graph node.
878
func (w *World) findOrCreateArchetypeSlow(mask Mask, relation int8) (*archetypeNode, bool) {
1,142✔
879
        if arch, ok := w.findArchetypeSlow(mask); ok {
1,149✔
880
                return arch, false
7✔
881
        }
7✔
882
        return w.createArchetypeNode(mask, relation), true
1,135✔
883
}
884

885
// Searches for an archetype by a mask.
886
func (w *World) findArchetypeSlow(mask Mask) (*archetypeNode, bool) {
1,146✔
887
        length := w.graph.Len()
1,146✔
888
        var i int32
1,146✔
889
        for i = 0; i < length; i++ {
525,152✔
890
                arch := w.graph.Get(i)
524,006✔
891
                if arch.mask == mask {
524,017✔
892
                        return arch, true
11✔
893
                }
11✔
894
        }
895
        return nil, false
1,135✔
896
}
897

898
// Creates a node in the archetype graph.
899
func (w *World) createArchetypeNode(mask Mask, relation int8) *archetypeNode {
1,214✔
900
        w.graph.Add(newArchetypeNode(mask, relation))
1,214✔
901
        node := w.graph.Get(w.graph.Len() - 1)
1,214✔
902
        return node
1,214✔
903
}
1,214✔
904

905
// Creates an archetype for the given archetype graph node.
906
// Initializes the archetype with a capacity according to CapacityIncrement if forStorage is true,
907
// and with a capacity of 1 otherwise.
908
func (w *World) createArchetype(node *archetypeNode, target Entity, targetComponent int8, forStorage bool) *archetype {
1,216✔
909
        mask := node.mask
1,216✔
910
        count := int(mask.TotalBitsSet())
1,216✔
911
        types := make([]componentType, count)
1,216✔
912

1,216✔
913
        start := 0
1,216✔
914
        end := MaskTotalBits
1,216✔
915
        if mask.Lo == 0 {
1,295✔
916
                start = wordSize
79✔
917
        }
79✔
918
        if mask.Hi == 0 {
2,432✔
919
                end = wordSize
1,216✔
920
        }
1,216✔
921

922
        idx := 0
1,216✔
923
        for i := start; i < end; i++ {
73,984✔
924
                id := ID(i)
72,768✔
925
                if mask.Get(id) {
78,051✔
926
                        types[idx] = componentType{ID: id, Type: w.registry.Types[id]}
5,283✔
927
                        idx++
5,283✔
928
                }
5,283✔
929
        }
930

931
        w.archetypes.Add(archetype{})
1,216✔
932
        arch := w.archetypes.Get(w.archetypes.Len() - 1)
1,216✔
933
        arch.Init(node, w.config.CapacityIncrement, forStorage, target, targetComponent, types...)
1,216✔
934

1,216✔
935
        node.SetArchetype(target, arch)
1,216✔
936

1,216✔
937
        w.filterCache.addArchetype(arch)
1,216✔
938
        return arch
1,216✔
939
}
940

941
// Returns all archetypes that match the given filter. Used by [Cache].
942
func (w *World) getArchetypes(filter Filter) archetypePointers {
6✔
943
        arches := []*archetype{}
6✔
944
        ln := int32(w.archetypes.Len())
6✔
945
        var i int32
6✔
946
        for i = 0; i < ln; i++ {
15✔
947
                a := w.archetypes.Get(i)
9✔
948
                if a.Matches(filter) {
11✔
949
                        arches = append(arches, a)
2✔
950
                }
2✔
951
        }
952
        return archetypePointers{arches}
6✔
953
}
954

955
// componentID returns the ID for a component type, and registers it if not already registered.
956
func (w *World) componentID(tp reflect.Type) ID {
132✔
957
        return w.registry.ComponentID(tp)
132✔
958
}
132✔
959

960
// resourceID returns the ID for a resource type, and registers it if not already registered.
961
func (w *World) resourceID(tp reflect.Type) ResID {
12✔
962
        return w.resources.registry.ComponentID(tp)
12✔
963
}
12✔
964

965
// closeQuery closes a query and unlocks the world.
966
func (w *World) closeQuery(query *Query) {
190✔
967
        query.archIndex = -2
190✔
968
        w.unlock(query.lockBit)
190✔
969

190✔
970
        if w.listener != nil {
212✔
971
                if arch, ok := query.archetypes.(batchArchetype); ok {
32✔
972
                        w.notifyQuery(&arch)
10✔
973
                }
10✔
974
        }
975
}
976

977
// notifies the listener for all entities on a batch query.
978
func (w *World) notifyQuery(batchArch *batchArchetype) {
10✔
979
        arch := batchArch.Archetype
10✔
980
        var i uintptr
10✔
981
        len := uintptr(arch.Len())
10✔
982
        event := EntityEvent{Entity{}, Mask{}, arch.Mask, arch.Ids, nil, arch.Ids, 1}
10✔
983

10✔
984
        for i = uintptr(batchArch.StartIndex); i < len; i++ {
911✔
985
                entity := arch.GetEntity(i)
901✔
986
                event.Entity = entity
901✔
987
                w.listener(&event)
901✔
988
        }
901✔
989
}
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