Fix a few network issues

This commit is contained in:
Bruno Rybársky 2024-08-31 15:35:19 +02:00
parent d310509035
commit 0ba96ca478
7 changed files with 694 additions and 576 deletions

62
base.go

@ -3,8 +3,12 @@ package main
import ( import (
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"image/color" "image/color"
"strconv"
"sync"
) )
var baseMutex sync.RWMutex
type Base struct { type Base struct {
ownerID uint32 ownerID uint32
openingWidth int32 openingWidth int32
@ -12,8 +16,9 @@ type Base struct {
} }
func (base *Base) tick(players map[uint32]*Player) { func (base *Base) tick(players map[uint32]*Player) {
playersMutex.RLock()
for playerID, player := range players { for playerID, player := range players {
if player.gameObject.baseRect.HasIntersection(&base.gameObject.baseRect) { if player.gameObject.baseRect.HasIntersection(base.gameObject.baseRect) {
if player.rechargeCooldown == 0 && player.energy < serverConfig.MaxEnergy { if player.rechargeCooldown == 0 && player.energy < serverConfig.MaxEnergy {
if serverConfig.MaxEnergy-player.energy < 4 { if serverConfig.MaxEnergy-player.energy < 4 {
player.energy++ player.energy++
@ -32,6 +37,7 @@ func (base *Base) tick(players map[uint32]*Player) {
} }
} }
} }
playersMutex.RUnlock()
} }
func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint32]*Base) { func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint32]*Base) {
@ -42,14 +48,6 @@ func (base *Base) render(camera *sdl.Rect, surface *sdl.Surface, bases map[uint3
} }
func (base *Base) build(gameMap *GameMap) { func (base *Base) build(gameMap *GameMap) {
borderWidth := base.gameObject.baseRect.W - base.openingWidth
borderWidthA := borderWidth / 2
if borderWidth < 0 {
panic("Bad border width")
}
if base.gameObject.baseRect.H < 9 {
panic("Bad border height")
}
if gameMap.width-uint32(base.gameObject.baseRect.X)-uint32(base.gameObject.baseRect.W) <= 0 { if gameMap.width-uint32(base.gameObject.baseRect.X)-uint32(base.gameObject.baseRect.W) <= 0 {
panic("Bad base x location") panic("Bad base x location")
} }
@ -57,30 +55,32 @@ func (base *Base) build(gameMap *GameMap) {
panic("Bad base y location") panic("Bad base y location")
} }
if base.gameObject.baseRect.X < 0 || base.gameObject.baseRect.Y < 0 { if base.gameObject.baseRect.X < 0 || base.gameObject.baseRect.Y < 0 {
panic("Bad base negative location") panic("Bad base negative location " + strconv.Itoa(int(base.gameObject.baseRect.X)) + " - " + strconv.Itoa(int(base.gameObject.baseRect.Y)))
} }
for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W+1; x++ { base.gameObject.adjustBaseRect()
for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H+1; y++ { for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W; x++ {
for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H; y++ {
gameMap.tiles[x][y] = 0 gameMap.tiles[x][y] = 0
} }
} }
for y := base.gameObject.baseRect.Y; y < base.gameObject.baseRect.Y+base.gameObject.baseRect.H; y++ { for _, rectT := range base.gameObject.getCurrentRects() {
gameMap.tiles[base.gameObject.baseRect.X][y] = 4 rect := sdl.Rect{
gameMap.tiles[base.gameObject.baseRect.X+base.gameObject.baseRect.W][y] = 4 X: rectT.rect.X + base.gameObject.baseRect.X,
} Y: rectT.rect.Y + base.gameObject.baseRect.Y,
for x := base.gameObject.baseRect.X; x < base.gameObject.baseRect.X+borderWidthA; x++ { W: rectT.rect.W,
gameMap.tiles[x][base.gameObject.baseRect.Y] = 4 H: rectT.rect.H,
gameMap.tiles[x][base.gameObject.baseRect.Y+base.gameObject.baseRect.H] = 4 }
} for x := rect.X; x < rect.X+rect.W; x++ {
for x := base.gameObject.baseRect.X + borderWidthA + base.openingWidth; x < base.gameObject.baseRect.X+base.gameObject.baseRect.W+1; x++ { for y := rect.Y; y < rect.Y+rect.H; y++ {
gameMap.tiles[x][base.gameObject.baseRect.Y] = 4 gameMap.tiles[x][y] = 4
gameMap.tiles[x][base.gameObject.baseRect.Y+base.gameObject.baseRect.H] = 4 }
}
} }
} }
func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, openingWidth uint32) *Base { func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, openingWidth uint32) *Base {
gameObject := &GameObject{} gameObject := &GameObject{}
gameObject.baseRect = sdl.Rect{ gameObject.baseRect = &sdl.Rect{
X: int32(posX), X: int32(posX),
Y: int32(posY), Y: int32(posY),
W: 35, W: 35,
@ -97,12 +97,12 @@ func createBase(gameMap *GameMap, baseColor color.Color, posX, posY, ownerID, op
panic("Bad border width") panic("Bad border width")
} }
gameObject.addColor(baseColor) gameObject.addColor(baseColor)
gameObject.addColoredRect(0, 0, 1, gameObject.baseRect.H, 0) gameObject.addColoredRect(0, 0, 1, gameObject.baseRect.H-1, 0)
gameObject.addColoredRect(gameObject.baseRect.W, 0, 1, gameObject.baseRect.H, 0) gameObject.addColoredRect(gameObject.baseRect.W, 0, 1, gameObject.baseRect.H-1, 0)
gameObject.addColoredRect(0, 0, borderWidthA, 1, 0) gameObject.addColoredRect(0, 0, borderWidthA, 1, 0)
gameObject.addColoredRect(borderWidthA+int32(openingWidth), 0, borderWidthB, 1, 0) gameObject.addColoredRect(borderWidthA+int32(openingWidth), 0, borderWidthB, 1, 0)
gameObject.addColoredRect(0, gameObject.baseRect.H, borderWidthA, 1, 0) gameObject.addColoredRect(0, gameObject.baseRect.H-1, borderWidthA, 1, 0)
gameObject.addColoredRect(borderWidthA+int32(openingWidth), gameObject.baseRect.H, borderWidthB, 1, 0) gameObject.addColoredRect(borderWidthA+int32(openingWidth), gameObject.baseRect.H-1, borderWidthB, 1, 0)
base := &Base{ base := &Base{
gameObject: gameObject, gameObject: gameObject,
ownerID: ownerID, ownerID: ownerID,
@ -118,12 +118,16 @@ func (base *Base) delete(bases map[uint32]*Base) {
gameMap.tiles[x][y] = 0 gameMap.tiles[x][y] = 0
} }
} }
baseMutex.Lock()
delete(bases, base.ownerID) delete(bases, base.ownerID)
baseMutex.Unlock()
} }
func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base { func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base {
bases := map[uint32]*Base{} bases := map[uint32]*Base{}
playersMutex.RLock()
for ownerID, player := range players { for ownerID, player := range players {
baseMutex.Lock()
bases[ownerID] = createBase(gameMap, bases[ownerID] = createBase(gameMap,
player.playerColors.body, player.playerColors.body,
uint32(player.gameObject.baseRect.X-14), uint32(player.gameObject.baseRect.X-14),
@ -131,6 +135,8 @@ func createBases(players map[uint32]*Player, gameMap *GameMap) map[uint32]*Base
ownerID, ownerID,
uint32(float64(player.gameObject.baseRect.W)*1.5), uint32(float64(player.gameObject.baseRect.W)*1.5),
) )
baseMutex.Unlock()
} }
playersMutex.RUnlock()
return bases return bases
} }

@ -4,11 +4,15 @@ import (
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"image/color" "image/color"
"math/rand" "math/rand"
"sync"
) )
var bulletLastID = uint32(0) var bulletLastID = uint32(0)
var bulletParticleLastID = uint32(0) var bulletParticleLastID = uint32(0)
var bulletMutex sync.RWMutex
var bulletParticleMutex sync.RWMutex
type Bullet struct { type Bullet struct {
posX, posY int32 posX, posY int32
direction uint8 direction uint8
@ -159,6 +163,7 @@ func (bullet *Bullet) tick(gameMap *GameMap,
H: 1, H: 1,
} }
hitPlayer := false hitPlayer := false
playersMutex.RLock()
for _, player := range players { for _, player := range players {
if player.playerID == bullet.ownerID { if player.playerID == bullet.ownerID {
continue continue
@ -174,6 +179,7 @@ func (bullet *Bullet) tick(gameMap *GameMap,
} }
} }
} }
playersMutex.RUnlock()
if collisionResult != 0 || hitPlayer { if collisionResult != 0 || hitPlayer {
bullet.explode(gameMap, bulletParticleMap) bullet.explode(gameMap, bulletParticleMap)
delete(bulletMap, bullet.id) delete(bulletMap, bullet.id)

@ -6,13 +6,14 @@ import (
) )
type GameObject struct { type GameObject struct {
baseRect sdl.Rect baseRect *sdl.Rect
prevBaseRect sdl.Rect prevBaseRect *sdl.Rect
borderRect sdl.Rect borderRect *sdl.Rect
orientation uint8 collisionRect *sdl.Rect
visualRects [][]*ColoredRect orientation uint8
colors []color.Color visualRects [][]*ColoredRect
inView bool colors []color.Color
inView bool
} }
type ColoredRect struct { type ColoredRect struct {
@ -38,6 +39,13 @@ func (gameObject *GameObject) adjustRectWorld(offset *sdl.Rect) *sdl.Rect {
} }
} }
func (gameObject *GameObject) adjustColoredRectWorld(offset *ColoredRect) *ColoredRect {
return &ColoredRect{
color: offset.color,
rect: gameObject.adjustRectWorld(offset.rect),
}
}
func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.Rect) *sdl.Rect { func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.Rect) *sdl.Rect {
return &sdl.Rect{ return &sdl.Rect{
X: gameObject.baseRect.X + offset.X - camera.X, X: gameObject.baseRect.X + offset.X - camera.X,
@ -47,20 +55,31 @@ func (gameObject *GameObject) adjustRectToCamera(offset *sdl.Rect, camera *sdl.R
} }
} }
func (gameObject *GameObject) adjustBaseRect() {
first := true
oldX, oldY := gameObject.baseRect.X, gameObject.baseRect.Y
for _, rect := range gameObject.getCurrentRects() {
if first {
gameObject.baseRect = gameObject.adjustRectWorld(rect.rect)
first = false
} else {
}
newRect := gameObject.baseRect.Union(gameObject.adjustRectWorld(rect.rect))
gameObject.baseRect = &newRect
gameObject.baseRect.X, gameObject.baseRect.Y = oldX, oldY
}
}
func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) { func (gameObject *GameObject) render(camera *sdl.Rect, surface *sdl.Surface) {
if camera.HasIntersection(&gameObject.baseRect) { if camera.HasIntersection(gameObject.baseRect) {
gameObject.inView = true gameObject.inView = true
if config.Debug { if config.Debug {
gameObject.borderRect = sdl.Rect{ baseRectFinal := adjustRectToCamera(gameObject.baseRect, camera)
X: gameObject.baseRect.X - 1,
Y: gameObject.baseRect.Y - 1,
W: gameObject.baseRect.W + 2,
H: gameObject.baseRect.H + 2,
}
borderRectFinal := adjustRectToCamera(&gameObject.borderRect, camera)
baseRectFinal := adjustRectToCamera(&gameObject.baseRect, camera)
surface.FillRect(borderRectFinal, sdl.MapRGBA(surface.Format, 20, 192, 128, 64))
surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64)) surface.FillRect(baseRectFinal, sdl.MapRGBA(surface.Format, 255, 20, 10, 64))
if !gameObject.collisionRect.Empty() {
surface.FillRect(adjustRectToCamera(gameObject.collisionRect, camera), sdl.MapRGBA(surface.Format, 40, 192, 255, 64))
}
} }
if config.RenderGameObjects { if config.RenderGameObjects {
for _, coloredRect := range gameObject.visualRects[gameObject.orientation] { for _, coloredRect := range gameObject.visualRects[gameObject.orientation] {

258
main.go

@ -8,10 +8,13 @@ import (
"log" "log"
"net" "net"
"os" "os"
"runtime/pprof"
"sync" "sync"
"time" "time"
) )
const GameVersion = "TunnEElineningnegbfbf Through the wiAldWest"
type ServerConfig struct { type ServerConfig struct {
MapWidth uint32 `json:"map_width"` MapWidth uint32 `json:"map_width"`
MapHeight uint32 `json:"map_height"` MapHeight uint32 `json:"map_height"`
@ -37,44 +40,47 @@ type ServerConfig struct {
MovementCooldownNoEnergy uint32 `json:"movement_cooldown_no_energy"` MovementCooldownNoEnergy uint32 `json:"movement_cooldown_no_energy"`
DiggingCooldownNoEnergy uint32 `json:"digging_cooldown_no_energy"` DiggingCooldownNoEnergy uint32 `json:"digging_cooldown_no_energy"`
ReloadCooldown uint32 `json:"reload_cooldown"` ReloadCooldown uint32 `json:"reload_cooldown"`
ReloadWait uint32 `json:"reload_wait"`
} }
type Config struct { type Config struct {
Debug bool `json:"debug"` Debug bool `json:"debug"`
MapWindow bool `json:"map_window"` MapWindow bool `json:"map_window"`
RenderGameObjects bool `json:"render_game_objects"` MapUpdateInterval uint16 `json:"map_update_interval"`
ProfilerOn bool `json:"profiler_on"` RenderGameObjects bool `json:"render_game_objects"`
ProfilerInterval uint64 `json:"profiler_interval"` ProfilerOn bool `json:"profiler_on"`
Server bool `json:"server"` ProfilerInterval uint64 `json:"profiler_interval"`
CameraW int32 `json:"camera_w"` Server bool `json:"server"`
CameraH int32 `json:"camera_h"` CameraW int32 `json:"camera_w"`
Version string `json:"version"` CameraH int32 `json:"camera_h"`
Client bool `json:"client"` Client bool `json:"client"`
Address string `json:"address"` Address string `json:"address"`
JoyStickDeadZone int16 `json:"joystick_dead_zone"` JoyStickDeadZone int16 `json:"joystick_dead_zone"`
DoAllKeymapsPlayers bool `json:"do_all_keymaps_players"` DoAllKeymapsPlayers bool `json:"do_all_keymaps_players"`
DoJoyStickPlayers bool `json:"do_joystick_players"` DoJoyStickPlayers bool `json:"do_joystick_players"`
DoKeymapPlayer bool `json:"do_keymap_player"` DoKeymapPlayer bool `json:"do_keymap_player"`
ServerConfig ServerConfig `json:"server_config"` RecentlyDugBlocksClearInterval uint16 `json:"recently_dug_blocks_clear_interval"`
ServerConfig ServerConfig `json:"server_config"`
} }
func loadOrCreateConfig(filename string) (*Config, error) { func loadOrCreateConfig(filename string) (*Config, error) {
config := &Config{ config := &Config{
Debug: false, Debug: false,
MapWindow: false, MapWindow: false,
RenderGameObjects: true, MapUpdateInterval: 999,
ProfilerOn: false, RenderGameObjects: true,
ProfilerInterval: 100, ProfilerOn: false,
Server: false, ProfilerInterval: 100,
CameraW: 76, Server: false,
CameraH: 76, CameraW: 76,
Version: "69420", CameraH: 76,
Client: true, Client: true,
Address: "192.168.1.8:5074", Address: "192.168.1.8:5074",
JoyStickDeadZone: 8000, JoyStickDeadZone: 8000,
DoAllKeymapsPlayers: false, DoAllKeymapsPlayers: false,
DoJoyStickPlayers: true, DoJoyStickPlayers: true,
DoKeymapPlayer: true, DoKeymapPlayer: true,
RecentlyDugBlocksClearInterval: 20,
ServerConfig: ServerConfig{ ServerConfig: ServerConfig{
MapWidth: 1000, MapWidth: 1000,
MapHeight: 1000, MapHeight: 1000,
@ -100,6 +106,7 @@ func loadOrCreateConfig(filename string) (*Config, error) {
MovementCooldownNoEnergy: 4, MovementCooldownNoEnergy: 4,
DiggingCooldownNoEnergy: 8, DiggingCooldownNoEnergy: 8,
ReloadCooldown: 16, ReloadCooldown: 16,
ReloadWait: 16,
}, },
} }
@ -149,6 +156,8 @@ var clientInitialized = false
var worldLock sync.Mutex var worldLock sync.Mutex
var totalRenderTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalScalingTime, totalRemotePlayerUpdate, tickCount, totalFrameTime, frameCount uint64
func main() { func main() {
initializeSDL() initializeSDL()
defer sdl.Quit() defer sdl.Quit()
@ -158,6 +167,17 @@ func main() {
} }
serverConfig = configX.ServerConfig serverConfig = configX.ServerConfig
config = configX config = configX
if config.ProfilerOn {
f, err := os.Create("cpuprofile")
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close() // error handling omitted for example
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)} mapRendererRect = &sdl.Rect{X: 0, Y: 0, W: int32(serverConfig.MapWidth), H: int32(serverConfig.MapHeight)}
players := make(map[uint32]*Player) players := make(map[uint32]*Player)
initPlayerColors() initPlayerColors()
@ -171,7 +191,7 @@ func main() {
if !config.Client { if !config.Client {
gameMap.createGameMap(true) gameMap.createGameMap(true)
createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players) createPlayers(getNeededPlayers(), playerColors, keyMaps, joyMaps, gameMap, players, bases)
bases = createBases(players, gameMap) bases = createBases(players, gameMap)
} else { } else {
// Create a connection to the server // Create a connection to the server
@ -195,9 +215,11 @@ func main() {
defer closeThings(players) defer closeThings(players)
playersMutex.Lock()
for playerIndex, player := range players { for playerIndex, player := range players {
initPlayer(uint8(playerIndex), player) initPlayer(uint8(playerIndex), player)
} }
playersMutex.Unlock()
if config.Server { if config.Server {
// Create a connection to the server // Create a connection to the server
@ -216,7 +238,12 @@ func main() {
for { for {
conn, err := listen.AcceptTCP() conn, err := listen.AcceptTCP()
if err != nil { if err != nil {
log.Fatal(err) if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {
log.Println("Listener closed, stopping server.")
return
}
log.Println("Error accepting connection:", err)
continue
} }
go handleRequest(conn, players, bullets, bases) go handleRequest(conn, players, bullets, bases)
} }
@ -227,7 +254,6 @@ func main() {
mapWindow, mapSurface = setupMapWindowAndSurface() mapWindow, mapSurface = setupMapWindowAndSurface()
} }
running := true running := true
var totalRenderTime, totalScalingTime, totalFrameTime, frameCount uint64
// Delta time management // Delta time management
var prevTime = sdl.GetTicks64() var prevTime = sdl.GetTicks64()
@ -239,17 +265,21 @@ func main() {
prevTime = currentTime prevTime = currentTime
worldLock.Lock() worldLock.Lock()
// Catch up in case of a large delta time totalGameLogicCatchUp += profileSection(func() {
for deltaTime > maxDeltaTime { // Catch up in case of a large delta time
deltaTime -= maxDeltaTime for deltaTime > maxDeltaTime {
deltaTime -= maxDeltaTime
// Run multiple logic ticks if deltaTime is too large // Run multiple logic ticks if deltaTime is too large
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
}
})
totalNormalGameLogic += profileSection(func() {
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles) runGameLogic(players, gameMap, bases, bullets, bulletParticles)
} })
// Run logic for the remaining delta time
runGameLogic(players, gameMap, bases, bullets, bulletParticles)
playersMutex.RLock()
for playerIndex, player := range players { for playerIndex, player := range players {
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)
@ -258,25 +288,34 @@ func main() {
} }
} }
} }
playersMutex.RUnlock()
// Render logic // Render logic
if config.MapWindow { if config.MapWindow && frameCount%uint64(config.MapUpdateInterval) == 0 {
// Profile rendering (aggregate all renderings) // Profile rendering (aggregate all renderings)
totalRenderTime += profileSection(func() { totalRenderTime += profileSection(func() {
running = running && handleEvents(mapWindow, mapSurface) running = running && handleEvents(mapWindow, mapSurface)
gameMap.render(mapRendererRect, mapSurface) gameMap.render(mapRendererRect, mapSurface)
bulletMutex.RLock()
for _, bullet := range bullets { for _, bullet := range bullets {
(*bullet).render(mapRendererRect, mapSurface, bullets) (*bullet).render(mapRendererRect, mapSurface, bullets)
} }
bulletMutex.RUnlock()
baseMutex.RLock()
for _, base := range bases { for _, base := range bases {
(*base).render(mapRendererRect, mapSurface, bases) (*base).render(mapRendererRect, mapSurface, bases)
} }
baseMutex.RUnlock()
playersMutex.RLock()
for _, playerLoop := range players { for _, playerLoop := range players {
(*playerLoop).render(mapRendererRect, mapSurface, players) (*playerLoop).render(mapRendererRect, mapSurface, players)
} }
playersMutex.RUnlock()
bulletParticleMutex.RLock()
for _, bulletParticle := range bulletParticles { for _, bulletParticle := range bulletParticles {
bulletParticle.render(mapRendererRect, mapSurface, bulletParticles) bulletParticle.render(mapRendererRect, mapSurface, bulletParticles)
} }
bulletParticleMutex.RUnlock()
}) })
totalScalingTime += profileSection(func() { totalScalingTime += profileSection(func() {
adjustWindow(mapWindow, mapSurface) adjustWindow(mapWindow, mapSurface)
@ -289,8 +328,8 @@ func main() {
// Log profiling information every 1000 frames // Log profiling information every 1000 frames
if frameCount%config.ProfilerInterval == 0 && config.ProfilerOn { if frameCount%config.ProfilerInterval == 0 && config.ProfilerOn {
logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, frameCount) logMapProfilingInfo(totalRenderTime, totalScalingTime, totalFrameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate, frameCount, tickCount)
resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &frameCount) resetMapProfilingCounters(&totalRenderTime, &totalScalingTime, &totalFrameTime, &totalGameLogicCatchUp, &totalNormalGameLogic, &totalTicking, &totalRemotePlayerUpdate, &frameCount, &tickCount)
} }
worldLock.Unlock() worldLock.Unlock()
enforceFrameRate(currentTime, 60) enforceFrameRate(currentTime, 60)
@ -303,51 +342,94 @@ func runGameLogic(players map[uint32]*Player, gameMap *GameMap, bases map[uint32
if config.Server { if config.Server {
//update remote player maps //update remote player maps
totalRemotePlayerUpdate += profileSection(func() {
var wgX sync.WaitGroup
var wgX2 sync.WaitGroup
playerUpdateMutex.Lock()
playersMutex.RLock()
for _, player := range players {
if player.local {
continue
}
wgX.Add(6)
go player.updateRemotePlayerMap(&wgX)
go player.updateRemotePlayerBases(bases, &wgX)
go player.updateRemotePlayerBullets(bullets, &wgX)
go player.updateRemotePlayerBulletParticles(bulletParticles, &wgX)
go player.updateRemotePlayerPlayers(players, &wgX)
playerX := player
go func() {
fail := playerX.sendInfoToPlayer(&wgX)
if fail {
if playerX.connection != nil {
(*playerX.connection).Close()
}
baseMutex.Lock()
if bases[playerX.playerID] != nil {
bases[playerX.playerID].delete(bases)
}
baseMutex.Unlock()
playersMutex.TryLock()
delete(players, playerX.playerID)
playersMutex.Unlock()
}
}()
}
playersMutex.RUnlock()
wgX.Wait()
playersMutex.Lock()
for _, player := range players {
wgX2.Add(1)
playerX := player
go func() {
fail := playerX.sendUpdatesToPlayer(players, &wgX2)
if fail {
if playerX.connection != nil {
(*playerX.connection).Close()
}
baseMutex.Lock()
if bases[playerX.playerID] != nil {
bases[playerX.playerID].delete(bases)
}
baseMutex.Unlock()
delete(players, playerX.playerID)
}
}()
}
playersMutex.Unlock()
wgX2.Wait()
playerUpdateMutex.Unlock()
})
}
totalTicking += profileSection(func() {
playerUpdateMutex.Lock()
bulletMutex.RLock()
for _, bullet := range bullets {
(*bullet).tick(gameMap, bulletParticles, bullets, players)
}
bulletMutex.RUnlock()
playersMutex.RLock()
for _, player := range players { for _, player := range players {
if player.local { if player.local {
continue continue
} }
go player.updateRemotePlayerMap() (*player).tick(bullets)
go player.updateRemotePlayerBases(bases)
go player.updateRemotePlayerBullets(bullets)
go player.updateRemotePlayerBulletParticles(bulletParticles)
go player.updateRemotePlayerPlayers(players)
fail := player.sendInfoToPlayer()
if fail {
if player.connection != nil {
(*player.connection).Close()
}
delete(players, player.playerID)
continue
}
fail = player.sendUpdatesToPlayer(players)
if fail {
if player.connection != nil {
(*player.connection).Close()
}
delete(players, player.playerID)
continue
}
} }
} playersMutex.RUnlock()
baseMutex.RLock()
playerUpdateMutex.Lock() for _, base := range bases {
for _, bullet := range bullets { (*base).tick(players)
(*bullet).tick(gameMap, bulletParticles, bullets, players)
}
for _, player := range players {
if player.local {
continue
} }
(*player).tick(bullets) baseMutex.RUnlock()
} bulletParticleMutex.RLock()
for _, base := range bases { for _, bulletParticle := range bulletParticles {
(*base).tick(players) bulletParticle.tick(bulletParticles)
} }
for _, bulletParticle := range bulletParticles { bulletParticleMutex.RUnlock()
bulletParticle.tick(bulletParticles) playerUpdateMutex.Unlock()
} })
playerUpdateMutex.Unlock() tickCount++
} }
func initPlayer(playerIndex uint8, player *Player) { func initPlayer(playerIndex uint8, player *Player) {
@ -387,18 +469,26 @@ func doPlayerFrame(playerIndex uint8, player *Player, players map[uint32]*Player
player.track(player.camera) player.track(player.camera)
gameMap.render(player.camera, player.playSurface) gameMap.render(player.camera, player.playSurface)
bulletMutex.RLock()
for _, bullet := range bullets { for _, bullet := range bullets {
(*bullet).render(player.camera, player.playSurface, bullets) (*bullet).render(player.camera, player.playSurface, bullets)
} }
bulletMutex.RUnlock()
baseMutex.RLock()
for _, base := range bases { for _, base := range bases {
(*base).render(player.camera, player.playSurface, bases) (*base).render(player.camera, player.playSurface, bases)
} }
baseMutex.RUnlock()
playersMutex.RLock()
for _, playerLoop := range players { for _, playerLoop := range players {
(*playerLoop).render(player.camera, player.playSurface, players) (*playerLoop).render(player.camera, player.playSurface, players)
} }
playersMutex.RUnlock()
bulletParticleMutex.RLock()
for _, bulletParticle := range bulletParticles { for _, bulletParticle := range bulletParticles {
bulletParticle.render(player.camera, player.playSurface, bulletParticles) bulletParticle.render(player.camera, player.playSurface, bulletParticles)
} }
bulletParticleMutex.RUnlock()
player.tick(bullets) player.tick(bullets)
player.gameObject.prevBaseRect = player.gameObject.baseRect player.gameObject.prevBaseRect = player.gameObject.baseRect

874
player.go

File diff suppressed because it is too large Load Diff

@ -18,14 +18,20 @@ func logProfilingInfo(handleEventsTime, renderTime, scaleTime, frameTime, frameC
fmt.Printf("Average render time: %f ms\n", float64(renderTime)/floatFrame) fmt.Printf("Average render time: %f ms\n", float64(renderTime)/floatFrame)
fmt.Printf("Average scaling time: %f ms\n", float64(scaleTime)/floatFrame) fmt.Printf("Average scaling time: %f ms\n", float64(scaleTime)/floatFrame)
fmt.Printf("Average frame time: %f ms\n", float64(frameTime)/floatFrame) fmt.Printf("Average frame time: %f ms\n", float64(frameTime)/floatFrame)
fmt.Print("\n") fmt.Print("\n")
} }
func logMapProfilingInfo(renderTime, scaleTime, frameTime, frameCount uint64) { func logMapProfilingInfo(renderTime, scaleTime, frameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate, tickCount, frameCount uint64) {
floatFrame := float64(frameCount) floatFrame := float64(frameCount)
floatTick := float64(tickCount)
fmt.Printf("Average map render time: %f ms\n", float64(renderTime)/floatFrame) 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 map scaling time: %f ms\n", float64(scaleTime)/floatFrame)
fmt.Printf("Average full render time: %f ms\n", float64(frameTime)/floatFrame) fmt.Printf("Average full render time: %f ms\n", float64(frameTime)/floatFrame)
fmt.Printf("Average total gamelogic catching up time: %f ms\n", float64(totalGameLogicCatchUp)/floatFrame)
fmt.Printf("Average normal gamelogic time: %f ms\n", float64(totalNormalGameLogic)/floatFrame)
fmt.Printf("Average ticking time: %f ms\n", float64(totalTicking)/floatTick)
fmt.Printf("Average remote player update time: %f ms\n", float64(totalRemotePlayerUpdate)/floatTick)
fmt.Print("\n") fmt.Print("\n")
} }
@ -37,11 +43,16 @@ func resetProfilingCounters(handleEventsTime, renderTime, scaleTime, frameTime *
*frameCount = 0 *frameCount = 0
} }
func resetMapProfilingCounters(renderTime, scaleTime, frameTime *uint64, frameCount *uint64) { func resetMapProfilingCounters(renderTime, scaleTime, frameTime, totalGameLogicCatchUp, totalNormalGameLogic, totalTicking, totalRemotePlayerUpdate *uint64, frameCount, tickCount *uint64) {
*renderTime = 0 *renderTime = 0
*scaleTime = 0 *scaleTime = 0
*frameTime = 0 *frameTime = 0
*frameCount = 0 *frameCount = 0
*totalGameLogicCatchUp = 0
*totalNormalGameLogic = 0
*totalTicking = 0
*totalRemotePlayerUpdate = 0
*tickCount = 0
} }
func enforceFrameRate(frameStart uint64, targetFPS int) { func enforceFrameRate(frameStart uint64, targetFPS int) {

@ -23,6 +23,7 @@ message Bullet {
Color color = 3; Color color = 3;
uint32 id = 4; uint32 id = 4;
bool super = 5; bool super = 5;
uint32 ownerID = 6;
} }
message Color { message Color {
@ -63,6 +64,7 @@ message ServerInfo {
uint32 blastRadius = 21; uint32 blastRadius = 21;
uint32 playerID = 22; uint32 playerID = 22;
uint32 playerColorID = 23; uint32 playerColorID = 23;
uint32 reloadWait = 24;
} }
message PlayerUpdate { message PlayerUpdate {