Load Balance Collision and Actor Loop Across CPU Cores

* Add sync.WaitGroup to some parts of the level collision detection
  function and Canvas.Loop() to speed up the frame rate by load
  balancing some work in parallel across multiple cores.
* Improves FPS from 30 to 55+ even for busy scenes with lots of mobile
  enemies walking around.
* Before the level collision optimization, framerate would sometimes dip
  to 30 FPS simply to move the player character on a completely blank
  map!
This commit is contained in:
Noah 2019-05-06 17:06:40 -07:00
parent d28745f89e
commit 61af068b80
3 changed files with 82 additions and 44 deletions

View File

@ -26,6 +26,10 @@ func init() {
if BuildDate == "" { if BuildDate == "" {
BuildDate = time.Now().Format(time.RFC3339) BuildDate = time.Now().Format(time.RFC3339)
} }
// Use all the CPU cores for collision detection and other load balanced
// goroutine work in the app.
runtime.GOMAXPROCS(runtime.NumCPU())
} }
func main() { func main() {

View File

@ -1,6 +1,8 @@
package collision package collision
import ( import (
"sync"
"git.kirsle.net/apps/doodle/lib/render" "git.kirsle.net/apps/doodle/lib/render"
"git.kirsle.net/apps/doodle/pkg/doodads" "git.kirsle.net/apps/doodle/pkg/doodads"
"git.kirsle.net/apps/doodle/pkg/level" "git.kirsle.net/apps/doodle/pkg/level"
@ -202,10 +204,34 @@ func (c *Collide) IsColliding() bool {
func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool { func (c *Collide) ScanBoundingBox(box render.Rect, grid *level.Chunker) bool {
col := GetCollisionBox(box) col := GetCollisionBox(box)
c.ScanGridLine(col.Top[0], col.Top[1], grid, Top) // Check all four edges of the box in parallel on different CPU cores.
c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom) type jobSide struct {
c.ScanGridLine(col.Left[0], col.Left[1], grid, Left) p1 render.Point
c.ScanGridLine(col.Right[0], col.Right[1], grid, Right) p2 render.Point
side Side
}
jobs := []jobSide{
jobSide{col.Top[0], col.Top[1], Top},
jobSide{col.Bottom[0], col.Bottom[1], Bottom},
jobSide{col.Left[0], col.Left[1], Left},
jobSide{col.Right[0], col.Right[1], Right},
}
var wg sync.WaitGroup
for _, job := range jobs {
wg.Add(1)
go func(job jobSide) {
defer wg.Done()
c.ScanGridLine(job.p1, job.p2, grid, job.side)
}(job)
}
wg.Wait()
// TODO: the old synchronous version of the above.
// c.ScanGridLine(col.Top[0], col.Top[1], grid, Top)
// c.ScanGridLine(col.Bottom[0], col.Bottom[1], grid, Bottom)
// c.ScanGridLine(col.Left[0], col.Left[1], grid, Left)
// c.ScanGridLine(col.Right[0], col.Right[1], grid, Right)
return c.IsColliding() return c.IsColliding()
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
"git.kirsle.net/apps/doodle/lib/events" "git.kirsle.net/apps/doodle/lib/events"
@ -191,7 +192,12 @@ func (w *Canvas) Loop(ev *events.State) error {
// Move any actors. As we iterate over all actors, track their bounding // Move any actors. As we iterate over all actors, track their bounding
// rectangles so we can later see if any pair of actors intersect each other. // rectangles so we can later see if any pair of actors intersect each other.
boxes := make([]render.Rect, len(w.actors)) boxes := make([]render.Rect, len(w.actors))
var wg sync.WaitGroup
for i, a := range w.actors { for i, a := range w.actors {
wg.Add(1)
go func(i int, a *Actor) {
defer wg.Done()
// Advance any animations for this actor. // Advance any animations for this actor.
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) { if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
if done := a.TickAnimation(a.activeAnimation); done { if done := a.TickAnimation(a.activeAnimation); done {
@ -214,7 +220,7 @@ func (w *Canvas) Loop(ev *events.State) error {
// If not moving, grab the bounding box right now. // If not moving, grab the bounding box right now.
if v == render.Origin { if v == render.Origin {
boxes[i] = doodads.GetBoundingRect(a) boxes[i] = doodads.GetBoundingRect(a)
continue return
} }
// Create a delta point from their current location to where they // Create a delta point from their current location to where they
@ -237,6 +243,8 @@ func (w *Canvas) Loop(ev *events.State) error {
// Store this actor's bounding box after they've moved. // Store this actor's bounding box after they've moved.
boxes[i] = doodads.GetBoundingRect(a) boxes[i] = doodads.GetBoundingRect(a)
}(i, a)
wg.Wait()
} }
// Check collisions between actors. // Check collisions between actors.