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:
parent
d28745f89e
commit
61af068b80
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,52 +192,59 @@ 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 {
|
||||||
// Advance any animations for this actor.
|
wg.Add(1)
|
||||||
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
go func(i int, a *Actor) {
|
||||||
if done := a.TickAnimation(a.activeAnimation); done {
|
defer wg.Done()
|
||||||
// Animation has finished, run the callback script.
|
|
||||||
if a.animationCallback.IsFunction() {
|
// Advance any animations for this actor.
|
||||||
a.animationCallback.Call(otto.NullValue())
|
if a.activeAnimation != nil && a.activeAnimation.nextFrameAt.Before(now) {
|
||||||
|
if done := a.TickAnimation(a.activeAnimation); done {
|
||||||
|
// Animation has finished, run the callback script.
|
||||||
|
if a.animationCallback.IsFunction() {
|
||||||
|
a.animationCallback.Call(otto.NullValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up the animation state.
|
||||||
|
a.StopAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the animation state.
|
|
||||||
a.StopAnimation()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Get the actor's velocity to see if it's moving this tick.
|
// Get the actor's velocity to see if it's moving this tick.
|
||||||
v := a.Velocity()
|
v := a.Velocity()
|
||||||
if a.hasGravity {
|
if a.hasGravity {
|
||||||
v.Y += int32(balance.Gravity)
|
v.Y += int32(balance.Gravity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a delta point from their current location to where they
|
||||||
|
// want to move to this tick.
|
||||||
|
delta := a.Position()
|
||||||
|
delta.Add(v)
|
||||||
|
|
||||||
|
// Check collision with level geometry.
|
||||||
|
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
||||||
|
if ok {
|
||||||
|
// Collision happened with world.
|
||||||
|
}
|
||||||
|
delta = info.MoveTo // Move us back where the collision check put us
|
||||||
|
|
||||||
|
// Move the actor's World Position to the new location.
|
||||||
|
a.MoveTo(delta)
|
||||||
|
|
||||||
|
// Keep the actor from leaving the world borders of bounded maps.
|
||||||
|
w.loopContainActorsInsideLevel(a)
|
||||||
|
|
||||||
|
// Store this actor's bounding box after they've moved.
|
||||||
boxes[i] = doodads.GetBoundingRect(a)
|
boxes[i] = doodads.GetBoundingRect(a)
|
||||||
continue
|
}(i, a)
|
||||||
}
|
wg.Wait()
|
||||||
|
|
||||||
// Create a delta point from their current location to where they
|
|
||||||
// want to move to this tick.
|
|
||||||
delta := a.Position()
|
|
||||||
delta.Add(v)
|
|
||||||
|
|
||||||
// Check collision with level geometry.
|
|
||||||
info, ok := collision.CollidesWithGrid(a, w.chunks, delta)
|
|
||||||
if ok {
|
|
||||||
// Collision happened with world.
|
|
||||||
}
|
|
||||||
delta = info.MoveTo // Move us back where the collision check put us
|
|
||||||
|
|
||||||
// Move the actor's World Position to the new location.
|
|
||||||
a.MoveTo(delta)
|
|
||||||
|
|
||||||
// Keep the actor from leaving the world borders of bounded maps.
|
|
||||||
w.loopContainActorsInsideLevel(a)
|
|
||||||
|
|
||||||
// Store this actor's bounding box after they've moved.
|
|
||||||
boxes[i] = doodads.GetBoundingRect(a)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check collisions between actors.
|
// Check collisions between actors.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user