...

Source file src/code.rocketnine.space/tslocum/gohan/world_test.go

Documentation: code.rocketnine.space/tslocum/gohan

     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  	// Fetch component again to ensure consistency.
    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  // Round values to eliminate floating point precision errors. This is only
   147  // necessary during testing because we validate the final values.
   148  func round(f float64) float64 {
   149  	return math.Round(f*10) / 10
   150  }
   151  
   152  // Note: Because drawing a System is functionally the same as updating a System,
   153  // as only an extra argument is passed, there are no drawing tests or benchmarks.
   154  

View as plain text