You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
3.5 KiB
Go
154 lines
3.5 KiB
Go
package gohan
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"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 !entities[i].Remove() {
|
|
t.Errorf("failed to remove entity %d", entities[i])
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
const expectedLength = 4096
|
|
if (len(AllEntities())) != expectedLength {
|
|
t.Fatalf("failed world consistency check: expected %d entities, got %d", expectedLength, len(AllEntities()))
|
|
}
|
|
|
|
var calls int
|
|
timer := time.NewTimer(time.Second)
|
|
go Query(func(e Entity, position *positionComponent, velocity *velocityComponent) {
|
|
if position == nil || velocity == nil {
|
|
return
|
|
}
|
|
calls++
|
|
})
|
|
select {
|
|
case <-timer.C:
|
|
}
|
|
if calls != expectedLength {
|
|
t.Fatalf("failed to query world for entities: expected %d callback function calls, got %d", expectedLength, calls)
|
|
}
|
|
}
|
|
|
|
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.
|