• 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

95.94
/ecs/world.go
1
package ecs
2

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

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

10
// World is the central type holding entity and component data, as well as resources.
11
//
12
// The World provides all the basic ECS functionality of Arche,
13
// like [World.Query], [World.NewEntity], [World.Add], [World.Remove] or [World.RemoveEntity].
14
//
15
// For more advanced functionality, see [World.Relations], [World.Resources],
16
// [World.Batch], [World.Cache] and [Builder].
17
type World struct {
18
        listener       Listener                  // EntityEvent listener.
19
        nodePointers   []*archNode               // Helper list of all node pointers for queries.
20
        entities       []entityIndex             // Mapping from entities to archetype and index.
21
        targetEntities bitSet                    // Whether entities are potential relation targets. Used for archetype cleanup.
22
        relationNodes  []*archNode               // Archetype nodes that have an entity relation.
23
        filterCache    Cache                     // Cache for registered filters.
24
        nodes          pagedSlice[archNode]      // The archetype graph.
25
        archetypeData  pagedSlice[archetypeData] // Storage for the actual archetype data (components).
26
        nodeData       pagedSlice[nodeData]      // The archetype graph's data.
27
        archetypes     pagedSlice[archetype]     // Archetypes that have no relations components.
28
        entityPool     entityPool                // Pool for entities.
29
        stats          stats.World               // Cached world statistics.
30
        resources      Resources                 // World resources.
31
        registry       componentRegistry         // Component registry.
32
        locks          lockMask                  // World locks.
33
        config         Config                    // World configuration.
34
}
35

36
// NewWorld creates a new [World] from an optional [Config].
37
//
38
// Uses the default [Config] if called without an argument.
39
// Accepts zero or one arguments.
40
func NewWorld(config ...Config) World {
139✔
41
        if len(config) > 1 {
140✔
42
                panic("can't use more than one Config")
1✔
43
        }
44
        if len(config) == 1 {
149✔
45
                return fromConfig(config[0])
11✔
46
        }
11✔
47
        return fromConfig(NewConfig())
127✔
48
}
49

50
// NewEntity returns a new or recycled [Entity].
51
// The given component types are added to the entity.
52
//
53
// Panics when called on a locked world.
54
// Do not use during [Query] iteration!
55
//
56
// ⚠️ Important:
57
// Entities are intended to be stored and passed around via copy, not via pointers! See [Entity].
58
//
59
// Note that calling a method with varargs in Go causes a slice allocation.
60
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
61
//
62
//        // fast
63
//        world.NewEntity(idA, idB, idC)
64
//        // even faster
65
//        world.NewEntity(ids...)
66
//
67
// For more advanced and batched entity creation, see [Builder] and [Batch].
68
// See also [World.NewEntityAndThen] the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
69
func (w *World) NewEntity(comps ...ID) Entity {
1,037,329✔
70
        return w.newEntity(nil, comps...)
1,037,329✔
71
}
1,037,329✔
72

73
// NewEntityAndThen returns a new or recycled [Entity].
74
// The given component types are added to the entity.
75
//
76
// The callback fn is called before the world's listener is notified.
77
// Use this to configure the entity's components so that listener subscribers
78
// are notified after full initialization.
79
//
80
// Panics when called on a locked world.
81
// Do not use during [Query] iteration!
82
//
83
// ⚠️ Important:
84
// Entities are intended to be stored and passed around via copy, not via pointers! See [Entity].
85
//
86
// Note that calling a method with varargs in Go causes a slice allocation.
87
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
88
//
89
//        // fast
90
//        world.NewEntityAndThen(nil, idA, idB, idC)
91
//        // even faster
92
//        world.NewEntity(nil, ids...)
93
//
94
// For more advanced and batched entity creation, see [Builder] and [Batch].
95
// See also [World.NewEntity] and the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
NEW
96
func (w *World) NewEntityAndThen(fn func(e Entity), comps ...ID) Entity {
×
NEW
97
        return w.newEntity(fn, comps...)
×
NEW
98
}
×
99

100
// newEntity returns a new or recycled [Entity].
101
// The given component types are added to the entity.
102
//
103
// The callback fn is called before the world's listener is notified.
104
func (w *World) newEntity(fn func(Entity), comps ...ID) Entity {
1,037,329✔
105
        w.checkLocked()
1,037,329✔
106

1,037,329✔
107
        arch := w.archetypes.Get(0)
1,037,329✔
108
        if len(comps) > 0 {
1,579,178✔
109
                arch = w.findOrCreateArchetype(arch, comps, nil, Entity{})
541,849✔
110
        }
541,849✔
111

112
        entity := w.createEntity(arch)
1,037,326✔
113

1,037,326✔
114
        if fn != nil {
1,037,326✔
NEW
115
                fn(entity)
×
NEW
116
        }
×
117

118
        if w.listener != nil {
1,037,367✔
119
                var newRel *ID
41✔
120
                if arch.HasRelationComponent {
48✔
121
                        newRel = &arch.RelationComponent
7✔
122
                }
7✔
123
                bits := subscription(true, false, len(comps) > 0, false, newRel != nil, newRel != nil)
41✔
124
                trigger := w.listener.Subscriptions() & bits
41✔
125
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, newRel) {
82✔
126
                        w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: comps, NewRelation: newRel, EventTypes: bits})
41✔
127
                }
41✔
128
        }
129
        return entity
1,037,326✔
130
}
131

132
// NewEntityWith returns a new or recycled [Entity].
133
// The given component values are assigned to the entity.
134
//
135
// Deprecated: This method is slow. Instead, use NewWith of the generic API
136
// under [github.com/mlange-42/arche/generic.Map1], etc.
137
// This method may be removed in a future version.
138
//
139
// The components in the Comp field of [Component] must be pointers.
140
// The passed pointers are no valid references to the assigned memory!
141
//
142
// Panics when called on a locked world.
143
// Do not use during [Query] iteration!
144
//
145
// ⚠️ Important:
146
// Entities are intended to be stored and passed around via copy, not via pointers! See [Entity].
147
//
148
// For more advanced and batched entity creation, see [Builder].
149
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
150
func (w *World) NewEntityWith(comps ...Component) Entity {
77✔
151
        w.checkLocked()
77✔
152

77✔
153
        if len(comps) == 0 {
78✔
154
                return w.NewEntity()
1✔
155
        }
1✔
156

157
        ids := make([]ID, len(comps))
76✔
158
        for i, c := range comps {
230✔
159
                ids[i] = c.ID
154✔
160
        }
154✔
161

162
        arch := w.archetypes.Get(0)
76✔
163
        arch = w.findOrCreateArchetype(arch, ids, nil, Entity{})
76✔
164

76✔
165
        entity := w.createEntity(arch)
76✔
166

76✔
167
        for _, c := range comps {
230✔
168
                w.copyTo(entity, c.ID, c.Comp)
154✔
169
        }
154✔
170

171
        if w.listener != nil {
77✔
172
                var newRel *ID
1✔
173
                if arch.HasRelationComponent {
2✔
174
                        newRel = &arch.RelationComponent
1✔
175
                }
1✔
176
                bits := subscription(true, false, len(comps) > 0, false, newRel != nil, newRel != nil)
1✔
177
                trigger := w.listener.Subscriptions() & bits
1✔
178
                if trigger != 0 && subscribes(trigger, &arch.Mask, nil, w.listener.Components(), nil, newRel) {
2✔
179
                        w.listener.Notify(w, EntityEvent{Entity: entity, Added: arch.Mask, AddedIDs: ids, NewRelation: newRel, EventTypes: bits})
1✔
180
                }
1✔
181
        }
182
        return entity
76✔
183
}
184

185
// RemoveEntity removes an [Entity], making it eligible for recycling.
186
//
187
// Panics when called on a locked world or for an already removed entity.
188
// Do not use during [Query] iteration!
189
func (w *World) RemoveEntity(entity Entity) {
1,031,428✔
190
        w.checkLocked()
1,031,428✔
191

1,031,428✔
192
        if !w.entityPool.Alive(entity) {
1,031,431✔
193
                panic("can't remove a dead entity")
3✔
194
        }
195

196
        index := &w.entities[entity.id]
1,031,424✔
197
        oldArch := index.arch
1,031,424✔
198

1,031,424✔
199
        if w.listener != nil {
1,031,534✔
200
                var oldRel *ID
110✔
201
                if oldArch.HasRelationComponent {
112✔
202
                        oldRel = &oldArch.RelationComponent
2✔
203
                }
2✔
204
                var oldIds []ID
110✔
205
                if len(oldArch.node.Ids) > 0 {
214✔
206
                        oldIds = oldArch.node.Ids
104✔
207
                }
104✔
208

209
                bits := subscription(false, true, false, len(oldIds) > 0, oldRel != nil, oldRel != nil)
110✔
210
                trigger := w.listener.Subscriptions() & bits
110✔
211
                if trigger != 0 && subscribes(trigger, nil, &oldArch.Mask, w.listener.Components(), oldRel, nil) {
220✔
212
                        lock := w.lock()
110✔
213
                        w.listener.Notify(w, EntityEvent{Entity: entity, Removed: oldArch.Mask, RemovedIDs: oldIds, OldRelation: oldRel, OldTarget: oldArch.RelationTarget, EventTypes: bits})
110✔
214
                        w.unlock(lock)
110✔
215
                }
110✔
216
        }
217

218
        swapped := oldArch.Remove(index.index)
1,031,424✔
219

1,031,424✔
220
        w.entityPool.Recycle(entity)
1,031,424✔
221

1,031,424✔
222
        if swapped {
2,060,630✔
223
                swapEntity := oldArch.GetEntity(index.index)
1,029,206✔
224
                w.entities[swapEntity.id].index = index.index
1,029,206✔
225
        }
1,029,206✔
226
        index.arch = nil
1,031,424✔
227

1,031,424✔
228
        if w.targetEntities.Get(entity.id) {
1,056,434✔
229
                w.cleanupArchetypes(entity)
25,010✔
230
                w.targetEntities.Set(entity.id, false)
25,010✔
231
        }
25,010✔
232

233
        w.cleanupArchetype(oldArch)
1,031,424✔
234
}
235

236
// Alive reports whether an entity is still alive.
237
func (w *World) Alive(entity Entity) bool {
25,058✔
238
        return w.entityPool.Alive(entity)
25,058✔
239
}
25,058✔
240

241
// Get returns a pointer to the given component of an [Entity].
242
// Returns nil if the entity has no such component.
243
//
244
// Panics when called for a removed (and potentially recycled) entity.
245
//
246
// See [World.GetUnchecked] for an optimized version for static entities.
247
// See also [github.com/mlange-42/arche/generic.Map.Get] for a generic variant.
248
func (w *World) Get(entity Entity, comp ID) unsafe.Pointer {
514,375✔
249
        if !w.entityPool.Alive(entity) {
514,378✔
250
                panic("can't get component of a dead entity")
3✔
251
        }
252
        index := &w.entities[entity.id]
514,372✔
253
        return index.arch.Get(index.index, comp)
514,372✔
254
}
255

256
// GetUnchecked returns a pointer to the given component of an [Entity].
257
// Returns nil if the entity has no such component.
258
//
259
// GetUnchecked is an optimized version of [World.Get],
260
// for cases where entities are static or checked with [World.Alive] in user code.
261
// It can also be used after getting another component of the same entity with [World.Get].
262
//
263
// Panics when called for a removed entity, but not for a recycled entity.
264
//
265
// See also [github.com/mlange-42/arche/generic.Map.Get] for a generic variant.
266
func (w *World) GetUnchecked(entity Entity, comp ID) unsafe.Pointer {
2✔
267
        index := &w.entities[entity.id]
2✔
268
        return index.arch.Get(index.index, comp)
2✔
269
}
2✔
270

271
// Has returns whether an [Entity] has a given component.
272
//
273
// Panics when called for a removed (and potentially recycled) entity.
274
//
275
// See [World.HasUnchecked] for an optimized version for static entities.
276
// See also [github.com/mlange-42/arche/generic.Map.Has] for a generic variant.
277
func (w *World) Has(entity Entity, comp ID) bool {
494,193✔
278
        if !w.entityPool.Alive(entity) {
494,194✔
279
                panic("can't check for component of a dead entity")
1✔
280
        }
281
        return w.entities[entity.id].arch.HasComponent(comp)
494,192✔
282
}
283

284
// HasUnchecked returns whether an [Entity] has a given component.
285
//
286
// HasUnchecked is an optimized version of [World.Has],
287
// for cases where entities are static or checked with [World.Alive] in user code.
288
//
289
// Panics when called for a removed entity, but not for a recycled entity.
290
//
291
// See also [github.com/mlange-42/arche/generic.Map.Has] for a generic variant.
292
func (w *World) HasUnchecked(entity Entity, comp ID) bool {
2✔
293
        return w.entities[entity.id].arch.HasComponent(comp)
2✔
294
}
2✔
295

296
// Add adds components to an [Entity].
297
//
298
// Panics:
299
//   - when called for a removed (and potentially recycled) entity.
300
//   - when called with components that can't be added because they are already present.
301
//   - when called on a locked world. Do not use during [Query] iteration!
302
//
303
// Note that calling a method with varargs in Go causes a slice allocation.
304
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
305
//
306
//        // fast
307
//        world.Add(entity, idA, idB, idC)
308
//        // even faster
309
//        world.Add(entity, ids...)
310
//
311
// See also [World.AddAndThen], [World.Exchange] and [World.ExchangeAndThen].
312
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
313
func (w *World) Add(entity Entity, comps ...ID) {
2,368✔
314
        w.Exchange(entity, comps, nil)
2,368✔
315
}
2,368✔
316

317
// Add adds components to an [Entity].
318
//
319
// The callback fn is called before the world's listener is notified.
320
// Use this to configure the entity's components so that listener subscribers
321
// are notified after full initialization.
322
//
323
// Panics:
324
//   - when called for a removed (and potentially recycled) entity.
325
//   - when called with components that can't be added because they are already present.
326
//   - when called on a locked world. Do not use during [Query] iteration!
327
//
328
// Note that calling a method with varargs in Go causes a slice allocation.
329
// For maximum performance, pre-allocate a slice of component IDs and pass it using ellipsis:
330
//
331
//        // fast
332
//        world.Add(entity, idA, idB, idC)
333
//        // even faster
334
//        world.Add(entity, ids...)
335
//
336
// See also [World.Add], [World.Exchange] and [World.ExchangeAndThen].
337
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
NEW
338
func (w *World) AddAndThen(entity Entity, fn func(Entity), comps ...ID) {
×
NEW
339
        w.ExchangeAndThen(entity, comps, nil, fn)
×
NEW
340
}
×
341

342
// Assign assigns multiple components to an [Entity], using pointers for the content.
343
//
344
// Deprecated: This method is slow. Instead, use Assign of the generic API
345
// under [github.com/mlange-42/arche/generic.Map1], etc.
346
// This method may be removed in a future version.
347
//
348
// The components in the Comp field of [Component] must be pointers.
349
// The passed pointers are no valid references to the assigned memory!
350
//
351
// Panics:
352
//   - when called for a removed (and potentially recycled) entity.
353
//   - when called with components that can't be added because they are already present.
354
//   - when called on a locked world. Do not use during [Query] iteration!
355
//
356
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
357
func (w *World) Assign(entity Entity, comps ...Component) {
493,248✔
358
        w.assign(entity, ID{}, false, Entity{}, comps...)
493,248✔
359
}
493,248✔
360

361
// Set overwrites a component for an [Entity], using the given pointer for the content.
362
//
363
// Deprecated: This method is slow. Instead, use Set of the generic API
364
// under [github.com/mlange-42/arche/generic.Map].
365
// This method may be removed in a future version.
366
//
367
// The passed component must be a pointer.
368
// Returns a pointer to the assigned memory.
369
// The passed in pointer is not a valid reference to that memory!
370
//
371
// Panics:
372
//   - when called for a removed (and potentially recycled) entity.
373
//   - if the entity does not have a component of that type.
374
//   - when called on a locked world. Do not use during [Query] iteration!
375
//
376
// See also [github.com/mlange-42/arche/generic.Map.Set] for a generic variant.
377
func (w *World) Set(entity Entity, id ID, comp interface{}) unsafe.Pointer {
2✔
378
        return w.copyTo(entity, id, comp)
2✔
379
}
2✔
380

381
// Remove removes components from an entity.
382
//
383
// Panics:
384
//   - when called for a removed (and potentially recycled) entity.
385
//   - when called with components that can't be removed because they are not present.
386
//   - when called on a locked world. Do not use during [Query] iteration!
387
//
388
// See also [World.Exchange].
389
// See also the generic variants under [github.com/mlange-42/arche/generic.Map1], etc.
390
func (w *World) Remove(entity Entity, comps ...ID) {
412✔
391
        w.Exchange(entity, nil, comps)
412✔
392
}
412✔
393

394
// Exchange adds and removes components in one pass.
395
// This is more efficient than subsequent use of [World.Add] and [World.Remove].
396
//
397
// When a [Relation] component is removed and another one is added,
398
// the target entity of the relation is reset to zero.
399
//
400
// Panics:
401
//   - when called for a removed (and potentially recycled) entity.
402
//   - when called with components that can't be added or removed because they are already present/not present, respectively.
403
//   - when called on a locked world. Do not use during [Query] iteration!
404
//
405
// See also [Relations.Exchange] and the generic variants under [github.com/mlange-42/arche/generic.Exchange].
406
func (w *World) Exchange(entity Entity, add []ID, rem []ID) {
2,793✔
407
        w.exchange(entity, add, rem, ID{}, false, Entity{}, nil)
2,793✔
408
}
2,793✔
409

410
// Exchange adds and removes components in one pass.
411
// This is more efficient than subsequent use of [World.Add] and [World.Remove].
412
//
413
// The callback fn is called before the world's listener is notified.
414
// Use this to configure the entity's components so that listener subscribers
415
// are notified after full initialization.
416
//
417
// When a [Relation] component is removed and another one is added,
418
// the target entity of the relation is reset to zero.
419
//
420
// Panics:
421
//   - when called for a removed (and potentially recycled) entity.
422
//   - when called with components that can't be added or removed because they are already present/not present, respectively.
423
//   - when called on a locked world. Do not use during [Query] iteration!
424
//
425
// See also [Relations.Exchange] and the generic variants under [github.com/mlange-42/arche/generic.Exchange].
NEW
426
func (w *World) ExchangeAndThen(entity Entity, add []ID, rem []ID, fn func(Entity)) {
×
NEW
427
        w.exchange(entity, add, rem, ID{}, false, Entity{}, fn)
×
UNCOV
428
}
×
429

430
// Reset removes all entities and resources from the world.
431
//
432
// Does NOT free reserved memory, remove archetypes, clear the registry, clear cached filters, etc.
433
// However, it removes archetypes with a relation component that is not zero.
434
//
435
// Can be used to run systematic simulations without the need to re-allocate memory for each run.
436
// Accelerates re-populating the world by a factor of 2-3.
437
func (w *World) Reset() {
32✔
438
        w.checkLocked()
32✔
439

32✔
440
        w.entities = w.entities[:1]
32✔
441
        w.targetEntities.Reset()
32✔
442
        w.entityPool.Reset()
32✔
443
        w.locks.Reset()
32✔
444
        w.resources.reset()
32✔
445

32✔
446
        len := w.nodes.Len()
32✔
447
        var i int32
32✔
448
        for i = 0; i < len; i++ {
129✔
449
                w.nodes.Get(i).Reset(w.Cache())
97✔
450
        }
97✔
451
}
452

453
// Query creates a [Query] iterator.
454
//
455
// Locks the world to prevent changes to component compositions.
456
// The lock is released automatically when the query finishes iteration, or when [Query.Close] is called.
457
// The number of simultaneous locks (and thus open queries) at a given time is limited to [MaskTotalBits] (256).
458
//
459
// A query can iterate through its entities only once, and can't be used anymore afterwards.
460
//
461
// To create a [Filter] for querying, see [All], [Mask.Without], [Mask.Exclusive] and [RelationFilter].
462
//
463
// For type-safe generics queries, see package [github.com/mlange-42/arche/generic].
464
// For advanced filtering, see package [github.com/mlange-42/arche/filter].
465
func (w *World) Query(filter Filter) Query {
2,379✔
466
        l := w.lock()
2,379✔
467
        if cached, ok := filter.(*CachedFilter); ok {
2,398✔
468
                return newCachedQuery(w, cached.filter, l, w.filterCache.get(cached).Archetypes.pointers)
19✔
469
        }
19✔
470

471
        return newQuery(w, filter, l, w.nodePointers)
2,359✔
472
}
473

474
// Resources of the world.
475
//
476
// Resources are component-like data that is not associated to an entity, but unique to the world.
477
func (w *World) Resources() *Resources {
17✔
478
        return &w.resources
17✔
479
}
17✔
480

481
// Cache returns the [Cache] of the world, for registering filters.
482
//
483
// See [Cache] for details on filter caching.
484
func (w *World) Cache() *Cache {
25,146✔
485
        if w.filterCache.getArchetypes == nil {
25,171✔
486
                w.filterCache.getArchetypes = w.getArchetypes
25✔
487
        }
25✔
488
        return &w.filterCache
25,146✔
489
}
490

491
// Batch creates a [Batch] processing helper.
492
// It provides the functionality to manipulate large numbers of entities in batches,
493
// which is more efficient than handling them one by one.
494
func (w *World) Batch() *Batch {
25,041✔
495
        return &Batch{w}
25,041✔
496
}
25,041✔
497

498
// Relations returns the [Relations] of the world, for accessing entity [Relation] targets.
499
//
500
// See [Relations] for details.
501
func (w *World) Relations() *Relations {
3,174✔
502
        return &Relations{world: w}
3,174✔
503
}
3,174✔
504

505
// IsLocked returns whether the world is locked by any queries.
506
func (w *World) IsLocked() bool {
2,671,511✔
507
        return w.locks.IsLocked()
2,671,511✔
508
}
2,671,511✔
509

510
// Mask returns the archetype [Mask] for the given [Entity].
511
func (w *World) Mask(entity Entity) Mask {
12✔
512
        if !w.entityPool.Alive(entity) {
14✔
513
                panic("can't get mask for a dead entity")
2✔
514
        }
515
        return w.entities[entity.id].arch.Mask
10✔
516
}
517

518
// Ids returns the component IDs for the archetype of the given [Entity].
519
//
520
// Returns a copy of the archetype's component IDs slice, for safety.
521
// This means that the result can be manipulated safely,
522
// but also that calling the method may incur some significant cost.
523
func (w *World) Ids(entity Entity) []ID {
7✔
524
        if !w.entityPool.Alive(entity) {
8✔
525
                panic("can't get component IDs for a dead entity")
1✔
526
        }
527
        return append([]ID{}, w.entities[entity.id].arch.node.Ids...)
6✔
528
}
529

530
// SetListener sets a [Listener] for the world.
531
// The listener is immediately called on every [ecs.Entity] change.
532
// Replaces the current listener. Call with nil to remove a listener.
533
//
534
// For details, see [EntityEvent], [Listener] and sub-package [event].
535
func (w *World) SetListener(listener Listener) {
15✔
536
        w.listener = listener
15✔
537
}
15✔
538

539
// Stats reports statistics for inspecting the World.
540
//
541
// The underlying [stats.World] object is re-used and updated between calls.
542
// The returned pointer should thus not be stored for later analysis.
543
// Rather, the required data should be extracted immediately.
544
func (w *World) Stats() *stats.World {
50,010✔
545
        w.stats.Entities = stats.Entities{
50,010✔
546
                Used:     w.entityPool.Len(),
50,010✔
547
                Total:    w.entityPool.Cap(),
50,010✔
548
                Recycled: w.entityPool.Available(),
50,010✔
549
                Capacity: w.entityPool.TotalCap(),
50,010✔
550
        }
50,010✔
551

50,010✔
552
        compCount := len(w.registry.Components)
50,010✔
553
        types := append([]reflect.Type{}, w.registry.Types[:compCount]...)
50,010✔
554

50,010✔
555
        memory := cap(w.entities)*int(entityIndexSize) + w.entityPool.TotalCap()*int(entitySize)
50,010✔
556

50,010✔
557
        cntOld := int32(len(w.stats.Nodes))
50,010✔
558
        cntNew := int32(w.nodes.Len())
50,010✔
559
        cntActive := 0
50,010✔
560
        var i int32
50,010✔
561
        for i = 0; i < cntOld; i++ {
200,032✔
562
                node := w.nodes.Get(i)
150,022✔
563
                nodeStats := &w.stats.Nodes[i]
150,022✔
564
                node.UpdateStats(nodeStats, &w.registry)
150,022✔
565
                if node.IsActive {
300,043✔
566
                        memory += nodeStats.Memory
150,021✔
567
                        cntActive++
150,021✔
568
                }
150,021✔
569
        }
570
        for i = cntOld; i < cntNew; i++ {
50,022✔
571
                node := w.nodes.Get(i)
12✔
572
                w.stats.Nodes = append(w.stats.Nodes, node.Stats(&w.registry))
12✔
573
                if node.IsActive {
22✔
574
                        memory += w.stats.Nodes[i].Memory
10✔
575
                        cntActive++
10✔
576
                }
10✔
577
        }
578

579
        w.stats.ComponentCount = compCount
50,010✔
580
        w.stats.ComponentTypes = types
50,010✔
581
        w.stats.Locked = w.IsLocked()
50,010✔
582
        w.stats.Memory = memory
50,010✔
583
        w.stats.CachedFilters = len(w.filterCache.filters)
50,010✔
584
        w.stats.ActiveNodeCount = cntActive
50,010✔
585

50,010✔
586
        return &w.stats
50,010✔
587
}
588

589
// DumpEntities dumps entity information into an [EntityDump] object.
590
// This dump can be used with [World.LoadEntities] to set the World's entity state.
591
//
592
// For world serialization with components and resources, see module [github.com/mlange-42/arche-serde].
593
func (w *World) DumpEntities() EntityDump {
3✔
594
        alive := []uint32{}
3✔
595

3✔
596
        query := w.Query(All())
3✔
597
        for query.Next() {
7✔
598
                alive = append(alive, uint32(query.Entity().id))
4✔
599
        }
4✔
600

601
        data := EntityDump{
3✔
602
                Entities:  append([]Entity{}, w.entityPool.entities...),
3✔
603
                Alive:     alive,
3✔
604
                Next:      uint32(w.entityPool.next),
3✔
605
                Available: w.entityPool.available,
3✔
606
        }
3✔
607

3✔
608
        return data
3✔
609
}
610

611
// LoadEntities resets all entities to the state saved with [World.DumpEntities].
612
//
613
// Use this only on an empty world! Can be used after [World.Reset].
614
//
615
// The resulting world will have the same entities (in terms of ID, generation and alive state)
616
// as the original world. This is necessary for proper serialization of entity relations.
617
// However, the entities will not have any components.
618
//
619
// Panics if the world has any dead or alive entities.
620
//
621
// For world serialization with components and resources, see module [github.com/mlange-42/arche-serde].
622
func (w *World) LoadEntities(data *EntityDump) {
3✔
623
        w.checkLocked()
3✔
624

3✔
625
        if len(w.entityPool.entities) > 1 || w.entityPool.available > 0 {
4✔
626
                panic("can set entity data only on a fresh or reset world")
1✔
627
        }
628

629
        capacity := capacity(len(data.Entities), w.config.CapacityIncrement)
2✔
630

2✔
631
        entities := make([]Entity, 0, capacity)
2✔
632
        entities = append(entities, data.Entities...)
2✔
633

2✔
634
        w.entityPool.entities = entities
2✔
635
        w.entityPool.next = eid(data.Next)
2✔
636
        w.entityPool.available = data.Available
2✔
637

2✔
638
        w.entities = make([]entityIndex, len(data.Entities), capacity)
2✔
639
        w.targetEntities = bitSet{}
2✔
640
        w.targetEntities.ExtendTo(capacity)
2✔
641

2✔
642
        arch := w.archetypes.Get(0)
2✔
643
        for _, idx := range data.Alive {
5✔
644
                entity := w.entityPool.entities[idx]
3✔
645
                archIdx := arch.Alloc(entity)
3✔
646
                w.entities[entity.id] = entityIndex{arch: arch, index: archIdx}
3✔
647
        }
3✔
648
}
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