Add goroutines to map renderer
This commit is contained in:
parent
696b827a7d
commit
842c41fdca
123
main.go
123
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,45 +72,85 @@ 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 {
|
||||||
gameMap.render(mapRendererRect, mapSurface)
|
// Profile rendering (aggregate all renderings)
|
||||||
for _, bullet := range *bullets {
|
totalRenderTime += profileSection(func() {
|
||||||
(*bullet).render(mapRendererRect, mapSurface)
|
gameMap.render(mapRendererRect, mapSurface)
|
||||||
}
|
for _, bullet := range *bullets {
|
||||||
for _, base := range *bases {
|
(*bullet).render(mapRendererRect, mapSurface)
|
||||||
(*base).render(mapRendererRect, mapSurface)
|
}
|
||||||
}
|
for _, base := range *bases {
|
||||||
for _, playerLoop := range *players {
|
(*base).render(mapRendererRect, mapSurface)
|
||||||
(*playerLoop).render(mapRendererRect, mapSurface)
|
}
|
||||||
}
|
for _, playerLoop := range *players {
|
||||||
for _, bulletParticle := range *bulletParticles {
|
(*playerLoop).render(mapRendererRect, mapSurface)
|
||||||
bulletParticle.render(mapRendererRect, mapSurface)
|
}
|
||||||
}
|
for _, bulletParticle := range *bulletParticles {
|
||||||
adjustWindow(mapWindow, mapSurface)
|
bulletParticle.render(mapRendererRect, mapSurface)
|
||||||
}
|
}
|
||||||
//now tick world
|
})
|
||||||
for _, bullet := range *bullets {
|
totalScalingTime += profileSection(func() {
|
||||||
(*bullet).tick(gameMap, bulletParticles, bullets, players)
|
adjustWindow(mapWindow, mapSurface)
|
||||||
}
|
})
|
||||||
for _, base := range *bases {
|
|
||||||
(*base).tick(players)
|
|
||||||
}
|
|
||||||
for _, bulletParticle := range *bulletParticles {
|
|
||||||
bulletParticle.tick(bulletParticles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enforceFrameRate(frameStart, 60)
|
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)
|
||||||
|
}
|
||||||
|
for _, base := range *bases {
|
||||||
|
(*base).tick(players)
|
||||||
|
}
|
||||||
|
for _, bulletParticle := range *bulletParticles {
|
||||||
|
bulletParticle.tick(bulletParticles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
isShooting, shouldContinue bool
|
||||||
totalScalingTime, frameCount uint64
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
76
map.go
76
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,26 +49,49 @@ 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 {
|
||||||
gameMap.tiles[i] = make([]uint8, height)
|
// Increment WaitGroup counter for each goroutine
|
||||||
for j := range gameMap.tiles[i] {
|
wg.Add(1)
|
||||||
perlinRock := perlinNoiseRock.Noise2D(float64(i)/float64(width)*6, float64(j)/float64(height)*6)
|
|
||||||
if perlinRock > 0.2 {
|
// Launch a goroutine for each row
|
||||||
gameMap.tiles[i][j] = 1
|
go func(i int) {
|
||||||
} else {
|
defer wg.Done() // Signal that this goroutine is done
|
||||||
perlinSoil := perlinNoiseSoil.Noise2D(float64(i)/float64(width)*10, float64(j)/float64(height)*10)
|
|
||||||
if perlinSoil >= 0 {
|
// Initialize the row
|
||||||
gameMap.tiles[i][j] = 2
|
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 {
|
||||||
|
gameMap.tiles[i][j] = 1
|
||||||
} else {
|
} else {
|
||||||
gameMap.tiles[i][j] = 3
|
perlinSoil := perlinNoiseSoil.Noise2D(float64(i)/float64(width)*10, float64(j)/float64(height)*10)
|
||||||
|
if perlinSoil >= 0 {
|
||||||
|
gameMap.tiles[i][j] = 2
|
||||||
|
} else {
|
||||||
|
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++ {
|
||||||
for y := fromY; y < toY; y++ {
|
// Increment the WaitGroup counter
|
||||||
cameraY := y - camera.Y
|
wg.Add(1)
|
||||||
cameraX := x - camera.X
|
|
||||||
pixelColor := gameMap.getTileColor(x, y)
|
|
||||||
surface.Set(int(cameraX), int(cameraY), pixelColor)
|
|
||||||
|
|
||||||
}
|
// 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 {
|
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
|
||||||
}
|
}
|
||||||
|
15
profiler.go
15
profiler.go
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user