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
Debug = false
MapWindow = true
MapWindow = false
RenderGameObjects = true
ProfilerOn = true
ProfilerInterval = 100
JoyStickDeadZone = 8000
DoAllKeymapsPlayers = true
DoAllKeymapsPlayers = false
DoJoyStickPlayers = true
DoKeymapPlayer = true
)
@ -71,18 +72,41 @@ func main() {
mapWindow, mapSurface = setupMapWindowAndSurface()
}
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 {
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 {
// Delay to achieve roughly 60 FPS
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 {
break
}
}
}
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
// Render logic
if MapWindow {
// Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() {
gameMap.render(mapRendererRect, mapSurface)
for _, bullet := range *bullets {
(*bullet).render(mapRendererRect, mapSurface)
@ -96,9 +120,29 @@ func main() {
for _, bulletParticle := range *bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface)
}
})
totalScalingTime += profileSection(func() {
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 {
(*bullet).tick(gameMap, bulletParticles, bullets, players)
}
@ -108,9 +152,6 @@ func main() {
for _, bulletParticle := range *bulletParticles {
bulletParticle.tick(bulletParticles)
}
enforceFrameRate(frameStart, 60)
}
}
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 {
running := true
var (
totalHandleEventsTime, totalRenderTime, totalFrameTime uint64
totalScalingTime, frameCount uint64
isShooting, shouldContinue bool
)
frameStart := sdl.GetTicks64()
// Profile handleEvents
totalHandleEventsTime += profileSection(func() {
player.totalHandleEventsTime += profileSection(func() {
running = handleEvents(player.window, player.logicalSurface)
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
})
// Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() {
player.totalRenderTime += profileSection(func() {
player.track(player.camera)
gameMap.render(player.camera, player.playSurface)
@ -169,18 +208,18 @@ func doPlayerFrame(playerIndex uint8, player *Player, players *[]*Player, gameMa
})
// Profile window adjustments
totalScalingTime += profileSection(func() {
player.totalScalingTime += profileSection(func() {
adjustWindow(player.window, player.logicalSurface)
})
frameEnd := sdl.GetTicks64()
totalFrameTime += frameEnd - frameStart
frameCount++
player.totalFrameTime += frameEnd - frameStart
player.frameCount++
// Log profiling information every 1000 frames
if frameCount%1000 == 0 && ProfilerOn && playerIndex == 0 {
logProfilingInfo(totalHandleEventsTime, totalRenderTime, totalScalingTime, totalFrameTime, frameCount)
resetProfilingCounters(&totalHandleEventsTime, &totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount)
if player.frameCount%ProfilerInterval == 0 && ProfilerOn && playerIndex == 0 {
logProfilingInfo(player.totalHandleEventsTime, player.totalRenderTime, player.totalScalingTime, player.totalFrameTime, player.frameCount)
resetProfilingCounters(&player.totalHandleEventsTime, &player.totalRenderTime, &player.totalScalingTime, &player.totalFrameTime, &player.frameCount)
}
return running
}

48
map.go

@ -4,6 +4,7 @@ import (
"github.com/aquilax/go-perlin"
"github.com/veandco/go-sdl2/sdl"
"image/color"
"sync"
"time"
)
@ -48,11 +49,29 @@ func (gameMap *GameMap) getTileColor(x int32, y int32) color.Color {
}
func (gameMap *GameMap) createGameMap(width int32, height int32) {
// Initialize the 2D slice for game tiles
gameMap.tiles = make([][]uint8, width)
// Create Perlin noise generators
perlinNoiseRock := perlin.NewPerlin(2, 4, 8, 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 {
// 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)
// Process each tile in the row
for j := range gameMap.tiles[i] {
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*6, float64(j)/float64(height)*6)
if perlinRock > 0.2 {
@ -65,9 +84,14 @@ func (gameMap *GameMap) createGameMap(width int32, height int32) {
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.height = height
}
@ -77,17 +101,31 @@ func (gameMap *GameMap) render(camera *sdl.Rect, surface *sdl.Surface) {
fromY := camera.Y
toX := camera.X + camera.W
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++ {
// 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++ {
cameraY := y - camera.Y
cameraX := x - camera.X
pixelColor := gameMap.getTileColor(x, y)
surface.Set(int(cameraX), int(cameraY), pixelColor)
}
}(x)
}
}
}
// Wait for all goroutines to finish
wg.Wait()
}
func (gameMap *GameMap) checkCollision(posX, posY int32) uint8 {
//0 no collision
//1 destructible collision

@ -35,6 +35,12 @@ type Player struct {
playSurfaceTargetRect *sdl.Rect
HUDSurfaceTargetRect *sdl.Rect
totalHandleEventsTime uint64
totalRenderTime uint64
totalFrameTime uint64
totalScalingTime uint64
frameCount uint64
}
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 {
return true, false
}

@ -21,6 +21,14 @@ func logProfilingInfo(handleEventsTime, renderTime, scaleTime, frameTime, frameC
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) {
*handleEventsTime = 0
*renderTime = 0
@ -29,6 +37,13 @@ func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime *
*frameCount = 0
}
func resetMapProfilingCounters(renderTime, scaleTime, frameTime *uint64, frameCount *uint64) {
*renderTime = 0
*scaleTime = 0
*frameTime = 0
*frameCount = 0
}
func enforceFrameRate(frameStart uint64, targetFPS int) {
frameDuration := 1000 / targetFPS
elapsed := sdl.GetTicks64() - frameStart