...
1 package gohan
2
3 import (
4 "math"
5 "testing"
6 "time"
7
8 "github.com/hajimehoshi/ebiten/v2"
9 )
10
11 type movementSystem struct {
12 Position *positionComponent
13 Velocity *velocityComponent
14 }
15
16 func (s *movementSystem) Update(e Entity) error {
17 s.Position.X, s.Position.Y = s.Position.X+s.Velocity.X, s.Position.Y+s.Velocity.Y
18 return nil
19 }
20
21 func (s *movementSystem) Draw(e Entity, screen *ebiten.Image) error {
22 return nil
23 }
24
25 func TestWorld(t *testing.T) {
26 const iterations = 1024
27
28 _, e, positionComponentID, velocityComponentID := newTestWorld()
29
30 entities := make([]Entity, iterations)
31
32 position := e.getComponent(positionComponentID).(*positionComponent)
33 velocity := e.getComponent(velocityComponentID).(*velocityComponent)
34
35 expectedX, expectedY := position.X+(velocity.X*iterations), position.Y+(velocity.Y*iterations)
36
37 for i := 0; i < iterations; i++ {
38 entities[i] = NewEntity()
39 if !entities[i].Remove() {
40 t.Errorf("failed to remove entity %d", entities[i])
41 }
42
43 err := Update()
44 if err != nil {
45 t.Fatal(err)
46 }
47 }
48
49
50 position = e.getComponent(positionComponentID).(*positionComponent)
51 if round(position.X) != round(expectedX) || round(position.Y) != round(expectedY) {
52 t.Errorf("failed to update system: expected position (%f,%f), got (%f,%f)", expectedX, expectedY, position.X, position.Y)
53 }
54
55 const expectedLength = 4096
56 if (len(AllEntities())) != expectedLength {
57 t.Fatalf("failed world consistency check: expected %d entities, got %d", expectedLength, len(AllEntities()))
58 }
59
60 var calls int
61 timer := time.NewTimer(time.Second)
62 go Query(func(e Entity, position *positionComponent, velocity *velocityComponent) {
63 if position == nil || velocity == nil {
64 return
65 }
66 calls++
67 })
68 select {
69 case <-timer.C:
70 }
71 if calls != expectedLength {
72 t.Fatalf("failed to query world for entities: expected %d callback function calls, got %d", expectedLength, calls)
73 }
74 }
75
76 func BenchmarkUpdateWorldInactive(b *testing.B) {
77 newTestWorld()
78
79 b.StopTimer()
80 b.ResetTimer()
81 b.ReportAllocs()
82 b.StartTimer()
83
84 for i := 0; i < b.N; i++ {
85 err := Update()
86 if err != nil {
87 b.Fatal(err)
88 }
89 }
90 }
91
92 func BenchmarkUpdateWorldActive(b *testing.B) {
93 newTestWorld()
94
95 b.StopTimer()
96 b.ResetTimer()
97 b.ReportAllocs()
98 b.StartTimer()
99
100 for i := 0; i < b.N; i++ {
101 e := NewEntity()
102 if !e.Remove() {
103 b.Errorf("failed to remove entity %d", e)
104 }
105
106 err := Update()
107 if err != nil {
108 b.Fatal(err)
109 }
110 }
111 }
112
113 func newTestWorld() (w *world, firstEntity Entity, positionComponentID componentID, velocityComponentID componentID) {
114 Reset()
115
116 movement := &movementSystem{}
117 AddSystem(movement)
118
119 const testEntities = 4096
120 for i := 0; i < testEntities; i++ {
121 ent := NewEntity()
122 if i == 0 {
123 firstEntity = ent
124 }
125
126 position := &positionComponent{
127 componentID: positionComponentID,
128 X: 108,
129 Y: 0,
130 }
131 ent.AddComponent(position)
132 positionComponentID = componentID(1)
133
134 velocity := &velocityComponent{
135 componentID: velocityComponentID,
136 X: -0.1,
137 Y: 0.2,
138 }
139 ent.AddComponent(velocity)
140 velocityComponentID = componentID(2)
141 }
142
143 return w, firstEntity, positionComponentID, velocityComponentID
144 }
145
146
147
148 func round(f float64) float64 {
149 return math.Round(f*10) / 10
150 }
151
152
153
154
View as plain text