Add goroutines to map renderer

This commit is contained in:
Bruno Rybársky 2024-08-29 21:12:18 +02:00
parent 696b827a7d
commit 842c41fdca
5 changed files with 160 additions and 62 deletions

79
main.go

@ -31,13 +31,14 @@ const (
ReloadCooldown = 16 ReloadCooldown = 16
Debug = false Debug = false
MapWindow = true MapWindow = false
RenderGameObjects = true RenderGameObjects = true
ProfilerOn = true ProfilerOn = true
ProfilerInterval = 100
JoyStickDeadZone = 8000 JoyStickDeadZone = 8000
DoAllKeymapsPlayers = true DoAllKeymapsPlayers = false
DoJoyStickPlayers = true DoJoyStickPlayers = true
DoKeymapPlayer = true DoKeymapPlayer = true
) )
@ -71,18 +72,41 @@ func main() {
mapWindow, mapSurface = setupMapWindowAndSurface() mapWindow, mapSurface = setupMapWindowAndSurface()
} }
running := true running := true
var totalRenderTime, totalScalingTime, totalFrameTime, frameCount uint64
// Delta time management
var prevTime = sdl.GetTicks64()
const maxDeltaTime uint64 = 1000 / 60 // max 60 FPS, ~16ms per frame
for running { for running {
frameStart := sdl.GetTicks64() currentTime := sdl.GetTicks64()
deltaTime := currentTime - prevTime
prevTime = currentTime
// Catch up in case of a large delta time
for deltaTime > maxDeltaTime {
deltaTime -= maxDeltaTime
// Run multiple logic ticks if deltaTime is too large
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
}
for playerIndex, player := range *players { for playerIndex, player := range *players {
// Delay to achieve roughly 60 FPS
if player.local { if player.local {
running = doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles) running := doPlayerFrame(uint8(playerIndex), player, players, gameMap, bases, bullets, bulletParticles)
if !running { if !running {
break break
} }
} }
} }
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
// Render logic
if MapWindow { if MapWindow {
// Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() {
gameMap.render(mapRendererRect, mapSurface) gameMap.render(mapRendererRect, mapSurface)
for _, bullet := range *bullets { for _, bullet := range *bullets {
(*bullet).render(mapRendererRect, mapSurface) (*bullet).render(mapRendererRect, mapSurface)
@ -96,9 +120,29 @@ func main() {
for _, bulletParticle := range *bulletParticles { for _, bulletParticle := range *bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface) bulletParticle.render(mapRendererRect, mapSurface)
} }
})
totalScalingTime += profileSection(func() {
adjustWindow(mapWindow, mapSurface) adjustWindow(mapWindow, mapSurface)
})
} }
//now tick world
frameEnd := sdl.GetTicks64()
totalFrameTime += frameEnd - currentTime
frameCount++
// Log profiling information every 1000 frames
if frameCount%ProfilerInterval == 0 && ProfilerOn {
logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, frameCount)
resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount)
}
enforceFrameRate(currentTime, 60)
}
}
// Separate function to handle game logic
func runGameLogic(players *[]*Player, gameMap *GameMap, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle) {
// Tick world
for _, bullet := range *bullets { for _, bullet := range *bullets {
(*bullet).tick(gameMap, bulletParticles, bullets, players) (*bullet).tick(gameMap, bulletParticles, bullets, players)
} }
@ -108,9 +152,6 @@ func main() {
for _, bulletParticle := range *bulletParticles { for _, bulletParticle := range *bulletParticles {
bulletParticle.tick(bulletParticles) bulletParticle.tick(bulletParticles)
} }
enforceFrameRate(frameStart, 60)
}
} }
func initPlayer(playerIndex uint8, player *Player) { func initPlayer(playerIndex uint8, player *Player) {
@ -130,22 +171,20 @@ func initPlayer(playerIndex uint8, player *Player) {
func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMap *GameMap, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle) bool { func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMap *GameMap, bases *[]*Base, bullets *[]*Bullet, bulletParticles *[]*BulletParticle) bool {
running := true running := true
var ( var (
totalHandleEventsTime, totalRenderTime, totalFrameTime uint64
totalScalingTime, frameCount uint64
isShooting, shouldContinue bool isShooting, shouldContinue bool
) )
frameStart := sdl.GetTicks64() frameStart := sdl.GetTicks64()
// Profile handleEvents // Profile handleEvents
totalHandleEventsTime += profileSection(func() { player.totalHandleEventsTime += profileSection(func() {
running = handleEvents(player.window, player.logicalSurface) running = handleEvents(player.window, player.logicalSurface)
keyboard := sdl.GetKeyboardState() keyboard := sdl.GetKeyboardState()
shouldContinue, isShooting = handleInput(keyboard, bullets, player, gameMap, player.playSurface.Format, players) shouldContinue, isShooting = handleInput(keyboard, bullets, player, gameMap, players)
running = running && shouldContinue running = running && shouldContinue
}) })
// Profile rendering (aggregate all renderings) // Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() { player.totalRenderTime += profileSection(func() {
player.track(player.camera) player.track(player.camera)
gameMap.render(player.camera, player.playSurface) gameMap.render(player.camera, player.playSurface)
@ -169,18 +208,18 @@ func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMa
}) })
// Profile window adjustments // Profile window adjustments
totalScalingTime += profileSection(func() { player.totalScalingTime += profileSection(func() {
adjustWindow(player.window, player.logicalSurface) adjustWindow(player.window, player.logicalSurface)
}) })
frameEnd := sdl.GetTicks64() frameEnd := sdl.GetTicks64()
totalFrameTime += frameEnd - frameStart player.totalFrameTime += frameEnd - frameStart
frameCount++ player.frameCount++
// Log profiling information every 1000 frames // Log profiling information every 1000 frames
if frameCount%1000 == 0 && ProfilerOn && playerIndex == 0 { if player.frameCount%ProfilerInterval == 0 && ProfilerOn && playerIndex == 0 {
logProfilingInfo(totalHandleEventsTime, totalRenderTime, totalScalingTime, totalFrameTime, frameCount) logProfilingInfo(player.totalHandleEventsTime, player.totalRenderTime, player.totalScalingTime, player.totalFrameTime, player.frameCount)
resetProfilingCounters(&totalHandleEventsTime, &totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount) resetProfilingCounters(&player.totalHandleEventsTime, &player.totalRenderTime, &player.totalScalingTime, &player.totalFrameTime, &player.frameCount)
} }
return running return running
} }

48
map.go

@ -4,6 +4,7 @@ import (
"github.com/aquilax/go-perlin" "github.com/aquilax/go-perlin"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"image/color" "image/color"
"sync"
"time" "time"
) )
@ -48,11 +49,29 @@ func (gameMap *GameMap) getTileColor(x int32, y int32) color.Color {
} }
func (gameMap *GameMap) createGameMap(width int32, height int32) { func (gameMap *GameMap) createGameMap(width int32, height int32) {
// Initialize the 2D slice for game tiles
gameMap.tiles = make([][]uint8, width) gameMap.tiles = make([][]uint8, width)
// Create Perlin noise generators
perlinNoiseRock := perlin.NewPerlin(2, 4, 8, time.Now().Unix()) perlinNoiseRock := perlin.NewPerlin(2, 4, 8, time.Now().Unix())
perlinNoiseSoil := perlin.NewPerlin(2, 8, 4, time.Now().Unix()) perlinNoiseSoil := perlin.NewPerlin(2, 8, 4, time.Now().Unix())
// WaitGroup to synchronize goroutines
var wg sync.WaitGroup
// Loop over the rows
for i := range gameMap.tiles { for i := range gameMap.tiles {
// Increment WaitGroup counter for each goroutine
wg.Add(1)
// Launch a goroutine for each row
go func(i int) {
defer wg.Done() // Signal that this goroutine is done
// Initialize the row
gameMap.tiles[i] = make([]uint8, height) gameMap.tiles[i] = make([]uint8, height)
// Process each tile in the row
for j := range gameMap.tiles[i] { for j := range gameMap.tiles[i] {
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*6, float64(j)/float64(height)*6) perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*6, float64(j)/float64(height)*6)
if perlinRock > 0.2 { if perlinRock > 0.2 {
@ -65,9 +84,14 @@ func (gameMap *GameMap) createGameMap(width int32, height int32) {
gameMap.tiles[i][j] = 3 gameMap.tiles[i][j] = 3
} }
} }
}
}(i)
}
} // Wait for all goroutines to finish
} wg.Wait()
// Set the dimensions of the game map
gameMap.width = width gameMap.width = width
gameMap.height = height gameMap.height = height
} }
@ -77,17 +101,31 @@ func (gameMap *GameMap) render(camera *sdl.Rect, surface *sdl.Surface) {
fromY := camera.Y fromY := camera.Y
toX := camera.X + camera.W toX := camera.X + camera.W
toY := camera.Y + camera.H toY := camera.Y + camera.H
// Create a WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
// Process columns instead of individual pixels
for x := fromX; x < toX; x++ { for x := fromX; x < toX; x++ {
// Increment the WaitGroup counter
wg.Add(1)
// Launch a goroutine for each column
go func(x int32) {
defer wg.Done() // Decrement the counter when the goroutine completes
for y := fromY; y < toY; y++ { for y := fromY; y < toY; y++ {
cameraY := y - camera.Y cameraY := y - camera.Y
cameraX := x - camera.X cameraX := x - camera.X
pixelColor := gameMap.getTileColor(x, y) pixelColor := gameMap.getTileColor(x, y)
surface.Set(int(cameraX), int(cameraY), pixelColor) surface.Set(int(cameraX), int(cameraY), pixelColor)
}
}(x)
}
} // Wait for all goroutines to finish
} wg.Wait()
} }
func (gameMap *GameMap) checkCollision(posX, posY int32) uint8 { func (gameMap *GameMap) checkCollision(posX, posY int32) uint8 {
//0 no collision //0 no collision
//1 destructible collision //1 destructible collision

@ -35,6 +35,12 @@ type Player struct {
playSurfaceTargetRect *sdl.Rect playSurfaceTargetRect *sdl.Rect
HUDSurfaceTargetRect *sdl.Rect HUDSurfaceTargetRect *sdl.Rect
totalHandleEventsTime uint64
totalRenderTime uint64
totalFrameTime uint64
totalScalingTime uint64
frameCount uint64
} }
func (player *Player) track(camera *sdl.Rect) { func (player *Player) track(camera *sdl.Rect) {

@ -285,7 +285,7 @@ func initPlayerColors() {
} }
} }
func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *GameMap, format *sdl.PixelFormat, players *[]*Player) (bool, bool) { func handleInput(keyboard []uint8, bullets *[]*Bullet, player *Player, gameMap *GameMap, players *[]*Player) (bool, bool) {
if !player.local || player.shields <= 0 || player.shields > MaxShields { if !player.local || player.shields <= 0 || player.shields > MaxShields {
return true, false return true, false
} }

@ -21,6 +21,14 @@ func logProfilingInfo(handleEventsTime, renderTime, scaleTime, frameTime, frameC
fmt.Print("\n") fmt.Print("\n")
} }
func logMapProfilingInfo(renderTime, scaleTime, frameTime, frameCount uint64) {
floatFrame := float64(frameCount)
fmt.Printf("Average map render time: %f ms\n", float64(renderTime)/floatFrame)
fmt.Printf("Average map scaling time: %f ms\n", float64(scaleTime)/floatFrame)
fmt.Printf("Average full render time: %f ms\n", float64(frameTime)/floatFrame)
fmt.Print("\n")
}
func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime *uint64, frameCount *uint64) { func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime *uint64, frameCount *uint64) {
*handleEventsTime = 0 *handleEventsTime = 0
*renderTime = 0 *renderTime = 0
@ -29,6 +37,13 @@ func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime *
*frameCount = 0 *frameCount = 0
} }
func resetMapProfilingCounters(renderTime, scaleTime, frameTime *uint64, frameCount *uint64) {
*renderTime = 0
*scaleTime = 0
*frameTime = 0
*frameCount = 0
}
func enforceFrameRate(frameStart uint64, targetFPS int) { func enforceFrameRate(frameStart uint64, targetFPS int) {
frameDuration := 1000 / targetFPS frameDuration := 1000 / targetFPS
elapsed := sdl.GetTicks64() - frameStart elapsed := sdl.GetTicks64() - frameStart