135 lines
3.0 KiB
Go
135 lines
3.0 KiB
Go
package gohan
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
)
|
|
|
|
type movementSystem struct {
|
|
Position *positionComponent
|
|
Velocity *velocityComponent
|
|
}
|
|
|
|
func (s *movementSystem) Update(e Entity) error {
|
|
s.Position.X, s.Position.Y = s.Position.X+s.Velocity.X, s.Position.Y+s.Velocity.Y
|
|
return nil
|
|
}
|
|
|
|
func (s *movementSystem) Draw(e Entity, screen *ebiten.Image) error {
|
|
return nil
|
|
}
|
|
|
|
func TestWorld(t *testing.T) {
|
|
const iterations = 1024
|
|
|
|
_, e, positionComponentID, velocityComponentID := newTestWorld()
|
|
|
|
entities := make([]Entity, iterations)
|
|
|
|
position := e.getComponent(positionComponentID).(*positionComponent)
|
|
velocity := e.getComponent(velocityComponentID).(*velocityComponent)
|
|
|
|
expectedX, expectedY := position.X+(velocity.X*iterations), position.Y+(velocity.Y*iterations)
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
entities[i] = NewEntity()
|
|
if i > 0 {
|
|
if !entities[i-1].Remove() {
|
|
t.Errorf("failed to remove entity %d", entities[i-1])
|
|
}
|
|
}
|
|
|
|
err := Update()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Fetch component again to ensure consistency.
|
|
position = e.getComponent(positionComponentID).(*positionComponent)
|
|
if round(position.X) != round(expectedX) || round(position.Y) != round(expectedY) {
|
|
t.Errorf("failed to update system: expected position (%f,%f), got (%f,%f)", expectedX, expectedY, position.X, position.Y)
|
|
}
|
|
}
|
|
|
|
func BenchmarkUpdateWorldInactive(b *testing.B) {
|
|
newTestWorld()
|
|
|
|
b.StopTimer()
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
b.StartTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := Update()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkUpdateWorldActive(b *testing.B) {
|
|
newTestWorld()
|
|
|
|
b.StopTimer()
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
b.StartTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
e := NewEntity()
|
|
if !e.Remove() {
|
|
b.Errorf("failed to remove entity %d", e)
|
|
}
|
|
|
|
err := Update()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newTestWorld() (w *world, firstEntity Entity, positionComponentID componentID, velocityComponentID componentID) {
|
|
Reset()
|
|
|
|
movement := &movementSystem{}
|
|
AddSystem(movement)
|
|
|
|
const testEntities = 4096
|
|
for i := 0; i < testEntities; i++ {
|
|
ent := NewEntity()
|
|
if i == 0 {
|
|
firstEntity = ent
|
|
}
|
|
|
|
position := &positionComponent{
|
|
componentID: positionComponentID,
|
|
X: 108,
|
|
Y: 0,
|
|
}
|
|
ent.AddComponent(position)
|
|
positionComponentID = componentID(1)
|
|
|
|
velocity := &velocityComponent{
|
|
componentID: velocityComponentID,
|
|
X: -0.1,
|
|
Y: 0.2,
|
|
}
|
|
ent.AddComponent(velocity)
|
|
velocityComponentID = componentID(2)
|
|
}
|
|
|
|
return w, firstEntity, positionComponentID, velocityComponentID
|
|
}
|
|
|
|
// Round values to eliminate floating point precision errors. This is only
|
|
// necessary during testing because we validate the final values.
|
|
func round(f float64) float64 {
|
|
return math.Round(f*10) / 10
|
|
}
|
|
|
|
// Note: Because drawing a System is functionally the same as updating a System,
|
|
// as only an extra argument is passed, there are no drawing tests or benchmarks.
|