Entity Component System framework for Ebiten
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

149 lines
3.4 KiB

package gohan
import (
"math"
"testing"
"github.com/hajimehoshi/ebiten/v2"
)
type movementSystem struct {
positionComponentID ComponentID
velocityComponentID ComponentID
}
func (s *movementSystem) Needs() []ComponentID {
return []ComponentID{
s.positionComponentID,
s.velocityComponentID,
}
}
func (s *movementSystem) Uses() []ComponentID {
return nil
}
func (s *movementSystem) Update(ctx *Context) error {
position := ctx.Component(s.positionComponentID).(*positionComponent)
velocity := ctx.Component(s.velocityComponentID).(*velocityComponent)
position.X, position.Y = position.X+velocity.X, position.Y+velocity.Y
return nil
}
func (s *movementSystem) Draw(ctx *Context, screen *ebiten.Image) error {
return nil
}
func TestWorld(t *testing.T) {
const iterations = 1024
w, e, positionComponentID, velocityComponentID := newTestWorld()
entities := make([]Entity, iterations)
position := w.Component(e, positionComponentID).(*positionComponent)
velocity := w.Component(e, velocityComponentID).(*velocityComponent)
expectedX, expectedY := position.X+(velocity.X*iterations), position.Y+(velocity.Y*iterations)
for i := 0; i < iterations; i++ {
entities[i] = w.NewEntity()
if i > 0 {
if !w.RemoveEntity(entities[i-1]) {
t.Errorf("failed to remove entity %d", entities[i-1])
}
}
err := w.Update()
if err != nil {
t.Fatal(err)
}
}
// Fetch component again to ensure consistency.
position = w.Component(e, 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) {
w, _, _, _ := newTestWorld()
b.StopTimer()
b.ResetTimer()
b.ReportAllocs()
b.StartTimer()
for i := 0; i < b.N; i++ {
err := w.Update()
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkUpdateWorldActive(b *testing.B) {
w, _, _, _ := newTestWorld()
entities := make([]Entity, b.N)
b.StopTimer()
b.ResetTimer()
b.ReportAllocs()
b.StartTimer()
for i := 0; i < b.N; i++ {
entities[i] = w.NewEntity()
if i > 0 {
if !w.RemoveEntity(entities[i-1]) {
b.Errorf("failed to remove entity %d", entities[i-1])
}
}
err := w.Update()
if err != nil {
b.Fatal(err)
}
}
}
func newTestWorld() (w *World, e Entity, positionComponentID ComponentID, velocityComponentID ComponentID) {
w = NewWorld()
e = w.NewEntity()
positionComponentID = w.NewComponentID()
position := &positionComponent{
componentID: positionComponentID,
X: 108,
Y: 0,
}
w.AddComponent(e, position)
velocityComponentID = w.NewComponentID()
velocity := &velocityComponent{
componentID: velocityComponentID,
X: -0.1,
Y: 0.2,
}
w.AddComponent(e, velocity)
movement := &movementSystem{
positionComponentID: positionComponentID,
velocityComponentID: velocityComponentID,
}
w.AddSystem(movement)
return w, e, 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.