diff --git a/internal/gameServer/server.go b/internal/gameServer/server.go index a8b7ae2..4632466 100644 --- a/internal/gameServer/server.go +++ b/internal/gameServer/server.go @@ -19,38 +19,38 @@ type Client struct { } type Registration struct { - RegID uint32 - Plugin byte - Raw byte + regID uint32 + plugin byte + raw byte } type GameServer struct { StartTime time.Time Players map[string]Client PlayersMutex sync.Mutex - TCPListener *net.TCPListener - UDPListener *net.UDPConn - Registrations map[byte]*Registration - RegistrationsMutex sync.Mutex - TCPMutex sync.Mutex - TCPFiles map[string][]byte - CustomData map[byte][]byte + tcpListener *net.TCPListener + udpListener *net.UDPConn + registrations map[byte]*Registration + registrationsMutex sync.Mutex + tcpMutex sync.Mutex + tcpFiles map[string][]byte + customData map[byte][]byte Logger logr.Logger GameName string Password string ClientSha string MD5 string Emulator string - TCPSettings []byte - GameData GameData - GameDataMutex sync.Mutex + tcpSettings []byte + gameData GameData + gameDataMutex sync.Mutex Port int - HasSettings bool + hasSettings bool Running bool Features map[string]string NeedsUpdatePlayers bool NumberOfPlayers int - BufferTarget uint32 + BufferTarget byte QuitChannel *chan bool } @@ -62,7 +62,7 @@ func (g *GameServer) CreateNetworkServers(basePort int, maxGames int, roomName s } if err := g.createUDPServer(); err != nil { g.Logger.Error(err, "error creating UDP server") - if err := g.TCPListener.Close(); err != nil && !g.isConnClosed(err) { + if err := g.tcpListener.Close(); err != nil && !g.isConnClosed(err) { g.Logger.Error(err, "error closing TcpListener") } return 0 @@ -71,12 +71,12 @@ func (g *GameServer) CreateNetworkServers(basePort int, maxGames int, roomName s } func (g *GameServer) CloseServers() { - if err := g.UDPListener.Close(); err != nil && !g.isConnClosed(err) { + if err := g.udpListener.Close(); err != nil && !g.isConnClosed(err) { g.Logger.Error(err, "error closing UdpListener") } else if err == nil { g.Logger.Info("UDP server closed") } - if err := g.TCPListener.Close(); err != nil && !g.isConnClosed(err) { + if err := g.tcpListener.Close(); err != nil && !g.isConnClosed(err) { g.Logger.Error(err, "error closing TcpListener") } else if err == nil { g.Logger.Info("TCP server closed") @@ -93,14 +93,18 @@ func (g *GameServer) isConnClosed(err error) bool { return strings.Contains(err.Error(), "use of closed network connection") } -func (g *GameServer) bufferHealthAverage(playerNumber int) (float32, error) { - if g.GameData.BufferHealth[playerNumber].Len() > 0 { - var bufferHealth float32 - for _, k := range g.GameData.BufferHealth[playerNumber].Keys() { - value, _ := g.GameData.BufferHealth[playerNumber].Peek(k) - bufferHealth += float32(value) +func (g *GameServer) lowestBufferHealth(playerNumber int) (byte, error) { + defer g.gameData.bufferHealth[playerNumber].Purge() + + if g.gameData.bufferHealth[playerNumber].Len() > 0 { + bufferHealth := byte(255) // Start with the maximum possible value + for _, k := range g.gameData.bufferHealth[playerNumber].Keys() { + value, _ := g.gameData.bufferHealth[playerNumber].Peek(k) + if value < bufferHealth { + bufferHealth = value + } } - return bufferHealth / float32(g.GameData.BufferHealth[playerNumber].Len()), nil + return bufferHealth, nil } else { return 0, fmt.Errorf("no buffer health data for player %d", playerNumber) } @@ -114,31 +118,31 @@ func (g *GameServer) ManageBuffer() { } // Find the largest buffer health - var bufferHealth float32 + var bufferHealth byte var activePlayers bool - g.GameDataMutex.Lock() // BufferHealth can be modified by processUDP in a different thread + var leadPlayer int + g.gameDataMutex.Lock() // BufferHealth can be modified by processUDP in a different thread for i := range 4 { - if g.GameData.CountLag[i] == 0 { - var err error - g.GameData.BufferHealthAverage[i], err = g.bufferHealthAverage(i) - if err == nil { - activePlayers = true - } - if g.GameData.BufferHealthAverage[i] > bufferHealth { - bufferHealth = g.GameData.BufferHealthAverage[i] + var err error + g.gameData.lowestBufferHealth[i], err = g.lowestBufferHealth(i) + if err == nil && g.gameData.countLag[i] == 0 { + activePlayers = true + + if g.gameData.lowestBufferHealth[i] > bufferHealth { + bufferHealth = g.gameData.lowestBufferHealth[i] + leadPlayer = i + 1 } } - g.GameData.BufferHealth[i].Purge() } - g.GameDataMutex.Unlock() + g.gameDataMutex.Unlock() if activePlayers { - if bufferHealth > float32(g.BufferTarget)+0.5 && g.GameData.BufferSize > 0 { - g.GameData.BufferSize-- - g.Logger.Info("reduced buffer size", "bufferHealth", bufferHealth, "bufferSize", g.GameData.BufferSize) - } else if bufferHealth < float32(g.BufferTarget)-0.5 { - g.GameData.BufferSize++ - g.Logger.Info("increased buffer size", "bufferHealth", bufferHealth, "bufferSize", g.GameData.BufferSize) + if bufferHealth > g.BufferTarget && g.gameData.bufferSize > 0 { + g.gameData.bufferSize-- + g.Logger.Info("reduced buffer size", "bufferHealth", bufferHealth, "bufferSize", g.gameData.bufferSize, "leadPlayer", leadPlayer) + } else if bufferHealth < g.BufferTarget { + g.gameData.bufferSize++ + g.Logger.Info("increased buffer size", "bufferHealth", bufferHealth, "bufferSize", g.gameData.bufferSize, "leadPlayer", leadPlayer) } } @@ -152,20 +156,20 @@ func (g *GameServer) ManagePlayers() { playersActive := false // used to check if anyone is still around var i byte - g.GameDataMutex.Lock() // PlayerAlive and Status can be modified by processUDP in a different thread + g.gameDataMutex.Lock() // PlayerAlive and Status can be modified by processUDP in a different thread for i = range 4 { - _, ok := g.Registrations[i] + _, ok := g.registrations[i] if ok { - if g.GameData.PlayerAlive[i] { - g.Logger.Info("player status", "player", i, "regID", g.Registrations[i].RegID, "bufferHealthAverage", g.GameData.BufferHealthAverage[i], "bufferSize", g.GameData.BufferSize, "countLag", g.GameData.CountLag[i], "address", g.GameData.PlayerAddresses[i]) + if g.gameData.playerAlive[i] { + g.Logger.Info("player status", "player", i, "regID", g.registrations[i].regID, "bufferHealth", g.gameData.lowestBufferHealth[i], "bufferSize", g.gameData.bufferSize, "countLag", g.gameData.countLag[i], "address", g.gameData.playerAddresses[i]) playersActive = true } else { - g.Logger.Info("player disconnected UDP", "player", i, "regID", g.Registrations[i].RegID, "address", g.GameData.PlayerAddresses[i]) - g.GameData.Status |= (0x1 << (i + 1)) + g.Logger.Info("player disconnected UDP", "player", i, "regID", g.registrations[i].regID, "address", g.gameData.playerAddresses[i]) + g.gameData.status |= (0x1 << (i + 1)) - g.RegistrationsMutex.Lock() // Registrations can be modified by processTCP - delete(g.Registrations, i) - g.RegistrationsMutex.Unlock() + g.registrationsMutex.Lock() // Registrations can be modified by processTCP + delete(g.registrations, i) + g.registrationsMutex.Unlock() for k, v := range g.Players { if v.Number == int(i) { @@ -175,12 +179,12 @@ func (g *GameServer) ManagePlayers() { g.PlayersMutex.Unlock() } } - g.GameData.BufferHealth[i].Purge() + g.gameData.bufferHealth[i].Purge() } } - g.GameData.PlayerAlive[i] = false + g.gameData.playerAlive[i] = false } - g.GameDataMutex.Unlock() + g.gameDataMutex.Unlock() if !playersActive { g.Logger.Info("no more players, closing room", "numPlayers", g.NumberOfPlayers, "clientSHA", g.ClientSha, "playTime", time.Since(g.StartTime).String()) diff --git a/internal/gameServer/tcp.go b/internal/gameServer/tcp.go index 90473a1..1622bfb 100644 --- a/internal/gameServer/tcp.go +++ b/internal/gameServer/tcp.go @@ -12,12 +12,12 @@ import ( ) type TCPData struct { - Filename string - Buffer bytes.Buffer - Filesize uint32 - Request byte - CustomID byte - CustomDatasize uint32 + filename string + buffer bytes.Buffer + filesize uint32 + request byte + customID byte + customDatasize uint32 } const ( @@ -43,9 +43,9 @@ func (g *GameServer) tcpSendFile(tcpData *TCPData, conn *net.TCPConn, withSize b startTime := time.Now() var ok bool for !ok { - g.TCPMutex.Lock() - _, ok = g.TCPFiles[tcpData.Filename] - g.TCPMutex.Unlock() + g.tcpMutex.Lock() + _, ok = g.tcpFiles[tcpData.filename] + g.tcpMutex.Unlock() if !ok { time.Sleep(time.Millisecond) if time.Since(startTime) > TCPTimeout { @@ -55,36 +55,36 @@ func (g *GameServer) tcpSendFile(tcpData *TCPData, conn *net.TCPConn, withSize b } else { if withSize { size := make([]byte, 4) - binary.BigEndian.PutUint32(size, uint32(len(g.TCPFiles[tcpData.Filename]))) + binary.BigEndian.PutUint32(size, uint32(len(g.tcpFiles[tcpData.filename]))) _, err := conn.Write(size) if err != nil { g.Logger.Error(err, "could not write size", "address", conn.RemoteAddr().String()) } } - if len(g.TCPFiles[tcpData.Filename]) > 0 { - _, err := conn.Write(g.TCPFiles[tcpData.Filename]) + if len(g.tcpFiles[tcpData.filename]) > 0 { + _, err := conn.Write(g.tcpFiles[tcpData.filename]) if err != nil { g.Logger.Error(err, "could not write file", "address", conn.RemoteAddr().String()) } } - // g.Logger.Info("sent save file", "filename", tcpData.Filename, "filesize", tcpData.Filesize, "address", conn.RemoteAddr().String()) - tcpData.Filename = "" - tcpData.Filesize = 0 + // g.Logger.Info("sent save file", "filename", tcpData.filename, "filesize", tcpData.filesize, "address", conn.RemoteAddr().String()) + tcpData.filename = "" + tcpData.filesize = 0 } } } func (g *GameServer) tcpSendSettings(conn *net.TCPConn) { startTime := time.Now() - for !g.HasSettings { + for !g.hasSettings { time.Sleep(time.Millisecond) if time.Since(startTime) > TCPTimeout { g.Logger.Info("TCP connection timed out in tcpSendSettings") return } } - _, err := conn.Write(g.TCPSettings) + _, err := conn.Write(g.tcpSettings) if err != nil { g.Logger.Error(err, "could not write settings", "address", conn.RemoteAddr().String()) } @@ -95,9 +95,9 @@ func (g *GameServer) tcpSendCustom(conn *net.TCPConn, customID byte) { startTime := time.Now() var ok bool for !ok { - g.TCPMutex.Lock() - _, ok = g.CustomData[customID] - g.TCPMutex.Unlock() + g.tcpMutex.Lock() + _, ok = g.customData[customID] + g.tcpMutex.Unlock() if !ok { time.Sleep(time.Millisecond) if time.Since(startTime) > TCPTimeout { @@ -105,7 +105,7 @@ func (g *GameServer) tcpSendCustom(conn *net.TCPConn, customID byte) { return } } else { - _, err := conn.Write(g.CustomData[customID]) + _, err := conn.Write(g.customData[customID]) if err != nil { g.Logger.Error(err, "could not write data", "address", conn.RemoteAddr().String()) } @@ -115,7 +115,7 @@ func (g *GameServer) tcpSendCustom(conn *net.TCPConn, customID byte) { func (g *GameServer) tcpSendReg(conn *net.TCPConn) { startTime := time.Now() - for len(g.Players) != len(g.Registrations) { + for len(g.Players) != len(g.registrations) { time.Sleep(time.Millisecond) if time.Since(startTime) > TCPTimeout { g.Logger.Info("TCP connection timed out in tcpSendReg") @@ -126,13 +126,13 @@ func (g *GameServer) tcpSendReg(conn *net.TCPConn) { registrations := make([]byte, 24) current := 0 for i = range 4 { - _, ok := g.Registrations[i] + _, ok := g.registrations[i] if ok { - binary.BigEndian.PutUint32(registrations[current:], g.Registrations[i].RegID) + binary.BigEndian.PutUint32(registrations[current:], g.registrations[i].regID) current += 4 - registrations[current] = g.Registrations[i].Plugin + registrations[current] = g.registrations[i].plugin current++ - registrations[current] = g.Registrations[i].Raw + registrations[current] = g.registrations[i].raw current++ } else { current += 6 @@ -148,11 +148,11 @@ func (g *GameServer) tcpSendReg(conn *net.TCPConn) { func (g *GameServer) processTCP(conn *net.TCPConn) { defer conn.Close() //nolint:errcheck - tcpData := &TCPData{Request: RequestNone} + tcpData := &TCPData{request: RequestNone} incomingBuffer := make([]byte, 1500) for { var readDeadline time.Time - if tcpData.Buffer.Len() > 0 { + if tcpData.buffer.Len() > 0 { // if there is pending data in the buffer, get to it quickly readDeadline = time.Now().Add(time.Millisecond) } else { @@ -172,12 +172,12 @@ func (g *GameServer) processTCP(conn *net.TCPConn) { continue } if length > 0 { - tcpData.Buffer.Write(incomingBuffer[:length]) + tcpData.buffer.Write(incomingBuffer[:length]) } - if tcpData.Request == RequestNone { // find the request type - if tcpData.Buffer.Len() > 0 { - tcpData.Request, err = tcpData.Buffer.ReadByte() + if tcpData.request == RequestNone { // find the request type + if tcpData.buffer.Len() > 0 { + tcpData.request, err = tcpData.buffer.ReadByte() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } @@ -186,127 +186,127 @@ func (g *GameServer) processTCP(conn *net.TCPConn) { } } - if (tcpData.Request == RequestSendSave || tcpData.Request == RequestReceiveSave || tcpData.Request == RequestReceiveSaveWithSize) && tcpData.Filename == "" { // get file name - if bytes.IndexByte(tcpData.Buffer.Bytes(), 0) != -1 { - filenameBytes, err := tcpData.Buffer.ReadBytes(0) + if (tcpData.request == RequestSendSave || tcpData.request == RequestReceiveSave || tcpData.request == RequestReceiveSaveWithSize) && tcpData.filename == "" { // get file name + if bytes.IndexByte(tcpData.buffer.Bytes(), 0) != -1 { + filenameBytes, err := tcpData.buffer.ReadBytes(0) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - tcpData.Filename = string(filenameBytes[:len(filenameBytes)-1]) + tcpData.filename = string(filenameBytes[:len(filenameBytes)-1]) } } - if tcpData.Request == RequestSendSave && tcpData.Filename != "" && tcpData.Filesize == 0 { // get file size from sender - if tcpData.Buffer.Len() >= 4 { + if tcpData.request == RequestSendSave && tcpData.filename != "" && tcpData.filesize == 0 { // get file size from sender + if tcpData.buffer.Len() >= 4 { filesizeBytes := make([]byte, 4) - _, err = tcpData.Buffer.Read(filesizeBytes) + _, err = tcpData.buffer.Read(filesizeBytes) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - tcpData.Filesize = binary.BigEndian.Uint32(filesizeBytes) - - if tcpData.Filesize == 0 { - g.TCPMutex.Lock() - g.TCPFiles[tcpData.Filename] = make([]byte, tcpData.Filesize) - g.TCPMutex.Unlock() - tcpData.Filename = "" - tcpData.Filesize = 0 - tcpData.Request = RequestNone + tcpData.filesize = binary.BigEndian.Uint32(filesizeBytes) + + if tcpData.filesize == 0 { + g.tcpMutex.Lock() + g.tcpFiles[tcpData.filename] = make([]byte, tcpData.filesize) + g.tcpMutex.Unlock() + tcpData.filename = "" + tcpData.filesize = 0 + tcpData.request = RequestNone } } } - if tcpData.Request == RequestSendSave && tcpData.Filename != "" && tcpData.Filesize != 0 { // read in file from sender - if tcpData.Buffer.Len() >= int(tcpData.Filesize) { - g.TCPMutex.Lock() - g.TCPFiles[tcpData.Filename] = make([]byte, tcpData.Filesize) - _, err = tcpData.Buffer.Read(g.TCPFiles[tcpData.Filename]) - g.TCPMutex.Unlock() + if tcpData.request == RequestSendSave && tcpData.filename != "" && tcpData.filesize != 0 { // read in file from sender + if tcpData.buffer.Len() >= int(tcpData.filesize) { + g.tcpMutex.Lock() + g.tcpFiles[tcpData.filename] = make([]byte, tcpData.filesize) + _, err = tcpData.buffer.Read(g.tcpFiles[tcpData.filename]) + g.tcpMutex.Unlock() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - g.Logger.Info("received save file", "filename", tcpData.Filename, "filesize", tcpData.Filesize, "address", conn.RemoteAddr().String()) - tcpData.Filename = "" - tcpData.Filesize = 0 - tcpData.Request = RequestNone + g.Logger.Info("received save file", "filename", tcpData.filename, "filesize", tcpData.filesize, "address", conn.RemoteAddr().String()) + tcpData.filename = "" + tcpData.filesize = 0 + tcpData.request = RequestNone } } - if tcpData.Request == RequestReceiveSave && tcpData.Filename != "" { // send requested file + if tcpData.request == RequestReceiveSave && tcpData.filename != "" { // send requested file go g.tcpSendFile(tcpData, conn, false) - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request == RequestReceiveSaveWithSize && tcpData.Filename != "" { // send requested file + if tcpData.request == RequestReceiveSaveWithSize && tcpData.filename != "" { // send requested file go g.tcpSendFile(tcpData, conn, true) - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request == RequestSendSettings { // get settings from P1 - if tcpData.Buffer.Len() >= SettingsSize { - _, err = tcpData.Buffer.Read(g.TCPSettings) + if tcpData.request == RequestSendSettings { // get settings from P1 + if tcpData.buffer.Len() >= SettingsSize { + _, err = tcpData.buffer.Read(g.tcpSettings) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - // g.Logger.Info("read settings via TCP", "bufferLeft", tcpData.Buffer.Len(), "address", conn.RemoteAddr().String()) - g.HasSettings = true - tcpData.Request = RequestNone + // g.Logger.Info("read settings via TCP", "bufferLeft", tcpData.buffer.Len(), "address", conn.RemoteAddr().String()) + g.hasSettings = true + tcpData.request = RequestNone } } - if tcpData.Request == RequestReceiveSettings { // send settings to P2-4 + if tcpData.request == RequestReceiveSettings { // send settings to P2-4 go g.tcpSendSettings(conn) - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request == RequestRegisterPlayer && tcpData.Buffer.Len() >= 7 { // register player - playerNumber, err := tcpData.Buffer.ReadByte() + if tcpData.request == RequestRegisterPlayer && tcpData.buffer.Len() >= 7 { // register player + playerNumber, err := tcpData.buffer.ReadByte() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - plugin, err := tcpData.Buffer.ReadByte() + plugin, err := tcpData.buffer.ReadByte() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - raw, err := tcpData.Buffer.ReadByte() + raw, err := tcpData.buffer.ReadByte() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } regIDBytes := make([]byte, 4) - _, err = tcpData.Buffer.Read(regIDBytes) + _, err = tcpData.buffer.Read(regIDBytes) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } regID := binary.BigEndian.Uint32(regIDBytes) response := make([]byte, 2) - _, ok := g.Registrations[playerNumber] + _, ok := g.registrations[playerNumber] if !ok { if playerNumber > 0 && plugin == 2 { // Only P1 can use mempak plugin = 1 } - g.RegistrationsMutex.Lock() // any player can modify this, which would be in a different thread - g.Registrations[playerNumber] = &Registration{ - RegID: regID, - Plugin: plugin, - Raw: raw, + g.registrationsMutex.Lock() // any player can modify this, which would be in a different thread + g.registrations[playerNumber] = &Registration{ + regID: regID, + plugin: plugin, + raw: raw, } - g.RegistrationsMutex.Unlock() + g.registrationsMutex.Unlock() response[0] = 1 - g.Logger.Info("registered player", "registration", g.Registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.Buffer.Len(), "address", conn.RemoteAddr().String()) + g.Logger.Info("registered player", "registration", g.registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.buffer.Len(), "address", conn.RemoteAddr().String()) - g.GameDataMutex.Lock() // any player can modify this, which would be in a different thread - g.GameData.PendingInput[playerNumber] = InputData{0, plugin} - g.GameData.PlayerAlive[playerNumber] = true - g.GameDataMutex.Unlock() + g.gameDataMutex.Lock() // any player can modify this, which would be in a different thread + g.gameData.pendingInput[playerNumber] = InputData{0, plugin} + g.gameData.playerAlive[playerNumber] = true + g.gameDataMutex.Unlock() } else { - if g.Registrations[playerNumber].RegID == regID { - g.Logger.Error(fmt.Errorf("re-registration"), "player already registered", "registration", g.Registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.Buffer.Len(), "address", conn.RemoteAddr().String()) + if g.registrations[playerNumber].regID == regID { + g.Logger.Error(fmt.Errorf("re-registration"), "player already registered", "registration", g.registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.buffer.Len(), "address", conn.RemoteAddr().String()) response[0] = 1 } else { - g.Logger.Error(fmt.Errorf("registration failure"), "could not register player", "registration", g.Registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.Buffer.Len(), "address", conn.RemoteAddr().String()) + g.Logger.Error(fmt.Errorf("registration failure"), "could not register player", "registration", g.registrations[playerNumber], "number", playerNumber, "bufferLeft", tcpData.buffer.Len(), "address", conn.RemoteAddr().String()) response[0] = 0 } } @@ -315,35 +315,35 @@ func (g *GameServer) processTCP(conn *net.TCPConn) { if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request == RequestGetRegistration { // send registration + if tcpData.request == RequestGetRegistration { // send registration go g.tcpSendReg(conn) - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request == RequestDisconnectNotice && tcpData.Buffer.Len() >= 4 { // disconnect notice + if tcpData.request == RequestDisconnectNotice && tcpData.buffer.Len() >= 4 { // disconnect notice regIDBytes := make([]byte, 4) - _, err = tcpData.Buffer.Read(regIDBytes) + _, err = tcpData.buffer.Read(regIDBytes) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } regID := binary.BigEndian.Uint32(regIDBytes) var i byte for i = range 4 { - v, ok := g.Registrations[i] + v, ok := g.registrations[i] if ok { - if v.RegID == regID { + if v.regID == regID { g.Logger.Info("player disconnected TCP", "regID", regID, "player", i, "address", conn.RemoteAddr().String()) - g.GameDataMutex.Lock() // any player can modify this, which would be in a different thread - g.GameData.PlayerAlive[i] = false - g.GameData.Status |= (0x1 << (i + 1)) + g.gameDataMutex.Lock() // any player can modify this, which would be in a different thread + g.gameData.playerAlive[i] = false + g.gameData.status |= (0x1 << (i + 1)) - g.RegistrationsMutex.Lock() // any player can modify this, which would be in a different thread - delete(g.Registrations, i) - g.RegistrationsMutex.Unlock() + g.registrationsMutex.Lock() // any player can modify this, which would be in a different thread + delete(g.registrations, i) + g.registrationsMutex.Unlock() for k, v := range g.Players { if v.Number == int(i) { @@ -353,49 +353,49 @@ func (g *GameServer) processTCP(conn *net.TCPConn) { g.PlayersMutex.Unlock() } } - g.GameData.BufferHealth[i].Purge() - g.GameDataMutex.Unlock() + g.gameData.bufferHealth[i].Purge() + g.gameDataMutex.Unlock() } } } - tcpData.Request = RequestNone + tcpData.request = RequestNone } - if tcpData.Request >= RequestSendCustomStart && tcpData.Request < RequestSendCustomStart+CustomDataOffset && tcpData.Buffer.Len() >= 4 && tcpData.CustomID == 0 { // get custom data (for example, plugin settings) + if tcpData.request >= RequestSendCustomStart && tcpData.request < RequestSendCustomStart+CustomDataOffset && tcpData.buffer.Len() >= 4 && tcpData.customID == 0 { // get custom data (for example, plugin settings) dataSizeBytes := make([]byte, 4) - _, err = tcpData.Buffer.Read(dataSizeBytes) + _, err = tcpData.buffer.Read(dataSizeBytes) if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - tcpData.CustomID = tcpData.Request - tcpData.CustomDatasize = binary.BigEndian.Uint32(dataSizeBytes) + tcpData.customID = tcpData.request + tcpData.customDatasize = binary.BigEndian.Uint32(dataSizeBytes) } - if tcpData.Request >= RequestSendCustomStart && tcpData.Request < RequestSendCustomStart+CustomDataOffset && tcpData.CustomID != 0 { // read in custom data from sender - if tcpData.Buffer.Len() >= int(tcpData.CustomDatasize) { - g.TCPMutex.Lock() - g.CustomData[tcpData.CustomID] = make([]byte, tcpData.CustomDatasize) - _, err = tcpData.Buffer.Read(g.CustomData[tcpData.CustomID]) - g.TCPMutex.Unlock() + if tcpData.request >= RequestSendCustomStart && tcpData.request < RequestSendCustomStart+CustomDataOffset && tcpData.customID != 0 { // read in custom data from sender + if tcpData.buffer.Len() >= int(tcpData.customDatasize) { + g.tcpMutex.Lock() + g.customData[tcpData.customID] = make([]byte, tcpData.customDatasize) + _, err = tcpData.buffer.Read(g.customData[tcpData.customID]) + g.tcpMutex.Unlock() if err != nil { g.Logger.Error(err, "TCP error", "address", conn.RemoteAddr().String()) } - tcpData.CustomID = 0 - tcpData.CustomDatasize = 0 - tcpData.Request = RequestNone + tcpData.customID = 0 + tcpData.customDatasize = 0 + tcpData.request = RequestNone } } - if tcpData.Request >= RequestSendCustomStart+CustomDataOffset && tcpData.Request < RequestSendCustomStart+CustomDataOffset+CustomDataOffset { // send custom data (for example, plugin settings) - go g.tcpSendCustom(conn, tcpData.Request-CustomDataOffset) - tcpData.Request = RequestNone + if tcpData.request >= RequestSendCustomStart+CustomDataOffset && tcpData.request < RequestSendCustomStart+CustomDataOffset+CustomDataOffset { // send custom data (for example, plugin settings) + go g.tcpSendCustom(conn, tcpData.request-CustomDataOffset) + tcpData.request = RequestNone } } } func (g *GameServer) watchTCP() { for { - conn, err := g.TCPListener.AcceptTCP() + conn, err := g.tcpListener.AcceptTCP() if err != nil && !g.isConnClosed(err) { g.Logger.Error(err, "error from TcpListener") continue @@ -429,14 +429,14 @@ func (g *GameServer) watchTCP() { func (g *GameServer) createTCPServer(basePort int, maxGames int) int { var err error for i := 1; i <= maxGames; i++ { - g.TCPListener, err = net.ListenTCP("tcp", &net.TCPAddr{Port: basePort + i}) + g.tcpListener, err = net.ListenTCP("tcp", &net.TCPAddr{Port: basePort + i}) if err == nil { g.Port = basePort + i g.Logger.Info("Created TCP server", "port", g.Port) - g.TCPFiles = make(map[string][]byte) - g.CustomData = make(map[byte][]byte) - g.TCPSettings = make([]byte, SettingsSize) - g.Registrations = map[byte]*Registration{} + g.tcpFiles = make(map[string][]byte) + g.customData = make(map[byte][]byte) + g.tcpSettings = make([]byte, SettingsSize) + g.registrations = map[byte]*Registration{} go g.watchTCP() return g.Port } diff --git a/internal/gameServer/udp.go b/internal/gameServer/udp.go index 651dc68..69ee374 100644 --- a/internal/gameServer/udp.go +++ b/internal/gameServer/udp.go @@ -19,19 +19,19 @@ type InputData struct { } type GameData struct { - SyncValues *lru.Cache[uint32, []byte] - PlayerAddresses [4]*net.UDPAddr - BufferSize uint32 - BufferHealth [4]*lru.Cache[uint32, byte] - BufferHealthAverage [4]float32 - Inputs [4]*lru.Cache[uint32, InputData] - PendingInput [4]InputData - CountLag [4]uint32 - sendBuffer []byte - recvBuffer []byte - PlayerAlive [4]bool - LeadCount uint32 - Status byte + syncValues *lru.Cache[uint32, []byte] + playerAddresses [4]*net.UDPAddr + bufferSize uint32 + lowestBufferHealth [4]byte + bufferHealth [4]*lru.Cache[uint32, byte] + inputs [4]*lru.Cache[uint32, InputData] + pendingInput [4]InputData + countLag [4]uint32 + sendBuffer []byte + recvBuffer []byte + playerAlive [4]bool + leadCount uint32 + status byte } const ( @@ -58,9 +58,9 @@ func uintLarger(v uint32, w uint32) bool { func (g *GameServer) getPlayerNumberByID(regID uint32) (byte, error) { var i byte for i = range 4 { - v, ok := g.Registrations[i] + v, ok := g.registrations[i] if ok { - if v.RegID == regID { + if v.regID == regID { return i, nil } } @@ -69,50 +69,50 @@ func (g *GameServer) getPlayerNumberByID(regID uint32) (byte, error) { } func (g *GameServer) fillInput(playerNumber byte, count uint32) InputData { - input, inputExists := g.GameData.Inputs[playerNumber].Get(count) + input, inputExists := g.gameData.inputs[playerNumber].Get(count) if !inputExists { - input = g.GameData.PendingInput[playerNumber] - g.GameData.Inputs[playerNumber].Add(count, input) + input = g.gameData.pendingInput[playerNumber] + g.gameData.inputs[playerNumber].Add(count, input) } return input } func (g *GameServer) sendUDPInput(count uint32, addr *net.UDPAddr, playerNumber byte, spectator bool, sendingPlayerNumber byte) uint32 { var countLag uint32 - if uintLarger(count, g.GameData.LeadCount) { + if uintLarger(count, g.gameData.leadCount) { if !spectator { - g.Logger.Error(fmt.Errorf("bad count lag"), "count is larger than LeadCount", "count", count, "LeadCount", g.GameData.LeadCount, "playerNumber", playerNumber) + g.Logger.Error(fmt.Errorf("bad count lag"), "count is larger than LeadCount", "count", count, "LeadCount", g.gameData.leadCount, "playerNumber", playerNumber) } } else { - countLag = g.GameData.LeadCount - count + countLag = g.gameData.leadCount - count } if sendingPlayerNumber == NoRegID { // if the incoming packet was KeyInfoClient, the regID isn't included in the packet - g.GameData.sendBuffer[0] = KeyInfoServerGratuitous // client will ignore countLag value in this case + g.gameData.sendBuffer[0] = KeyInfoServerGratuitous // client will ignore countLag value in this case } else { - g.GameData.sendBuffer[0] = KeyInfoServer + g.gameData.sendBuffer[0] = KeyInfoServer } - g.GameData.sendBuffer[1] = playerNumber - g.GameData.sendBuffer[2] = g.GameData.Status - g.GameData.sendBuffer[3] = uint8(countLag) + g.gameData.sendBuffer[1] = playerNumber + g.gameData.sendBuffer[2] = g.gameData.status + g.gameData.sendBuffer[3] = uint8(countLag) currentByte := 5 start := count - end := start + g.GameData.BufferSize - _, ok := g.GameData.Inputs[playerNumber].Get(count) // check if input exists for this count - for (currentByte < len(g.GameData.sendBuffer)-9) && ((!spectator && countLag == 0 && uintLarger(end, count)) || ok) { - binary.BigEndian.PutUint32(g.GameData.sendBuffer[currentByte:], count) + end := start + g.gameData.bufferSize + _, ok := g.gameData.inputs[playerNumber].Get(count) // check if input exists for this count + for (currentByte < len(g.gameData.sendBuffer)-9) && ((!spectator && countLag == 0 && uintLarger(end, count)) || ok) { + binary.BigEndian.PutUint32(g.gameData.sendBuffer[currentByte:], count) currentByte += 4 input := g.fillInput(playerNumber, count) - binary.BigEndian.PutUint32(g.GameData.sendBuffer[currentByte:], input.keys) + binary.BigEndian.PutUint32(g.gameData.sendBuffer[currentByte:], input.keys) currentByte += 4 - g.GameData.sendBuffer[currentByte] = input.plugin + g.gameData.sendBuffer[currentByte] = input.plugin currentByte++ count++ - _, ok = g.GameData.Inputs[playerNumber].Get(count) // check if input exists for this count + _, ok = g.gameData.inputs[playerNumber].Get(count) // check if input exists for this count } if count > start { - g.GameData.sendBuffer[4] = uint8(count - start) // number of counts in packet - _, err := g.UDPListener.WriteToUDP(g.GameData.sendBuffer[0:currentByte], addr) + g.gameData.sendBuffer[4] = uint8(count - start) // number of counts in packet + _, err := g.udpListener.WriteToUDP(g.gameData.sendBuffer[0:currentByte], addr) if err != nil { g.Logger.Error(err, "could not send input") } @@ -121,49 +121,49 @@ func (g *GameServer) sendUDPInput(count uint32, addr *net.UDPAddr, playerNumber } func (g *GameServer) processUDP(addr *net.UDPAddr) { - playerNumber := g.GameData.recvBuffer[1] - switch g.GameData.recvBuffer[0] { + playerNumber := g.gameData.recvBuffer[1] + switch g.gameData.recvBuffer[0] { case KeyInfoClient: - g.GameData.PlayerAddresses[playerNumber] = addr - count := binary.BigEndian.Uint32(g.GameData.recvBuffer[2:]) + g.gameData.playerAddresses[playerNumber] = addr + count := binary.BigEndian.Uint32(g.gameData.recvBuffer[2:]) - g.GameData.PendingInput[playerNumber] = InputData{ - keys: binary.BigEndian.Uint32(g.GameData.recvBuffer[6:]), - plugin: g.GameData.recvBuffer[10], + g.gameData.pendingInput[playerNumber] = InputData{ + keys: binary.BigEndian.Uint32(g.gameData.recvBuffer[6:]), + plugin: g.gameData.recvBuffer[10], } for i := range 4 { - if g.GameData.PlayerAddresses[i] != nil { - g.sendUDPInput(count, g.GameData.PlayerAddresses[i], playerNumber, true, NoRegID) + if g.gameData.playerAddresses[i] != nil { + g.sendUDPInput(count, g.gameData.playerAddresses[i], playerNumber, true, NoRegID) } } case PlayerInputRequest: - regID := binary.BigEndian.Uint32(g.GameData.recvBuffer[2:]) - count := binary.BigEndian.Uint32(g.GameData.recvBuffer[6:]) - spectator := g.GameData.recvBuffer[10] - if uintLarger(count, g.GameData.LeadCount) && spectator == 0 { - g.GameData.LeadCount = count + regID := binary.BigEndian.Uint32(g.gameData.recvBuffer[2:]) + count := binary.BigEndian.Uint32(g.gameData.recvBuffer[6:]) + spectator := g.gameData.recvBuffer[10] + if uintLarger(count, g.gameData.leadCount) && spectator == 0 { + g.gameData.leadCount = count } sendingPlayerNumber, err := g.getPlayerNumberByID(regID) if err != nil { g.Logger.Error(err, "could not process request", "regID", regID) return } - g.GameData.CountLag[sendingPlayerNumber] = g.sendUDPInput(count, addr, playerNumber, spectator != 0, sendingPlayerNumber) - g.GameDataMutex.Lock() // PlayerAlive and BufferHealth can be modified in different threads - g.GameData.BufferHealth[sendingPlayerNumber].Add(count, g.GameData.recvBuffer[11]) - g.GameData.PlayerAlive[sendingPlayerNumber] = true - g.GameDataMutex.Unlock() + g.gameData.countLag[sendingPlayerNumber] = g.sendUDPInput(count, addr, playerNumber, spectator != 0, sendingPlayerNumber) + g.gameDataMutex.Lock() // PlayerAlive and BufferHealth can be modified in different threads + g.gameData.bufferHealth[sendingPlayerNumber].Add(count, g.gameData.recvBuffer[11]) + g.gameData.playerAlive[sendingPlayerNumber] = true + g.gameDataMutex.Unlock() case CP0Info: - if g.GameData.Status&StatusDesync == 0 { - viCount := binary.BigEndian.Uint32(g.GameData.recvBuffer[1:]) - syncValue, ok := g.GameData.SyncValues.Get(viCount) + if g.gameData.status&StatusDesync == 0 { + viCount := binary.BigEndian.Uint32(g.gameData.recvBuffer[1:]) + syncValue, ok := g.gameData.syncValues.Get(viCount) if !ok { - g.GameData.SyncValues.Add(viCount, bytes.Clone(g.GameData.recvBuffer[5:133])) - } else if !bytes.Equal(syncValue, g.GameData.recvBuffer[5:133]) { - g.GameDataMutex.Lock() // Status can be modified by ManagePlayers in a different thread - g.GameData.Status |= StatusDesync - g.GameDataMutex.Unlock() + g.gameData.syncValues.Add(viCount, bytes.Clone(g.gameData.recvBuffer[5:133])) + } else if !bytes.Equal(syncValue, g.gameData.recvBuffer[5:133]) { + g.gameDataMutex.Lock() // Status can be modified by ManagePlayers in a different thread + g.gameData.status |= StatusDesync + g.gameDataMutex.Unlock() g.Logger.Error(fmt.Errorf("desync"), "game has desynced", "numPlayers", g.NumberOfPlayers, "clientSHA", g.ClientSha, "playTime", time.Since(g.StartTime).String(), "features", g.Features) } @@ -173,7 +173,7 @@ func (g *GameServer) processUDP(addr *net.UDPAddr) { func (g *GameServer) watchUDP() { for { - _, addr, err := g.UDPListener.ReadFromUDP(g.GameData.recvBuffer) + _, addr, err := g.udpListener.ReadFromUDP(g.gameData.recvBuffer) if err != nil && !g.isConnClosed(err) { g.Logger.Error(err, "error from UdpListener") continue @@ -198,26 +198,26 @@ func (g *GameServer) watchUDP() { func (g *GameServer) createUDPServer() error { var err error - g.UDPListener, err = net.ListenUDP("udp", &net.UDPAddr{Port: g.Port}) + g.udpListener, err = net.ListenUDP("udp", &net.UDPAddr{Port: g.Port}) if err != nil { return err } - if err := ipv4.NewConn(g.UDPListener).SetTOS(CS4 << 2); err != nil { + if err := ipv4.NewConn(g.udpListener).SetTOS(CS4 << 2); err != nil { g.Logger.Error(err, "could not set IPv4 DSCP") } - if err := ipv6.NewConn(g.UDPListener).SetTrafficClass(CS4 << 2); err != nil { + if err := ipv6.NewConn(g.udpListener).SetTrafficClass(CS4 << 2); err != nil { g.Logger.Error(err, "could not set IPv6 DSCP") } g.Logger.Info("Created UDP server", "port", g.Port) - g.GameData.BufferSize = 3 + g.gameData.bufferSize = 3 for i := range 4 { - g.GameData.Inputs[i], _ = lru.New[uint32, InputData](InputDataMax) - g.GameData.BufferHealth[i], _ = lru.New[uint32, byte](InputDataMax) + g.gameData.inputs[i], _ = lru.New[uint32, InputData](InputDataMax) + g.gameData.bufferHealth[i], _ = lru.New[uint32, byte](InputDataMax) } - g.GameData.SyncValues, _ = lru.New[uint32, []byte](100) // Store up to 100 sync values - g.GameData.sendBuffer = make([]byte, 508) - g.GameData.recvBuffer = make([]byte, 1500) + g.gameData.syncValues, _ = lru.New[uint32, []byte](100) // Store up to 100 sync values + g.gameData.sendBuffer = make([]byte, 508) + g.gameData.recvBuffer = make([]byte, 1500) go g.watchUDP() return nil diff --git a/internal/lobbyServer/lobby.go b/internal/lobbyServer/lobby.go index 60f12c1..6bd47ad 100644 --- a/internal/lobbyServer/lobby.go +++ b/internal/lobbyServer/lobby.go @@ -63,7 +63,7 @@ const ( ) type LobbyServer struct { - GameServers map[string]*gameserver.GameServer + gameServers map[string]*gameserver.GameServer Logger logr.Logger Name string Motd string @@ -74,7 +74,7 @@ type LobbyServer struct { CloseOnFinish bool quitChannel chan bool Timeout int - SendMutex sync.Mutex + sendMutex sync.Mutex } type RoomData struct { @@ -85,7 +85,7 @@ type RoomData struct { RoomName string `json:"room_name"` MD5 string `json:"MD5"` Port int `json:"port"` - BufferTarget uint32 `json:"buffer_target,omitempty"` + BufferTarget byte `json:"buffer_target,omitempty"` } type SocketMessage struct { @@ -107,9 +107,9 @@ const NetplayAPIVersion = 17 func (s *LobbyServer) sendData(ws *websocket.Conn, message SocketMessage) error { // s.Logger.Info("sending message", "message", message, "address", ws.Request().RemoteAddr) - s.SendMutex.Lock() + s.sendMutex.Lock() err := ws.WriteJSON(message) - s.SendMutex.Unlock() + s.sendMutex.Unlock() if err != nil { if !errors.Is(err, websocket.ErrCloseSent) { return err @@ -120,7 +120,7 @@ func (s *LobbyServer) sendData(ws *websocket.Conn, message SocketMessage) error // this function finds the GameServer pointer based on the port number. func (s *LobbyServer) findGameServer(port int) (string, *gameserver.GameServer) { - for i, v := range s.GameServers { + for i, v := range s.gameServers { if v.Port == port { return i, v } @@ -232,7 +232,7 @@ func (s *LobbyServer) watchGameServer(name string, g *gameserver.GameServer) { for { if !g.Running { g.Logger.Info("game server deleted", "port", g.Port) - delete(s.GameServers, name) + delete(s.gameServers, name) return } if g.NeedsUpdatePlayers { @@ -299,7 +299,7 @@ func (s *LobbyServer) wsHandler(w http.ResponseWriter, r *http.Request) { err := ws.ReadJSON(&receivedMessage) if err != nil { if e, ok := err.(*websocket.CloseError); ok { - for i, v := range s.GameServers { + for i, v := range s.gameServers { for k, w := range v.Players { if w.Socket == ws { v.Logger.Info("Player has left lobby", "closeCode", e.Code, "player", k, "address", ws.RemoteAddr()) @@ -320,7 +320,7 @@ func (s *LobbyServer) wsHandler(w http.ResponseWriter, r *http.Request) { if len(v.Players) == 0 { v.Logger.Info("No more players in lobby, deleting") v.CloseServers() - delete(s.GameServers, i) + delete(s.gameServers, i) } } } @@ -337,7 +337,7 @@ func (s *LobbyServer) wsHandler(w http.ResponseWriter, r *http.Request) { if receivedMessage.Type == TypeRequestCreateRoom { sendMessage.Type = TypeReplyCreateRoom - _, exists := s.GameServers[receivedMessage.Room.RoomName] + _, exists := s.gameServers[receivedMessage.Room.RoomName] if exists { sendMessage.Accept = DuplicateName sendMessage.Message = "Room with this name already exists" @@ -410,7 +410,7 @@ func (s *LobbyServer) wsHandler(w http.ResponseWriter, r *http.Request) { Socket: ws, InLobby: true, } - s.GameServers[receivedMessage.Room.RoomName] = &g + s.gameServers[receivedMessage.Room.RoomName] = &g g.Logger.Info("Created new room", "port", g.Port, "creator", receivedMessage.PlayerName, "clientSHA", receivedMessage.ClientSha, "creatorIP", ws.RemoteAddr(), "buffer_target", g.BufferTarget, "features", receivedMessage.Room.Features) sendMessage.Accept = Accepted sendMessage.Room.RoomName = receivedMessage.Room.RoomName @@ -492,7 +492,7 @@ func (s *LobbyServer) wsHandler(w http.ResponseWriter, r *http.Request) { } else { authenticated = true var rooms []RoomData - for i, v := range s.GameServers { + for i, v := range s.gameServers { if v.Running { continue } @@ -786,7 +786,7 @@ func (s *LobbyServer) runBroadcastServer(broadcastPort int) { } func (s *LobbyServer) RunSocketServer(broadcastPort int) error { - s.GameServers = make(map[string]*gameserver.GameServer) + s.gameServers = make(map[string]*gameserver.GameServer) if !s.DisableBroadcast { go s.runBroadcastServer(broadcastPort) } @@ -801,7 +801,7 @@ func (s *LobbyServer) RunSocketServer(broadcastPort int) error { if s.Timeout > 0 { go func() { time.Sleep(time.Duration(s.Timeout) * time.Minute) - if len(s.GameServers) == 0 { + if len(s.gameServers) == 0 { s.Logger.Info("timeout reached, closing server") s.quitChannel <- true } @@ -822,7 +822,7 @@ func (s *LobbyServer) LogServerStats() { for { memStats := runtime.MemStats{} runtime.ReadMemStats(&memStats) - s.Logger.Info("server stats", "games", len(s.GameServers), "NumGoroutine", runtime.NumGoroutine(), "HeapAlloc", memStats.HeapAlloc, "HeapObjects", memStats.HeapObjects) + s.Logger.Info("server stats", "games", len(s.gameServers), "NumGoroutine", runtime.NumGoroutine(), "HeapAlloc", memStats.HeapAlloc, "HeapObjects", memStats.HeapObjects) time.Sleep(time.Minute) } }