forked from unitoftime/ecs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
world.go
182 lines (154 loc) · 6.17 KB
/
world.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package ecs
import (
"math"
)
const (
InvalidEntity Id = 0 // Represents the default entity Id, which is invalid
firstEntity Id = 1
MaxEntity Id = math.MaxUint32
)
// World is the main data-holder. You usually pass it to other functions to do things.
type World struct {
nextId Id
minId, maxId Id // This is the range of Ids returned by NewId
arch map[Id]archetypeId
engine *archEngine
}
// Creates a new world
func NewWorld() *World {
return &World{
nextId: firstEntity + 1,
minId: firstEntity + 1,
maxId: MaxEntity,
arch: make(map[Id]archetypeId),
engine: newArchEngine(),
}
}
// Sets an range of Ids that the world will use when creating new Ids. Potentially helpful when you have multiple worlds and don't want their Id space to collide.
// Deprecated: This API is tentative. It may be better to just have the user create Ids as they see fit
func (w *World) SetIdRange(min, max Id) {
if min <= firstEntity {
panic("max must be greater than 1")
}
if max <= firstEntity {
panic("max must be greater than 1")
}
if min > max {
panic("min must be less than max!")
}
w.minId = min
w.maxId = max
}
// Creates a new Id which can then be used to create an entity
func (w *World) NewId() Id {
if w.nextId < w.minId {
w.nextId = w.minId
}
id := w.nextId
if w.nextId == w.maxId {
w.nextId = w.minId
} else {
w.nextId++
}
return id
}
// func (w *World) Count(anything ...any) int {
// return w.engine.Count(anything...)
// }
// func (w *World) Print(amount int) {
// fmt.Println("--- World ---")
// fmt.Printf("nextId: %d\n", w.nextId)
// // max := amount
// // for id, archId := range w.arch {
// // fmt.Printf("id(%d) -> archId(%d)\n", id, archId)
// // max--; if max <= 0 { break }
// // }
// // w.engine.Print(amount)
// }
// // A debug function for describing the current state of memory allocations in the ECS
// func (w *World) DescribeMemory() {
// fmt.Println("--- World ---")
// fmt.Printf("nextId: %d\n", w.nextId)
// fmt.Printf("Active Ent Count: %d\n", len(w.arch))
// for archId, lookup := range w.engine.lookup {
// efficiency := 100 * (1.0 - float64(len(lookup.holes))/float64(len(lookup.id)))
// fmt.Printf("Lookup[%d] = {len(index)=%d, len(id)=%d, len(holes)=%d} | Efficiency=%.2f%%\n", archId, len(lookup.index), len(lookup.id), len(lookup.holes), efficiency)
// }
// }
// TODO - Note: This function is not safe inside Maps or view iteraions
// TODO - make this loop-safe by:
// 1. Read the entire entity into an entity object
// 2. Call loop-safe delete method on that ID (which tags it somehow to indicate it needs to be cleaned up)
// 3. Modify the entity object by removing the requested components
// 4. Write the entity object to the destination archetype
// 4.a If the destination archetype is currently locked/flagged to indicate we are looping over it then wait for the lock release before writing the entity
// 4.b When creating Maps and Views we need to lock each archId that needs to be processed. Notably this guarantees that all "Writes" to this archetypeId will be done AFTER the lambda has processed - Meaning that we won't execute the same entity twice.
// 4.b.i When creating a view I may need like a "Close" method or "end" or something otherwise I'm not sure how to unlock the archId for modification
// Question: Why not write directly to holes if possible?
// Writes components to the entity specified at id. This API can potentially break if you call it inside of a loop. Specifically, if you cause the archetype of the entity to change by writing a new component, then the loop may act in mysterious ways.
// Deprecated: This API is tentative, I might replace it with something similar to bevy commands to alleviate the above concern
func Write(world *World, id Id, comp ...Component) {
world.Write(id, comp...)
}
func (world *World) Write(id Id, comp ...Component) {
if len(comp) <= 0 { return } // Do nothing if there are no components
archId, ok := world.arch[id]
if ok {
newarchetypeId := world.engine.rewriteArch(archId, id, comp...)
world.arch[id] = newarchetypeId
} else {
// Id does not yet exist, we need to add it for the first time
archId = world.engine.GetarchetypeId(comp...)
world.arch[id] = archId
// Write all components to that archetype
// TODO - Push this inward for efficiency?
for i := range comp {
comp[i].write(world.engine, archId, id)
}
}
}
// Reads a specific component of the entity specified at id.
// Returns true if the entity was found and had that component, else returns false.
// Deprecated: This API is tentative, I'm trying to improve the QueryN construct so that it can capture this usecase.
func Read[T any](world *World, id Id) (T, bool) {
var ret T
archId, ok := world.arch[id]
if !ok {
return ret, false
}
return readArch[T](world.engine, archId, id)
}
// Reads a pointer to the component of the entity at the specified id.
// Returns true if the entity was found and had that component, else returns false.
// This pointer is short lived and can become invalid if any other entity changes in the world
// Deprecated: This API is tentative, I'm trying to improve the QueryN construct so that it can capture this usecase.
func ReadPtr[T any](world *World, id Id) *T {
archId, ok := world.arch[id]
if !ok {
return nil
}
return readPtrArch[T](world.engine, archId, id)
}
// This is safe for maps and loops
// 1. This deletes the high level id -> archId lookup
// 2. This creates a "hole" in the archetype list
// Returns true if the entity was deleted, else returns false if the entity does not exist (or was already deleted)
// Deletes the entire entity specified by the id
// This can be called inside maps and loops, it will delete the entity immediately.
// Returns true if the entity exists and was actually deleted, else returns false
func Delete(world *World, id Id) bool {
archId, ok := world.arch[id]
if !ok {
return false
}
delete(world.arch, id)
world.engine.TagForDeletion(archId, id)
// Note: This was the old, more direct way, but isn't loop safe
// - world.engine.DeleteAll(archId, id)
return true
}
// Returns true if the entity exists in the world else it returns false
func (world *World) Exists(id Id) bool {
_, ok := world.arch[id]
return ok
}