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

mlange-42 / ark / 13719271841

07 Mar 2025 11:00AM CUT coverage: 99.408% (-0.02%) from 99.423%
13719271841

Pull #150

github

web-flow
Merge 13e26c186 into ec5db5ac0
Pull Request #150: Add `Unsafe.IDs`

5 of 6 new or added lines in 1 file covered. (83.33%)

6377 of 6415 relevant lines covered (99.41%)

28823.31 hits per line

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

98.92
/ecs/unsafe.go
1
package ecs
2

3
import "unsafe"
4

5
// Unsafe provides access to Ark's unsafe ID-based API.
6
// Get an instance via [World.Unsafe].
7
//
8
// The unsafe API is significantly slower than the type-safe API,
9
// and should only be used when component types are not known at compile time.
10
type Unsafe struct {
11
        world *World
12
}
13

14
// NewEntity creates a new entity with the given components.
15
func (u Unsafe) NewEntity(ids ...ID) Entity {
42✔
16
        return u.world.newEntityWith(ids, nil, nil)
42✔
17
}
42✔
18

19
// NewEntityRel creates a new entity with the given components and relation targets.
20
func (u Unsafe) NewEntityRel(ids []ID, relations ...RelationID) Entity {
33✔
21
        return u.world.newEntityWith(ids, nil, relations)
33✔
22
}
33✔
23

24
// Get returns a pointer to the given component of an [Entity].
25
//
26
// ⚠️ Important: The obtained pointer should not be stored persistently!
27
//
28
// Panics if the entity does not have the given component.
29
// Panics when called for a removed (and potentially recycled) entity.
30
func (u Unsafe) Get(entity Entity, comp ID) unsafe.Pointer {
3✔
31
        return u.world.storage.get(entity, comp)
3✔
32
}
3✔
33

34
// GetUnchecked returns a pointer to the given component of an [Entity].
35
// In contrast to [Unsafe.Get], it does not check whether the entity is alive.
36
//
37
// ⚠️ Important: The obtained pointer should not be stored persistently!
38
//
39
// Panics if the entity does not have the given component.
40
func (u Unsafe) GetUnchecked(entity Entity, comp ID) unsafe.Pointer {
1✔
41
        return u.world.storage.getUnchecked(entity, comp)
1✔
42
}
1✔
43

44
// Has returns whether an [Entity] has the given component.
45
//
46
// Panics when called for a removed (and potentially recycled) entity.
47
func (u Unsafe) Has(entity Entity, comp ID) bool {
11✔
48
        return u.world.storage.has(entity, comp)
11✔
49
}
11✔
50

51
// HasUnchecked returns whether an [Entity] has the given component.
52
// In contrast to [Unsafe.Has], it does not check whether the entity is alive.
53
//
54
// Panics when called for a removed (and potentially recycled) entity.
55
func (u Unsafe) HasUnchecked(entity Entity, comp ID) bool {
2✔
56
        return u.world.storage.hasUnchecked(entity, comp)
2✔
57
}
2✔
58

59
// GetRelation returns the relation target for the entity and the mapped component.
60
func (u Unsafe) GetRelation(entity Entity, comp ID) Entity {
4✔
61
        return u.world.storage.getRelation(entity, comp)
4✔
62
}
4✔
63

64
// GetRelationUnchecked returns the relation target for the entity and the mapped component.
65
// In contrast to [Unsafe.GetRelation], it does not check whether the entity is alive.
66
// Can be used as an optimization when it is certain that the entity is alive.
67
func (u Unsafe) GetRelationUnchecked(entity Entity, comp ID) Entity {
2✔
68
        return u.world.storage.getRelationUnchecked(entity, comp)
2✔
69
}
2✔
70

71
// SetRelations sets relation targets for an entity.
72
func (u Unsafe) SetRelations(entity Entity, relations ...RelationID) {
1✔
73
        u.world.setRelations(entity, relations)
1✔
74
}
1✔
75

76
// Add the given components to an entity.
77
func (u Unsafe) Add(entity Entity, comp ...ID) {
22✔
78
        if !u.world.Alive(entity) {
23✔
79
                panic("can't add components to a dead entity")
1✔
80
        }
81
        u.world.exchange(entity, comp, nil, nil, nil)
21✔
82
}
83

84
// AddRel adds the given components and relation targets to an entity.
85
func (u Unsafe) AddRel(entity Entity, comps []ID, relations ...RelationID) {
2✔
86
        if !u.world.Alive(entity) {
3✔
87
                panic("can't add components to a dead entity")
1✔
88
        }
89
        u.world.exchange(entity, comps, nil, nil, relations)
1✔
90
}
91

92
// Remove the given components from an entity.
93
func (u Unsafe) Remove(entity Entity, comp ...ID) {
12✔
94
        if !u.world.Alive(entity) {
13✔
95
                panic("can't remove components from a dead entity")
1✔
96
        }
97
        u.world.exchange(entity, nil, comp, nil, nil)
11✔
98
}
99

100
// Exchange the given components on entity.
101
func (u Unsafe) Exchange(entity Entity, add []ID, remove []ID, relations ...RelationID) {
2✔
102
        if !u.world.Alive(entity) {
3✔
103
                panic("can't exchange components on a dead entity")
1✔
104
        }
105
        u.world.exchange(entity, add, remove, nil, relations)
1✔
106
}
107

108
// IDs returns all component IDs of an entity.
109
func (u Unsafe) IDs(entity Entity) []ID {
1✔
110
        if !u.world.Alive(entity) {
1✔
NEW
111
                panic("can't get component IDs of a dead entity")
×
112
        }
113
        index := u.world.storage.entities[entity.id]
1✔
114
        ids := u.world.storage.tables[index.table].ids
1✔
115
        return append([]ID(nil), ids...)
1✔
116
}
117

118
// DumpEntities dumps entity information into an [EntityDump] object.
119
// This dump can be used with [Unsafe.LoadEntities] to set the World's entity state.
120
//
121
// For world serialization with components and resources, see module [github.com/mlange-42/ark-serde].
122
func (u Unsafe) DumpEntities() EntityDump {
3✔
123
        alive := []uint32{}
3✔
124

3✔
125
        filter := NewFilter(u.world)
3✔
126
        query := filter.Query()
3✔
127
        for query.Next() {
7✔
128
                alive = append(alive, uint32(query.Entity().id))
4✔
129
        }
4✔
130

131
        data := EntityDump{
3✔
132
                Entities:  append([]Entity{}, u.world.storage.entityPool.entities...),
3✔
133
                Alive:     alive,
3✔
134
                Next:      uint32(u.world.storage.entityPool.next),
3✔
135
                Available: u.world.storage.entityPool.available,
3✔
136
        }
3✔
137

3✔
138
        return data
3✔
139
}
140

141
// LoadEntities resets all entities to the state saved with [Unsafe.DumpEntities].
142
//
143
// Use this only on an empty world! Can be used after [World.Reset].
144
//
145
// The resulting world will have the same entities (in terms of ID, generation and alive state)
146
// as the original world. This is necessary for proper serialization of entity relations.
147
// However, the entities will not have any components.
148
//
149
// Panics if the world has any dead or alive entities.
150
//
151
// For world serialization with components and resources, see module [github.com/mlange-42/ark-serde].
152
func (u Unsafe) LoadEntities(data *EntityDump) {
3✔
153
        u.world.checkLocked()
3✔
154

3✔
155
        if len(u.world.storage.entityPool.entities) > 2 || u.world.storage.entityPool.available > 0 {
4✔
156
                panic("can set entity data only on a fresh or reset world")
1✔
157
        }
158

159
        capacity := len(data.Entities)
2✔
160

2✔
161
        entities := make([]Entity, 0, capacity)
2✔
162
        entities = append(entities, data.Entities...)
2✔
163

2✔
164
        if len(data.Entities) > 0 {
4✔
165
                u.world.storage.entityPool = entityPool{
2✔
166
                        entities:  entities,
2✔
167
                        next:      entityID(data.Next),
2✔
168
                        available: data.Available,
2✔
169
                        reserved:  entityID(reservedEntities),
2✔
170
                }
2✔
171
                u.world.storage.entityPool.pointer = unsafe.Pointer(&u.world.storage.entityPool.entities[0])
2✔
172
        }
2✔
173

174
        u.world.storage.entities = make([]entityIndex, len(data.Entities), capacity)
2✔
175
        u.world.storage.isTarget = make([]bool, len(data.Entities), capacity)
2✔
176

2✔
177
        table := &u.world.storage.tables[0]
2✔
178
        for _, idx := range data.Alive {
5✔
179
                entity := u.world.storage.entityPool.entities[idx]
3✔
180
                tableIdx := table.Add(entity)
3✔
181
                u.world.storage.entities[entity.id] = entityIndex{table: table.id, row: tableIdx}
3✔
182
        }
3✔
183
}
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