From 541f53c06a72c62223e95bffa39c5c92d726ba0a Mon Sep 17 00:00:00 2001 From: bdnugget <1001337108312v3@gmail.com> Date: Wed, 16 Apr 2025 12:28:30 +0200 Subject: [PATCH] Prevent chat and other player rendering race conditions when logging in at the same time --- game/chat.go | 51 +++++++++++++++++++++++++++----------- game/game.go | 57 +++++++++++++++++++++++++++++++----------- network/network.go | 62 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 133 insertions(+), 37 deletions(-) diff --git a/game/chat.go b/game/chat.go index f5b9499..098f9ac 100644 --- a/game/chat.go +++ b/game/chat.go @@ -59,6 +59,12 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) { // Convert protobuf messages to our local type for _, msg := range messages { + // Skip invalid messages + if msg == nil { + log.Printf("Warning: Received nil chat message") + continue + } + localMsg := types.ChatMessage{ PlayerID: msg.PlayerId, Username: msg.Username, @@ -81,24 +87,41 @@ func (c *Chat) HandleServerMessages(messages []*pb.ChatMessage) { } // Add floating message to the player - if game, ok := c.userData.(*Game); ok { - if msg.PlayerId == game.Player.ID { - game.Player.Lock() - game.Player.FloatingMessage = &types.FloatingMessage{ + if game, ok := c.userData.(*Game); ok && game != nil { + // Make sure each game component exists before using it + if game.PlayerManager == nil { + log.Printf("Warning: PlayerManager is nil when processing chat message") + continue + } + + if msg.PlayerId == game.PlayerManager.LocalPlayer.ID { + // Check if local player exists + if game.PlayerManager.LocalPlayer == nil { + log.Printf("Warning: Local player is nil when trying to add floating message") + continue + } + + game.PlayerManager.LocalPlayer.Lock() + game.PlayerManager.LocalPlayer.FloatingMessage = &types.FloatingMessage{ Content: msg.Content, ExpireTime: time.Now().Add(6 * time.Second), } - game.Player.Unlock() - } else if otherPlayer, exists := game.OtherPlayers[msg.PlayerId]; exists { - otherPlayer.Lock() - otherPlayer.FloatingMessage = &types.FloatingMessage{ - Content: msg.Content, - ExpireTime: time.Now().Add(6 * time.Second), - } - otherPlayer.Unlock() - log.Printf("Added floating message to other player %d", msg.PlayerId) + game.PlayerManager.LocalPlayer.Unlock() } else { - log.Printf("Could not find other player %d to add floating message", msg.PlayerId) + // The other player might not be in our list yet, handle safely + player := game.PlayerManager.GetPlayer(msg.PlayerId) + if player == nil { + log.Printf("Could not find other player %d to add floating message (player not in game yet)", msg.PlayerId) + continue + } + + player.Lock() + player.FloatingMessage = &types.FloatingMessage{ + Content: msg.Content, + ExpireTime: time.Now().Add(6 * time.Second), + } + player.Unlock() + log.Printf("Added floating message to other player %d", msg.PlayerId) } } } diff --git a/game/game.go b/game/game.go index d496bc2..84ac39a 100644 --- a/game/game.go +++ b/game/game.go @@ -53,6 +53,8 @@ func New() *Game { Projection: rl.CameraPerspective, }, quitChan: make(chan struct{}), + // Initialize empty maps to avoid nil references + OtherPlayers: make(map[int32]*types.Player), } // Initialize legacy fields (for backward compatibility) @@ -117,20 +119,32 @@ func (g *Game) Update(deltaTime float32) { } g.PlayerManager.LocalPlayer = &types.Player{ - Speed: 50.0, - TargetPath: []types.Tile{}, - UserData: g, - QuitDone: make(chan struct{}), - ID: playerID, + Speed: 50.0, + TargetPath: []types.Tile{}, + ActionQueue: []*pb.Action{}, + QuitDone: make(chan struct{}), + ID: playerID, } g.AssignModelToPlayer(g.PlayerManager.LocalPlayer) + // Update the legacy Player field + g.Player = g.PlayerManager.LocalPlayer + + // Set user data to allow chat message handling + g.PlayerManager.LocalPlayer.UserData = g + go network.HandleServerCommunication(conn, playerID, g.PlayerManager.LocalPlayer, g.PlayerManager.OtherPlayers, g.quitChan) g.UIManager.IsLoggedIn = true } return } + // Skip update logic if player is not initialized yet + if g.PlayerManager.LocalPlayer == nil { + log.Printf("Warning: LocalPlayer is nil during update, skipping") + return + } + // Handle ESC for menu if rl.IsKeyPressed(rl.KeyEscape) { g.UIManager.MenuOpen = !g.UIManager.MenuOpen @@ -142,6 +156,7 @@ func (g *Game) Update(deltaTime float32) { return } + // Handle chat updates if message, sent := g.UIManager.Chat.Update(); sent { g.PlayerManager.LocalPlayer.Lock() g.PlayerManager.LocalPlayer.ActionQueue = append(g.PlayerManager.LocalPlayer.ActionQueue, &pb.Action{ @@ -152,9 +167,11 @@ func (g *Game) Update(deltaTime float32) { g.PlayerManager.LocalPlayer.Unlock() } + // Process player input g.HandleInput() - if len(g.PlayerManager.LocalPlayer.TargetPath) > 0 { + // Update local player movement + if g.PlayerManager.LocalPlayer.TargetPath != nil && len(g.PlayerManager.LocalPlayer.TargetPath) > 0 { g.PlayerManager.LocalPlayer.MoveTowards(g.PlayerManager.LocalPlayer.TargetPath[0], deltaTime, GetMapGrid()) } @@ -163,8 +180,12 @@ func (g *Game) Update(deltaTime float32) { if g.frameCounter%300 == 0 { rl.TraceLog(rl.LogInfo, "There are %d other players", len(g.PlayerManager.OtherPlayers)) for id, other := range g.PlayerManager.OtherPlayers { - rl.TraceLog(rl.LogInfo, "Other player ID: %d, Position: (%f, %f, %f), Has model: %v", - id, other.PosActual.X, other.PosActual.Y, other.PosActual.Z, other.Model.Meshes != nil) + if other != nil { + rl.TraceLog(rl.LogInfo, "Other player ID: %d, Position: (%f, %f, %f), Has model: %v", + id, other.PosActual.X, other.PosActual.Y, other.PosActual.Z, other.Model.Meshes != nil) + } else { + rl.TraceLog(rl.LogInfo, "Other player ID: %d is nil", id) + } } } @@ -174,17 +195,18 @@ func (g *Game) Update(deltaTime float32) { continue } - // Make sure other players have models assigned + if other.TargetPath != nil && len(other.TargetPath) > 0 { + target := other.TargetPath[0] + other.MoveTowards(target, deltaTime, GetMapGrid()) + } + + // Assign model if needed if other.Model.Meshes == nil { g.AssignModelToPlayer(other) } - - // Update other player movement - if len(other.TargetPath) > 0 { - other.MoveTowards(other.TargetPath[0], deltaTime, GetMapGrid()) - } } + // Update camera position UpdateCamera(&g.Camera, g.PlayerManager.LocalPlayer.PosActual, deltaTime) // Update music if available @@ -502,7 +524,12 @@ func (g *Game) Shutdown() { } func (g *Game) HandleServerMessages(messages []*pb.ChatMessage) { - g.UIManager.Chat.HandleServerMessages(messages) + // Check if Chat is properly initialized + if g.UIManager != nil && g.UIManager.Chat != nil { + g.UIManager.Chat.HandleServerMessages(messages) + } else { + log.Printf("Warning: Cannot handle server messages, Chat is not initialized") + } } func (g *Game) AssignModelToPlayer(player *types.Player) { diff --git a/network/network.go b/network/network.go index 3527962..d9913b7 100644 --- a/network/network.go +++ b/network/network.go @@ -91,6 +91,22 @@ func (mh *MessageHandler) WriteMessage(msg proto.Message) error { // UpdateGameState processes a server message and updates game state func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, otherPlayers map[int32]*types.Player) { + // Safety check for nil inputs + if serverMessage == nil { + log.Printf("Warning: Received nil server message") + return + } + + if player == nil { + log.Printf("Warning: Local player is nil when updating game state") + return + } + + if otherPlayers == nil { + log.Printf("Warning: otherPlayers map is nil when updating game state") + return + } + playerID := player.ID player.Lock() @@ -99,7 +115,7 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe tickDiff := serverMessage.CurrentTick - player.CurrentTick if tickDiff > types.MaxTickDesync { for _, state := range serverMessage.Players { - if state.PlayerId == playerID { + if state != nil && state.PlayerId == playerID { player.ForceResync(state) break } @@ -110,6 +126,12 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe // Process player states validPlayerIds := make(map[int32]bool) for _, state := range serverMessage.Players { + // Skip invalid player states + if state == nil { + log.Printf("Warning: Received nil player state") + continue + } + validPlayerIds[state.PlayerId] = true if state.PlayerId == playerID { @@ -129,7 +151,13 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe // Update or create other players if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { - otherPlayer.UpdatePosition(state, types.ServerTickRate) + if otherPlayer != nil { + otherPlayer.UpdatePosition(state, types.ServerTickRate) + } else { + // Replace nil player with a new one + log.Printf("Replacing nil player with ID: %d", state.PlayerId) + otherPlayers[state.PlayerId] = types.NewPlayer(state) + } } else { log.Printf("Creating new player with ID: %d", state.PlayerId) otherPlayers[state.PlayerId] = types.NewPlayer(state) @@ -144,14 +172,32 @@ func UpdateGameState(serverMessage *pb.ServerMessage, player *types.Player, othe } } - // Handle chat messages - if handler, ok := player.UserData.(types.ChatMessageHandler); ok && len(serverMessage.ChatMessages) > 0 { + // Handle chat messages with safety checks + if handler, ok := player.UserData.(types.ChatMessageHandler); ok && handler != nil && len(serverMessage.ChatMessages) > 0 { log.Printf("Received %d chat messages from server", len(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] + // Make sure we have valid chat messages + validMessages := make([]*pb.ChatMessage, 0, len(serverMessage.ChatMessages)) + for _, msg := range serverMessage.ChatMessages { + if msg != nil { + validMessages = append(validMessages, msg) + } + } + + if len(validMessages) > 0 { + // Use a separate goroutine to handle messages to prevent blocking + // network handling if there's an issue with chat processing + go func(msgs []*pb.ChatMessage) { + defer func() { + if r := recover(); r != nil { + log.Printf("Recovered from panic in chat message handler: %v", r) + } + }() + handler.HandleServerMessages(msgs) + }(validMessages) + + // Update the last seen message timestamp to the most recent message + lastMsg := validMessages[len(validMessages)-1] lastSeenMessageTimestamp = lastMsg.Timestamp log.Printf("Updated last seen message timestamp to %d", lastSeenMessageTimestamp) }