Skip to content

Commit b9682c8

Browse files
authored
added systems to be able to be automatically populated by the world (#31)
1 parent e23dfcb commit b9682c8

File tree

4 files changed

+391
-9
lines changed

4 files changed

+391
-9
lines changed

README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,109 @@ func (m *MyAwesomeSystem) Update(dt float32) {
189189
}
190190
}
191191
```
192+
193+
# Automatically add entities to systems
194+
When your game gets *really* big, adding each entity to every system would be time consuming and buggy using the methods mentioned above. However, you can easily add entities to systems based solely on the interfaces that entity implements by
195+
utilizing the `SystemAddByInterfacer`. This takes a bit of work up front, but makes things much easier if your number of systems and entities increases. We're going to start with an example `System` MySystem, with `Component` ComponentA
196+
197+
```go
198+
type ComponentA struct {
199+
num int
200+
}
201+
202+
type mySystemEntity struct {
203+
ecs.BasicEntity
204+
*ComponentA
205+
}
206+
207+
type MySystem struct {
208+
entities []mySystemEntity
209+
}
210+
211+
type (m *MySystem) Add(basic ecs.BasicEntity, a *ComponentA) { /* Add stuff goes here */ }
212+
type (m *MySystem) Remove(basic ecs.BasicEntity) { /* Remove stuff here */ }
213+
type (m *MySystem) Update(dt float32) { /* Update stuff here */ }
214+
```
215+
216+
The components need to have corresponding Getters and Interfaces in order to be utilized. Let's add them
217+
218+
```go
219+
func (a *ComponentA) GetComponentA() *ComponentA {
220+
reurn a
221+
}
222+
223+
type AFace interface {
224+
GetComponentA() *ComponentA
225+
}
226+
```
227+
228+
### Note
229+
The convention is that we add Face to the end of the component's name for the interface.
230+
231+
Now that we have interfaces for all the components, we need to add an interface to tell if we use the system or not. (BasicEntity already has this setup for you, as does any component or system that uses entities in `engo/common`)
232+
233+
```go
234+
type Myable interface {
235+
ecs.BasicFace
236+
AFace
237+
}
238+
```
239+
240+
### Note
241+
The convention is to add able to the end of the system's name for the interface
242+
243+
Finally, we have to add the AddByInterface function to the system. Don't worry about the casting, it can't panic as the world makes sure it implements the required interface befor passing entities to it.
244+
245+
```go
246+
func (m *MySystem) AddByInterface(o ecs.Identifier) {
247+
obj := o.(Myable)
248+
m.Add(obj.GetBasicEntity(), obj.GetComponentA())
249+
}
250+
```
251+
252+
To use the system, instead of `w.AddSystem()` use
253+
254+
```go
255+
var myable *Myable
256+
w.AddSystemInterface(&MySystem{}, myable, nil)
257+
```
258+
259+
### Note
260+
This takes **a pointer to** the interface that the system needs implemented to use AddByInterface.
261+
262+
Finally, to add an entity, rather than looping through all the systems, you can just
263+
264+
```go
265+
w.AddEntity(&entity)
266+
```
267+
268+
## Exclude flags
269+
You can also add an interface to the system for components that can act as flags to NOT add an entity to that system. First you'll have to make the component. It'll have to have a Getter and Interface as well.
270+
271+
```go
272+
type NotMyComponent struct {}
273+
type NotMyFace interface {
274+
GetNotMyComponent() *NotMyComponent
275+
}
276+
func (n *NotMyComponent) GetNotMyComponent() *NotMyComponent {
277+
return n
278+
}
279+
```
280+
281+
Then you can make the interface for the system
282+
283+
```go
284+
type NotMyable interface {
285+
NotMyFace
286+
}
287+
```
288+
289+
Finally, we add it to the world
290+
291+
```go
292+
var myable *Myable
293+
var notMyable *NotMyable
294+
w.AddSystemInterface(&MySystem{}, myable, notMyable)
295+
```
296+
297+
Now our system can automatically, and it'll include all the entities that implement the Myable interface, except any entity that implements the NotMyable interface.

entity_test.go

Lines changed: 220 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ type MySystemOneEntity struct {
1212
c1 *MyComponent1
1313
}
1414

15+
type MySystemOneable interface {
16+
BasicFace
17+
MyComponent1Face
18+
}
19+
1520
type MySystemOne struct {
1621
entities []MySystemOneEntity
1722
}
@@ -20,20 +25,34 @@ func (*MySystemOne) Priority() int { return 0 }
2025
func (*MySystemOne) New(*World) {}
2126
func (sys *MySystemOne) Update(dt float32) {
2227
for _, e := range sys.entities {
23-
e.c1.A = 5
28+
e.c1.A++
2429
}
2530
}
2631
func (*MySystemOne) Remove(e BasicEntity) {}
2732
func (sys *MySystemOne) Add(e *BasicEntity, c1 *MyComponent1) {
2833
sys.entities = append(sys.entities, MySystemOneEntity{e, c1})
2934
}
35+
func (sys *MySystemOne) AddByInterface(o Identifier) {
36+
obj := o.(MySystemOneable)
37+
sys.Add(obj.GetBasicEntity(), obj.GetMyComponent1())
38+
}
3039

3140
type MySystemOneTwoEntity struct {
3241
e *BasicEntity
3342
c1 *MyComponent1
3443
c2 *MyComponent2
3544
}
3645

46+
type MySystemOneTwoable interface {
47+
BasicFace
48+
MyComponent1Face
49+
MyComponent2Face
50+
}
51+
52+
type NotMySystemOneTwoable interface {
53+
NotMyComponent12Face
54+
}
55+
3756
type MySystemOneTwo struct {
3857
entities []MySystemOneTwoEntity
3958
}
@@ -42,13 +61,8 @@ func (*MySystemOneTwo) Priority() int { return 0 }
4261
func (*MySystemOneTwo) New(*World) {}
4362
func (sys *MySystemOneTwo) Update(dt float32) {
4463
for _, e := range sys.entities {
45-
if e.c1 == nil {
46-
return
47-
}
48-
49-
if e.c2 == nil {
50-
return
51-
}
64+
e.c1.B++
65+
e.c2.D++
5266
}
5367
}
5468
func (sys *MySystemOneTwo) Remove(e BasicEntity) {
@@ -65,6 +79,58 @@ func (sys *MySystemOneTwo) Remove(e BasicEntity) {
6579
func (sys *MySystemOneTwo) Add(e *BasicEntity, c1 *MyComponent1, c2 *MyComponent2) {
6680
sys.entities = append(sys.entities, MySystemOneTwoEntity{e, c1, c2})
6781
}
82+
func (sys *MySystemOneTwo) AddByInterface(o Identifier) {
83+
obj := o.(MySystemOneTwoable)
84+
sys.Add(obj.GetBasicEntity(), obj.GetMyComponent1(), obj.GetMyComponent2())
85+
}
86+
87+
type MySystemTwoEntity struct {
88+
e *BasicEntity
89+
c2 *MyComponent2
90+
}
91+
92+
type MySystemTwoable interface {
93+
BasicFace
94+
MyComponent2Face
95+
}
96+
97+
type NotMySystemTwoable interface {
98+
NotMyComponent2Face
99+
}
100+
101+
type MySystemTwo struct {
102+
entities []MySystemTwoEntity
103+
}
104+
105+
func (*MySystemTwo) Priority() int { return 0 }
106+
func (*MySystemTwo) New(*World) {}
107+
func (sys *MySystemTwo) Update(dt float32) {
108+
for _, e := range sys.entities {
109+
e.c2.C++
110+
}
111+
}
112+
func (sys *MySystemTwo) Remove(e BasicEntity) {
113+
delete := -1
114+
for index, entity := range sys.entities {
115+
if entity.e.ID() == e.ID() {
116+
delete = index
117+
}
118+
}
119+
if delete >= 0 {
120+
sys.entities = append(sys.entities[:delete], sys.entities[delete+1:]...)
121+
}
122+
}
123+
func (sys *MySystemTwo) Add(e *BasicEntity, c2 *MyComponent2) {
124+
sys.entities = append(sys.entities, MySystemTwoEntity{e, c2})
125+
}
126+
func (sys *MySystemTwo) AddByInterface(o Identifier) {
127+
obj := o.(MySystemTwoable)
128+
sys.Add(obj.GetBasicEntity(), obj.GetMyComponent2())
129+
}
130+
131+
type BasicFace interface {
132+
GetBasicEntity() *BasicEntity
133+
}
68134

69135
type MyEntity1 struct {
70136
BasicEntity
@@ -85,10 +151,42 @@ type MyEntity12 struct {
85151
type MyComponent1 struct {
86152
A, B int
87153
}
154+
type MyComponent1Face interface {
155+
GetMyComponent1() *MyComponent1
156+
}
157+
158+
func (c *MyComponent1) GetMyComponent1() *MyComponent1 {
159+
return c
160+
}
88161

89162
type MyComponent2 struct {
90163
C, D int
91164
}
165+
type MyComponent2Face interface {
166+
GetMyComponent2() *MyComponent2
167+
}
168+
169+
func (c *MyComponent2) GetMyComponent2() *MyComponent2 {
170+
return c
171+
}
172+
173+
type NotMyComponent2 struct{}
174+
type NotMyComponent2Face interface {
175+
GetNotMyComponent2() *NotMyComponent2
176+
}
177+
178+
func (n *NotMyComponent2) GetNotMyComponent2() *NotMyComponent2 {
179+
return n
180+
}
181+
182+
type NotMyComponent12 struct{}
183+
type NotMyComponent12Face interface {
184+
GetNotMyComponent12() *NotMyComponent12
185+
}
186+
187+
func (n *NotMyComponent12) GetNotMyComponent12() *NotMyComponent12 {
188+
return n
189+
}
92190

93191
// TestCreateEntity ensures IDs which are created, are unique
94192
func TestCreateEntity(t *testing.T) {
@@ -169,6 +267,120 @@ func TestSortableIdentifierSlice(t *testing.T) {
169267
assert.ObjectsAreEqual(e2, entities[1])
170268
}
171269

270+
// TestSystemEntityFiltering checks that entities go into the right systems and the flags are obeyed
271+
func TestSystemEntityFiltering(t *testing.T) {
272+
w := &World{}
273+
274+
var sys1in *MySystemOneable
275+
w.AddSystemInterface(&MySystemOne{}, sys1in, nil)
276+
277+
var sys2in *MySystemTwoable
278+
var sys2out *NotMySystemTwoable
279+
w.AddSystemInterface(&MySystemTwo{}, sys2in, sys2out)
280+
281+
var sys12in *MySystemOneTwoable
282+
var sys12out *NotMySystemOneTwoable
283+
w.AddSystemInterface(&MySystemOneTwo{}, sys12in, sys12out)
284+
285+
e1 := struct {
286+
BasicEntity
287+
*MyComponent1
288+
}{
289+
NewBasic(),
290+
&MyComponent1{},
291+
}
292+
w.AddEntity(&e1)
293+
294+
e2 := struct {
295+
BasicEntity
296+
*MyComponent2
297+
}{
298+
NewBasic(),
299+
&MyComponent2{},
300+
}
301+
w.AddEntity(&e2)
302+
303+
e12 := struct {
304+
BasicEntity
305+
*MyComponent1
306+
*MyComponent2
307+
}{
308+
NewBasic(),
309+
&MyComponent1{},
310+
&MyComponent2{},
311+
}
312+
w.AddEntity(&e12)
313+
314+
e12x2 := struct {
315+
BasicEntity
316+
*MyComponent1
317+
*MyComponent2
318+
*NotMyComponent2
319+
}{
320+
NewBasic(),
321+
&MyComponent1{},
322+
&MyComponent2{},
323+
&NotMyComponent2{},
324+
}
325+
w.AddEntity(&e12x2)
326+
327+
e12x12 := struct {
328+
BasicEntity
329+
*MyComponent1
330+
*MyComponent2
331+
*NotMyComponent12
332+
}{
333+
NewBasic(),
334+
&MyComponent1{},
335+
&MyComponent2{},
336+
&NotMyComponent12{},
337+
}
338+
w.AddEntity(&e12x12)
339+
340+
e12x12x2 := struct {
341+
BasicEntity
342+
*MyComponent1
343+
*MyComponent2
344+
*NotMyComponent12
345+
*NotMyComponent2
346+
}{
347+
NewBasic(),
348+
&MyComponent1{},
349+
&MyComponent2{},
350+
&NotMyComponent12{},
351+
&NotMyComponent2{},
352+
}
353+
w.AddEntity(&e12x12x2)
354+
355+
w.Update(0.125)
356+
357+
assert.Equal(t, 1, e1.A, "e1 was not updated by system 1")
358+
assert.Equal(t, 0, e1.B, "e1 was updated by system 12")
359+
360+
assert.Equal(t, 1, e2.C, "e2 was not updated by system 2")
361+
assert.Equal(t, 0, e2.D, "e2 was updated by system 12")
362+
363+
assert.Equal(t, 1, e12.A, "e12 was not updated by system 1")
364+
assert.Equal(t, 1, e12.B, "e12 was not updated by system 12")
365+
assert.Equal(t, 1, e12.C, "e12 was not updated by system 2")
366+
assert.Equal(t, 1, e12.D, "e12 was not updated by system 12")
367+
368+
assert.Equal(t, 1, e12x2.A, "e12x2 was not updated by system 1")
369+
assert.Equal(t, 1, e12x2.B, "e12x2 was not updated by system 12")
370+
assert.Equal(t, 0, e12x2.C, "e12x2 was updated by system 2")
371+
assert.Equal(t, 1, e12x2.D, "e12x2 was not updated by system 12")
372+
373+
assert.Equal(t, 1, e12x12.A, "e12x12 was not updated by system 1")
374+
assert.Equal(t, 0, e12x12.B, "e12x12 was updated by system 12")
375+
assert.Equal(t, 1, e12x12.C, "e12x12 was not updated by system 2")
376+
assert.Equal(t, 0, e12x12.D, "e12x12 was updated by system 12")
377+
378+
assert.Equal(t, 1, e12x12x2.A, "e12x12x2 was not updated by system 1")
379+
assert.Equal(t, 0, e12x12x2.B, "e12x12x2 was updated by system 12")
380+
assert.Equal(t, 0, e12x12x2.C, "e12x12x2 was updated by system 2")
381+
assert.Equal(t, 0, e12x12x2.D, "e12x12x2 was updated by system 12")
382+
}
383+
172384
func BenchmarkIdiomatic(b *testing.B) {
173385
preload := func() {}
174386
setup := func(w *World) {

0 commit comments

Comments
 (0)