Add additional benchmarks and optimize
Allocate memory less often.
This commit is contained in:
parent
83c9c1fb0c
commit
62caf1bdd2
|
@ -7,7 +7,7 @@ import (
|
|||
var componentMutex sync.Mutex
|
||||
|
||||
// ComponentID is a component identifier. Each Component is assigned a unique ID
|
||||
// via NewComponentID, and implements a ComponentID method returning its ID.
|
||||
// via World.NewComponentID, and implements a ComponentID method returning its ID.
|
||||
type ComponentID int
|
||||
|
||||
// Component represents data for an entity, and how it interacts with the world.
|
||||
|
|
15
context.go
15
context.go
|
@ -2,6 +2,9 @@ package gohan
|
|||
|
||||
import "log"
|
||||
|
||||
// Context represents the current iteration of a System's matching entities. It
|
||||
// provides methods for retrieving components for the currently matched Entity,
|
||||
// and removing the currently matched Entity.
|
||||
type Context struct {
|
||||
Entity Entity
|
||||
|
||||
|
@ -12,7 +15,7 @@ type Context struct {
|
|||
|
||||
// Component gets a Component of the currently handled Entity.
|
||||
func (ctx *Context) Component(componentID ComponentID) interface{} {
|
||||
if debug {
|
||||
if debug != 0 {
|
||||
var found bool
|
||||
for _, id := range ctx.c {
|
||||
if id == componentID {
|
||||
|
@ -21,15 +24,15 @@ func (ctx *Context) Component(componentID ComponentID) interface{} {
|
|||
}
|
||||
}
|
||||
if !found {
|
||||
log.Panicf("illegal component access: component %d is not queried by system %s", componentID, ctx.w.systemName(ctx.s))
|
||||
log.Panicf("illegal component access: component %d is not queried by %s", componentID, ctx.w.systemName(ctx.s))
|
||||
}
|
||||
}
|
||||
return ctx.w.Component(ctx.Entity, componentID)
|
||||
}
|
||||
|
||||
// RemoveEntity removes the currently handled Entity's components, causing it
|
||||
// to no longer be handled by any system. Because Gohan reuses removed EntityIDs,
|
||||
// applications must also remove any internal references to the removed Entity.
|
||||
func (ctx *Context) RemoveEntity() {
|
||||
ctx.w.RemoveEntity(ctx.Entity)
|
||||
// to no longer be handled by any system. Because Gohan reuses removed Entity
|
||||
// IDs, applications must also remove any other references to the removed Entity.
|
||||
func (ctx *Context) RemoveEntity() bool {
|
||||
return ctx.w.RemoveEntity(ctx.Entity)
|
||||
}
|
||||
|
|
21
doc.go
21
doc.go
|
@ -27,6 +27,17 @@ should be public (start with an uppercase letter) and may have any number of
|
|||
publicly accessible data fields. They should not have any logic (i.e. game code)
|
||||
within them, as all logic should be implemented within a System.
|
||||
|
||||
Rather than accessing components via Context.Component, using helper functions
|
||||
(such as the following) helps to reduce code verbosity.
|
||||
|
||||
func Position(ctx *gohan.Context) *PositionComponent {
|
||||
c, ok := ctx.Component(PositionComponentID).(*PositionComponent)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
System Design Guidelines
|
||||
|
||||
Systems are located in a separate package, typically named system. They should
|
||||
|
@ -35,5 +46,15 @@ named as follows: NewSystemNameHere(). Data should be stored within components
|
|||
attached to one or more entities, rather than within the systems themselves.
|
||||
References to components must not be maintained outside each Update and Draw
|
||||
call, or else the application will encounter race conditions.
|
||||
|
||||
Environment Variables
|
||||
|
||||
Running an application with the environment variable GOHAN_DEBUG set to 1
|
||||
enables verification of Systems' access to Components. This verification is
|
||||
disabled by default for performance reasons. While verification is enabled,
|
||||
if a System attempts to access a Component which is not included in the
|
||||
System's Needs or Uses, the application will panic and print information about
|
||||
the illegal Component access. Setting GOHAN_DEBUG to 1 will also enable
|
||||
printing System registration events and statistics.
|
||||
*/
|
||||
package gohan
|
||||
|
|
19
entity.go
19
entity.go
|
@ -16,9 +16,9 @@ func (w *World) NewEntity() Entity {
|
|||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
if len(w.availableEntityIDs) > 0 {
|
||||
id := w.availableEntityIDs[0]
|
||||
w.availableEntityIDs = w.availableEntityIDs[1:]
|
||||
if len(w.availableEntities) > 0 {
|
||||
id := w.availableEntities[0]
|
||||
w.availableEntities = w.availableEntities[1:]
|
||||
w.allEntities = append(w.allEntities, id)
|
||||
return id
|
||||
}
|
||||
|
@ -32,17 +32,24 @@ func (w *World) NewEntity() Entity {
|
|||
// RemoveEntity removes the provided Entity's components, causing it to no
|
||||
// longer be handled by any system. Because Gohan reuses removed EntityIDs,
|
||||
// applications must also remove any internal references to the removed Entity.
|
||||
func (w *World) RemoveEntity(entity Entity) {
|
||||
func (w *World) RemoveEntity(entity Entity) bool {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
for i, e := range w.allEntities {
|
||||
if e == entity {
|
||||
w.allEntities = append(w.allEntities[:i], w.allEntities[i+1:]...)
|
||||
w.removedEntities = append(w.removedEntities, e)
|
||||
return
|
||||
|
||||
// Remove components.
|
||||
for i := range w.components[entity] {
|
||||
w.components[entity][i] = nil
|
||||
}
|
||||
|
||||
w.removedEntities = append(w.removedEntities, entity)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var numEntities int
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
project_name: twinstick
|
||||
|
||||
builds:
|
||||
-
|
||||
id: twinstick
|
||||
flags:
|
||||
- -tags=example
|
||||
goos:
|
||||
- js
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- wasm
|
||||
archives:
|
||||
-
|
||||
id: twinstick
|
||||
builds:
|
||||
- twinstick
|
||||
replacements:
|
||||
386: i386
|
||||
format_overrides:
|
||||
- goos: js
|
||||
format: zip
|
||||
- goos: windows
|
||||
format: zip
|
||||
# files:
|
||||
# - src: '../../*.md'
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
|
@ -20,10 +20,6 @@ func NewDrawBulletsSystem() *DrawBulletsSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Name() string {
|
||||
return "DrawBullets"
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.PositionComponentID,
|
||||
|
@ -35,7 +31,7 @@ func (s *DrawBulletsSystem) Uses() []gohan.ComponentID {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *DrawBulletsSystem) Update(ctx *gohan.Context) error {
|
||||
func (s *DrawBulletsSystem) Update(_ *gohan.Context) error {
|
||||
return gohan.ErrSystemWithoutUpdate
|
||||
}
|
||||
|
||||
|
|
|
@ -22,10 +22,6 @@ func NewDrawPlayerSystem(player gohan.Entity) *drawPlayerSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *drawPlayerSystem) Name() string {
|
||||
return "DrawPlayer"
|
||||
}
|
||||
|
||||
func (s *drawPlayerSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.PositionComponentID,
|
||||
|
|
|
@ -27,10 +27,6 @@ func NewFireInputSystem(player gohan.Entity) *fireInputSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *fireInputSystem) Name() string {
|
||||
return "FireInput"
|
||||
}
|
||||
|
||||
func (_ *fireInputSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.PositionComponentID,
|
||||
|
@ -107,6 +103,6 @@ func (s *fireInputSystem) Update(ctx *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (_ *fireInputSystem) Draw(ctx *gohan.Context, _ *ebiten.Image) error {
|
||||
func (_ *fireInputSystem) Draw(_ *gohan.Context, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -19,10 +19,6 @@ func NewMovementInputSystem(player gohan.Entity) *movementInputSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Name() string {
|
||||
return "MovementInput"
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.VelocityComponentID,
|
||||
|
@ -63,6 +59,6 @@ func (s *movementInputSystem) Update(ctx *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *movementInputSystem) Draw(ctx *gohan.Context, _ *ebiten.Image) error {
|
||||
func (s *movementInputSystem) Draw(_ *gohan.Context, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -27,10 +27,6 @@ func NewProfileSystem(player gohan.Entity) *profileSystem {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *profileSystem) Name() string {
|
||||
return "Profile"
|
||||
}
|
||||
|
||||
func (s *profileSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.WeaponComponentID,
|
||||
|
@ -41,13 +37,12 @@ func (s *profileSystem) Uses() []gohan.ComponentID {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *profileSystem) Update(ctx *gohan.Context) error {
|
||||
func (s *profileSystem) Update(_ *gohan.Context) error {
|
||||
if ebiten.IsKeyPressed(ebiten.KeyControl) && inpututil.IsKeyJustPressed(ebiten.KeyP) {
|
||||
if s.cpuProfile == nil {
|
||||
log.Println("CPU profiling started...")
|
||||
|
||||
runtime.SetCPUProfileRate(0)
|
||||
runtime.SetCPUProfileRate(1000)
|
||||
runtime.SetCPUProfileRate(10000)
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
|
@ -75,6 +70,6 @@ func (s *profileSystem) Update(ctx *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *profileSystem) Draw(ctx *gohan.Context, _ *ebiten.Image) error {
|
||||
func (s *profileSystem) Draw(_ *gohan.Context, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -14,10 +14,6 @@ type MovementSystem struct {
|
|||
Player gohan.Entity
|
||||
}
|
||||
|
||||
func (s *MovementSystem) Name() string {
|
||||
return "Movement"
|
||||
}
|
||||
|
||||
func (_ *MovementSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.PositionComponentID,
|
||||
|
@ -81,6 +77,6 @@ func (s *MovementSystem) Update(ctx *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (_ *MovementSystem) Draw(ctx *gohan.Context, _ *ebiten.Image) error {
|
||||
func (_ *MovementSystem) Draw(_ *gohan.Context, _ *ebiten.Image) error {
|
||||
return gohan.ErrSystemWithoutDraw
|
||||
}
|
||||
|
|
|
@ -29,10 +29,6 @@ func NewPrintInfoSystem(player gohan.Entity) *printInfoSystem {
|
|||
return p
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Name() string {
|
||||
return "PrintInfo"
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.WeaponComponentID,
|
||||
|
@ -43,11 +39,11 @@ func (s *printInfoSystem) Uses() []gohan.ComponentID {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Update(ctx *gohan.Context) error {
|
||||
func (s *printInfoSystem) Update(_ *gohan.Context) error {
|
||||
return gohan.ErrSystemWithoutUpdate
|
||||
}
|
||||
|
||||
func (s *printInfoSystem) Draw(ctx *gohan.Context, screen *ebiten.Image) error {
|
||||
func (s *printInfoSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error {
|
||||
w := world.World
|
||||
|
||||
s.img.Clear()
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
//go:build example
|
||||
// +build example
|
||||
|
||||
package world
|
||||
|
||||
import "code.rocketnine.space/tslocum/gohan"
|
||||
|
|
10
system.go
10
system.go
|
@ -11,12 +11,8 @@ import (
|
|||
// indicating that the system does not utilize one of the methods. System
|
||||
// methods which return one of these special error values will not be called again.
|
||||
//
|
||||
// See ErrSystemWithoutUpdate and ErrSystemWithoutDraw.
|
||||
// See the special error values ErrSystemWithoutUpdate and ErrSystemWithoutDraw.
|
||||
type System interface {
|
||||
// Name returns the name of the System. This is only used when printing
|
||||
// debug and error messages.
|
||||
Name() string
|
||||
|
||||
// Needs returns a list of Components (specified by ID) which are required
|
||||
// for an Entity to be handled by the System. When the game is running,
|
||||
// matching entities will be passed to the Update and Draw methods.
|
||||
|
@ -28,10 +24,10 @@ type System interface {
|
|||
// return Components which are not also returned by Needs.
|
||||
Uses() []ComponentID
|
||||
|
||||
// Update is called once for each matching entity each time the game state is updated.
|
||||
// Update is called once for each matching Entity each time the game state is updated.
|
||||
Update(ctx *Context) error
|
||||
|
||||
// Draw is called once for each matching entity each time the game is drawn to the screen.
|
||||
// Draw is called once for each matching Entity each time the game is drawn to the screen.
|
||||
Draw(ctx *Context, screen *ebiten.Image) error
|
||||
}
|
||||
|
||||
|
|
107
world.go
107
world.go
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -12,14 +13,22 @@ import (
|
|||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
var debug bool
|
||||
var debug int
|
||||
|
||||
func init() {
|
||||
debugEnv := os.Getenv("GOHAN_DEBUG")
|
||||
debugEnv = strings.TrimSpace(debugEnv)
|
||||
debugEnv = strings.ToLower(debugEnv)
|
||||
|
||||
debug = debugEnv == "1" || debugEnv == "t" || debugEnv == "y" || debugEnv == "on" || debugEnv == "yes" || debugEnv == "true"
|
||||
i, err := strconv.Atoi(debugEnv)
|
||||
if err == nil {
|
||||
debug = i
|
||||
return
|
||||
}
|
||||
|
||||
if debugEnv == "t" || debugEnv == "y" || debugEnv == "on" || debugEnv == "yes" || debugEnv == "true" {
|
||||
debug = 1
|
||||
}
|
||||
}
|
||||
|
||||
// World represents a collection of Entities, Components and Systems.
|
||||
|
@ -28,16 +37,18 @@ type World struct {
|
|||
|
||||
maxComponentID ComponentID
|
||||
|
||||
components [][]interface{}
|
||||
components [][]interface{} // components[Entity][ComponentID]Component
|
||||
|
||||
allEntities []Entity
|
||||
|
||||
modifiedEntities []Entity
|
||||
removedEntities []Entity
|
||||
|
||||
// availableEntityIDs is the set of EntityIDs available because they were
|
||||
handledModifiedEntities map[Entity]bool
|
||||
|
||||
// availableEntities is the set of EntityIDs available because they were
|
||||
// removed from the game.
|
||||
availableEntityIDs []Entity
|
||||
availableEntities []Entity
|
||||
|
||||
systems []System
|
||||
systemEntities [][]Entity // Slice of entities matching each system.
|
||||
|
@ -62,9 +73,12 @@ type World struct {
|
|||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewWorld returns a new World.
|
||||
func NewWorld() *World {
|
||||
w := &World{
|
||||
cacheTime: time.Second,
|
||||
|
||||
handledModifiedEntities: make(map[Entity]bool),
|
||||
}
|
||||
|
||||
w.ctx = &Context{
|
||||
|
@ -91,8 +105,8 @@ ATTACH:
|
|||
|
||||
w.systemEntities[systemIndex] = append(w.systemEntities[systemIndex], entity)
|
||||
|
||||
if debug {
|
||||
log.Printf("Attached entity %d to system %s.", entity, w.systemName(systemIndex))
|
||||
if debug > 1 {
|
||||
log.Printf("Attached entity %d to %s.", entity, w.systemName(systemIndex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,12 +137,13 @@ func AddSystemAfter(system System, after ...System) {
|
|||
attachEntitiesToSystem(system)
|
||||
}
|
||||
*/
|
||||
|
||||
func (w *World) systemName(i int) string {
|
||||
name := w.systems[i].Name()
|
||||
if name == "" {
|
||||
name = strconv.Itoa(i)
|
||||
t := reflect.TypeOf(w.systems[i])
|
||||
for t.Kind() == reflect.Ptr {
|
||||
return strings.Title(t.Elem().Name())
|
||||
}
|
||||
return name
|
||||
return strings.Title(t.Name())
|
||||
}
|
||||
|
||||
func (w *World) updateSystem(i int) (int, error) {
|
||||
|
@ -144,17 +159,14 @@ func (w *World) updateSystem(i int) (int, error) {
|
|||
w.systemReceivesUpdate[i] = false
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("failed to update system %s for entity %d: %+v", w.systemName(i), entity, err)
|
||||
return 0, fmt.Errorf("failed to update %s for entity %d: %+v", w.systemName(i), entity, err)
|
||||
}
|
||||
updated++
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (w *World) propagateEntityChanges() {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
func (w *World) _handleRemovedEntities() {
|
||||
for _, entity := range w.removedEntities {
|
||||
// Remove from attached systems.
|
||||
REMOVED:
|
||||
|
@ -166,17 +178,21 @@ func (w *World) propagateEntityChanges() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove components.
|
||||
w.components[entity] = make([]interface{}, w.maxComponentID+1)
|
||||
}
|
||||
|
||||
// Mark EntityIDs as available.
|
||||
w.availableEntityIDs = append(w.availableEntityIDs, w.removedEntities...)
|
||||
// Mark EntityID as available.
|
||||
w.availableEntities = append(w.availableEntities, w.removedEntities...)
|
||||
|
||||
w.removedEntities = nil
|
||||
w.removedEntities = w.removedEntities[:0]
|
||||
}
|
||||
|
||||
func (w *World) _handleModifiedEntities() {
|
||||
for _, entity := range w.modifiedEntities {
|
||||
if w.handledModifiedEntities[entity] {
|
||||
continue
|
||||
}
|
||||
w.handledModifiedEntities[entity] = true
|
||||
|
||||
for i := range w.systems {
|
||||
systemEntityIndex := -1
|
||||
for j, systemEntity := range w.systemEntities[i] {
|
||||
|
@ -201,8 +217,8 @@ func (w *World) propagateEntityChanges() {
|
|||
|
||||
w.systemEntities[i] = append(w.systemEntities[i], entity)
|
||||
|
||||
if debug {
|
||||
log.Printf("Attached entity %d to system %s.", entity, w.systemName(i))
|
||||
if debug > 1 {
|
||||
log.Printf("Attached entity %d to %s.", entity, w.systemName(i))
|
||||
}
|
||||
} else if systemEntityIndex != -1 {
|
||||
// Detach from system.
|
||||
|
@ -210,7 +226,20 @@ func (w *World) propagateEntityChanges() {
|
|||
}
|
||||
}
|
||||
}
|
||||
w.modifiedEntities = nil
|
||||
|
||||
for k := range w.handledModifiedEntities {
|
||||
delete(w.handledModifiedEntities, k)
|
||||
}
|
||||
|
||||
w.modifiedEntities = w.modifiedEntities[:0]
|
||||
}
|
||||
|
||||
func (w *World) propagateEntityChanges() {
|
||||
entityMutex.Lock()
|
||||
defer entityMutex.Unlock()
|
||||
|
||||
w._handleRemovedEntities()
|
||||
w._handleModifiedEntities()
|
||||
}
|
||||
|
||||
// Update updates the game state.
|
||||
|
@ -221,10 +250,10 @@ func (w *World) Update() error {
|
|||
w.propagateEntityChanges()
|
||||
|
||||
var t time.Time
|
||||
if debug {
|
||||
if debug != 0 {
|
||||
t = time.Now()
|
||||
}
|
||||
var systems int
|
||||
|
||||
var entitiesUpdated int
|
||||
for i, registered := range w.systemReceivesUpdate {
|
||||
if !registered {
|
||||
|
@ -236,16 +265,15 @@ func (w *World) Update() error {
|
|||
}
|
||||
|
||||
entitiesUpdated += updated
|
||||
systems++
|
||||
|
||||
if debug {
|
||||
log.Printf("System %s: updated %d entities.", w.systemName(i), updated)
|
||||
if debug != 0 {
|
||||
log.Printf("- %s: %d updated.", w.systemName(i), updated)
|
||||
}
|
||||
}
|
||||
w.systemUpdatedEntities = entitiesUpdated
|
||||
|
||||
if debug {
|
||||
log.Printf("Finished updating %d systems in %.2fms.", systems, float64(time.Since(t).Microseconds())/1000)
|
||||
if debug != 0 {
|
||||
log.Printf("Handled %d entity updates in %.2fms.", entitiesUpdated, float64(time.Since(t).Microseconds())/1000)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -274,7 +302,7 @@ func (w *World) drawSystem(i int, screen *ebiten.Image) (int, error) {
|
|||
w.systemReceivesDraw[i] = false
|
||||
return 0, nil
|
||||
}
|
||||
return 0, fmt.Errorf("failed to draw system %s for entity %d: %+v", w.systemName(i), entity, err)
|
||||
return 0, fmt.Errorf("failed to draw %s for entity %d: %+v", w.systemName(i), entity, err)
|
||||
}
|
||||
drawn++
|
||||
}
|
||||
|
@ -288,6 +316,11 @@ func (w *World) Draw(screen *ebiten.Image) error {
|
|||
|
||||
w.propagateEntityChanges()
|
||||
|
||||
var t time.Time
|
||||
if debug != 0 {
|
||||
t = time.Now()
|
||||
}
|
||||
|
||||
var entitiesDrawn int
|
||||
for i, registered := range w.systemReceivesDraw {
|
||||
if !registered {
|
||||
|
@ -300,8 +333,16 @@ func (w *World) Draw(screen *ebiten.Image) error {
|
|||
}
|
||||
|
||||
entitiesDrawn += drawn
|
||||
|
||||
if debug != 0 {
|
||||
log.Printf("- %s: %d drawn.", w.systemName(i), drawn)
|
||||
}
|
||||
}
|
||||
w.systemDrawnEntities = entitiesDrawn
|
||||
|
||||
if debug != 0 {
|
||||
log.Printf("Handled %d entity draws in %.2fms.", entitiesDrawn, float64(time.Since(t).Microseconds())/1000)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,6 @@ type movementSystem struct {
|
|||
velocityComponentID ComponentID
|
||||
}
|
||||
|
||||
func (s *movementSystem) Name() string {
|
||||
return "Movement"
|
||||
}
|
||||
|
||||
func (s *movementSystem) Needs() []ComponentID {
|
||||
return []ComponentID{
|
||||
s.positionComponentID,
|
||||
|
@ -44,12 +40,21 @@ func TestWorld(t *testing.T) {
|
|||
|
||||
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)
|
||||
|
@ -63,7 +68,7 @@ func TestWorld(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkUpdateWorld(b *testing.B) {
|
||||
func BenchmarkUpdateWorldInactive(b *testing.B) {
|
||||
w, _, _, _ := newTestWorld()
|
||||
|
||||
b.StopTimer()
|
||||
|
@ -79,6 +84,31 @@ func BenchmarkUpdateWorld(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
@ -114,3 +144,6 @@ func newTestWorld() (w *World, e Entity, positionComponentID ComponentID, veloci
|
|||
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.
|
||||
|
|
Loading…
Reference in New Issue