refactor/less-complexity #5

Manually merged
bdnugget merged 7 commits from refactor/less-complexity into master 2025-04-16 11:10:01 +00:00
2 changed files with 84 additions and 10 deletions
Showing only changes of commit b866ac879e - Show all commits

@ -1 +1 @@
Subproject commit f9ec811b10bbab54e843199eb68156e9e7c143cc Subproject commit c720b668180d46b8eb37747f9c24d2fa1f49de72

View File

@ -19,6 +19,7 @@ import (
const protoVersion = 1 const protoVersion = 1
var serverAddr = "boner.be:6969" // Default server address var serverAddr = "boner.be:6969" // Default server address
var lastSeenMessageTimestamp int64 = 0 // Track the last message timestamp seen by this client
func SetServerAddr(addr string) { func SetServerAddr(addr string) {
serverAddr = addr serverAddr = addr
@ -26,10 +27,32 @@ func SetServerAddr(addr string) {
} }
func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) { func ConnectToServer(username, password string, isRegistering bool) (net.Conn, int32, error) {
conn, err := net.Dial("tcp", serverAddr) log.Printf("Connecting to server at %s...", serverAddr)
if err != nil {
log.Printf("Failed to dial server: %v", err) var err error
return nil, 0, err var conn net.Conn
// Try connecting with a timeout
connChan := make(chan net.Conn, 1)
errChan := make(chan error, 1)
go func() {
c, e := net.Dial("tcp", serverAddr)
if e != nil {
errChan <- e
return
}
connChan <- c
}()
// Wait for connection with timeout
select {
case conn = <-connChan:
// Connection successful, continue
case err = <-errChan:
return nil, 0, fmt.Errorf("failed to dial server: %v", err)
case <-time.After(5 * time.Second):
return nil, 0, fmt.Errorf("connection timeout after 5 seconds")
} }
log.Println("Connected to server. Authenticating...") log.Println("Connected to server. Authenticating...")
@ -54,8 +77,12 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i
return nil, 0, fmt.Errorf("failed to send auth: %v", err) return nil, 0, fmt.Errorf("failed to send auth: %v", err)
} }
// Read server response // Read server response with timeout
reader := bufio.NewReader(conn) reader := bufio.NewReader(conn)
// Set a read deadline for authentication
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
lengthBuf := make([]byte, 4) lengthBuf := make([]byte, 4)
if _, err := io.ReadFull(reader, lengthBuf); err != nil { if _, err := io.ReadFull(reader, lengthBuf); err != nil {
conn.Close() conn.Close()
@ -63,12 +90,21 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i
} }
messageLength := binary.BigEndian.Uint32(lengthBuf) messageLength := binary.BigEndian.Uint32(lengthBuf)
// Sanity check message size
if messageLength > 1024*1024 { // 1MB max message size
conn.Close()
return nil, 0, fmt.Errorf("authentication response too large: %d bytes", messageLength)
}
messageBuf := make([]byte, messageLength) messageBuf := make([]byte, messageLength)
if _, err := io.ReadFull(reader, messageBuf); err != nil { if _, err := io.ReadFull(reader, messageBuf); err != nil {
conn.Close() conn.Close()
return nil, 0, fmt.Errorf("failed to read auth response body: %v", err) return nil, 0, fmt.Errorf("failed to read auth response body: %v", err)
} }
// Clear read deadline after authentication
conn.SetReadDeadline(time.Time{})
var response pb.ServerMessage var response pb.ServerMessage
if err := proto.Unmarshal(messageBuf, &response); err != nil { if err := proto.Unmarshal(messageBuf, &response); err != nil {
conn.Close() conn.Close()
@ -88,6 +124,10 @@ func ConnectToServer(username, password string, isRegistering bool) (net.Conn, i
playerID := response.GetPlayerId() playerID := response.GetPlayerId()
log.Printf("Successfully authenticated with player ID: %d", playerID) log.Printf("Successfully authenticated with player ID: %d", playerID)
// Reset the lastSeenMessageTimestamp when reconnecting
lastSeenMessageTimestamp = 0
return conn, playerID, nil return conn, playerID, nil
} }
@ -110,6 +150,12 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
errChan := make(chan error, 1) errChan := make(chan error, 1)
done := make(chan struct{}) done := make(chan struct{})
// Add a heartbeat ticker to detect connection issues
heartbeatTicker := time.NewTicker(5 * time.Second)
defer heartbeatTicker.Stop()
lastMessageTime := time.Now()
// Start message sending goroutine // Start message sending goroutine
go func() { go func() {
defer func() { defer func() {
@ -135,6 +181,22 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
return return
case <-done: case <-done:
return return
case <-heartbeatTicker.C:
// If no message has been sent for a while, send a heartbeat
timeSinceLastMessage := time.Since(lastMessageTime)
if timeSinceLastMessage > 5*time.Second {
// Send an empty batch as a heartbeat
emptyBatch := &pb.ActionBatch{
PlayerId: playerID,
LastSeenMessageTimestamp: lastSeenMessageTimestamp,
}
if err := writeMessage(conn, emptyBatch); err != nil {
log.Printf("Failed to send heartbeat: %v", err)
errChan <- err
return
}
lastMessageTime = time.Now()
}
case <-actionTicker.C: case <-actionTicker.C:
player.Lock() player.Lock()
if len(player.ActionQueue) > 0 { if len(player.ActionQueue) > 0 {
@ -144,6 +206,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
PlayerId: playerID, PlayerId: playerID,
Actions: actions, Actions: actions,
Tick: player.CurrentTick, Tick: player.CurrentTick,
LastSeenMessageTimestamp: lastSeenMessageTimestamp,
} }
player.ActionQueue = player.ActionQueue[:0] player.ActionQueue = player.ActionQueue[:0]
player.Unlock() player.Unlock()
@ -152,6 +215,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
errChan <- err errChan <- err
return return
} }
lastMessageTime = time.Now()
} else { } else {
player.Unlock() player.Unlock()
} }
@ -260,6 +324,13 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 {
log.Printf("Received %d chat messages from server", len(serverMessage.ChatMessages)) log.Printf("Received %d chat messages from server", len(serverMessage.ChatMessages))
handler.HandleServerMessages(serverMessage.ChatMessages) handler.HandleServerMessages(serverMessage.ChatMessages)
// Update the last seen message timestamp to the most recent message
if len(serverMessage.ChatMessages) > 0 {
lastMsg := serverMessage.ChatMessages[len(serverMessage.ChatMessages)-1]
lastSeenMessageTimestamp = lastMsg.Timestamp
log.Printf("Updated last seen message timestamp to %d", lastSeenMessageTimestamp)
}
} }
} }
} }
@ -268,6 +339,7 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
// Wait for error or quit signal // Wait for error or quit signal
select { select {
case <-quitChan: case <-quitChan:
log.Printf("Received quit signal, sending disconnect message")
// Send disconnect message // Send disconnect message
disconnectMsg := &pb.ActionBatch{ disconnectMsg := &pb.ActionBatch{
PlayerId: playerID, PlayerId: playerID,
@ -277,8 +349,10 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *types.Play
}}, }},
} }
writeMessage(conn, disconnectMsg) writeMessage(conn, disconnectMsg)
close(done)
case err := <-errChan: case err := <-errChan:
log.Printf("Network error: %v", err) log.Printf("Network error: %v", err)
close(done)
} }
} }